com.github.alexfalappa.nbspringboot.cfgprops.completion.CfgPropsCompletionProvider.java Source code

Java tutorial

Introduction

Here is the source code for com.github.alexfalappa.nbspringboot.cfgprops.completion.CfgPropsCompletionProvider.java

Source

/*
 * Copyright 2015 Keevosh ULP.
 * Modifications copyright 2016 Alessandro Falappa.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.alexfalappa.nbspringboot.cfgprops.completion;

import java.util.logging.Logger;
import java.util.prefs.Preferences;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.JTextComponent;
import javax.swing.text.StyledDocument;

import org.netbeans.api.editor.mimelookup.MimeRegistration;
import org.netbeans.api.project.Project;
import org.netbeans.spi.editor.completion.CompletionProvider;
import org.netbeans.spi.editor.completion.CompletionResultSet;
import org.netbeans.spi.editor.completion.CompletionTask;
import org.netbeans.spi.editor.completion.support.AsyncCompletionQuery;
import org.netbeans.spi.editor.completion.support.AsyncCompletionTask;
import org.openide.filesystems.FileUtil;
import org.openide.util.Exceptions;
import org.openide.util.NbPreferences;
import org.openide.util.Utilities;
import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty;
import org.springframework.boot.configurationmetadata.ValueHint;

import com.github.alexfalappa.nbspringboot.PrefConstants;
import com.github.alexfalappa.nbspringboot.Utils;
import com.github.alexfalappa.nbspringboot.cfgprops.lexer.CfgPropsLanguage;
import com.github.alexfalappa.nbspringboot.projects.service.api.SpringBootService;

import static com.github.alexfalappa.nbspringboot.PrefConstants.PREF_DEPR_ERROR_SHOW;
import static com.github.alexfalappa.nbspringboot.PrefConstants.PREF_DEPR_SORT_LAST;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.FINER;

/**
 * The Spring Boot Configuration implementation of {@code CompletionProvider}.
 * <p>
 * The entry point of completion support. This provider is registered for text/x-properties files and is enabled if spring-boot is available
 * on the classpath.
 * <p>
 * It scans the classpath for {@code META-INF/spring-configuration-metadata.json} files, then unmarshals the files into the corresponding {@link
 * ConfigurationMetadata} classes and later in the query task scans for items and fills the {@link CompletionResultSet}.
 * <p>
 * The provider organizes properties, groups and hints in maps indexed by name. It also maintains a cache of configuration metadata parsed
 * from JSON files in jars to speed up completion.
 *
 * @author Aggelos Karalias
 * @author Alessandro Falappa
 */
@MimeRegistration(mimeType = CfgPropsLanguage.MIME_TYPE, service = CompletionProvider.class)
public class CfgPropsCompletionProvider implements CompletionProvider {

    private static final Logger logger = Logger.getLogger(CfgPropsCompletionProvider.class.getName());
    private static final Pattern PATTERN_PROP_NAME = Pattern.compile("[^=\\s]+");

    @Override
    public CompletionTask createTask(int queryType, JTextComponent jtc) {
        if (queryType != CompletionProvider.COMPLETION_QUERY_TYPE) {
            return null;
        }
        Project prj = Utilities.actionsGlobalContext().lookup(Project.class);
        if (prj == null) {
            return null;
        }
        logger.log(FINE, "Completing within context of prj {0}",
                FileUtil.getFileDisplayName(prj.getProjectDirectory()));
        final SpringBootService sbs = prj.getLookup().lookup(SpringBootService.class);
        if (sbs == null) {
            return null;
        }
        logger.fine("Creating completion task");
        return new AsyncCompletionTask(new AsyncCompletionQuery() {
            @Override
            protected void query(CompletionResultSet completionResultSet, Document document, int caretOffset) {
                final StyledDocument styDoc = (StyledDocument) document;
                Element lineElement = styDoc.getParagraphElement(caretOffset);
                int lineStartOffset = lineElement.getStartOffset();
                try {
                    String lineToCaret = styDoc.getText(lineStartOffset, caretOffset - lineStartOffset);
                    logger.log(FINER, "Completion on line to caret: {0}", lineToCaret);
                    if (!lineToCaret.contains("#")) {
                        String[] parts = lineToCaret.split("=");
                        //property name extraction from part before =
                        Matcher matcher = PATTERN_PROP_NAME.matcher(parts[0]);
                        String propPrefix = null;
                        int propPrefixOffset = 0;
                        while (matcher.find()) {
                            propPrefix = matcher.group();
                            propPrefixOffset = matcher.start();
                        }
                        // check which kind of completion
                        final int equalSignOffset = lineToCaret.indexOf('=');
                        if (parts.length > 1) {
                            //value completion
                            String valPrefix = parts[1].trim();
                            completePropValue(sbs, completionResultSet, propPrefix, valPrefix,
                                    lineStartOffset + lineToCaret.indexOf(valPrefix, equalSignOffset), caretOffset);
                        } else if (equalSignOffset >= 0) {
                            //value completion with empty filter
                            completePropValue(sbs, completionResultSet, propPrefix, null,
                                    lineStartOffset + equalSignOffset + 1, caretOffset);
                        } else {
                            // property completion
                            completePropName(sbs, completionResultSet, propPrefix,
                                    lineStartOffset + propPrefixOffset, caretOffset);
                        }
                    }
                } catch (BadLocationException ex) {
                    Exceptions.printStackTrace(ex);
                }
                completionResultSet.finish();
            }
        }, jtc);
    }

    @Override
    public int getAutoQueryTypes(JTextComponent jtc, String string) {
        return 0;
    }

    // Create a completion result list of config properties based on a filter string, classpath and document offsets.
    private void completePropName(SpringBootService sbs, CompletionResultSet completionResultSet, String filter,
            int startOffset, int caretOffset) {
        final Preferences prefs = NbPreferences.forModule(PrefConstants.class);
        final boolean bDeprLast = prefs.getBoolean(PREF_DEPR_SORT_LAST, true);
        final boolean bErrorShow = prefs.getBoolean(PREF_DEPR_ERROR_SHOW, false);
        long mark = System.currentTimeMillis();
        logger.log(FINER, "Completing property name: {0}", filter);
        for (ConfigurationMetadataProperty propMeta : sbs.queryPropertyMetadata(filter)) {
            if (Utils.isErrorDeprecated(propMeta)) {
                // show error level deprecated props based on pref
                if (bErrorShow) {
                    completionResultSet
                            .addItem(new CfgPropCompletionItem(propMeta, sbs, startOffset, caretOffset, bDeprLast));
                }
            } else {
                completionResultSet
                        .addItem(new CfgPropCompletionItem(propMeta, sbs, startOffset, caretOffset, bDeprLast));
            }
        }
        final long elapsed = System.currentTimeMillis() - mark;
        logger.log(FINER, "Property completion of ''{0}'' took: {1} msecs", new Object[] { filter, elapsed });
    }

    // Create a completion result list of properties values based on a property name, filter string, classpath and document offsets.
    public void completePropValue(SpringBootService sbs, CompletionResultSet completionResultSet, String propName,
            String filter, int startOffset, int caretOffset) {
        long mark = System.currentTimeMillis();
        logger.log(FINER, "Completing property value: {0}", filter);
        for (ValueHint valueHint : sbs.queryHintMetadata(propName, filter)) {
            completionResultSet.addItem(new CfgPropValueCompletionItem(valueHint, startOffset, caretOffset));
        }
        final long elapsed = System.currentTimeMillis() - mark;
        logger.log(FINER, "Value completion of ''{0}'' on ''{1}'' took: {2} msecs",
                new Object[] { filter, propName, elapsed });
    }

}