| /* |
| * 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.java.completion; |
| |
| import java.io.IOException; |
| import java.util.*; |
| import java.util.concurrent.Callable; |
| |
| import javax.lang.model.element.Element; |
| import static javax.lang.model.element.ElementKind.METHOD; |
| import javax.lang.model.element.ExecutableElement; |
| import static javax.lang.model.element.Modifier.*; |
| import javax.lang.model.element.TypeElement; |
| import javax.lang.model.type.DeclaredType; |
| import javax.lang.model.type.TypeKind; |
| import javax.lang.model.type.TypeMirror; |
| import javax.lang.model.util.Types; |
| import javax.tools.Diagnostic; |
| |
| import com.sun.source.tree.*; |
| import com.sun.source.tree.Tree.Kind; |
| import com.sun.source.util.SourcePositions; |
| import com.sun.source.util.TreePath; |
| import org.netbeans.api.java.source.support.ErrorAwareTreeScanner; |
| import javax.lang.model.element.Name; |
| |
| import org.netbeans.api.java.lexer.JavaTokenId; |
| import org.netbeans.api.java.source.*; |
| import org.netbeans.api.java.source.support.ReferencesCount; |
| import org.netbeans.api.lexer.TokenSequence; |
| import org.netbeans.modules.parsing.api.ResultIterator; |
| import org.netbeans.modules.parsing.api.UserTask; |
| import org.netbeans.modules.parsing.spi.Parser; |
| |
| /** |
| * |
| * @author Dusan Balek |
| */ |
| abstract class BaseTask extends UserTask { |
| |
| protected final int caretOffset; |
| protected final Callable<Boolean> cancel; |
| private int caretInSnapshot; |
| |
| protected BaseTask(int caretOffset, Callable<Boolean> cancel) { |
| this.caretOffset = caretOffset; |
| this.cancel = cancel; |
| } |
| |
| |
| final int getCaretInSnapshot() { |
| return caretInSnapshot; |
| } |
| |
| private CompilationController controller; |
| |
| final int snapshotPos(int pos) { |
| if (pos < 0) { |
| return pos; |
| } |
| int r = controller.getSnapshot().getEmbeddedOffset(pos); |
| if (r == -1) { |
| return pos; |
| } else { |
| return r; |
| } |
| } |
| |
| @Override |
| public void run(ResultIterator resultIterator) throws Exception { |
| Parser.Result result = resultIterator.getParserResult(caretOffset); |
| CompilationController controller = result != null ? CompilationController.get(result) : null; |
| if (controller != null && (cancel == null || !cancel.call())) { |
| try { |
| this.controller = controller; |
| caretInSnapshot = snapshotPos(caretOffset); |
| resolve(controller); |
| } finally { |
| this.controller = null; |
| } |
| } |
| } |
| |
| protected abstract void resolve(CompilationController controller) throws IOException; |
| |
| Tree unwrapErrTree(Tree tree) { |
| if (tree != null && tree.getKind() == Tree.Kind.ERRONEOUS) { |
| Iterator<? extends Tree> it = ((ErroneousTree) tree).getErrorTrees().iterator(); |
| tree = it.hasNext() ? it.next() : null; |
| } |
| return tree; |
| } |
| |
| TypeMirror asMemberOf(Element element, TypeMirror type, Types types) { |
| TypeMirror ret = element.asType(); |
| TypeMirror enclType = element.getEnclosingElement().asType(); |
| if (enclType.getKind() == TypeKind.DECLARED) { |
| enclType = types.erasure(enclType); |
| } |
| while (type != null && type.getKind() == TypeKind.DECLARED) { |
| if ((enclType.getKind() != TypeKind.DECLARED || ((DeclaredType) enclType).asElement().getSimpleName().length() > 0) && types.isSubtype(type, enclType)) { |
| ret = types.asMemberOf((DeclaredType) type, element); |
| break; |
| } |
| type = ((DeclaredType) type).getEnclosingType(); |
| } |
| return ret; |
| } |
| |
| List<Tree> getArgumentsUpToPos(Env env, Iterable<? extends ExpressionTree> args, int startPos, int position, boolean strict) { |
| List<Tree> ret = new ArrayList<>(); |
| CompilationUnitTree root = env.getRoot(); |
| SourcePositions sourcePositions = env.getSourcePositions(); |
| if (args == null) { |
| return null; //TODO: member reference??? |
| } |
| for (ExpressionTree e : args) { |
| int pos = (int) sourcePositions.getEndPosition(root, e); |
| if (pos != Diagnostic.NOPOS && (position > pos || !strict && position == pos)) { |
| startPos = pos; |
| ret.add(e); |
| } else { |
| break; |
| } |
| } |
| if (startPos < 0) { |
| return ret; |
| } |
| if (position >= startPos) { |
| TokenSequence<JavaTokenId> last = findLastNonWhitespaceToken(env, startPos, position); |
| if (last == null) { |
| if (!strict && !ret.isEmpty()) { |
| ret.remove(ret.size() - 1); |
| return ret; |
| } |
| } else if (last.token().id() == JavaTokenId.LPAREN || last.token().id() == JavaTokenId.COMMA) { |
| return ret; |
| } |
| } |
| return null; |
| } |
| |
| TokenSequence<JavaTokenId> findFirstNonWhitespaceToken(Env env, int startPos, int endPos) { |
| TokenSequence<JavaTokenId> ts = env.getController().getTokenHierarchy().tokenSequence(JavaTokenId.language()); |
| ts.move(startPos); |
| ts = nextNonWhitespaceToken(ts); |
| if (ts == null || ts.offset() >= endPos) { |
| return null; |
| } |
| return ts; |
| } |
| |
| TokenSequence<JavaTokenId> nextNonWhitespaceToken(TokenSequence<JavaTokenId> ts) { |
| while (ts.moveNext()) { |
| switch (ts.token().id()) { |
| case WHITESPACE: |
| case LINE_COMMENT: |
| case BLOCK_COMMENT: |
| case JAVADOC_COMMENT: |
| break; |
| default: |
| return ts; |
| } |
| } |
| return null; |
| } |
| |
| TokenSequence<JavaTokenId> findLastNonWhitespaceToken(Env env, Tree tree, int position) { |
| int startPos = (int) env.getSourcePositions().getStartPosition(env.getRoot(), tree); |
| return findLastNonWhitespaceToken(env, startPos, position); |
| } |
| |
| TokenSequence<JavaTokenId> findLastNonWhitespaceToken(Env env, int startPos, int endPos) { |
| TokenSequence<JavaTokenId> ts = env.getController().getTokenHierarchy().tokenSequence(JavaTokenId.language()); |
| ts.move(endPos); |
| ts = previousNonWhitespaceToken(ts); |
| if (ts == null || ts.offset() < startPos) { |
| return null; |
| } |
| return ts; |
| } |
| |
| TokenSequence<JavaTokenId> previousNonWhitespaceToken(TokenSequence<JavaTokenId> ts) { |
| while (ts.movePrevious()) { |
| switch (ts.token().id()) { |
| case WHITESPACE: |
| case LINE_COMMENT: |
| case BLOCK_COMMENT: |
| case JAVADOC_COMMENT: |
| break; |
| default: |
| return ts; |
| } |
| } |
| return null; |
| } |
| |
| Env getCompletionEnvironment(CompilationController controller, boolean bottomUpSearch) throws IOException { |
| controller.toPhase(JavaSource.Phase.PARSED); |
| int offset = controller.getSnapshot().getEmbeddedOffset(caretOffset); |
| if (offset < 0 || offset > controller.getText().length()) { |
| return null; |
| } |
| String prefix = null; |
| if (offset > 0) { |
| if (bottomUpSearch) { |
| TokenSequence<JavaTokenId> ts = controller.getTokenHierarchy().tokenSequence(JavaTokenId.language()); |
| // When right at the token end move to previous token; otherwise move to the token that "contains" the offset |
| if (ts.move(offset) == 0 || !ts.moveNext()) { |
| ts.movePrevious(); |
| } |
| int len = offset - ts.offset(); |
| if (len > 0 && ts.token().length() >= len) { |
| if (ts.token().id() == JavaTokenId.IDENTIFIER |
| || ts.token().id().primaryCategory().startsWith("keyword") || //NOI18N |
| ts.token().id().primaryCategory().startsWith("string") || //NOI18N |
| ts.token().id().primaryCategory().equals("literal")) //NOI18N |
| { //TODO: Use isKeyword(...) when available |
| prefix = ts.token().text().toString().substring(0, len); |
| offset = ts.offset(); |
| } else if ((ts.token().id() == JavaTokenId.DOUBLE_LITERAL |
| || ts.token().id() == JavaTokenId.FLOAT_LITERAL |
| || ts.token().id() == JavaTokenId.FLOAT_LITERAL_INVALID |
| || ts.token().id() == JavaTokenId.LONG_LITERAL) |
| && ts.token().text().charAt(0) == '.') { |
| prefix = ts.token().text().toString().substring(1, len); |
| offset = ts.offset() + 1; |
| } |
| } |
| } else { |
| TokenSequence<JavaTokenId> ts = controller.getTokenHierarchy().tokenSequence(JavaTokenId.language()); |
| // When right at the token start move offset to the position "inside" the token |
| ts.move(offset); |
| if (!ts.moveNext()) { |
| ts.movePrevious(); |
| } |
| if (ts.offset() == offset && ts.token().length() > 0 |
| && (ts.token().id() == JavaTokenId.IDENTIFIER |
| || ts.token().id().primaryCategory().startsWith("keyword") || //NOI18N |
| ts.token().id().primaryCategory().startsWith("string") || //NOI18N |
| ts.token().id().primaryCategory().equals("literal"))) { //NOI18N |
| offset++; |
| } |
| } |
| } |
| offset = Math.min(offset, controller.getText().length()); |
| TreePath path = controller.getTreeUtilities().pathFor(offset); |
| if (bottomUpSearch) { |
| TreePath treePath = path; |
| while (treePath != null) { |
| TreePath pPath = treePath.getParentPath(); |
| TreePath gpPath = pPath != null ? pPath.getParentPath() : null; |
| JavaCompletionTask.Env env = getEnvImpl(controller, path, treePath, pPath, gpPath, offset, prefix, true); |
| if (env != null) { |
| return env; |
| } |
| treePath = treePath.getParentPath(); |
| } |
| } else { |
| if (JavaSource.Phase.RESOLVED.compareTo(controller.getPhase()) > 0) { |
| LinkedList<TreePath> reversePath = new LinkedList<>(); |
| TreePath treePath = path; |
| while (treePath != null) { |
| reversePath.addFirst(treePath); |
| treePath = treePath.getParentPath(); |
| } |
| for (TreePath tp : reversePath) { |
| TreePath pPath = tp.getParentPath(); |
| TreePath gpPath = pPath != null ? pPath.getParentPath() : null; |
| Env env = getEnvImpl(controller, path, tp, pPath, gpPath, offset, prefix, false); |
| if (env != null) { |
| return env; |
| } |
| } |
| } |
| } |
| return new Env(offset, prefix, controller, path, controller.getTrees().getSourcePositions(), null); |
| } |
| |
| private Env getEnvImpl(CompilationController controller, TreePath orig, TreePath path, TreePath pPath, TreePath gpPath, int offset, String prefix, boolean upToOffset) throws IOException { |
| Tree tree = path != null ? path.getLeaf() : null; |
| Tree parent = pPath != null ? pPath.getLeaf() : null; |
| Tree grandParent = gpPath != null ? gpPath.getLeaf() : null; |
| SourcePositions sourcePositions = controller.getTrees().getSourcePositions(); |
| CompilationUnitTree root = controller.getCompilationUnit(); |
| TreeUtilities tu = controller.getTreeUtilities(); |
| if (upToOffset && TreeUtilities.CLASS_TREE_KINDS.contains(tree.getKind())) { |
| controller.toPhase(withinAnonymousOrLocalClass(tu, path) ? JavaSource.Phase.RESOLVED : JavaSource.Phase.ELEMENTS_RESOLVED); |
| return new Env(offset, prefix, controller, orig, sourcePositions, null); |
| } else if (parent != null && tree.getKind() == Tree.Kind.BLOCK |
| && (parent.getKind() == Tree.Kind.METHOD || TreeUtilities.CLASS_TREE_KINDS.contains(parent.getKind()))) { |
| controller.toPhase(withinAnonymousOrLocalClass(tu, path) ? JavaSource.Phase.RESOLVED : JavaSource.Phase.ELEMENTS_RESOLVED); |
| int blockPos = (int) sourcePositions.getStartPosition(root, tree); |
| String blockText = controller.getText().substring(blockPos, upToOffset ? offset : (int) sourcePositions.getEndPosition(root, tree)); |
| final SourcePositions[] sp = new SourcePositions[1]; |
| final StatementTree block = (((BlockTree) tree).isStatic() ? tu.parseStaticBlock(blockText, sp) : tu.parseStatement(blockText, sp)); |
| if (block == null) { |
| return null; |
| } |
| sourcePositions = new SourcePositionsImpl(block, sourcePositions, sp[0], blockPos, upToOffset ? offset : -1); |
| Scope scope = controller.getTrees().getScope(path); |
| path = tu.pathFor(new TreePath(pPath, block), offset, sourcePositions); |
| if (upToOffset) { |
| Tree last = path.getLeaf(); |
| List<? extends StatementTree> stmts = null; |
| switch (path.getLeaf().getKind()) { |
| case BLOCK: |
| stmts = ((BlockTree) path.getLeaf()).getStatements(); |
| break; |
| case FOR_LOOP: |
| stmts = ((ForLoopTree) path.getLeaf()).getInitializer(); |
| break; |
| case ENHANCED_FOR_LOOP: |
| stmts = Collections.singletonList(((EnhancedForLoopTree) path.getLeaf()).getStatement()); |
| break; |
| case METHOD: |
| stmts = ((MethodTree) path.getLeaf()).getParameters(); |
| break; |
| case SWITCH: |
| CaseTree lastCase = null; |
| for (CaseTree caseTree : ((SwitchTree) path.getLeaf()).getCases()) { |
| lastCase = caseTree; |
| } |
| if (lastCase != null) { |
| stmts = lastCase.getStatements(); |
| } |
| break; |
| case CASE: |
| stmts = ((CaseTree) path.getLeaf()).getStatements(); |
| break; |
| case CONDITIONAL_AND: case CONDITIONAL_OR: |
| BinaryTree bt = (BinaryTree) last; |
| if (sourcePositions.getStartPosition(path.getCompilationUnit(), bt.getRightOperand()) == offset && |
| bt.getRightOperand().getKind() == Kind.ERRONEOUS) { |
| last = bt.getRightOperand(); |
| } |
| break; |
| } |
| if (stmts != null) { |
| for (StatementTree st : stmts) { |
| if (sourcePositions.getEndPosition(root, st) <= offset) { |
| last = st; |
| } |
| } |
| } |
| scope = tu.reattributeTreeTo(block, scope, last); |
| } else { |
| tu.reattributeTreeTo(block, scope, block); |
| } |
| return new Env(offset, prefix, controller, path, sourcePositions, scope); |
| } else if (tree.getKind() == Tree.Kind.LAMBDA_EXPRESSION) { |
| controller.toPhase(JavaSource.Phase.RESOLVED); |
| Tree lambdaBody = ((LambdaExpressionTree) tree).getBody(); |
| Scope scope = null; |
| TreePath blockPath = path.getParentPath(); |
| int bodyPos = 0; |
| while (blockPath != null) { |
| if (blockPath.getLeaf().getKind() == Tree.Kind.BLOCK) { |
| if (blockPath.getParentPath().getLeaf().getKind() == Tree.Kind.METHOD |
| || TreeUtilities.CLASS_TREE_KINDS.contains(blockPath.getParentPath().getLeaf().getKind())) { |
| final int blockPos = (int) sourcePositions.getStartPosition(root, blockPath.getLeaf()); |
| final String blockText = upToOffset && getCaretInSnapshot() > offset |
| ? controller.getText().substring(blockPos, offset) + whitespaceString(getCaretInSnapshot() - offset) + controller.getText().substring(getCaretInSnapshot(), (int) sourcePositions.getEndPosition(root, blockPath.getLeaf())) |
| : controller.getText().substring(blockPos, (int) sourcePositions.getEndPosition(root, blockPath.getLeaf())); |
| final SourcePositions[] sp = new SourcePositions[1]; |
| final StatementTree block = (((BlockTree) blockPath.getLeaf()).isStatic() ? tu.parseStaticBlock(blockText, sp) : tu.parseStatement(blockText, sp)); |
| if (block == null) { |
| return null; |
| } |
| sourcePositions = new SourcePositionsImpl(block, sourcePositions, sp[0], blockPos, -1); |
| path = tu.getPathElementOfKind(Tree.Kind.LAMBDA_EXPRESSION, tu.pathFor(new TreePath(blockPath.getParentPath(), block), offset, sourcePositions)); |
| if (path == null) { |
| return null; |
| } |
| lambdaBody = ((LambdaExpressionTree) path.getLeaf()).getBody(); |
| bodyPos = (int) sourcePositions.getStartPosition(root, lambdaBody); |
| if (bodyPos >= offset) { |
| TokenSequence<JavaTokenId> ts = controller.getTokenHierarchy().tokenSequence(JavaTokenId.language()); |
| ts.move(offset); |
| while (ts.movePrevious()) { |
| switch (ts.token().id()) { |
| case WHITESPACE: |
| case LINE_COMMENT: |
| case BLOCK_COMMENT: |
| case JAVADOC_COMMENT: |
| break; |
| case ARROW: |
| scope = controller.getTrees().getScope(blockPath); |
| scope = tu.reattributeTreeTo(block, scope, lambdaBody); |
| return new Env(offset, prefix, controller, path, sourcePositions, scope); |
| default: |
| return null; |
| } |
| } |
| } |
| scope = controller.getTrees().getScope(blockPath); |
| scope = tu.reattributeTreeTo(block, scope, lambdaBody); |
| break; |
| } |
| } |
| blockPath = blockPath.getParentPath(); |
| } |
| if (scope == null) { |
| scope = controller.getTrees().getScope(new TreePath(path, lambdaBody)); |
| bodyPos = (int) sourcePositions.getStartPosition(root, lambdaBody); |
| if (bodyPos >= offset) { |
| TokenSequence<JavaTokenId> ts = controller.getTokenHierarchy().tokenSequence(JavaTokenId.language()); |
| ts.move(offset); |
| while (ts.movePrevious()) { |
| switch (ts.token().id()) { |
| case WHITESPACE: |
| case LINE_COMMENT: |
| case BLOCK_COMMENT: |
| case JAVADOC_COMMENT: |
| break; |
| case ARROW: |
| return new Env(offset, prefix, controller, path, sourcePositions, scope); |
| default: |
| return null; |
| } |
| } |
| } |
| } |
| String bodyText = controller.getText().substring(bodyPos, upToOffset ? offset : (int) sourcePositions.getEndPosition(root, lambdaBody)); |
| final SourcePositions[] sp = new SourcePositions[1]; |
| final Tree body = bodyText.charAt(0) == '{' ? tu.parseStatement(bodyText, sp) : tu.parseExpression(bodyText, sp); |
| final Tree fake = body instanceof ExpressionTree ? new ExpressionStatementTree() { |
| @Override |
| public Object accept(TreeVisitor v, Object p) { |
| return v.visitExpressionStatement(this, p); |
| } |
| |
| @Override |
| public ExpressionTree getExpression() { |
| return (ExpressionTree) body; |
| } |
| |
| @Override |
| public Tree.Kind getKind() { |
| return Tree.Kind.EXPRESSION_STATEMENT; |
| } |
| } : body; |
| sourcePositions = new SourcePositionsImpl(fake, sourcePositions, sp[0], bodyPos, upToOffset ? offset : -1); |
| path = tu.pathFor(new TreePath(path, fake), offset, sourcePositions); |
| if (upToOffset && !(body instanceof ExpressionTree)) { |
| Tree last = path.getLeaf(); |
| List<? extends StatementTree> stmts = null; |
| switch (path.getLeaf().getKind()) { |
| case BLOCK: |
| stmts = ((BlockTree) path.getLeaf()).getStatements(); |
| break; |
| case FOR_LOOP: |
| stmts = ((ForLoopTree) path.getLeaf()).getInitializer(); |
| break; |
| case ENHANCED_FOR_LOOP: |
| stmts = Collections.singletonList(((EnhancedForLoopTree) path.getLeaf()).getStatement()); |
| break; |
| case METHOD: |
| stmts = ((MethodTree) path.getLeaf()).getParameters(); |
| break; |
| case SWITCH: |
| CaseTree lastCase = null; |
| for (CaseTree caseTree : ((SwitchTree) path.getLeaf()).getCases()) { |
| lastCase = caseTree; |
| } |
| if (lastCase != null) { |
| stmts = lastCase.getStatements(); |
| } |
| break; |
| case CASE: |
| stmts = ((CaseTree) path.getLeaf()).getStatements(); |
| break; |
| } |
| if (stmts != null) { |
| for (StatementTree st : stmts) { |
| if (sourcePositions.getEndPosition(root, st) <= offset) { |
| last = st; |
| } |
| } |
| } |
| scope = tu.reattributeTreeTo(body, scope, last); |
| } else { |
| scope = tu.reattributeTreeTo(body, scope, body); |
| } |
| return new Env(offset, prefix, controller, path, sourcePositions, scope); |
| } else if (grandParent != null && TreeUtilities.CLASS_TREE_KINDS.contains(grandParent.getKind()) |
| && parent != null && parent.getKind() == Tree.Kind.VARIABLE && unwrapErrTree(((VariableTree) parent).getInitializer()) == tree) { |
| if (tu.isEnum((ClassTree) grandParent)) { |
| controller.toPhase(JavaSource.Phase.RESOLVED); |
| return null; |
| } |
| controller.toPhase(withinAnonymousOrLocalClass(tu, path) ? JavaSource.Phase.RESOLVED : JavaSource.Phase.ELEMENTS_RESOLVED); |
| Scope scope = controller.getTrees().getScope(path); |
| final int initPos = (int) sourcePositions.getStartPosition(root, tree); |
| String initText = controller.getText().substring(initPos, upToOffset ? offset : (int) sourcePositions.getEndPosition(root, tree)); |
| if (initText.length() > 0) { |
| final SourcePositions[] sp = new SourcePositions[1]; |
| final ExpressionTree init = tu.parseVariableInitializer(initText, sp); |
| final ExpressionStatementTree fake = new ExpressionStatementTree() { |
| @Override |
| public Object accept(TreeVisitor v, Object p) { |
| return v.visitExpressionStatement(this, p); |
| } |
| |
| @Override |
| public ExpressionTree getExpression() { |
| return init; |
| } |
| |
| @Override |
| public Tree.Kind getKind() { |
| return Tree.Kind.EXPRESSION_STATEMENT; |
| } |
| }; |
| sourcePositions = new SourcePositionsImpl(fake, sourcePositions, sp[0], initPos, upToOffset ? offset : -1); |
| path = tu.pathFor(new TreePath(pPath, fake), offset, sourcePositions); |
| if (upToOffset && sp[0].getEndPosition(root, init) + initPos > offset) { |
| scope = tu.reattributeTreeTo(init, scope, path.getLeaf()); |
| } else { |
| tu.reattributeTree(init, scope); |
| } |
| } |
| return new Env(offset, prefix, controller, path, sourcePositions, scope); |
| } else if (parent != null && TreeUtilities.CLASS_TREE_KINDS.contains(parent.getKind()) && tree.getKind() == Tree.Kind.VARIABLE |
| && ((VariableTree) tree).getInitializer() != null && orig == path |
| && sourcePositions.getStartPosition(root, ((VariableTree) tree).getInitializer()) >= 0 |
| && sourcePositions.getStartPosition(root, ((VariableTree) tree).getInitializer()) <= offset) { |
| controller.toPhase(withinAnonymousOrLocalClass(tu, path) ? JavaSource.Phase.RESOLVED : JavaSource.Phase.ELEMENTS_RESOLVED); |
| tree = ((VariableTree) tree).getInitializer(); |
| Scope scope = controller.getTrees().getScope(new TreePath(path, tree)); |
| final int initPos = (int) sourcePositions.getStartPosition(root, tree); |
| String initText = controller.getText().substring(initPos, offset); |
| if (initText.length() > 0) { |
| final SourcePositions[] sp = new SourcePositions[1]; |
| final ExpressionTree init = tu.parseVariableInitializer(initText, sp); |
| final ExpressionStatementTree fake = new ExpressionStatementTree() { |
| @Override |
| public Object accept(TreeVisitor v, Object p) { |
| return v.visitExpressionStatement(this, p); |
| } |
| |
| @Override |
| public ExpressionTree getExpression() { |
| return init; |
| } |
| |
| @Override |
| public Tree.Kind getKind() { |
| return Tree.Kind.EXPRESSION_STATEMENT; |
| } |
| }; |
| sourcePositions = new SourcePositionsImpl(fake, sourcePositions, sp[0], initPos, offset); |
| path = tu.pathFor(new TreePath(path, fake), offset, sourcePositions); |
| tu.reattributeTree(init, scope); |
| } |
| return new Env(offset, prefix, controller, path, sourcePositions, scope); |
| } |
| return null; |
| } |
| |
| private static String whitespaceString(int length) { |
| StringBuilder sb = new StringBuilder(); |
| for (int i = 0; i < length; i++) { |
| sb.append(' '); |
| } |
| return sb.toString(); |
| } |
| |
| private static class SourcePositionsImpl extends ErrorAwareTreeScanner<Void, Tree> implements SourcePositions { |
| |
| private final Tree root; |
| private final SourcePositions original; |
| private final SourcePositions modified; |
| private final int startOffset; |
| private final int endOffset; |
| |
| private boolean found; |
| |
| private SourcePositionsImpl(Tree root, SourcePositions original, SourcePositions modified, int startOffset, int endOffset) { |
| this.root = root; |
| this.original = original; |
| this.modified = modified; |
| this.startOffset = startOffset; |
| this.endOffset = endOffset; |
| } |
| |
| @Override |
| public long getStartPosition(CompilationUnitTree compilationUnitTree, Tree tree) { |
| if (tree == root) { |
| return startOffset; |
| } |
| found = false; |
| scan(root, tree); |
| return found ? modified.getStartPosition(compilationUnitTree, tree) + startOffset : original.getStartPosition(compilationUnitTree, tree); |
| } |
| |
| @Override |
| public long getEndPosition(CompilationUnitTree compilationUnitTree, Tree tree) { |
| if (tree == root) { |
| return endOffset; |
| } |
| found = false; |
| scan(root, tree); |
| return found ? modified.getEndPosition(compilationUnitTree, tree) + startOffset : original.getEndPosition(compilationUnitTree, tree); |
| } |
| |
| @Override |
| public Void scan(Tree node, Tree p) { |
| if (node == p) { |
| found = true; |
| } else { |
| super.scan(node, p); |
| } |
| return null; |
| } |
| |
| } |
| |
| private static boolean isCamelCasePrefix(String prefix) { |
| if (prefix == null || prefix.length() < 2 || prefix.charAt(0) == '"') { |
| return false; |
| } |
| for (int i = 1; i < prefix.length(); i++) { |
| if (Character.isUpperCase(prefix.charAt(i))) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private static boolean withinAnonymousOrLocalClass(TreeUtilities tu, TreePath path) { |
| do { |
| path = tu.getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, path); |
| if (path == null) { |
| return false; |
| } |
| path = path.getParentPath(); |
| if (path.getLeaf().getKind() != Tree.Kind.COMPILATION_UNIT && !TreeUtilities.CLASS_TREE_KINDS.contains(path.getLeaf().getKind())) { |
| return true; |
| } |
| } while (true); |
| } |
| |
| static final class Env { |
| |
| private final int offset; |
| private final String prefix; |
| private final boolean isCamelCasePrefix; |
| private final CompilationController controller; |
| private final TreePath path; |
| private final SourcePositions sourcePositions; |
| private Scope scope; |
| private ReferencesCount referencesCount; |
| private Map<Name, Element> refs = null; |
| private boolean afterExtends = false; |
| private boolean insideNew = false; |
| private boolean insideForEachExpression = false; |
| private boolean insideClass = false; |
| private Set<? extends TypeMirror> smartTypes = null; |
| private Set<Element> excludes = null; |
| private Set<String> kws = new HashSet<>(); |
| private boolean addSemicolon = false; |
| private boolean checkAddSemicolon = true; |
| private int assignToVarPos = -2; |
| private boolean checkAccessibility = true; |
| |
| private Env(int offset, String prefix, CompilationController controller, TreePath path, SourcePositions sourcePositions, Scope scope) { |
| this.offset = offset; |
| this.prefix = prefix; |
| this.isCamelCasePrefix = BaseTask.isCamelCasePrefix(prefix); |
| this.controller = controller; |
| this.path = path; |
| this.sourcePositions = sourcePositions; |
| this.scope = scope; |
| } |
| |
| public int getOffset() { |
| return offset; |
| } |
| |
| public String getPrefix() { |
| return prefix; |
| } |
| |
| public boolean isCamelCasePrefix() { |
| return isCamelCasePrefix; |
| } |
| |
| public CompilationController getController() { |
| return controller; |
| } |
| |
| public CompilationUnitTree getRoot() { |
| return path.getCompilationUnit(); |
| } |
| |
| public TreePath getPath() { |
| return path; |
| } |
| |
| public SourcePositions getSourcePositions() { |
| return sourcePositions; |
| } |
| |
| public Scope getScope() throws IOException { |
| if (scope == null) { |
| controller.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED); |
| scope = controller.getTreeUtilities().scopeFor(offset); |
| } |
| return scope; |
| } |
| |
| public ReferencesCount getReferencesCount() { |
| if (referencesCount == null) { |
| referencesCount = ReferencesCount.get(controller.getClasspathInfo()); |
| } |
| return referencesCount; |
| } |
| |
| public Map<Name, ? extends Element> getForwardReferences() { |
| if (refs == null) { |
| refs = new HashMap<>(); |
| for (Element ref : SourceUtils.getForwardReferences(path, offset, sourcePositions, controller.getTrees())) { |
| refs.put(ref.getSimpleName(), ref); |
| } |
| } |
| return refs; |
| } |
| |
| public boolean isAfterExtends() { |
| return afterExtends; |
| } |
| |
| public void afterExtends() { |
| this.afterExtends = true; |
| } |
| |
| public void insideForEachExpression() { |
| this.insideForEachExpression = true; |
| } |
| |
| public boolean isInsideForEachExpression() { |
| return insideForEachExpression; |
| } |
| |
| public void insideNew() { |
| this.insideNew = true; |
| } |
| |
| public boolean isInsideNew() { |
| return insideNew; |
| } |
| |
| public void insideClass() { |
| this.insideClass = true; |
| } |
| |
| public boolean isInsideClass() { |
| return insideClass; |
| } |
| |
| public void setSmartTypes(Set<? extends TypeMirror> smartTypes) throws IOException { |
| this.smartTypes = smartTypes; |
| } |
| |
| public Set<? extends TypeMirror> getSmartTypes() throws IOException { |
| return smartTypes; |
| } |
| |
| public void addToExcludes(Element toExclude) { |
| if (toExclude != null) { |
| if (excludes == null) { |
| excludes = new HashSet<>(); |
| } |
| excludes.add(toExclude); |
| } |
| } |
| |
| public Set<? extends Element> getExcludes() { |
| return excludes; |
| } |
| |
| public void addExcludedKW(String kw) { |
| if (kw != null) { |
| kws.add(kw); |
| } |
| } |
| |
| public boolean isExcludedKW(String kw) { |
| return kws.contains(kw); |
| } |
| |
| public void skipAccessibilityCheck() { |
| this.checkAccessibility = false; |
| } |
| |
| public boolean isAccessible(Scope scope, Element member, TypeMirror type, boolean selectSuper) { |
| if (!checkAccessibility) { |
| return true; |
| } |
| if (type.getKind() != TypeKind.DECLARED) { |
| return member.getModifiers().contains(PUBLIC); |
| } |
| if (getController().getTrees().isAccessible(scope, member, (DeclaredType) type)) { |
| return true; |
| } |
| return selectSuper |
| && member.getModifiers().contains(PROTECTED) && !member.getModifiers().contains(STATIC) |
| && !member.getKind().isClass() && !member.getKind().isInterface() |
| && getController().getTrees().isAccessible(scope, (TypeElement) ((DeclaredType) type).asElement()) |
| && (member.getKind() != METHOD |
| || getController().getElementUtilities().getImplementationOf((ExecutableElement) member, (TypeElement) ((DeclaredType) type).asElement()) == member); |
| } |
| |
| public boolean addSemicolon() { |
| if (checkAddSemicolon) { |
| TreePath tp = getPath(); |
| Tree tree = tp.getLeaf(); |
| if (tree.getKind() == Tree.Kind.IDENTIFIER || tree.getKind() == Tree.Kind.PRIMITIVE_TYPE) { |
| tp = tp.getParentPath(); |
| if (tp.getLeaf().getKind() == Tree.Kind.VARIABLE && ((VariableTree) tp.getLeaf()).getType() == tree) { |
| addSemicolon = true; |
| } |
| } |
| if (tp.getLeaf().getKind() == Tree.Kind.MEMBER_SELECT |
| || (tp.getLeaf().getKind() == Tree.Kind.METHOD_INVOCATION && ((MethodInvocationTree) tp.getLeaf()).getMethodSelect() == tree) |
| || tp.getLeaf().getKind() == Tree.Kind.VARIABLE) { |
| tp = tp.getParentPath(); |
| } |
| if (tp.getLeaf().getKind() == Tree.Kind.EXPRESSION_STATEMENT |
| && tp.getParentPath().getLeaf().getKind() != Tree.Kind.LAMBDA_EXPRESSION |
| || tp.getLeaf().getKind() == Tree.Kind.BLOCK |
| || tp.getLeaf().getKind() == Tree.Kind.RETURN) { |
| addSemicolon = true; |
| } |
| checkAddSemicolon = false; |
| } |
| return addSemicolon; |
| } |
| |
| public int assignToVarPos() { |
| if (assignToVarPos < -1) { |
| TreePath tp = getPath(); |
| Tree tree = tp.getLeaf(); |
| if (tp.getLeaf().getKind() == Tree.Kind.MEMBER_SELECT |
| || (tp.getLeaf().getKind() == Tree.Kind.METHOD_INVOCATION && ((MethodInvocationTree) tp.getLeaf()).getMethodSelect() == tree)) { |
| tp = tp.getParentPath(); |
| } |
| if (tp.getLeaf().getKind() == Tree.Kind.EXPRESSION_STATEMENT) { |
| assignToVarPos = getController().getSnapshot().getOriginalOffset((int) getSourcePositions().getStartPosition(getRoot(), tree)); |
| } else if (tp.getLeaf().getKind() == Tree.Kind.BLOCK) { |
| assignToVarPos = getController().getSnapshot().getOriginalOffset(offset); |
| } else { |
| assignToVarPos = -1; |
| } |
| } |
| return assignToVarPos; |
| } |
| } |
| } |