| /* |
| * 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.api.java.source; |
| |
| import java.io.IOException; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.InvocationTargetException; |
| import java.net.URI; |
| import java.nio.CharBuffer; |
| |
| import com.sun.source.doctree.DocCommentTree; |
| import com.sun.source.doctree.DocTree; |
| import com.sun.source.doctree.ReferenceTree; |
| import com.sun.source.tree.*; |
| import com.sun.source.tree.Tree.Kind; |
| import com.sun.source.util.DocSourcePositions; |
| import com.sun.source.util.DocTreePath; |
| import com.sun.source.util.DocTreePathScanner; |
| import com.sun.source.util.SourcePositions; |
| import com.sun.source.util.TreePath; |
| import org.netbeans.api.java.source.support.ErrorAwareTreePathScanner; |
| import org.netbeans.api.java.source.support.ErrorAwareTreeScanner; |
| import com.sun.source.util.Trees; |
| import com.sun.tools.javac.api.JavacScope; |
| import com.sun.tools.javac.api.JavacTrees; |
| import com.sun.tools.javac.code.Flags; |
| import com.sun.tools.javac.code.Symbol; |
| import com.sun.tools.javac.code.Type; |
| import com.sun.tools.javac.comp.AttrContext; |
| import com.sun.tools.javac.comp.Enter; |
| import com.sun.tools.javac.comp.Env; |
| import com.sun.tools.javac.comp.Resolve; |
| import com.sun.tools.javac.tree.DCTree.DCReference; |
| import com.sun.tools.javac.tree.JCTree; |
| import com.sun.tools.javac.tree.JCTree.JCMethodDecl; |
| import com.sun.tools.javac.tree.TreeInfo; |
| import com.sun.tools.javac.util.Context; |
| |
| import java.util.*; |
| import java.util.function.Function; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| |
| import javax.lang.model.SourceVersion; |
| import javax.lang.model.element.*; |
| import javax.lang.model.type.DeclaredType; |
| import javax.lang.model.type.TypeKind; |
| import javax.lang.model.type.TypeMirror; |
| import javax.lang.model.type.UnionType; |
| import javax.lang.model.util.ElementFilter; |
| import javax.lang.model.util.Elements; |
| import javax.lang.model.util.Types; |
| import javax.tools.Diagnostic; |
| import javax.tools.JavaFileObject; |
| import javax.tools.SimpleJavaFileObject; |
| |
| import com.sun.source.util.DocTrees; |
| import com.sun.tools.javac.api.JavacTaskImpl; |
| import com.sun.tools.javac.code.Kinds; |
| import com.sun.tools.javac.comp.ArgumentAttr; |
| import com.sun.tools.javac.comp.Attr; |
| import com.sun.tools.javac.comp.Check.CheckContext; |
| import com.sun.tools.javac.comp.DeferredAttr; |
| import com.sun.tools.javac.comp.InferenceContext; |
| import com.sun.tools.javac.main.JavaCompiler; |
| import com.sun.tools.javac.parser.JavacParser; |
| import com.sun.tools.javac.parser.Parser; |
| import com.sun.tools.javac.parser.ParserFactory; |
| import com.sun.tools.javac.parser.ScannerFactory; |
| import com.sun.tools.javac.tree.JCTree.JCBlock; |
| import com.sun.tools.javac.tree.JCTree.JCClassDecl; |
| import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; |
| import com.sun.tools.javac.tree.JCTree.JCExpression; |
| import com.sun.tools.javac.tree.JCTree.JCVariableDecl; |
| import com.sun.tools.javac.util.JCDiagnostic; |
| import com.sun.tools.javac.util.Log; |
| import com.sun.tools.javac.util.Names; |
| import com.sun.tools.javac.util.Warner; |
| import java.lang.reflect.Method; |
| import org.netbeans.api.annotations.common.CheckForNull; |
| import org.netbeans.api.annotations.common.NonNull; |
| import org.netbeans.api.annotations.common.NullAllowed; |
| import org.netbeans.api.java.lexer.JavaTokenId; |
| import org.netbeans.api.java.lexer.JavadocTokenId; |
| import org.netbeans.api.java.source.JavaSource.Phase; |
| import org.netbeans.api.lexer.TokenSequence; |
| import org.netbeans.lib.nbjavac.services.CancelService; |
| import org.netbeans.lib.nbjavac.services.NBAttr; |
| import org.netbeans.lib.nbjavac.services.NBParserFactory; |
| import org.netbeans.lib.nbjavac.services.NBResolve; |
| import org.netbeans.modules.java.source.TreeShims; |
| import org.netbeans.modules.java.source.TreeUtilitiesAccessor; |
| import org.netbeans.modules.java.source.builder.CommentHandlerService; |
| import org.netbeans.modules.java.source.builder.CommentSetImpl; |
| import org.netbeans.modules.java.source.matching.CopyFinder; |
| import org.netbeans.modules.java.source.matching.CopyFinder.HackScope; |
| import org.netbeans.modules.java.source.pretty.ImportAnalysis2; |
| import org.netbeans.modules.java.source.transform.ImmutableDocTreeTranslator; |
| import org.netbeans.modules.java.source.transform.ImmutableTreeTranslator; |
| import org.openide.util.Exceptions; |
| |
| /** |
| * |
| * @author Jan Lahoda, Dusan Balek, Tomas Zezula |
| */ |
| public final class TreeUtilities { |
| |
| /**{@link Kind}s that are represented by {@link ClassTree}. |
| * |
| * @since 0.67 |
| */ |
| public static final Set<Kind> CLASS_TREE_KINDS = EnumSet.of(Kind.ANNOTATION_TYPE, Kind.CLASS, Kind.ENUM, Kind.INTERFACE); |
| static { |
| Kind recKind = null; |
| try { |
| recKind = Kind.valueOf(TreeShims.RECORD); |
| CLASS_TREE_KINDS.add(recKind); |
| } catch (IllegalArgumentException ex) { |
| } |
| } |
| private final CompilationInfo info; |
| private final CommentHandlerService handler; |
| |
| /** Creates a new instance of CommentUtilities */ |
| TreeUtilities(final CompilationInfo info) { |
| assert info != null; |
| this.info = info; |
| this.handler = CommentHandlerService.instance(info.impl.getJavacTask().getContext()); |
| } |
| |
| /**Checks whether the given tree represents a class. |
| * @deprecated since 0.67, <code>Tree.getKind() == Kind.CLASS</code> should be used instead. |
| */ |
| @Deprecated |
| public boolean isClass(ClassTree tree) { |
| return (((JCTree.JCModifiers)tree.getModifiers()).flags & (Flags.INTERFACE | Flags.ENUM | Flags.ANNOTATION)) == 0; |
| } |
| |
| /**Checks whether the given tree represents an interface. |
| * @deprecated since 0.67, <code>Tree.getKind() == Kind.INTERFACE</code> should be used instead. |
| */ |
| @Deprecated |
| public boolean isInterface(ClassTree tree) { |
| final long flags = ((JCTree.JCModifiers) tree.getModifiers()).flags; |
| return (flags & Flags.INTERFACE) != 0 && (flags & Flags.ANNOTATION) == 0; |
| } |
| |
| /**Checks whether the given tree represents an enum. |
| * @deprecated since 0.67, <code>Tree.getKind() == Kind.ENUM</code> should be used instead. |
| */ |
| @Deprecated |
| public boolean isEnum(ClassTree tree) { |
| return (((JCTree.JCModifiers)tree.getModifiers()).flags & Flags.ENUM) != 0; |
| } |
| |
| /** |
| * Checks wheteher given variable tree represents an enum constant. |
| */ |
| public boolean isEnumConstant(VariableTree tree) { |
| return (((JCTree.JCModifiers) tree.getModifiers()).flags & Flags.ENUM) != 0; |
| } |
| |
| /**Checks whether the given tree represents an annotation. |
| * @deprecated since 0.67, <code>Tree.getKind() == Kind.ANNOTATION_TYPE</code> should be used instead. |
| */ |
| @Deprecated |
| public boolean isAnnotation(ClassTree tree) { |
| return (((JCTree.JCModifiers)tree.getModifiers()).flags & Flags.ANNOTATION) != 0; |
| } |
| |
| /** |
| * Checks wheteher given compilation unit represents a package-info. |
| * @since 2.23 |
| */ |
| public boolean isPackageInfo(CompilationUnitTree tree) { |
| return TreeInfo.isPackageInfo((JCTree.JCCompilationUnit)tree); |
| } |
| |
| /** |
| * Checks wheteher given compilation unit represents a module-info. |
| * @since 2.23 |
| */ |
| public boolean isModuleInfo(CompilationUnitTree tree) { |
| return TreeInfo.isModuleInfo((JCTree.JCCompilationUnit)tree); |
| } |
| |
| /**Returns whether or not the given tree is synthetic - generated by the parser. |
| * Please note that this method does not check trees transitively - a child of a syntetic tree |
| * may be considered non-syntetic. |
| * |
| * @return true if the given tree is synthetic, false otherwise |
| * @throws NullPointerException if the given tree is null |
| */ |
| public boolean isSynthetic(TreePath path) throws NullPointerException { |
| if (path == null) |
| throw new NullPointerException(); |
| |
| while (path != null) { |
| if (isSynthetic(path.getCompilationUnit(), path.getLeaf())) |
| return true; |
| if (path.getParentPath() != null && |
| path.getParentPath().getParentPath() != null && |
| path.getParentPath().getParentPath().getLeaf().getKind() == Kind.NEW_CLASS) { |
| NewClassTree nct = (NewClassTree) path.getParentPath().getParentPath().getLeaf(); |
| ClassTree body = nct.getClassBody(); |
| |
| if (body != null && |
| (body.getExtendsClause() == path.getLeaf() || |
| body.getImplementsClause().contains(path.getLeaf()))) { |
| return true; |
| } |
| } |
| |
| path = path.getParentPath(); |
| } |
| |
| return false; |
| } |
| |
| boolean isSynthetic(CompilationUnitTree cut, Tree leaf) throws NullPointerException { |
| JCTree tree = (JCTree) leaf; |
| |
| if (tree.pos == (-1)) |
| return true; |
| |
| if (leaf.getKind() == Kind.METHOD) { |
| //check for synthetic constructor: |
| return (((JCMethodDecl)leaf).mods.flags & Flags.GENERATEDCONSTR) != 0L; |
| } |
| |
| //check for synthetic superconstructor call: |
| if (leaf.getKind() == Kind.EXPRESSION_STATEMENT) { |
| ExpressionStatementTree est = (ExpressionStatementTree) leaf; |
| |
| if (est.getExpression().getKind() == Kind.METHOD_INVOCATION) { |
| MethodInvocationTree mit = (MethodInvocationTree) est.getExpression(); |
| |
| if (mit.getMethodSelect().getKind() == Kind.IDENTIFIER) { |
| IdentifierTree it = (IdentifierTree) mit.getMethodSelect(); |
| |
| if ("super".equals(it.getName().toString())) { |
| SourcePositions sp = info.getTrees().getSourcePositions(); |
| |
| return sp.getEndPosition(cut, leaf) == (-1); |
| } |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Returns path to the deepest tree of the given kind containing the given |
| * starting tree. |
| * @param kind requested tree kind |
| * @param path path to the starting tree |
| * @return requested path or null if no tree of the given kind encloses |
| * the given starting tree |
| * |
| * @since 0.136 |
| */ |
| public TreePath getPathElementOfKind(Tree.Kind kind, TreePath path) { |
| return getPathElementOfKind(EnumSet.of(kind), path); |
| } |
| |
| /** |
| * Returns path to the deepest tree of one of the given kinds containing |
| * the given starting tree. |
| * @param kinds requested tree kinds |
| * @param path path to the starting tree |
| * @return requested path or null if no tree of the given kinds encloses |
| * the given starting tree |
| * |
| * @since 0.136 |
| */ |
| public TreePath getPathElementOfKind(Set<Tree.Kind> kinds, TreePath path) { |
| while (path != null) { |
| if (kinds.contains(path.getLeaf().getKind())) { |
| return path; |
| } |
| path = path.getParentPath(); |
| } |
| return null; |
| } |
| |
| /**Returns list of comments attached to a given tree. Can return either |
| * preceding or trailing comments. |
| * |
| * @param tree for which comments should be returned |
| * @param preceding true if preceding comments should be returned, false if trailing comments should be returned. |
| * @return list of preceding/trailing comments attached to the given tree |
| */ |
| public List<Comment> getComments(Tree tree, boolean preceding) { |
| CommentSetImpl set = handler.getComments(tree); |
| |
| ensureCommentsMapped(info, tree, set); |
| |
| List<Comment> comments = preceding ? set.getPrecedingComments() : set.getTrailingComments(); |
| |
| return Collections.unmodifiableList(comments); |
| } |
| |
| static void ensureCommentsMapped(CompilationInfo info, @NullAllowed Tree tree, CommentSetImpl set) { |
| if (!set.areCommentsMapped() && tree != null) { |
| boolean assertsEnabled = false; |
| boolean automap = true; |
| |
| assert assertsEnabled = true; |
| |
| TreePath tp = info.getCompilationUnit() == tree ? new TreePath(info.getCompilationUnit()) : TreePath.getPath(info.getCompilationUnit(), tree); |
| |
| if (tp == null) { |
| if (assertsEnabled && !info.getTreeUtilities().isSynthetic(info.getCompilationUnit(), tree)) { |
| // HACK: if info is a working copy, the tree might be introduced by rewriting; |
| // in that case, no log should be printed |
| if (!(info instanceof WorkingCopy) || !((WorkingCopy)info).validateIsReplacement(tree)) { |
| Logger.getLogger(TreeUtilities.class.getName()).log(assertsEnabled ? Level.WARNING : Level.FINE, "Comment automap requested for Tree not from the root compilation info. Please, make sure to call GeneratorUtilities.importComments before Treeutilities.getComments. Tree: {0}", tree); |
| Logger.getLogger(TreeUtilities.class.getName()).log(assertsEnabled ? Level.INFO : Level.FINE, "Caller", new Exception()); |
| } |
| } |
| automap = false; |
| } |
| |
| if (automap) { |
| GeneratorUtilities.importComments(info, tree, info.getCompilationUnit()); |
| } |
| } |
| } |
| |
| public TreePath pathFor(int pos) { |
| return pathFor(new TreePath(info.getCompilationUnit()), pos); |
| } |
| |
| public TreePath pathFor(TreePath path, int pos) { |
| return pathFor(path, pos, info.getTrees().getSourcePositions()); |
| } |
| |
| public TreePath pathFor(TreePath path, int pos, SourcePositions sourcePositions) { |
| if (info == null || path == null || sourcePositions == null) |
| throw new IllegalArgumentException(); |
| |
| class Result extends Error { |
| TreePath path; |
| Result(TreePath path) { |
| this.path = path; |
| } |
| } |
| |
| class PathFinder extends ErrorAwareTreePathScanner<Void,Void> { |
| private int pos; |
| private SourcePositions sourcePositions; |
| |
| private PathFinder(int pos, SourcePositions sourcePositions) { |
| this.pos = pos; |
| this.sourcePositions = sourcePositions; |
| } |
| |
| public Void scan(Tree tree, Void p) { |
| if (tree != null) { |
| long endPos = sourcePositions.getEndPosition(getCurrentPath().getCompilationUnit(), tree); |
| if (endPos == (-1) && tree.getKind() == Kind.ASSIGNMENT && getCurrentPath().getLeaf().getKind() == Kind.ANNOTATION) { |
| ExpressionTree value = ((AssignmentTree) tree).getExpression(); |
| endPos = sourcePositions.getEndPosition(getCurrentPath().getCompilationUnit(), value); |
| } |
| if (sourcePositions.getStartPosition(getCurrentPath().getCompilationUnit(), tree) < pos && endPos >= pos) { |
| if (tree.getKind() == Tree.Kind.ERRONEOUS) { |
| tree.accept(this, p); |
| throw new Result(getCurrentPath()); |
| } |
| super.scan(tree, p); |
| throw new Result(new TreePath(getCurrentPath(), tree)); |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public Void visitVariable(VariableTree node, Void p) { |
| int[] span = findNameSpan(node); |
| |
| if (span != null && span[0] <= pos && pos < span[1]) { |
| throw new Result(getCurrentPath()); |
| } |
| |
| return super.visitVariable(node, p); |
| } |
| |
| @Override |
| public Void visitMethod(MethodTree node, Void p) { |
| int[] span = findNameSpan(node); |
| |
| if (span != null && span[0] <= pos && pos < span[1]) { |
| throw new Result(getCurrentPath()); |
| } |
| |
| return super.visitMethod(node, p); |
| } |
| |
| @Override |
| public Void visitEnhancedForLoop(EnhancedForLoopTree node, Void p) { |
| int exprEndPos = (int) sourcePositions.getEndPosition(getCurrentPath().getCompilationUnit(), node.getExpression()); |
| if (exprEndPos < pos) { |
| TokenSequence<JavaTokenId> ts = info.getTokenHierarchy().tokenSequence(JavaTokenId.language()).subSequence(exprEndPos, pos); |
| boolean hasNonWhiteSpace; |
| while (hasNonWhiteSpace = ts.moveNext()) { |
| if (!IGNORE_TOKENS.contains(ts.token().id())) |
| break; |
| } |
| if (!hasNonWhiteSpace) { |
| pos = exprEndPos; |
| } |
| } |
| return super.visitEnhancedForLoop(node, p); |
| } |
| |
| } |
| |
| try { |
| new PathFinder(pos, sourcePositions).scan(path, null); |
| } catch (Result result) { |
| path = result.path; |
| } |
| |
| if (path.getLeaf() == path.getCompilationUnit()) |
| return path; |
| |
| TokenSequence<JavaTokenId> tokenList = tokensFor(path.getLeaf(), sourcePositions, pos); |
| tokenList.moveEnd(); |
| if (tokenList.movePrevious() && tokenList.offset() < pos) { |
| switch (tokenList.token().id()) { |
| case GTGTGT: |
| case GTGT: |
| case GT: |
| if (path.getLeaf().getKind() == Tree.Kind.MEMBER_SELECT || TreeUtilities.CLASS_TREE_KINDS.contains(path.getLeaf().getKind()) || |
| path.getLeaf().getKind() == Tree.Kind.MEMBER_REFERENCE || path.getLeaf().getKind() == Tree.Kind.GREATER_THAN) |
| break; |
| case RPAREN: |
| if (path.getLeaf().getKind() == Tree.Kind.PARENTHESIZED) { |
| path = path.getParentPath(); |
| break; |
| } else if (path.getLeaf().getKind() == Tree.Kind.ENHANCED_FOR_LOOP || path.getLeaf().getKind() == Tree.Kind.FOR_LOOP || |
| path.getLeaf().getKind() == Tree.Kind.IF || path.getLeaf().getKind() == Tree.Kind.WHILE_LOOP || |
| path.getLeaf().getKind() == Tree.Kind.DO_WHILE_LOOP || path.getLeaf().getKind() == Tree.Kind.TYPE_CAST || |
| path.getLeaf().getKind() == Tree.Kind.LAMBDA_EXPRESSION) |
| break; |
| case SEMICOLON: |
| if (path.getLeaf().getKind() == Tree.Kind.EMPTY_STATEMENT || |
| path.getLeaf().getKind() == Tree.Kind.TRY || |
| (path.getLeaf().getKind() == Tree.Kind.FOR_LOOP && |
| tokenList.offset() <= sourcePositions.getStartPosition(path.getCompilationUnit(), ((ForLoopTree)path.getLeaf()).getUpdate().get(0)))) |
| break; |
| if (path.getParentPath().getLeaf().getKind() == Tree.Kind.TRY && |
| ((TryTree) path.getParentPath().getLeaf()).getResources().contains(path.getLeaf())) { |
| path = path.getParentPath(); |
| break; |
| } |
| case RBRACE: |
| path = path.getParentPath(); |
| switch (path.getLeaf().getKind()) { |
| case CATCH: |
| path = path.getParentPath(); |
| case METHOD: |
| case FOR_LOOP: |
| case ENHANCED_FOR_LOOP: |
| case SYNCHRONIZED: |
| case WHILE_LOOP: |
| case TRY: |
| path = path.getParentPath(); |
| break; |
| case IF: |
| do { |
| path = path.getParentPath(); |
| } while (path != null && path.getLeaf().getKind() == Tree.Kind.IF); |
| break; |
| } |
| break; |
| } |
| } |
| return path; |
| } |
| |
| private static final Set<JavaTokenId> IGNORE_TOKENS = EnumSet.of( |
| JavaTokenId.BLOCK_COMMENT, JavaTokenId.JAVADOC_COMMENT, |
| JavaTokenId.LINE_COMMENT, JavaTokenId.WHITESPACE |
| ); |
| |
| /**Return the deepest DocTreePath at the given position. |
| * |
| * @param treepath for which the {@code doc} comment was determined |
| * @param doc the documentation comment inside which the search should be performed |
| * @param pos the position to search for |
| * @return the deepest DocTreePath at the given position |
| * @since 0.124 |
| */ |
| public DocTreePath pathFor(TreePath treepath, DocCommentTree doc, int pos) { |
| return pathFor(new DocTreePath(treepath, doc), pos); |
| } |
| |
| /**Return the deepest DocTreePath at the given position. |
| * |
| * @param path where the search should start |
| * @param pos the position to search for |
| * @return the deepest DocTreePath at the given position |
| * @since 0.124 |
| */ |
| public DocTreePath pathFor(DocTreePath path, int pos) { |
| return pathFor(path, pos, info.getDocTrees().getSourcePositions()); |
| } |
| |
| /**Return the deepest DocTreePath at the given position. |
| * |
| * @param path where the search should start |
| * @param pos the position to search for |
| * @param sourcePositions to determine spans of {@link DocTree}s |
| * @return the deepest DocTreePath at the given position |
| * @since 0.124 |
| */ |
| public DocTreePath pathFor(DocTreePath path, int pos, DocSourcePositions sourcePositions) { |
| if (info == null || path == null || sourcePositions == null) |
| throw new IllegalArgumentException(); |
| |
| class Result extends Error { |
| DocTreePath path; |
| Result(DocTreePath path) { |
| this.path = path; |
| } |
| } |
| |
| class PathFinder extends DocTreePathScanner<Void,TreePath> { |
| private int pos; |
| private DocSourcePositions sourcePositions; |
| |
| private PathFinder(int pos, DocSourcePositions sourcePositions) { |
| this.pos = pos; |
| this.sourcePositions = sourcePositions; |
| } |
| |
| public Void scan(DocTree tree, TreePath p) { |
| if (tree != null) { |
| if (sourcePositions.getStartPosition(p.getCompilationUnit(), getCurrentPath().getDocComment(), tree) < pos && sourcePositions.getEndPosition(p.getCompilationUnit(), getCurrentPath().getDocComment(), tree) >= pos) { |
| if (tree.getKind() == DocTree.Kind.ERRONEOUS) { |
| tree.accept(this, p); |
| throw new Result(getCurrentPath()); |
| } |
| super.scan(tree, p); |
| throw new Result(new DocTreePath(getCurrentPath(), tree)); |
| } |
| } |
| return null; |
| } |
| |
| // @Override |
| // public Void visitVariable(VariableTree node, Void p) { |
| // int[] span = findNameSpan(node); |
| // |
| // if (span != null && span[0] <= pos && pos < span[1]) { |
| // throw new Result(getCurrentPath()); |
| // } |
| // |
| // return super.visitVariable(node, p); |
| // } |
| // |
| // @Override |
| // public Void visitMethod(MethodTree node, Void p) { |
| // int[] span = findNameSpan(node); |
| // |
| // if (span != null && span[0] <= pos && pos < span[1]) { |
| // throw new Result(getCurrentPath()); |
| // } |
| // |
| // return super.visitMethod(node, p); |
| // } |
| } |
| |
| try { |
| new PathFinder(pos, sourcePositions).scan(path, path.getTreePath()); |
| } catch (Result result) { |
| path = result.path; |
| } |
| |
| if (path.getLeaf() == path.getDocComment()) |
| return path; |
| |
| return path; |
| } |
| |
| /**Parses given type in given context. |
| * |
| * @param expr type specification |
| * @param scope in which simple names should be resolved |
| * @return parsed {@link TypeMirror} or null if the given specification cannot be parsed |
| */ |
| public TypeMirror parseType(String expr, TypeElement scope) { |
| Enter enter = Enter.instance(info.impl.getJavacTask().getContext()); |
| com.sun.tools.javac.tree.TreeMaker jcMaker = com.sun.tools.javac.tree.TreeMaker.instance(info.impl.getJavacTask().getContext()); |
| int oldPos = jcMaker.pos; |
| try { |
| if (enter.getClassEnv((Symbol.TypeSymbol)scope) == null) { |
| if (info.getTrees().getTree(scope) == null) |
| return null; |
| } |
| return info.impl.getJavacTask().parseType(expr, scope); |
| } finally { |
| jcMaker.pos = oldPos; |
| } |
| } |
| |
| /**Parses given type in given context. |
| * |
| * @param expr type specification |
| * @param scope in which simple names should be resolved |
| * @return parsed {@link TypeMirror} or null if the given specification cannot be parsed |
| */ |
| Tree parseType(String expr) { |
| return doParse(expr, null, 0, Parser::parseType); |
| } |
| |
| /**Parses given statement. |
| * |
| * @param stmt statement code |
| * @param sourcePositions return value - new SourcePositions for the new tree |
| * @return parsed {@link StatementTree} or null? |
| */ |
| public StatementTree parseStatement(String stmt, SourcePositions[] sourcePositions) { |
| return parseStatementImpl(info.impl.getJavacTask(), stmt, sourcePositions); |
| } |
| |
| private static StatementTree parseStatementImpl(JavacTaskImpl task, String stmt, SourcePositions[] sourcePositions) { |
| return doParse(task, stmt, sourcePositions, 0, Parser::parseStatement); |
| } |
| |
| /**Parses given expression. |
| * |
| * @param expr expression code |
| * @param sourcePositions return value - new SourcePositions for the new tree |
| * @return parsed {@link ExpressionTree} or null? |
| */ |
| public ExpressionTree parseExpression(String expr, SourcePositions[] sourcePositions) { |
| return doParse(expr, sourcePositions, 0, Parser::parseExpression); |
| } |
| |
| /**Parses given variable initializer. |
| * |
| * @param init initializer code |
| * @param sourcePositions return value - new SourcePositions for the new tree |
| * @return parsed {@link ExpressionTree} or null? |
| */ |
| public ExpressionTree parseVariableInitializer(String init, SourcePositions[] sourcePositions) { |
| return doParse(init, sourcePositions, 0, p -> ((JavacParser) p).variableInitializer()); |
| } |
| |
| /**Parses given static block. |
| * |
| * @param block block code |
| * @param sourcePositions return value - new SourcePositions for the new tree |
| * @return parsed {@link BlockTree} or null? |
| */ |
| public BlockTree parseStaticBlock(String block, SourcePositions[] sourcePositions) { |
| return doParse("{ class C { " + block + " }", sourcePositions, "{ class C { ".length(), p -> { |
| JCClassDecl decl = (JCClassDecl) ((BlockTree) p.parseStatement()).getStatements().get(0); |
| return decl.defs.head.getKind() == Kind.BLOCK ? (BlockTree) decl.defs.head : null; //check result |
| }); |
| } |
| |
| /**Parses given statement. |
| * |
| * @param text code |
| * @param sourcePositions return value - new SourcePositions for the new tree |
| * @return parsed {@link T} or null? |
| */ |
| private <T extends Tree> T doParse(String text, SourcePositions[] sourcePositions, int offset, Function<Parser, T> actualParse) { |
| return doParse(info.impl.getJavacTask(), text, sourcePositions, offset, actualParse); |
| } |
| |
| private static <T extends Tree> T doParse(JavacTaskImpl task, String text, SourcePositions[] sourcePositions, int offset, Function<Parser, T> actualParse) { |
| if (text == null || (sourcePositions != null && sourcePositions.length != 1)) |
| throw new IllegalArgumentException(); |
| //use a working init order: |
| com.sun.tools.javac.main.JavaCompiler.instance(task.getContext()); |
| com.sun.tools.javac.tree.TreeMaker jcMaker = com.sun.tools.javac.tree.TreeMaker.instance(task.getContext()); |
| int oldPos = jcMaker.pos; |
| |
| try { |
| //from org/netbeans/modules/java/hints/spiimpl/Utilities.java: |
| Context context = task.getContext(); |
| JavaCompiler compiler = JavaCompiler.instance(context); |
| JavaFileObject prev = compiler.log.useSource(new DummyJFO()); |
| Log.DiagnosticHandler discardHandler = new Log.DiscardDiagnosticHandler(compiler.log) { |
| @Override |
| public void report(JCDiagnostic diag) { |
| //ignore: |
| } |
| }; |
| try { |
| CharBuffer buf = CharBuffer.wrap((text+"\u0000").toCharArray(), 0, text.length()); |
| ParserFactory factory = ParserFactory.instance(context); |
| Parser parser = factory.newParser(buf, false, true, false, false); |
| if (parser instanceof JavacParser) { |
| if (sourcePositions != null) |
| sourcePositions[0] = new ParserSourcePositions((JavacParser)parser, offset); |
| return actualParse.apply(parser); |
| } |
| return null; |
| } finally { |
| compiler.log.useSource(prev); |
| compiler.log.popDiagnosticHandler(discardHandler); |
| } |
| } finally { |
| jcMaker.pos = oldPos; |
| } |
| } |
| |
| //from org/netbeans/modules/java/hints/spiimpl/Utilities.java: |
| private static final class DummyJFO extends SimpleJavaFileObject { |
| private DummyJFO() { |
| super(URI.create("dummy.java"), JavaFileObject.Kind.SOURCE); |
| } |
| @Override |
| public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { |
| return ""; |
| } |
| } |
| |
| //from org/netbeans/modules/java/hints/spiimpl/Utilities.java: |
| private static class ParserSourcePositions implements SourcePositions { |
| |
| private final JavacParser parser; |
| private final int offset; |
| |
| private ParserSourcePositions(JavacParser parser, int offset) { |
| this.parser = parser; |
| this.offset = offset; |
| } |
| |
| public long getStartPosition(CompilationUnitTree file, Tree tree) { |
| return parser.getStartPos((JCTree)tree) - offset; |
| } |
| |
| public long getEndPosition(CompilationUnitTree file, Tree tree) { |
| return parser.getEndPos((JCTree)tree) - offset; |
| } |
| } |
| |
| //XXX: parseAnnotationValue |
| |
| /**Computes {@link Scope} for the given position. |
| */ |
| public Scope scopeFor(int pos) { |
| List<? extends StatementTree> stmts = null; |
| SourcePositions sourcePositions = info.getTrees().getSourcePositions(); |
| TreePath path = pathFor(pos); |
| CompilationUnitTree root = path.getCompilationUnit(); |
| 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 CASE: |
| stmts = ((CaseTree)path.getLeaf()).getStatements(); |
| break; |
| case METHOD: |
| stmts = ((MethodTree)path.getLeaf()).getParameters(); |
| break; |
| } |
| if (stmts != null) { |
| Tree tree = null; |
| for (StatementTree st : stmts) { |
| if (sourcePositions.getStartPosition(root, st) < pos) |
| tree = st; |
| } |
| if (tree != null) |
| path = new TreePath(path, tree); |
| } |
| Scope scope = info.getTrees().getScope(path); |
| if (TreeUtilities.CLASS_TREE_KINDS.contains(path.getLeaf().getKind())) { |
| TokenSequence<JavaTokenId> ts = info.getTokenHierarchy().tokenSequence(JavaTokenId.language()); |
| ts.move(pos); |
| while(ts.movePrevious()) { |
| switch (ts.token().id()) { |
| case WHITESPACE: |
| case LINE_COMMENT: |
| case BLOCK_COMMENT: |
| case JAVADOC_COMMENT: |
| break; |
| case EXTENDS: |
| case IMPLEMENTS: |
| ((JavacScope)scope).getEnv().baseClause = true; |
| default: |
| return scope; |
| } |
| } |
| } |
| return scope; |
| } |
| |
| /** |
| * Creates {@link Scope} capable to access all private methods and fields when |
| * parsing and evaluating expressions. When using this Scope, the accessibility |
| * checks would not be enforced during a tree attribution. |
| * @param scope an existing scope |
| * @return scope with disabled accessibility checks |
| * @since 0.129 |
| */ |
| public Scope toScopeWithDisabledAccessibilityChecks(Scope scope) { |
| return new NBScope((JavacScope)scope); |
| } |
| |
| private static Env<AttrContext> getEnv(Scope scope) { |
| if (scope instanceof NBScope) { |
| scope = ((NBScope) scope).delegate; |
| } |
| |
| if (scope instanceof HackScope) { |
| return ((HackScope) scope).getEnv(); |
| } |
| |
| return ((JavacScope) scope).getEnv(); |
| } |
| |
| /**Attribute the given tree in the given context. |
| */ |
| public TypeMirror attributeTree(Tree tree, Scope scope) { |
| Env<AttrContext> env = getEnv(scope); |
| if (scope instanceof NBScope && ((NBScope)scope).areAccessibilityChecksDisabled()) { |
| NBResolve.instance(info.impl.getJavacTask().getContext()).disableAccessibilityChecks(); |
| } |
| try { |
| return attributeTree(info.impl.getJavacTask(), env.toplevel, (JCTree) tree, scope, new ArrayList<>()); |
| } finally { |
| NBResolve.instance(info.impl.getJavacTask().getContext()).restoreAccessbilityChecks(); |
| } |
| } |
| |
| /**Attribute the given tree until the given <code>to</code> tree is reached. |
| * Returns scope valid at point when <code>to</code> is reached. |
| */ |
| public Scope attributeTreeTo(Tree tree, Scope scope, Tree to) { |
| Env<AttrContext> env = getEnv(scope); |
| if (scope instanceof NBScope && ((NBScope)scope).areAccessibilityChecksDisabled()) { |
| NBResolve.instance(info.impl.getJavacTask().getContext()).disableAccessibilityChecks(); |
| } |
| try { |
| return attributeTreeTo(info.impl.getJavacTask(), env.toplevel, (JCTree)tree, scope, (JCTree)to, new ArrayList<>()); |
| } finally { |
| NBResolve.instance(info.impl.getJavacTask().getContext()).restoreAccessbilityChecks(); |
| } |
| } |
| |
| public TypeMirror reattributeTree(Tree tree, Scope scope) { |
| Env<AttrContext> env = getEnv(scope); |
| if (scope instanceof NBScope && ((NBScope)scope).areAccessibilityChecksDisabled()) { |
| NBResolve.instance(info.impl.getJavacTask().getContext()).disableAccessibilityChecks(); |
| } |
| try { |
| return attributeTree(info.impl.getJavacTask(), env.toplevel, (JCTree)tree, scope, new ArrayList<>()); |
| } finally { |
| NBResolve.instance(info.impl.getJavacTask().getContext()).restoreAccessbilityChecks(); |
| } |
| } |
| |
| public Scope reattributeTreeTo(Tree tree, Scope scope, Tree to) { |
| Env<AttrContext> env = getEnv(scope); |
| if (scope instanceof NBScope && ((NBScope)scope).areAccessibilityChecksDisabled()) { |
| NBResolve.instance(info.impl.getJavacTask().getContext()).disableAccessibilityChecks(); |
| } |
| try { |
| return attributeTreeTo(info.impl.getJavacTask(), env.toplevel, (JCTree)tree, scope, (JCTree)to, new ArrayList<>()); |
| } finally { |
| NBResolve.instance(info.impl.getJavacTask().getContext()).restoreAccessbilityChecks(); |
| } |
| } |
| |
| //from org/netbeans/modules/java/hints/spiimpl/Utilities.java: |
| private static TypeMirror attributeTree(JavacTaskImpl jti, CompilationUnitTree cut, Tree tree, Scope scope, final List<Diagnostic<? extends JavaFileObject>> errors) { |
| Log log = Log.instance(jti.getContext()); |
| JavaFileObject prev = log.useSource(new DummyJFO()); |
| Log.DiagnosticHandler discardHandler = new Log.DiscardDiagnosticHandler(log) { |
| @Override |
| public void report(JCDiagnostic diag) { |
| errors.add(diag); |
| } |
| }; |
| NBResolve resolve = NBResolve.instance(jti.getContext()); |
| resolve.disableAccessibilityChecks(); |
| // Enter enter = Enter.instance(jti.getContext()); |
| // enter.shadowTypeEnvs(true); |
| // ArgumentAttr argumentAttr = ArgumentAttr.instance(jti.getContext()); |
| // ArgumentAttr.LocalCacheContext cacheContext = argumentAttr.withLocalCacheContext(); |
| try { |
| Attr attr = Attr.instance(jti.getContext()); |
| Env<AttrContext> env = getEnv(scope); |
| if (tree instanceof JCExpression) |
| return attr.attribExpr((JCTree) tree,env, Type.noType); |
| if (env.tree != null && env.tree.getKind() == Kind.VARIABLE && !VARIABLE_CAN_OWN_VARIABLES) { |
| env = env.next; |
| } |
| return attr.attribStat((JCTree) tree,env); |
| } finally { |
| unenter(jti.getContext(), (JCCompilationUnit) cut, (JCTree) tree); |
| // cacheContext.leave(); |
| log.useSource(prev); |
| log.popDiagnosticHandler(discardHandler); |
| resolve.restoreAccessbilityChecks(); |
| // enter.shadowTypeEnvs(false); |
| } |
| } |
| |
| private static void unenter(Context ctx, JCCompilationUnit cut, JCTree tree) { |
| try { |
| Method m = Enter.class.getDeclaredMethod("unenter", JCCompilationUnit.class, JCTree.class); |
| m.invoke(Enter.instance(ctx), cut, tree); |
| } catch (Throwable t) { |
| t.printStackTrace(); |
| } |
| } |
| |
| private static boolean VARIABLE_CAN_OWN_VARIABLES; |
| static { |
| boolean result; |
| try { |
| SourceVersion.valueOf("RELEASE_12"); |
| result = true; |
| } catch (IllegalArgumentException ex) { |
| result = false; |
| } |
| VARIABLE_CAN_OWN_VARIABLES = result; |
| } |
| |
| private static Scope attributeTreeTo(JavacTaskImpl jti, CompilationUnitTree cut, Tree tree, Scope scope, Tree to, final List<Diagnostic<? extends JavaFileObject>> errors) { |
| Log log = Log.instance(jti.getContext()); |
| JavaFileObject prev = log.useSource(new DummyJFO()); |
| Log.DiagnosticHandler discardHandler = new Log.DiscardDiagnosticHandler(log) { |
| @Override |
| public void report(JCDiagnostic diag) { |
| errors.add(diag); |
| } |
| }; |
| NBResolve resolve = NBResolve.instance(jti.getContext()); |
| resolve.disableAccessibilityChecks(); |
| // Enter enter = Enter.instance(jti.getContext()); |
| // enter.shadowTypeEnvs(true); |
| // ArgumentAttr argumentAttr = ArgumentAttr.instance(jti.getContext()); |
| // ArgumentAttr.LocalCacheContext cacheContext = argumentAttr.withLocalCacheContext(); |
| try { |
| NBAttr attr = (NBAttr) NBAttr.instance(jti.getContext()); |
| Env<AttrContext> env = getEnv(scope); |
| Env<AttrContext> result = attr.attributeAndCapture((JCTree) tree, env, (JCTree) to); |
| try { |
| Constructor<JavacScope> c = JavacScope.class.getDeclaredConstructor(Env.class); |
| c.setAccessible(true); |
| return c.newInstance(result); |
| } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { |
| throw new IllegalStateException(ex); |
| } |
| } finally { |
| unenter(jti.getContext(), (JCCompilationUnit) cut, (JCTree) tree); |
| // cacheContext.leave(); |
| log.useSource(prev); |
| log.popDiagnosticHandler(discardHandler); |
| resolve.restoreAccessbilityChecks(); |
| // enter.shadowTypeEnvs(false); |
| } |
| } |
| |
| /**Returns tokens for a given tree. |
| */ |
| public TokenSequence<JavaTokenId> tokensFor(Tree tree) { |
| return tokensFor(tree, info.getTrees().getSourcePositions()); |
| } |
| |
| /**Returns tokens for a given tree. Uses specified {@link SourcePositions}. |
| */ |
| public TokenSequence<JavaTokenId> tokensFor(Tree tree, SourcePositions sourcePositions) { |
| return tokensFor(tree, sourcePositions, -1); |
| } |
| |
| private TokenSequence<JavaTokenId> tokensFor(Tree tree, SourcePositions sourcePositions, int farEnd) { |
| int start = (int)sourcePositions.getStartPosition(info.getCompilationUnit(), tree); |
| int end = (int)sourcePositions.getEndPosition(info.getCompilationUnit(), tree); |
| |
| return info.getTokenHierarchy().tokenSequence(JavaTokenId.language()).subSequence(start, Math.max(end, farEnd)); |
| } |
| |
| /** |
| * Checks whether the given element is accessible as a member of the given |
| * type in a given scope. |
| * @param scope the scope to be checked |
| * @param member the member to be checked |
| * @param type the type for which to check if the member is accessible |
| * @return true if {@code member} is accessible in {@code type} |
| * @deprecated since 0.111, {@link Trees#isAccessible(Scope, Element, DeclaredType)} should be used instead. |
| */ |
| public boolean isAccessible(Scope scope, Element member, TypeMirror type) { |
| return type instanceof DeclaredType ? info.getTrees().isAccessible(scope, member, (DeclaredType)type) : false; |
| } |
| |
| /**Checks whether the given scope is in "static" context. |
| */ |
| public boolean isStaticContext(Scope scope) { |
| return NBResolve.isStatic(getEnv(scope)); |
| } |
| |
| /**Returns uncaught exceptions inside the given tree path. |
| */ |
| public Set<TypeMirror> getUncaughtExceptions(TreePath path) { |
| Set<TypeMirror> set = new UnrelatedTypeMirrorSet(info.getTypes()); |
| new UncaughtExceptionsVisitor(info).scan(path, set); |
| return set; |
| } |
| |
| /** |
| * Returns all uninitialized fields of the given class. |
| * @param path to class to inspect |
| * @return set of all uninitialized fields |
| * |
| * @since 0.136 |
| */ |
| public Set<? extends VariableElement> getUninitializedFields(TreePath path) { |
| final Set<VariableElement> fields = new LinkedHashSet<>(); |
| if (path == null) { |
| return fields; |
| } |
| final Trees trees = info.getTrees(); |
| Element element = trees.getElement(path); |
| if (element == null || !element.getKind().isClass()) { |
| return fields; |
| } |
| for (VariableElement ve : ElementFilter.fieldsIn(((TypeElement)element).getEnclosedElements())) { |
| if (ve instanceof Symbol && (((Symbol)ve).flags() & (Flags.HASINIT | Flags.STATIC)) == 0) { |
| fields.add(ve); |
| } |
| } |
| new ErrorAwareTreePathScanner<Void, Boolean>() { |
| @Override |
| public Void visitAssignment(AssignmentTree node, Boolean p) { |
| Element el = trees.getElement(new TreePath(getCurrentPath(), node.getVariable())); |
| fields.remove(el); |
| return null; |
| } |
| @Override |
| public Void visitClass(ClassTree node, Boolean p) { |
| //do not analyse the inner classes: |
| return p ? super.visitClass(node, Boolean.FALSE) : null; |
| } |
| @Override |
| public Void visitMethod(MethodTree node, Boolean p) { |
| return null; |
| } |
| }.scan(path, Boolean.TRUE); |
| return fields; |
| } |
| |
| /**Find span of the {@link ClassTree}'s body in the source. |
| * Returns starting and ending offset of the body in the source code that was parsed |
| * (ie. {@link CompilationInfo.getText()}, which may differ from the positions in the source |
| * document if it has been already altered. |
| * |
| * @param clazz class which body should be searched for |
| * @return the span of the body, or null if cannot be found |
| * @since 0.127 |
| */ |
| public int[] findBodySpan(ClassTree clazz) { |
| JCTree jcTree = (JCTree) clazz; |
| int pos = jcTree.pos; |
| |
| if (pos < 0) |
| return null; |
| |
| TokenSequence<JavaTokenId> tokenSequence = info.getTokenHierarchy().tokenSequence(JavaTokenId.language()); |
| tokenSequence.move(pos); |
| |
| int startPos = -1; |
| int endPos = (int) info.getTrees().getSourcePositions().getEndPosition(info.getCompilationUnit(), clazz); |
| while(tokenSequence.moveNext()) { |
| if(tokenSequence.token().id() == JavaTokenId.LBRACE) { |
| startPos = tokenSequence.offset(); |
| break; |
| } |
| } |
| |
| if(startPos == -1 || endPos == -1) { |
| return null; |
| } |
| |
| return new int[] { |
| startPos, |
| endPos |
| }; |
| } |
| |
| /**Find span of the {@link ClassTree#getSimpleName()} identifier in the source. |
| * Returns starting and ending offset of the name in the source code that was parsed |
| * (ie. {@link CompilationInfo.getText()}, which may differ from the positions in the source |
| * document if it has been already altered. |
| * |
| * @param clazz class which name should be searched for |
| * @return the span of the name, or null if cannot be found |
| * @since 0.25 |
| */ |
| public int[] findNameSpan(ClassTree clazz) { |
| return findNameSpan(clazz.getSimpleName().toString(), clazz, JavaTokenId.CLASS, JavaTokenId.INTERFACE, JavaTokenId.ENUM, JavaTokenId.AT, JavaTokenId.WHITESPACE, JavaTokenId.BLOCK_COMMENT, JavaTokenId.LINE_COMMENT, JavaTokenId.JAVADOC_COMMENT); |
| } |
| |
| /**Find span of the {@link MethodTree#getName()} identifier in the source. |
| * Returns starting and ending offset of the name in the source code that was parsed |
| * (ie. {@link CompilationInfo.getText()}, which may differ from the positions in the source |
| * document if it has been already altered. |
| * |
| * @param method method which name should be searched for |
| * @return the span of the name, or null if cannot be found |
| * @since 0.25 |
| */ |
| public int[] findNameSpan(MethodTree method) { |
| if (isSynthetic(info.getCompilationUnit(), method)) { |
| return null; |
| } |
| JCMethodDecl jcm = (JCMethodDecl) method; |
| String name; |
| if (jcm.name == jcm.name.table.names.init) { |
| TreePath path = info.getTrees().getPath(info.getCompilationUnit(), jcm); |
| if (path == null) { |
| return null; |
| } |
| Element em = info.getTrees().getElement(path); |
| Element clazz; |
| if (em == null || (clazz = em.getEnclosingElement()) == null || !clazz.getKind().isClass()) { |
| return null; |
| } |
| |
| name = clazz.getSimpleName().toString(); |
| } else { |
| name = method.getName().toString(); |
| } |
| return findNameSpan(name, method); |
| } |
| |
| /**Find span of the {@link VariableTree#getName()} identifier in the source. |
| * Returns starting and ending offset of the name in the source code that was parsed |
| * (ie. {@link CompilationInfo.getText()}, which may differ from the positions in the source |
| * document if it has been already altered. |
| * |
| * @param var variable which name should be searched for |
| * @return the span of the name, or null if cannot be found |
| * @since 0.25 |
| */ |
| public int[] findNameSpan(VariableTree var) { |
| return findNameSpan(var.getName().toString(), var); |
| } |
| |
| /**Find span of the {@link LabeledStatementTree#getLabel()} identifier in the source. |
| * Returns starting and ending offset of the name in the source code that was parsed |
| * (ie. {@link CompilationInfo.getText()}, which may differ from the positions in the source |
| * document if it has been already altered. |
| * |
| * @param lst labeled statement which name should be searched for |
| * @return the span of the name, or null if cannot be found |
| * @since 0.131 |
| */ |
| public int[] findNameSpan(LabeledStatementTree lst) { |
| return findNameSpan(lst.getLabel().toString(), lst); |
| } |
| |
| /**Find span of the {@link TypeParameterTree#getName()} identifier in the source. |
| * Returns starting and ending offset of the name in the source code that was parsed |
| * (ie. {@link CompilationInfo.getText()}, which may differ from the positions in the source |
| * document if it has been already altered. |
| * |
| * @param tpt type parameter which name should be searched for |
| * @return the span of the name, or null if cannot be found |
| * @since 0.131 |
| */ |
| public int[] findNameSpan(TypeParameterTree tpt) { |
| return findNameSpan(tpt.getName().toString(), tpt); |
| } |
| |
| /**Find span of the {@link LabeledStatementTree#getLabel()} identifier in the source. |
| * Returns starting and ending offset of the name in the source code that was parsed |
| * (ie. {@link CompilationInfo.getText()}, which may differ from the positions in the source |
| * document if it has been already altered. |
| * |
| * @param brk labeled statement which name should be searched for |
| * @return the span of the name, or null if cannot be found |
| * @since 0.131 |
| */ |
| public int[] findNameSpan(BreakTree brk) { |
| return findNameSpan(brk.getLabel().toString(), brk); |
| } |
| |
| /**Find span of the {@link LabeledStatementTree#getLabel()} identifier in the source. |
| * Returns starting and ending offset of the name in the source code that was parsed |
| * (ie. {@link CompilationInfo.getText()}, which may differ from the positions in the source |
| * document if it has been already altered. |
| * |
| * @param cont labeled statement which name should be searched for |
| * @return the span of the name, or null if cannot be found |
| * @since 0.131 |
| */ |
| public int[] findNameSpan(ContinueTree cont) { |
| return findNameSpan(cont.getLabel().toString(), cont); |
| } |
| |
| /**Find span of the {@link MethodTree#getParameters()} parameter list in the source. |
| * Returns the position of the opening and closing parentheses of the parameter list |
| * in the source code that was parsed (ie. {@link CompilationInfo.getText()}, which |
| * may differ from the positions in the source document if it has been already altered. |
| * |
| * @param method method which parameter list should be searched for |
| * @return the span of the parameter list, or null if cannot be found |
| * @since 0.81 |
| */ |
| public @CheckForNull int[] findMethodParameterSpan(@NonNull MethodTree method) { |
| JCTree jcTree = (JCTree) method; |
| int pos = jcTree.pos; |
| |
| if (pos < 0) |
| return null; |
| |
| TokenSequence<JavaTokenId> tokenSequence = info.getTokenHierarchy().tokenSequence(JavaTokenId.language()); |
| tokenSequence.move(pos); |
| |
| int startPos = -1; |
| int endPos = -1; |
| while(tokenSequence.moveNext()) { |
| if(tokenSequence.token().id() == JavaTokenId.LPAREN) { |
| startPos = tokenSequence.offset(); |
| } |
| if(tokenSequence.token().id() == JavaTokenId.RPAREN) { |
| endPos = tokenSequence.offset(); |
| break; |
| } |
| if(tokenSequence.token().id() == JavaTokenId.LBRACE) { |
| return null; |
| } |
| } |
| |
| if(startPos == -1 || endPos == -1) { |
| return null; |
| } |
| |
| return new int[] { |
| startPos, |
| endPos |
| }; |
| } |
| |
| /**Find span of the {@link MemberSelectTree#getIdentifier()} identifier in the source. |
| * Returns starting and ending offset of the name in the source code that was parsed |
| * (ie. {@link CompilationInfo.getText()}, which may differ from the positions in the source |
| * document if it has been already altered. |
| * |
| * @param mst member select which identifier should be searched for |
| * @return the span of the name, or null if cannot be found |
| * @since 0.25 |
| */ |
| public int[] findNameSpan(MemberSelectTree mst) { |
| return findNameSpan(mst.getIdentifier().toString(), mst, JavaTokenId.DOT, JavaTokenId.WHITESPACE, JavaTokenId.BLOCK_COMMENT, JavaTokenId.LINE_COMMENT, JavaTokenId.JAVADOC_COMMENT); |
| } |
| |
| /**Find span of the {@link MemberReferenceTree#getName()} identifier in the source. |
| * Returns starting and ending offset of the name in the source code that was parsed |
| * (ie. {@link CompilationInfo.getText()}, which may differ from the positions in the source |
| * document if it has been already altered. |
| * |
| * @param mst member reference for which the identifier should be searched for |
| * @return the span of the name, or null if cannot be found |
| * @since 0.124 |
| */ |
| public int[] findNameSpan(MemberReferenceTree mst) { |
| return findNameSpan(mst.getName().toString(), mst, JavaTokenId.DOT, JavaTokenId.WHITESPACE, JavaTokenId.BLOCK_COMMENT, JavaTokenId.LINE_COMMENT, JavaTokenId.JAVADOC_COMMENT); |
| } |
| |
| /**Find span of the name in the DocTree's reference tree (see {@link #getReferenceName(com.sun.source.util.DocTreePath)} |
| * identifier in the source. Returns starting and ending offset of the name in |
| * the source code that was parsed (ie. {@link CompilationInfo.getText()}, which |
| * may differ from the positions in the source document if it has been already |
| * altered. |
| * |
| * @param ref reference for which the identifier should be found |
| * @return the span of the name, or null if cannot be found |
| * @since 0.124 |
| */ |
| public int[] findNameSpan(DocCommentTree docTree, ReferenceTree ref) { |
| Name name = ((DCReference) ref).memberName; |
| if (name == null || !SourceVersion.isIdentifier(name)) { |
| //names like "<error>", etc. |
| return null; |
| } |
| |
| int pos = (int) info.getDocTrees().getSourcePositions().getStartPosition(info.getCompilationUnit(), docTree, ref); |
| |
| if (pos < 0) |
| return null; |
| |
| TokenSequence<JavaTokenId> tokenSequence = info.getTokenHierarchy().tokenSequence(JavaTokenId.language()); |
| |
| tokenSequence.move(pos); |
| |
| if (!tokenSequence.moveNext() || tokenSequence.token().id() != JavaTokenId.JAVADOC_COMMENT) return null; |
| |
| TokenSequence<JavadocTokenId> jdocTS = tokenSequence.embedded(JavadocTokenId.language()); |
| |
| jdocTS.move(pos); |
| |
| boolean wasNext; |
| |
| while ((wasNext = jdocTS.moveNext()) && jdocTS.token().id() != JavadocTokenId.HASH) |
| ; |
| |
| if (wasNext && jdocTS.moveNext()) { |
| if (jdocTS.token().id() == JavadocTokenId.IDENT && |
| name.contentEquals(jdocTS.token().text())) { |
| return new int[] { |
| jdocTS.offset(), |
| jdocTS.offset() + jdocTS.token().length() |
| }; |
| } |
| } |
| |
| return null; |
| } |
| |
| private int[] findNameSpan(String name, Tree t, JavaTokenId... allowedTokens) { |
| if (!SourceVersion.isIdentifier(name)) { |
| //names like "<error>", etc. |
| return null; |
| } |
| |
| JCTree jcTree = (JCTree) t; |
| int pos = jcTree.pos; |
| |
| if (pos < 0) |
| return null; |
| |
| Set<JavaTokenId> allowedTokensSet = EnumSet.noneOf(JavaTokenId.class); |
| |
| allowedTokensSet.addAll(Arrays.asList(allowedTokens)); |
| |
| TokenSequence<JavaTokenId> tokenSequence = info.getTokenHierarchy().tokenSequence(JavaTokenId.language()); |
| |
| tokenSequence.move(pos); |
| |
| boolean wasNext; |
| |
| while ((wasNext = tokenSequence.moveNext()) && allowedTokensSet.contains(tokenSequence.token().id())) |
| ; |
| |
| if (wasNext) { |
| if (tokenSequence.token().id() == JavaTokenId.IDENTIFIER) { |
| boolean nameMatches; |
| |
| if (!(nameMatches = name.contentEquals(tokenSequence.token().text()))) { |
| ExpressionTree expr = info.getTreeUtilities().parseExpression(tokenSequence.token().text().toString(), new SourcePositions[1]); |
| |
| nameMatches = expr.getKind() == Kind.IDENTIFIER && name.contentEquals(((IdentifierTree) expr).getName()); |
| } |
| |
| if (nameMatches) { |
| return new int[] { |
| tokenSequence.offset(), |
| tokenSequence.offset() + tokenSequence.token().length() |
| }; |
| } |
| } |
| } |
| |
| tokenSequence.move(pos); |
| |
| if (tokenSequence.moveNext() && tokenSequence.token().id() == JavaTokenId.JAVADOC_COMMENT) { |
| //TODO: this is not precise enough |
| return new int[] { |
| pos + 1, |
| pos + name.length() + 1 |
| }; |
| } |
| return null; |
| } |
| |
| /**Find the target of <code>break</code> or <code>continue</code>. The given |
| * {@link CompilationInfo} has to be at least in the {@link Phase#RESOLVED} phase. |
| * |
| * @param breakOrContinue {@link TreePath} to the tree that should be inspected. |
| * The <code>breakOrContinue.getLeaf().getKind()</code> |
| * has to be one of {@link Kind#BREAK}, {@link Kind#CONTINUE}, or {@link Kind#YIELD}, or |
| * an IllegalArgumentException is thrown |
| * @return the tree that is the "target" for the given break or continue statement, or null if there is none. Tree can be of type StatementTree or ExpressionTree |
| * @throws IllegalArgumentException if the given tree is not a break or continue tree or if the given {@link CompilationInfo} |
| * is not in the {@link Phase#RESOLVED} phase. |
| * @since 2.40 |
| */ |
| public Tree getBreakContinueTargetTree(TreePath breakOrContinue) throws IllegalArgumentException { |
| if (info.getPhase().compareTo(Phase.RESOLVED) < 0) |
| throw new IllegalArgumentException("Not in correct Phase. Required: Phase.RESOLVED, got: Phase." + info.getPhase().toString()); |
| |
| Tree leaf = breakOrContinue.getLeaf(); |
| |
| switch (leaf.getKind()) { |
| case BREAK: |
| return (Tree) ((JCTree.JCBreak) leaf).target; |
| case CONTINUE: |
| Tree target = (Tree) ((JCTree.JCContinue) leaf).target; |
| |
| if (target == null) |
| return null; |
| |
| if (((JCTree.JCContinue) leaf).label == null) |
| return target; |
| |
| TreePath tp = breakOrContinue; |
| |
| while (tp.getLeaf() != target) { |
| tp = tp.getParentPath(); |
| } |
| |
| Tree parent = tp.getParentPath().getLeaf(); |
| |
| if (parent.getKind() == Kind.LABELED_STATEMENT) { |
| return (StatementTree) parent; |
| } else { |
| return target; |
| } |
| default: |
| if (TreeShims.YIELD.equals(leaf.getKind().name())) { |
| return TreeShims.getTarget(leaf); |
| } |
| throw new IllegalArgumentException("Unsupported kind: " + leaf.getKind()); |
| } |
| } |
| |
| /**Find the target of <code>break</code> or <code>continue</code>. The given |
| * {@link CompilationInfo} has to be at least in the {@link Phase#RESOLVED} phase. |
| * |
| * @param breakOrContinue {@link TreePath} to the tree that should be inspected. |
| * The <code>breakOrContinue.getLeaf().getKind()</code> |
| * has to be either {@link Kind#BREAK} or {@link Kind#CONTINUE}, or |
| * an IllegalArgumentException is thrown |
| * @return the tree that is the "target" for the given break or continue statement, or null if there is none. |
| * @throws IllegalArgumentException if the given tree is not a break or continue tree or if the given {@link CompilationInfo} |
| * is not in the {@link Phase#RESOLVED} phase. |
| * @since 0.16 |
| */ |
| public StatementTree getBreakContinueTarget(TreePath breakOrContinue) throws IllegalArgumentException { |
| if (info.getPhase().compareTo(Phase.RESOLVED) < 0) |
| throw new IllegalArgumentException("Not in correct Phase. Required: Phase.RESOLVED, got: Phase." + info.getPhase().toString()); |
| |
| Tree leaf = breakOrContinue.getLeaf(); |
| |
| switch (leaf.getKind()) { |
| case BREAK: |
| Tree breakTarget = (Tree) ((JCTree.JCBreak) leaf).target; |
| if (breakTarget == null || !(breakTarget instanceof StatementTree)) { |
| return null; |
| } |
| return (StatementTree) breakTarget; |
| case CONTINUE: |
| StatementTree target = (StatementTree) ((JCTree.JCContinue) leaf).target; |
| |
| if (target == null) |
| return null; |
| |
| if (((JCTree.JCContinue) leaf).label == null) |
| return target; |
| |
| TreePath tp = breakOrContinue; |
| |
| while (tp.getLeaf() != target) { |
| tp = tp.getParentPath(); |
| } |
| |
| Tree parent = tp.getParentPath().getLeaf(); |
| |
| if (parent.getKind() == Kind.LABELED_STATEMENT) { |
| return (StatementTree) parent; |
| } else { |
| return target; |
| } |
| default: |
| throw new IllegalArgumentException("Unsupported kind: " + leaf.getKind()); |
| } |
| } |
| |
| /**Decode escapes defined in: http://wikis.sun.com/display/mlvm/ProjectCoinProposal, 3.1-3.9. |
| * Must be a full token text, including possible #". |
| * |
| * @param text to decode |
| * @return decoded escapes from the identifier |
| * @see http://wikis.sun.com/display/mlvm/ProjectCoinProposal |
| * @since 0.56 |
| */ |
| public @NonNull CharSequence decodeIdentifier(@NonNull CharSequence text) { |
| return decodeIdentifierInternal(text); |
| } |
| |
| /**Encode identifier using escapes defined in: http://wikis.sun.com/display/mlvm/ProjectCoinProposal, 3.1-3.9. |
| * |
| * @param text to encode |
| * @return encoded identifier, including #" if necessary |
| * @see http://wikis.sun.com/display/mlvm/ProjectCoinProposal |
| * @since 0.56 |
| */ |
| public @NonNull CharSequence encodeIdentifier(@NonNull CharSequence ident) { |
| return encodeIdentifierInternal(ident); |
| } |
| |
| static @NonNull CharSequence decodeIdentifierInternal(@NonNull CharSequence text) { |
| if (text.charAt(0) != '#') { |
| return text; |
| } |
| |
| int count = text.charAt(text.length() - 1) == '"' ? text.length() - 1 : text.length(); |
| StringBuilder sb = new StringBuilder(text.length()); |
| |
| for (int c = 2; c < count; c++) { |
| if (text.charAt(c) == '\\' && ++c < count) { |
| if (EXOTIC_ESCAPE.contains(text.charAt(c))) { |
| sb.append('\\'); |
| sb.append(text.charAt(c)); |
| } else { |
| //XXX: handle \012 |
| Character remaped = ESCAPE_UNENCODE.get(text.charAt(c)); |
| |
| if (remaped != null) { |
| sb.append(remaped); |
| } else { |
| //TODO: illegal? |
| sb.append(text.charAt(c)); |
| } |
| } |
| } else { |
| sb.append(text.charAt(c)); |
| } |
| } |
| |
| return sb.toString(); |
| } |
| |
| static @NonNull CharSequence encodeIdentifierInternal(@NonNull CharSequence ident) { |
| if (ident.length() == 0) { |
| //??? |
| return ident; |
| } |
| |
| StringBuilder sb = new StringBuilder(ident.length()); |
| boolean needsExotic = Character.isJavaIdentifierStart(ident.charAt(0)); |
| |
| //XXX: code points? |
| for (int i = 0; i < ident.length(); i++) { |
| char c = ident.charAt(i); |
| |
| if (Character.isJavaIdentifierPart(c)) { |
| sb.append(c); |
| continue; |
| } |
| |
| needsExotic = true; |
| |
| Character target = ESCAPE_ENCODE.get(c); |
| |
| if (target != null) { |
| sb.append('\\'); |
| sb.append(target); |
| } else { |
| sb.append(c); |
| } |
| } |
| |
| if (needsExotic) { |
| sb.append("\""); |
| sb.insert(0, "#\""); |
| |
| return sb.toString(); |
| } else { |
| return ident; |
| } |
| } |
| |
| static Set<Character> EXOTIC_ESCAPE = new HashSet<Character>( |
| Arrays.<Character>asList('!', '#', '$', '%', '&', '(', ')', '*', '+', ',', '-', |
| ':', '=', '?', '@', '^', '_', '`', '{', '|', '}') |
| ); |
| |
| private static final Map<Character, Character> ESCAPE_UNENCODE; |
| private static final Map<Character, Character> ESCAPE_ENCODE; |
| |
| static { |
| Map<Character, Character> unencode = new HashMap<Character, Character>(); |
| |
| unencode.put('n', '\n'); |
| unencode.put('t', '\t'); |
| unencode.put('b', '\b'); |
| unencode.put('r', '\r'); |
| |
| ESCAPE_UNENCODE = Collections.unmodifiableMap(unencode); |
| |
| Map<Character, Character> encode = new HashMap<Character, Character>(); |
| |
| encode.put('\n', 'n'); |
| encode.put('\t', 't'); |
| encode.put('\b', 'b'); |
| encode.put('\r', 'r'); |
| |
| ESCAPE_ENCODE = Collections.unmodifiableMap(encode); |
| } |
| |
| /**Returns new tree based on {@code original}, such that each visited subtree |
| * that occurs as a key in {@code original2Translated} is replaced by the corresponding |
| * value from {@code original2Translated}. The value is then translated using the same |
| * algorithm. Each key from {@code original2Translated} is used at most once. |
| * Unless the provided {@code original} tree is a key in {@code original2Translated}, |
| * the resulting tree has the same type as {@code original}. |
| * |
| * Principally, the method inner workings are: |
| * <pre> |
| * translate(original, original2Translated) { |
| * if (original2Translated.containsKey(original)) |
| * return translate(original2Translated.remove(original)); |
| * newTree = copyOf(original); |
| * for (Tree child : allChildrenOf(original)) { |
| * newTree.replace(child, translate(child, original2Translated)); |
| * } |
| * return newTree; |
| * } |
| * </pre> |
| * |
| * @param original the tree that should be translated |
| * @param original2Translated map containing trees that should be translated |
| * @return translated tree. |
| * @since 0.64 |
| */ |
| public @NonNull Tree translate(final @NonNull Tree original, final @NonNull Map<? extends Tree, ? extends Tree> original2Translated) { |
| return translate(original, original2Translated, new NoImports(info), null); |
| } |
| |
| @NonNull Tree translate(final @NonNull Tree original, final @NonNull Map<? extends Tree, ? extends Tree> original2Translated, ImportAnalysis2 ia, Map<Tree, Object> tree2Tag) { |
| ImmutableTreeTranslator itt = new ImmutableTreeTranslator(info instanceof WorkingCopy ? (WorkingCopy)info : null) { |
| private @NonNull Map<Tree, Tree> map = new HashMap<Tree, Tree>(original2Translated); |
| @Override |
| public Tree translate(Tree tree) { |
| Tree translated = map.remove(tree); |
| |
| if (translated != null) { |
| return translate(translated); |
| } else { |
| return super.translate(tree); |
| } |
| } |
| }; |
| |
| Context c = info.impl.getJavacTask().getContext(); |
| |
| itt.attach(c, ia, tree2Tag); |
| |
| return itt.translate(original); |
| } |
| |
| /**Returns new tree based on {@code original}, such that each visited subtree |
| * that occurs as a key in {@code original2Translated} is replaced by the corresponding |
| * value from {@code original2Translated}. The value is then translated using the same |
| * algorithm. Each key from {@code original2Translated} is used at most once. |
| * Unless the provided {@code original} tree is a key in {@code original2Translated}, |
| * the resulting tree has the same type as {@code original}. |
| * |
| * Principally, the method inner workings are: |
| * <pre> |
| * translate(original, original2Translated) { |
| * if (original2Translated.containsKey(original)) |
| * return translate(original2Translated.remove(original)); |
| * newTree = copyOf(original); |
| * for (Tree child : allChildrenOf(original)) { |
| * newTree.replace(child, translate(child, original2Translated)); |
| * } |
| * return newTree; |
| * } |
| * </pre> |
| * |
| * @param original the tree that should be translated |
| * @param original2Translated map containing trees that should be translated |
| * @return translated tree. |
| * @since 0.124 |
| */ |
| public @NonNull DocTree translate(final @NonNull DocTree original, final @NonNull Map<? extends DocTree, ? extends DocTree> original2Translated) { |
| return translate(original, original2Translated, new NoImports(info), null); |
| } |
| |
| @NonNull DocTree translate(final @NonNull DocTree original, final @NonNull Map<? extends DocTree, ? extends DocTree> original2Translated, ImportAnalysis2 ia, Map<Tree, Object> tree2Tag) { |
| ImmutableDocTreeTranslator itt = new ImmutableDocTreeTranslator(info instanceof WorkingCopy ? (WorkingCopy)info : null) { |
| private @NonNull Map<DocTree, DocTree> map = new HashMap<DocTree, DocTree>(original2Translated); |
| @Override |
| public DocTree translate(DocTree tree) { |
| DocTree translated = map.remove(tree); |
| |
| if (translated != null) { |
| return translate(translated); |
| } else { |
| return super.translate(tree); |
| } |
| } |
| }; |
| |
| Context c = info.impl.getJavacTask().getContext(); |
| |
| itt.attach(c, ia, tree2Tag); |
| |
| return itt.translate(original); |
| } |
| |
| private static final class NoImports extends ImportAnalysis2 { |
| |
| public NoImports(CompilationInfo info) { |
| super(info); |
| } |
| |
| @Override |
| public void classEntered(ClassTree clazz) {} |
| |
| @Override |
| public void enterVisibleThroughClasses(ClassTree clazz) {} |
| |
| @Override |
| public void classLeft() {} |
| |
| @Override |
| public ExpressionTree resolveImport(MemberSelectTree orig, Element element) { |
| return orig; |
| } |
| |
| @Override |
| public void setCompilationUnit(CompilationUnitTree cut) {} |
| |
| @Override |
| public void setImports(List<? extends ImportTree> importsToAdd) { |
| } |
| |
| @Override |
| public Set<? extends Element> getImports() { |
| return Collections.emptySet(); |
| } |
| |
| @Override |
| public void setPackage(ExpressionTree packageNameTree) {} |
| |
| } |
| |
| private static class UncaughtExceptionsVisitor extends ErrorAwareTreePathScanner<Void, Set<TypeMirror>> { |
| |
| private final CompilationInfo info; |
| |
| private UncaughtExceptionsVisitor(final CompilationInfo info) { |
| this.info = info; |
| } |
| |
| public Void visitMethodInvocation(MethodInvocationTree node, Set<TypeMirror> p) { |
| super.visitMethodInvocation(node, p); |
| Element el = info.getTrees().getElement(getCurrentPath()); |
| if (el != null && el.getKind() == ElementKind.METHOD) |
| p.addAll(((ExecutableElement)el).getThrownTypes()); |
| return null; |
| } |
| |
| public Void visitNewClass(NewClassTree node, Set<TypeMirror> p) { |
| super.visitNewClass(node, p); |
| Element el = info.getTrees().getElement(getCurrentPath()); |
| if (el != null && el.getKind() == ElementKind.CONSTRUCTOR) |
| p.addAll(((ExecutableElement)el).getThrownTypes()); |
| return null; |
| } |
| |
| public Void visitThrow(ThrowTree node, Set<TypeMirror> p) { |
| super.visitThrow(node, p); |
| TypeMirror tm = info.getTrees().getTypeMirror(new TreePath(getCurrentPath(), node.getExpression())); |
| if (tm != null) { |
| if (tm.getKind() == TypeKind.DECLARED) |
| p.add(tm); |
| else if (tm.getKind() == TypeKind.UNION) |
| p.addAll(((UnionType)tm).getAlternatives()); |
| } |
| return null; |
| } |
| |
| public Void visitTry(TryTree node, Set<TypeMirror> p) { |
| Set<TypeMirror> s = new LinkedHashSet<TypeMirror>(); |
| Trees trees = info.getTrees(); |
| Types types = info.getTypes(); |
| Elements elements = info.getElements(); |
| for (Tree res : node.getResources()) { |
| TypeMirror resType = trees.getTypeMirror(new TreePath(getCurrentPath(), res)); |
| if (resType != null && resType.getKind() == TypeKind.DECLARED) { |
| for (ExecutableElement method : ElementFilter.methodsIn(elements.getAllMembers((TypeElement)((DeclaredType)resType).asElement()))) { |
| if ("close".contentEquals(method.getSimpleName()) //NOI18N |
| && method.getParameters().isEmpty() |
| && method.getTypeParameters().isEmpty()) { |
| s.addAll(method.getThrownTypes()); |
| } |
| } |
| } |
| } |
| scan(node.getBlock(), s); |
| Set<TypeMirror> c = new LinkedHashSet<TypeMirror>(); |
| for (CatchTree ct : node.getCatches()) { |
| TypeMirror t = trees.getTypeMirror(new TreePath(getCurrentPath(), ct.getParameter().getType())); |
| if (t != null) { |
| if (t.getKind() == TypeKind.UNION) { |
| for (TypeMirror tm : ((UnionType)t).getAlternatives()) { |
| if (tm != null && tm.getKind() != TypeKind.ERROR) |
| c.add(tm); |
| } |
| } else if (t.getKind() != TypeKind.ERROR) { |
| c.add(t); |
| } |
| } |
| } |
| for (TypeMirror t : c) { |
| for (Iterator<TypeMirror> it = s.iterator(); it.hasNext();) { |
| if (types.isSubtype(it.next(), t)) |
| it.remove(); |
| } |
| } |
| p.addAll(s); |
| scan(node.getCatches(), p); |
| scan(node.getFinallyBlock(), p); |
| return null; |
| } |
| |
| public Void visitMethod(MethodTree node, Set<TypeMirror> p) { |
| Set<TypeMirror> s = new LinkedHashSet<TypeMirror>(); |
| scan(node.getBody(), s); |
| for (ExpressionTree et : node.getThrows()) { |
| TypeMirror t = info.getTrees().getTypeMirror(new TreePath(getCurrentPath(), et)); |
| if (t != null && t.getKind() != TypeKind.ERROR) { |
| for (Iterator<TypeMirror> it = s.iterator(); it.hasNext();) |
| if (info.getTypes().isSubtype(it.next(), t)) |
| it.remove(); |
| } |
| } |
| p.addAll(s); |
| return null; |
| } |
| |
| @Override |
| public Void visitClass(ClassTree node, Set<TypeMirror> p) { |
| return null; |
| } |
| |
| @Override |
| public Void visitLambdaExpression(LambdaExpressionTree node, Set<TypeMirror> p) { |
| return null; |
| } |
| } |
| |
| private static class UnrelatedTypeMirrorSet extends AbstractSet<TypeMirror> { |
| |
| private Types types; |
| private LinkedList<TypeMirror> list = new LinkedList<TypeMirror>(); |
| |
| public UnrelatedTypeMirrorSet(Types types) { |
| this.types = types; |
| } |
| |
| @Override |
| public boolean add(TypeMirror typeMirror) { |
| for (ListIterator<TypeMirror> it = list.listIterator(); it.hasNext(); ) { |
| TypeMirror tm = it.next(); |
| if (types.isSubtype(typeMirror, tm)) |
| return false; |
| if (types.isSubtype(tm, typeMirror)) |
| it.remove(); |
| } |
| return list.add(typeMirror); |
| } |
| |
| @Override |
| public Iterator<TypeMirror> iterator() { |
| return list.iterator(); |
| } |
| |
| @Override |
| public int size() { |
| return list.size(); |
| } |
| } |
| |
| /**Checks whether the given expression is a compile-time constant, as per JLS 15.28. |
| * |
| * @param expression the expression to check |
| * @return true if and only if the given expression represents a compile-time constant value |
| * @since 0.91 |
| */ |
| public boolean isCompileTimeConstantExpression(TreePath expression) { |
| TypeMirror attributeTree = info.getTrees().getTypeMirror(expression); |
| Type attributeTreeImpl = (Type) attributeTree; |
| |
| return attributeTreeImpl != null && attributeTreeImpl.constValue() != null; |
| } |
| |
| /**Find the type (the part before {@code #}) that is being referenced by the given {@link ReferenceTree}. |
| * |
| * @param path the leaf must be {@link ReferenceTree} |
| * @return the referred type, or {@code null} if none. |
| * @since 0.124 |
| */ |
| public @CheckForNull ExpressionTree getReferenceClass(@NonNull DocTreePath path) { |
| TreePath tp = path.getTreePath(); |
| DCReference ref = (DCReference) path.getLeaf(); |
| |
| ((DocTrees) this.info.getTrees()).getElement(path); |
| // was: |
| // ((JavacTrees) this.info.getTrees()).ensureDocReferenceAttributed(tp, ref); |
| |
| return (ExpressionTree) ref.qualifierExpression; |
| } |
| |
| /**Find the name (the name after {@code #}) that is being referenced by the given {@link ReferenceTree}. |
| * |
| * @param path the leaf must be {@link ReferenceTree} |
| * @return the referred member name, or {@code null} if none. |
| * @since 0.124 |
| */ |
| public @CheckForNull Name getReferenceName(@NonNull DocTreePath path) { |
| return ((DCReference) path.getLeaf()).memberName; |
| } |
| |
| /**Find the parameters that are specified in the given {@link ReferenceTree}. |
| * |
| * @param path the leaf must be {@link ReferenceTree} |
| * @return the parameters for the referred method, or {@code null} if none. |
| * @since 0.124 |
| */ |
| public @CheckForNull List<? extends Tree> getReferenceParameters(@NonNull DocTreePath path) { |
| TreePath tp = path.getTreePath(); |
| DCReference ref = (DCReference) path.getLeaf(); |
| |
| ((DocTrees) this.info.getTrees()).getElement(path); |
| // was: |
| // ((JavacTrees) this.info.getTrees()).ensureDocReferenceAttributed(tp, ref); |
| |
| return ref.paramTypes; |
| } |
| |
| /**Check the var type variable in given tree path {@link TreePath}. |
| * |
| * @param path the path of tree {@link TreePath} |
| * @return the true if tree contains var keyword else return false |
| * @since 2.31.0 |
| */ |
| public boolean isVarType(@NonNull TreePath path) { |
| TokenSequence<JavaTokenId> tokenSequence = tokensFor(path.getLeaf()); |
| tokenSequence.moveStart(); |
| while(tokenSequence.moveNext() && tokenSequence.token().id() != JavaTokenId.EQ && tokenSequence.token().id() != JavaTokenId.COLON && tokenSequence.token().id() != JavaTokenId.RPAREN && tokenSequence.token().id() != JavaTokenId.SEMICOLON){ |
| if(tokenSequence.token().id() == JavaTokenId.VAR){ |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /**Check the tree is the end of compound declaration. {@link Tree}. |
| * |
| * @param tree the tree {@link Tree} |
| * @return the true if tree is end of compound declaration else return false |
| * @since 2.33.0 |
| */ |
| public boolean isEndOfCompoundVariableDeclaration(@NonNull Tree tree) { |
| TokenSequence<JavaTokenId> tokenSequence = tokensFor(tree); |
| tokenSequence.moveEnd(); |
| if (tokenSequence.movePrevious() && tokenSequence.token().id() != JavaTokenId.COMMA) { |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Checks whether tree is part of compound variable declaration. |
| * |
| * @param tree tree to examine. |
| * @return true if {@code tree} is part of compound var declaration. |
| * @since 2.34.0 |
| */ |
| public boolean isPartOfCompoundVariableDeclaration(@NonNull Tree tree) { |
| TokenSequence<JavaTokenId> tokenSequence = tokensFor(tree); |
| |
| if (tree.getKind() != Tree.Kind.VARIABLE) { |
| return false; |
| } |
| |
| // If tree ends with comma then tree is part of compound variable declaration. |
| tokenSequence.moveEnd(); |
| if (tokenSequence.movePrevious() && tokenSequence.token().id() == JavaTokenId.COMMA) { |
| return true; |
| } |
| |
| int startPos = (int) info.getTrees().getSourcePositions().getStartPosition(info.getCompilationUnit(), tree); |
| tokenSequence.moveStart(); |
| |
| int tokensLength = 0; |
| |
| // To find out the first subtree from compound varaible declaration statement(if any). |
| while (tokenSequence.moveNext()) { |
| tokensLength += tokenSequence.token().length(); |
| if (tokenSequence.token().id() == JavaTokenId.IDENTIFIER) { |
| |
| Tree path = pathFor(startPos + tokensLength).getLeaf(); |
| TokenSequence<JavaTokenId> TokenSeq = tokensFor(path); |
| TokenSeq.moveEnd(); |
| |
| if (TokenSeq.movePrevious() && TokenSeq.token().id() == JavaTokenId.COMMA) { |
| return true; |
| } |
| break; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Check the tree has compile error in given errors. |
| * |
| * @param tree compilation tree |
| * @param errors Array of error code |
| * @return true if tree has compile error present in list of errors. |
| * @since 2.37 |
| */ |
| public boolean hasError(@NonNull Tree tree, String... errors) { |
| long startPos = info.getTrees().getSourcePositions().getStartPosition(info.getCompilationUnit(), tree); |
| long endPos = info.getTrees().getSourcePositions().getEndPosition(info.getCompilationUnit(), tree); |
| |
| List<Diagnostic> diagnosticsList = info.getDiagnostics(); |
| for (Diagnostic d : diagnosticsList) { |
| if ((d.getKind() == Diagnostic.Kind.ERROR) && ((d.getStartPosition() >= startPos) && (d.getEndPosition() <= endPos))) { |
| if (errors == null || errors.length == 0) { |
| return true; |
| } else { |
| for (String error : errors) { |
| if (error.equals(d.getCode())) { |
| return true; |
| } |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| private static final class NBScope implements Scope { |
| |
| private final JavacScope delegate; |
| |
| private NBScope(JavacScope delegate) { |
| this.delegate = delegate; |
| } |
| |
| private boolean areAccessibilityChecksDisabled() { |
| return true; |
| } |
| |
| @Override |
| public Scope getEnclosingScope() { |
| //TODO: wrap? |
| return delegate.getEnclosingScope(); |
| } |
| |
| @Override |
| public TypeElement getEnclosingClass() { |
| return delegate.getEnclosingClass(); |
| } |
| |
| @Override |
| public ExecutableElement getEnclosingMethod() { |
| return delegate.getEnclosingMethod(); |
| } |
| |
| @Override |
| public Iterable<? extends Element> getLocalElements() { |
| return delegate.getLocalElements(); |
| } |
| } |
| |
| static { |
| TreeUtilitiesAccessor.setInstance(new TreeUtilitiesAccessor() { |
| @Override |
| public StatementTree parseStatement(JavacTaskImpl task, String stmt, SourcePositions[] sourcePositions) { |
| return parseStatementImpl(task, stmt, sourcePositions); |
| } |
| }); |
| } |
| } |