blob: bbfbe4faa31ab52b7fc28a58e3b70e3ea86496f4 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.netbeans.modules.editor.java;
import com.sun.source.tree.Tree;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.text.JTextComponent;
import com.sun.source.tree.CaseTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.util.SourcePositions;
import com.sun.source.util.TreePath;
import org.netbeans.api.java.lexer.JavaTokenId;
import org.netbeans.api.java.source.CompilationController;
import org.netbeans.api.java.source.JavaSource.Phase;
import org.netbeans.api.java.source.SourceUtils;
import org.netbeans.api.java.source.TreeUtilities;
import org.netbeans.api.lexer.Token;
import org.netbeans.api.lexer.TokenHierarchy;
import org.netbeans.api.lexer.TokenId;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.api.progress.BaseProgressUtils;
import org.netbeans.lib.editor.codetemplates.api.CodeTemplate;
import org.netbeans.lib.editor.codetemplates.spi.CodeTemplateFilter;
import org.netbeans.modules.parsing.api.ParserManager;
import org.netbeans.modules.parsing.api.ResultIterator;
import org.netbeans.modules.parsing.api.Source;
import org.netbeans.modules.parsing.api.UserTask;
import org.netbeans.modules.parsing.spi.ParseException;
import org.netbeans.modules.parsing.spi.Parser;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
/**
*
* @author Dusan Balek
*/
public class JavaCodeTemplateFilter implements CodeTemplateFilter {
private static final String EXPRESSION = "EXPRESSION"; //NOI18N
private static final String CLASS_HEADER = "CLASS_HEADER"; //NOI18N
private Tree.Kind treeKindCtx = null;
private String stringCtx = null;
private JavaCodeTemplateFilter(JTextComponent component, int offset) {
if (Utilities.isJavaContext(component, offset, true)) {
final int startOffset = offset;
final int endOffset = component.getSelectionStart() == offset ? component.getSelectionEnd() : -1;
final Source source = Source.create(component.getDocument());
if (source != null) {
final AtomicBoolean cancel = new AtomicBoolean();
BaseProgressUtils.runOffEventDispatchThread(() -> {
try {
ParserManager.parse(Collections.singleton(source), new UserTask() {
@Override
public void run(ResultIterator resultIterator) throws Exception {
if (cancel.get()) {
return;
}
Parser.Result result = resultIterator.getParserResult(startOffset);
CompilationController controller = result != null ? CompilationController.get(result) : null;
if (controller != null && Phase.PARSED.compareTo(controller.toPhase(Phase.PARSED)) <= 0) {
TreeUtilities tu = controller.getTreeUtilities();
int eo = endOffset;
int so = startOffset;
if (so >= 0) {
so = result.getSnapshot().getEmbeddedOffset(startOffset);
}
if (endOffset >= 0) {
eo = result.getSnapshot().getEmbeddedOffset(endOffset);
TokenSequence<JavaTokenId> ts = SourceUtils.getJavaTokenSequence(controller.getTokenHierarchy(), so);
int delta = ts.move(so);
if (delta == 0 || ts.moveNext() && ts.token().id() == JavaTokenId.WHITESPACE) {
delta = ts.move(eo);
if (delta == 0 || ts.moveNext() && ts.token().id() == JavaTokenId.WHITESPACE) {
String selectedText = controller.getText().substring(so, eo).trim();
SourcePositions[] sp = new SourcePositions[1];
ExpressionTree expr = selectedText.length() > 0 ? tu.parseExpression(selectedText, sp) : null;
if (expr != null && expr.getKind() != Tree.Kind.IDENTIFIER && !Utilities.containErrors(expr) && sp[0].getEndPosition(null, expr) >= selectedText.length()) {
stringCtx = EXPRESSION;
}
}
}
}
Tree tree = tu.pathFor(so).getLeaf();
if (eo >= 0 && so != eo) {
if (tu.pathFor(eo).getLeaf() != tree) {
return;
}
}
treeKindCtx = tree.getKind();
switch (treeKindCtx) {
case CASE:
if (so < controller.getTrees().getSourcePositions().getEndPosition(controller.getCompilationUnit(), ((CaseTree)tree).getExpression())) {
treeKindCtx = null;
}
break;
case CLASS:
SourcePositions sp = controller.getTrees().getSourcePositions();
int startPos = (int)sp.getEndPosition(controller.getCompilationUnit(), ((ClassTree)tree).getModifiers());
if (startPos <= 0) {
startPos = (int)sp.getStartPosition(controller.getCompilationUnit(), tree);
}
String headerText = controller.getText().substring(startPos, so);
int idx = headerText.indexOf('{'); //NOI18N
if (idx < 0) {
treeKindCtx = null;
stringCtx = CLASS_HEADER;
}
break;
case FOR_LOOP:
case ENHANCED_FOR_LOOP:
if (!isRightParenthesisOfLoopPresent(controller, so)) {
treeKindCtx = null;
}
break;
case PARENTHESIZED:
if (isPartOfWhileLoop(controller, so)) {
if (!isRightParenthesisOfLoopPresent(controller, so)) {
treeKindCtx = null;
}
}
break;
}
}
}
});
} catch (ParseException ex) {
Exceptions.printStackTrace(ex);
}
}, NbBundle.getMessage(JavaCodeTemplateProcessor.class, "JCT-init"), cancel, false); //NOI18N
}
}
}
private boolean isRightParenthesisOfLoopPresent(CompilationController controller, int abbrevStartOffset) {
TokenHierarchy<?> tokenHierarchy = controller.getTokenHierarchy();
TokenSequence<?> tokenSequence = tokenHierarchy.tokenSequence();
tokenSequence.move(abbrevStartOffset);
if (tokenSequence.moveNext()) {
TokenId tokenId = skipNextWhitespaces(tokenSequence);
return tokenId == null ? false : (tokenId == JavaTokenId.RPAREN);
}
return false;
}
private TokenId skipNextWhitespaces(TokenSequence<?> tokenSequence) {
TokenId tokenId = null;
while (tokenSequence.moveNext()) {
Token<?> token = tokenSequence.token();
if (token != null) {
tokenId = token.id();
}
if (tokenId != JavaTokenId.WHITESPACE) {
break;
}
}
return tokenId;
}
private boolean isPartOfWhileLoop(CompilationController controller, int abbrevStartOffset) {
TreeUtilities treeUtilities = controller.getTreeUtilities();
TreePath currentPath = treeUtilities.pathFor(abbrevStartOffset);
TreePath parentPath = treeUtilities.getPathElementOfKind(Tree.Kind.WHILE_LOOP, currentPath);
return parentPath != null;
}
@Override
public synchronized boolean accept(CodeTemplate template) {
if (treeKindCtx == null && stringCtx == null) {
return false;
}
EnumSet<Tree.Kind> treeKindContexts = EnumSet.noneOf(Tree.Kind.class);
HashSet stringContexts = new HashSet();
getTemplateContexts(template, treeKindContexts, stringContexts);
return treeKindContexts.isEmpty() && stringContexts.isEmpty() && treeKindCtx != Tree.Kind.STRING_LITERAL || treeKindContexts.contains(treeKindCtx) || stringContexts.contains(stringCtx);
}
private void getTemplateContexts(CodeTemplate template, EnumSet<Tree.Kind> treeKindContexts, HashSet<String> stringContexts) {
List<String> contexts = template.getContexts();
if (contexts != null) {
for(String context : contexts) {
try {
treeKindContexts.add(Tree.Kind.valueOf(context));
} catch (IllegalArgumentException iae) {
stringContexts.add(context);
}
}
}
}
public static final class Factory implements CodeTemplateFilter.ContextBasedFactory {
@Override
public CodeTemplateFilter createFilter(JTextComponent component, int offset) {
return new JavaCodeTemplateFilter(component, offset);
}
@Override
public List<String> getSupportedContexts() {
Tree.Kind[] values = Tree.Kind.values();
List<String> contexts = new ArrayList<>(values.length + 1);
for (Tree.Kind value : values) {
contexts.add(value.name());
}
contexts.add(CLASS_HEADER);
Collections.sort(contexts);
return contexts;
}
}
}