| /* |
| * 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.refactoring.java.plugins; |
| |
| import com.sun.source.tree.*; |
| import com.sun.source.tree.Tree.Kind; |
| import com.sun.source.util.TreePath; |
| import org.netbeans.api.java.source.support.ErrorAwareTreePathScanner; |
| import com.sun.source.util.Trees; |
| import java.io.IOException; |
| import java.util.*; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import javax.lang.model.element.*; |
| import javax.lang.model.type.TypeKind; |
| import javax.lang.model.type.TypeMirror; |
| import javax.lang.model.util.ElementFilter; |
| import org.netbeans.api.java.lexer.JavaTokenId; |
| import org.netbeans.api.java.source.ClasspathInfo.PathKind; |
| import org.netbeans.api.java.source.CompilationController; |
| import org.netbeans.api.java.source.ElementUtilities; |
| import org.netbeans.api.java.source.JavaSource; |
| import org.netbeans.api.lexer.Token; |
| import org.netbeans.api.lexer.TokenSequence; |
| import org.netbeans.modules.refactoring.java.RefactoringUtils; |
| import org.netbeans.modules.refactoring.java.WhereUsedElement; |
| import org.netbeans.modules.refactoring.java.spi.JavaWhereUsedFilters; |
| import org.netbeans.modules.refactoring.java.spi.ToPhaseException; |
| import org.openide.ErrorManager; |
| import org.openide.filesystems.FileObject; |
| import org.openide.util.Exceptions; |
| |
| /** |
| * |
| * @author Jan Becicka |
| */ |
| public class FindUsagesVisitor extends ErrorAwareTreePathScanner<Tree, Element> { |
| |
| private Collection<TreePath> usages = new ArrayList<>(); |
| private List<WhereUsedElement> elements = new ArrayList<>(); |
| protected CompilationController workingCopy; |
| private boolean findInComments = false; |
| private final boolean isSearchOverloadedMethods; |
| private final boolean fromTestRoot; |
| private final boolean fromPlatform; |
| private final boolean fromDependency; |
| private final AtomicBoolean inImport; |
| private boolean usagesInComments; |
| private final AtomicBoolean isCancelled; |
| private List<ExecutableElement> methods; |
| |
| public FindUsagesVisitor(CompilationController workingCopy, AtomicBoolean isCancelled) { |
| this(workingCopy, isCancelled, false, false); |
| } |
| |
| public FindUsagesVisitor(CompilationController workingCopy, AtomicBoolean isCancelled, boolean findInComments, boolean isSearchOverloadedMethods) { |
| this(workingCopy, isCancelled, findInComments, isSearchOverloadedMethods, RefactoringUtils.isFromTestRoot(workingCopy.getFileObject(), workingCopy.getClasspathInfo().getClassPath(PathKind.SOURCE)), false, false, new AtomicBoolean()); |
| } |
| |
| public FindUsagesVisitor(CompilationController workingCopy, AtomicBoolean isCancelled, boolean findInComments, boolean isSearchOverloadedMethods, boolean fromTestRoot, boolean fromPlatform, boolean fromDependency, AtomicBoolean inImport) { |
| try { |
| setWorkingCopy(workingCopy); |
| } catch (ToPhaseException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| this.findInComments = findInComments; |
| this.isSearchOverloadedMethods = isSearchOverloadedMethods; |
| this.fromTestRoot = fromTestRoot; |
| this.fromPlatform = fromPlatform; |
| this.fromDependency = fromDependency; |
| this.inImport = inImport; |
| this.isCancelled = isCancelled; |
| this.methods = new LinkedList<>(); |
| } |
| |
| @Override |
| public Tree visitCompilationUnit(CompilationUnitTree node, Element p) { |
| if (findInComments) { |
| String originalName; |
| if(p.getKind() == ElementKind.CONSTRUCTOR) { |
| originalName = p.getEnclosingElement().getSimpleName().toString(); |
| } else { |
| originalName = p.getSimpleName().toString(); |
| } |
| TokenSequence<JavaTokenId> ts = workingCopy.getTokenHierarchy().tokenSequence(JavaTokenId.language()); |
| |
| while (ts.moveNext()) { |
| if(isCancelled.get()) { |
| return null; |
| } |
| Token t = ts.token(); |
| |
| if (t.id() == JavaTokenId.BLOCK_COMMENT || t.id() == JavaTokenId.LINE_COMMENT || t.id() == JavaTokenId.JAVADOC_COMMENT) { |
| Scanner tokenizer = new Scanner(t.text().toString()); |
| tokenizer.useDelimiter("[^a-zA-Z0-9_]"); //NOI18N |
| while (tokenizer.hasNext()) { |
| String current = tokenizer.next(); |
| if (current.equals(originalName)) { |
| WhereUsedElement comment = WhereUsedElement.create(ts.offset() + tokenizer.match().start(), |
| ts.offset() + tokenizer.match().end(), workingCopy, fromTestRoot, fromPlatform, fromDependency); |
| elements.add(comment); |
| usagesInComments = true; |
| } |
| } |
| } |
| } |
| } |
| if(RefactoringUtils.isExecutableElement(p)) { |
| ExecutableElement method = (ExecutableElement) p; |
| methods.add(method); |
| TypeElement enclosingTypeElement = workingCopy.getElementUtilities().enclosingTypeElement(method); |
| if(isSearchOverloadedMethods) { |
| for (Element overloaded : enclosingTypeElement.getEnclosedElements()) { |
| if(method != overloaded && |
| method.getKind() == overloaded.getKind() && |
| ((ExecutableElement)overloaded).getSimpleName().contentEquals(method.getSimpleName())) { |
| methods.add((ExecutableElement)overloaded); |
| } |
| } |
| } |
| } |
| return super.visitCompilationUnit(node, p); |
| } |
| |
| private void addIfMatch(TreePath path, Tree tree, Element elementToFind) { |
| if(isCancelled.get()) { |
| return; |
| } |
| if (JavaPluginUtils.isSyntheticPath(workingCopy, path)) { |
| if (ElementKind.CONSTRUCTOR != elementToFind.getKind() |
| || tree.getKind() != Tree.Kind.IDENTIFIER |
| || !"super".contentEquals(((IdentifierTree) tree).getName())) { // NOI18N |
| // do not skip synthetic usages of constructor |
| return; |
| } |
| } |
| Trees trees = workingCopy.getTrees(); |
| Element el = trees.getElement(path); |
| if (el == null) { |
| path = path.getParentPath(); |
| if (path != null && path.getLeaf().getKind() == Kind.IMPORT) { |
| ImportTree impTree = (ImportTree) path.getLeaf(); |
| if (!impTree.isStatic()) { |
| return; |
| } |
| Tree idTree = impTree.getQualifiedIdentifier(); |
| if (idTree.getKind() != Kind.MEMBER_SELECT) { |
| return; |
| } |
| final Name id = ((MemberSelectTree) idTree).getIdentifier(); |
| if (id.contentEquals("*")) { |
| return; |
| } |
| Tree classTree = ((MemberSelectTree) idTree).getExpression(); |
| path = trees.getPath(workingCopy.getCompilationUnit(), classTree); |
| el = trees.getElement(path); |
| if (el == null) { |
| return; |
| } |
| Iterator iter = workingCopy.getElementUtilities().getMembers(el.asType(), new ElementUtilities.ElementAcceptor() { |
| @Override |
| public boolean accept(Element e, TypeMirror type) { |
| return id.equals(e.getSimpleName()); |
| } |
| }).iterator(); |
| if (iter.hasNext()) { |
| el = (Element) iter.next(); |
| } |
| if (iter.hasNext()) { |
| return; |
| } |
| } else { |
| return; |
| } |
| } |
| if (elementToFind != null && elementToFind.getKind() == ElementKind.METHOD && el.getKind() == ElementKind.METHOD) { |
| for (ExecutableElement executableElement : methods) { |
| if (el.equals(executableElement) |
| || workingCopy.getElements().overrides((ExecutableElement) el, |
| executableElement, (TypeElement) elementToFind.getEnclosingElement())) { |
| addUsage(path); |
| } |
| } |
| } else if (el.equals(elementToFind)) { |
| final ElementKind kind = elementToFind.getKind(); |
| if(kind.isField() || kind == ElementKind.LOCAL_VARIABLE || kind == ElementKind.RESOURCE_VARIABLE || kind == ElementKind.PARAMETER) { |
| JavaWhereUsedFilters.ReadWrite access; |
| Element collectionElement = workingCopy.getElementUtilities().findElement("java.util.Collection"); //NOI18N |
| Element mapElement = workingCopy.getElementUtilities().findElement("java.util.Map"); //NOI18N |
| if(collectionElement != null && |
| workingCopy.getTypes().isSubtype( |
| workingCopy.getTypes().erasure(el.asType()), |
| workingCopy.getTypes().erasure(collectionElement.asType()))) { |
| access = analyzeCollectionAccess(path); |
| } else if(mapElement != null && |
| workingCopy.getTypes().isSubtype( |
| workingCopy.getTypes().erasure(el.asType()), |
| workingCopy.getTypes().erasure(mapElement.asType()))) { |
| access = analyzeCollectionAccess(path); |
| } else { |
| access = analyzeVarAccess(path, elementToFind, tree); |
| } |
| addUsage(path, access); |
| } else { |
| addUsage(path); |
| } |
| } |
| } |
| |
| private Set<String> writeMethods = new HashSet<>(Arrays.asList( |
| "add", "addAll", "putAll", "remove", "removeAll", "retainAll", |
| "removeIf", "clear")); |
| private Set<String> readMethods = new HashSet<>(Arrays.asList( |
| "get", "getOrDefault", "first", "last", "firstKey", "lastKey", |
| "contains", "containsKey", "containsValue", "containsAll", "size", |
| "isEmpty", "indexOf")); |
| private Set<String> readWriteMethods = new HashSet<>(Arrays.asList( |
| "sort", "set", "put", "putIfAbsent", "replace")); |
| private JavaWhereUsedFilters.ReadWrite analyzeCollectionAccess(TreePath path) { |
| JavaWhereUsedFilters.ReadWrite result = null; |
| TreePath parentPath = path.getParentPath(); |
| Tree parentTree = parentPath.getLeaf(); |
| Kind parentKind = parentTree.getKind(); |
| if(parentKind == Kind.MEMBER_SELECT) { |
| Element member = workingCopy.getTrees().getElement(parentPath); |
| if(member != null && member.getKind() == ElementKind.METHOD) { |
| ExecutableElement method = (ExecutableElement) member; |
| if (writeMethods.contains(method.getSimpleName().toString())) { |
| result = JavaWhereUsedFilters.ReadWrite.WRITE; |
| } else if (readMethods.contains(method.getSimpleName().toString())) { |
| result = JavaWhereUsedFilters.ReadWrite.READ; |
| } else if (readWriteMethods.contains(method.getSimpleName().toString())) { |
| result = JavaWhereUsedFilters.ReadWrite.READ_WRITE; |
| } |
| } |
| } |
| return result; |
| } |
| |
| private JavaWhereUsedFilters.ReadWrite analyzeVarAccess(TreePath path, Element elementToFind, Tree tree) { |
| JavaWhereUsedFilters.ReadWrite access = JavaWhereUsedFilters.ReadWrite.READ; |
| TreePath parentPath = path.getParentPath(); |
| Tree parentTree = parentPath.getLeaf(); |
| Kind parentKind = parentTree.getKind(); |
| if(elementToFind.asType().getKind() == TypeKind.ARRAY && |
| parentKind == Kind.ARRAY_ACCESS) { |
| tree = parentPath.getLeaf(); |
| parentPath = parentPath.getParentPath(); |
| parentTree = parentPath.getLeaf(); |
| parentKind = parentTree.getKind(); |
| } |
| switch(parentKind) { |
| case ARRAY_ACCESS: |
| case MEMBER_SELECT: |
| // TODO: Check usages of arrays for writing |
| break; |
| |
| case POSTFIX_INCREMENT: |
| case POSTFIX_DECREMENT: |
| case PREFIX_INCREMENT: |
| case PREFIX_DECREMENT: |
| access = JavaWhereUsedFilters.ReadWrite.READ_WRITE; |
| break; |
| |
| case ASSIGNMENT: { |
| AssignmentTree assignmentTree = (AssignmentTree) parentTree; |
| ExpressionTree left = assignmentTree.getVariable(); |
| if (left.equals(tree)) { |
| access = JavaWhereUsedFilters.ReadWrite.WRITE; |
| } |
| break; |
| } |
| case MULTIPLY_ASSIGNMENT: |
| case DIVIDE_ASSIGNMENT: |
| case REMAINDER_ASSIGNMENT: |
| case PLUS_ASSIGNMENT: |
| case MINUS_ASSIGNMENT: |
| case LEFT_SHIFT_ASSIGNMENT: |
| case RIGHT_SHIFT_ASSIGNMENT: |
| case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: |
| case AND_ASSIGNMENT: |
| case XOR_ASSIGNMENT: |
| case OR_ASSIGNMENT: { |
| CompoundAssignmentTree compoundAssignmentTree = (CompoundAssignmentTree) parentTree; |
| ExpressionTree left = compoundAssignmentTree.getVariable(); |
| if (left.equals(tree)) { |
| access = JavaWhereUsedFilters.ReadWrite.READ_WRITE; |
| } |
| break; |
| } |
| } |
| return access; |
| } |
| |
| /** |
| * |
| * @param workingCopy |
| * @throws org.netbeans.modules.refactoring.java.spi.ToPhaseException |
| */ |
| public final void setWorkingCopy(CompilationController workingCopy) throws ToPhaseException { |
| this.workingCopy = workingCopy; |
| try { |
| if (this.workingCopy.toPhase(JavaSource.Phase.RESOLVED) != JavaSource.Phase.RESOLVED) { |
| throw new ToPhaseException(); |
| } |
| } catch (IOException ioe) { |
| ErrorManager.getDefault().notify(ioe); |
| } |
| } |
| |
| protected void addUsage(TreePath tp, JavaWhereUsedFilters.ReadWrite access) { |
| assert tp != null; |
| elements.add(WhereUsedElement.create(workingCopy, tp, access, fromTestRoot, fromPlatform, fromDependency, inImport)); |
| usages.add(tp); |
| } |
| |
| public boolean isInImport() { |
| return inImport.get(); |
| } |
| |
| protected void addUsage(TreePath tp) { |
| assert tp != null; |
| elements.add(WhereUsedElement.create(workingCopy, tp, fromTestRoot, fromPlatform, fromDependency, inImport)); |
| usages.add(tp); |
| } |
| |
| public Collection<WhereUsedElement> getElements() { |
| if(findInComments) { // the elements need to be sorted. Comments are searched for the whole file at once. |
| Collections.sort(elements, new Comparator<WhereUsedElement>() { |
| |
| @Override |
| public int compare(WhereUsedElement o1, WhereUsedElement o2) { |
| return o1.getPosition().getBegin().getOffset() - o2.getPosition().getBegin().getOffset(); |
| } |
| }); |
| } |
| return elements; |
| } |
| |
| public Collection<TreePath> getUsages() { |
| return usages; |
| } |
| |
| @Override |
| public Tree visitMemberReference(MemberReferenceTree node, Element p) { |
| if(isCancelled.get()) { |
| return null; |
| } |
| addIfMatch(getCurrentPath(), node, p); |
| return super.visitMemberReference(node, p); |
| } |
| |
| @Override |
| public Tree visitIdentifier(IdentifierTree node, Element p) { |
| if(isCancelled.get()) { |
| return null; |
| } |
| addIfMatch(getCurrentPath(), node, p); |
| return super.visitIdentifier(node, p); |
| } |
| |
| @Override |
| public Tree visitMemberSelect(MemberSelectTree node, Element p) { |
| if(isCancelled.get()) { |
| return null; |
| } |
| addIfMatch(getCurrentPath(), node, p); |
| return super.visitMemberSelect(node, p); |
| } |
| |
| @Override |
| public Tree visitNewClass(NewClassTree node, Element p) { |
| if(isCancelled.get()) { |
| return null; |
| } |
| Trees trees = workingCopy.getTrees(); |
| ClassTree classTree = ((NewClassTree) node).getClassBody(); |
| if (classTree != null && p.getKind() == ElementKind.CONSTRUCTOR) { |
| for (Tree t : classTree.getMembers()) { |
| Element elem = workingCopy.getTrees().getElement(TreePath.getPath(workingCopy.getCompilationUnit(), t)); |
| if ((elem != null) && (elem.getKind() == ElementKind.CONSTRUCTOR)) { |
| TreePath superCall = trees.getPath(workingCopy.getCompilationUnit(), ((ExpressionStatementTree) ((MethodTree) t).getBody().getStatements().get(0)).getExpression()); |
| Element superCallElement = trees.getElement(superCall); |
| if (superCallElement != null && superCallElement.equals(p) && !workingCopy.getTreeUtilities().isSynthetic(superCall)) { |
| addUsage(superCall); |
| } |
| } |
| } |
| } else { |
| addIfMatch(getCurrentPath(), node, p); |
| } |
| return super.visitNewClass(node, p); |
| } |
| |
| public boolean usagesInComments() { |
| return usagesInComments; |
| } |
| } |