| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.netbeans.modules.java.editor.base.imports; |
| |
| import com.sun.source.tree.ClassTree; |
| import com.sun.source.tree.CompilationUnitTree; |
| import com.sun.source.tree.IdentifierTree; |
| import com.sun.source.tree.ImportTree; |
| import com.sun.source.tree.MemberSelectTree; |
| import com.sun.source.tree.MethodTree; |
| import com.sun.source.tree.Tree; |
| import com.sun.source.tree.Tree.Kind; |
| import com.sun.source.tree.VariableTree; |
| import com.sun.source.util.TreePath; |
| import org.netbeans.api.java.source.support.ErrorAwareTreeScanner; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import javax.lang.model.SourceVersion; |
| import javax.lang.model.element.Element; |
| import javax.lang.model.element.ElementKind; |
| import javax.lang.model.element.Modifier; |
| import javax.lang.model.element.Name; |
| import javax.lang.model.element.TypeElement; |
| import javax.lang.model.type.TypeKind; |
| import javax.lang.model.type.TypeMirror; |
| import javax.lang.model.util.ElementFilter; |
| import org.netbeans.api.annotations.common.NullAllowed; |
| import org.netbeans.api.java.source.CompilationInfo; |
| import org.netbeans.api.java.source.CompilationInfo.CacheClearPolicy; |
| import org.netbeans.api.java.source.TreePathHandle; |
| import org.netbeans.api.java.source.support.CancellableTreePathScanner; |
| import org.netbeans.modules.java.editor.base.javadoc.JavadocImports; |
| |
| /** |
| * |
| * @author lahvac |
| */ |
| public class UnusedImports { |
| private static final Object KEY_CACHE = new Object(); |
| |
| public static List<TreePathHandle> computeUnusedImports(CompilationInfo info) { |
| final List<TreePathHandle> result = new ArrayList<>(); |
| |
| for (TreePath unused : process(info, new AtomicBoolean())) { |
| result.add(TreePathHandle.create(unused, info)); |
| } |
| |
| return result; |
| } |
| |
| public static Collection<TreePath> process(CompilationInfo info, AtomicBoolean cancel) { |
| Collection<TreePath> result = (Collection<TreePath>) info.getCachedValue(KEY_CACHE); |
| |
| if (result != null) return result; |
| |
| DetectorVisitor v = new DetectorVisitor(info, cancel); |
| |
| CompilationUnitTree cu = info.getCompilationUnit(); |
| |
| v.scan(cu, null); |
| |
| if (cancel.get()) |
| return null; |
| |
| List<TreePath> allUnusedImports = new ArrayList<TreePath>(); |
| |
| for (TreePath tree : v.getUnusedImports().values()) { |
| if (cancel.get()) { |
| return null; |
| } |
| |
| allUnusedImports.add(tree); |
| } |
| |
| allUnusedImports = Collections.unmodifiableList(allUnusedImports); |
| |
| info.putCachedValue(KEY_CACHE, allUnusedImports, CacheClearPolicy.ON_CHANGE); |
| |
| return allUnusedImports; |
| } |
| |
| private static class DetectorVisitor extends CancellableTreePathScanner<Void, Void> { |
| |
| private final CompilationInfo info; |
| |
| private final Map<Element, ImportTree> element2Import = new HashMap<>(); |
| private final Set<Element> importedBySingleImport = new HashSet<>(); |
| private final Map<String, Collection<ImportTree>> simpleName2UnresolvableImports = new HashMap<>(); |
| private final Set<ImportTree> unresolvablePackageImports = new HashSet<>(); |
| private final Map<ImportTree, TreePath/*ImportTree*/> import2Highlight = new HashMap<>(); |
| private final Map<ImportTree, Integer> usageCounts = new HashMap<>(); |
| |
| private DetectorVisitor(CompilationInfo info, AtomicBoolean cancel) { |
| super(cancel); |
| |
| this.info = info; |
| } |
| |
| private void handleJavadoc(TreePath classMember) { |
| if (classMember == null) { |
| return; |
| } |
| for (Element el : JavadocImports.computeReferencedElements(info, classMember)) { |
| typeUsed(el, null, false); |
| } |
| } |
| |
| public Map<ImportTree, TreePath/*ImportTree*/> getUnusedImports() { |
| Map<ImportTree, TreePath> ret = new HashMap<>(import2Highlight); |
| ret.keySet().removeAll(usageCounts.keySet()); |
| return ret; |
| } |
| |
| @Override |
| public Void visitClass(ClassTree node, Void p) { |
| handleJavadoc(getCurrentPath()); |
| return super.visitClass(node, p); |
| } |
| |
| @Override |
| public Void visitMethod(MethodTree node, Void p) { |
| handleJavadoc(getCurrentPath()); |
| return super.visitMethod(node, p); |
| } |
| |
| @Override |
| public Void visitVariable(VariableTree node, Void p) { |
| Element e = info.getTrees().getElement(getCurrentPath()); |
| |
| if (e != null && e.getKind().isField()) { |
| handleJavadoc(getCurrentPath()); |
| } |
| |
| return super.visitVariable(node, p); |
| } |
| |
| @Override |
| public Void visitCompilationUnit(CompilationUnitTree tree, Void d) { |
| //ignore package X.Y.Z;: |
| //scan(tree.getPackageDecl(), p); |
| scan(tree.getImports(), d); |
| scan(tree.getPackageAnnotations(), d); |
| scan(tree.getTypeDecls(), d); |
| return null; |
| } |
| |
| @Override |
| public Void visitIdentifier(IdentifierTree tree, Void d) { |
| if (info.getTrees().getSourcePositions().getStartPosition(getCurrentPath().getCompilationUnit(), tree) < 0) |
| return null; |
| |
| typeUsed(info.getTrees().getElement(getCurrentPath()), getCurrentPath(), getCurrentPath().getParentPath().getLeaf().getKind() == Kind.METHOD_INVOCATION); |
| |
| return super.visitIdentifier(tree, null); |
| } |
| |
| private boolean isStar(ImportTree tree) { |
| Tree qualIdent = tree.getQualifiedIdentifier(); |
| |
| if (qualIdent == null || qualIdent.getKind() == Kind.IDENTIFIER) { |
| return false; |
| } |
| |
| return ((MemberSelectTree) qualIdent).getIdentifier().contentEquals("*"); |
| } |
| |
| private boolean parseErrorInImport(ImportTree imp) { |
| if (isStar(imp)) return false; |
| final StringBuilder fqn = new StringBuilder(); |
| new ErrorAwareTreeScanner<Void, Void>() { |
| @Override |
| public Void visitMemberSelect(MemberSelectTree node, Void p) { |
| super.visitMemberSelect(node, p); |
| fqn.append('.'); |
| fqn.append(node.getIdentifier()); |
| return null; |
| } |
| @Override |
| public Void visitIdentifier(IdentifierTree node, Void p) { |
| fqn.append(node.getName()); |
| return null; |
| } |
| }.scan(imp.getQualifiedIdentifier(), null); |
| |
| return !SourceVersion.isName(fqn); |
| } |
| |
| public boolean isErroneous(@NullAllowed Element e) { |
| if (e == null) { |
| return true; |
| } |
| if (e.getKind() == ElementKind.MODULE) { |
| return false; |
| } |
| final TypeMirror type = e.asType(); |
| if (type == null) { |
| return false; |
| } |
| return type.getKind() == TypeKind.ERROR || type.getKind() == TypeKind.OTHER; |
| } |
| |
| @Override |
| public Void visitImport(ImportTree tree, Void d) { |
| if (parseErrorInImport(tree)) { |
| return super.visitImport(tree, null); |
| } |
| if (tree.getQualifiedIdentifier() == null || |
| tree.getQualifiedIdentifier().getKind() != Tree.Kind.MEMBER_SELECT) { |
| return super.visitImport(tree, null); |
| } |
| MemberSelectTree qualIdent = (MemberSelectTree) tree.getQualifiedIdentifier(); |
| boolean assign = false; |
| |
| // static imports and star imports only use the qualifier part |
| boolean star = isStar(tree); |
| TreePath tp = tree.isStatic() || star ? |
| new TreePath(new TreePath(getCurrentPath(), qualIdent), qualIdent.getExpression()) : |
| new TreePath(getCurrentPath(), tree.getQualifiedIdentifier()); |
| Element decl = info.getTrees().getElement(tp); |
| |
| import2Highlight.put(tree, getCurrentPath()); |
| if (decl != null && !isErroneous(decl)) { |
| if (!tree.isStatic()) { |
| if (star) { |
| List<TypeElement> types = ElementFilter.typesIn(decl.getEnclosedElements()); |
| for (TypeElement te : types) { |
| assign = true; |
| if (!element2Import.containsKey(te)) { |
| element2Import.put(te, tree); |
| } |
| } |
| } else { |
| element2Import.put(decl, tree); |
| importedBySingleImport.add(decl); |
| } |
| } else if (decl.getKind().isClass() || decl.getKind().isInterface()) { |
| Name simpleName = star ? null : qualIdent.getIdentifier(); |
| |
| for (Element e : info.getElements().getAllMembers((TypeElement) decl)) { |
| if (!e.getModifiers().contains(Modifier.STATIC)) continue; |
| if (simpleName != null && !e.getSimpleName().equals(simpleName)) { |
| continue; |
| } |
| if (!star || !element2Import.containsKey(e)) { |
| element2Import.put(e, tree); |
| } |
| assign = true; |
| } |
| } |
| } |
| if (!assign) { |
| if (!tree.isStatic() && star) { |
| unresolvablePackageImports.add(tree); |
| } else { |
| addUnresolvableImport(qualIdent.getIdentifier(), tree); |
| } |
| } |
| super.visitImport(tree, null); |
| return null; |
| } |
| |
| private void addUnresolvableImport(Name name, ImportTree imp) { |
| String key = name.toString(); |
| |
| Collection<ImportTree> l = simpleName2UnresolvableImports.get(key); |
| |
| if (l == null) { |
| simpleName2UnresolvableImports.put(key, l = new LinkedList<ImportTree>()); |
| } |
| |
| l.add(imp); |
| } |
| |
| private void addUsage(ImportTree imp) { |
| Integer i = usageCounts.get(imp); |
| if (i == null) { |
| i = 0; |
| } |
| usageCounts.put(imp, i++); |
| } |
| |
| private void typeUsed(Element decl, TreePath expr, boolean methodInvocation) { |
| if (decl != null && (expr == null || expr.getLeaf().getKind() == Kind.IDENTIFIER || expr.getLeaf().getKind() == Kind.PARAMETERIZED_TYPE)) { |
| if (!isErroneous(decl)) { |
| ImportTree imp = element2Import.get(decl); |
| |
| if (imp != null) { |
| addUsage(imp); |
| if (isStar(imp)) { |
| //TODO: explain |
| handleUnresolvableImports(decl, methodInvocation, false); |
| } |
| } |
| } else { |
| handleUnresolvableImports(decl, methodInvocation, true); |
| |
| for (Entry<Element, ImportTree> e : element2Import.entrySet()) { |
| if (importedBySingleImport.contains(e.getKey())) continue; |
| |
| if (e.getKey().getSimpleName().equals(decl.getSimpleName())) { |
| import2Highlight.remove(e.getValue()); |
| } |
| } |
| } |
| } |
| } |
| |
| private void handleUnresolvableImports(Element decl, |
| boolean methodInvocation, boolean removeStarImports) { |
| Name simpleName = decl.getSimpleName(); |
| if (simpleName != null) { |
| Collection<ImportTree> imps = simpleName2UnresolvableImports.get(simpleName.toString()); |
| |
| if (imps != null) { |
| for (ImportTree imp : imps) { |
| if (!methodInvocation || imp.isStatic()) { |
| import2Highlight.remove(imp); |
| } |
| } |
| } else { |
| if (removeStarImports) { |
| //TODO: explain |
| for (ImportTree unresolvable : unresolvablePackageImports) { |
| if (!methodInvocation || unresolvable.isStatic()) { |
| import2Highlight.remove(unresolvable); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| } |