| /* |
| * 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.imports; |
| |
| import com.sun.source.tree.AnnotationTree; |
| import com.sun.source.tree.AssignmentTree; |
| import com.sun.source.tree.ClassTree; |
| import com.sun.source.tree.CompilationUnitTree; |
| import com.sun.source.tree.ExpressionTree; |
| import com.sun.source.tree.IdentifierTree; |
| import com.sun.source.tree.MemberSelectTree; |
| import com.sun.source.tree.MethodInvocationTree; |
| import com.sun.source.tree.MethodTree; |
| import com.sun.source.tree.NewArrayTree; |
| import com.sun.source.tree.NewClassTree; |
| import com.sun.source.tree.ParameterizedTypeTree; |
| import com.sun.source.tree.Scope; |
| 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 java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.EnumSet; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import java.util.prefs.PreferenceChangeEvent; |
| import java.util.prefs.PreferenceChangeListener; |
| import java.util.prefs.Preferences; |
| import javax.lang.model.SourceVersion; |
| import javax.lang.model.element.Element; |
| import javax.lang.model.element.ElementKind; |
| import javax.lang.model.element.ExecutableElement; |
| import javax.lang.model.element.Modifier; |
| import javax.lang.model.element.ModuleElement; |
| import javax.lang.model.element.PackageElement; |
| import javax.lang.model.element.TypeElement; |
| import javax.lang.model.element.VariableElement; |
| import javax.lang.model.type.ArrayType; |
| import javax.lang.model.type.DeclaredType; |
| import javax.lang.model.type.ExecutableType; |
| import javax.lang.model.type.TypeKind; |
| import javax.lang.model.type.TypeMirror; |
| import javax.lang.model.util.ElementFilter; |
| import javax.lang.model.util.Types; |
| import org.netbeans.api.annotations.common.NonNull; |
| import org.netbeans.api.annotations.common.NullAllowed; |
| import org.netbeans.api.editor.mimelookup.MimeLookup; |
| import org.netbeans.api.java.classpath.ClassPath; |
| import org.netbeans.api.java.lexer.JavaTokenId; |
| import org.netbeans.api.java.source.ClassIndex; |
| import org.netbeans.api.java.source.CompilationInfo; |
| import org.netbeans.api.java.source.ClassIndex.NameKind; |
| import org.netbeans.api.java.source.ClassIndex.Symbols; |
| import org.netbeans.api.java.source.ClasspathInfo; |
| import org.netbeans.api.java.source.CompilationController; |
| import org.netbeans.api.java.source.CompilationInfo.CacheClearPolicy; |
| import org.netbeans.api.java.source.ElementHandle; |
| import org.netbeans.api.java.source.ElementUtilities.ElementAcceptor; |
| import org.netbeans.api.java.source.JavaSource; |
| import org.netbeans.api.java.source.Task; |
| import org.netbeans.api.java.source.support.CancellableTreePathScanner; |
| import org.netbeans.modules.java.completion.Utilities; |
| import org.netbeans.modules.java.editor.base.javadoc.JavadocImports; |
| import org.netbeans.modules.parsing.api.ResultIterator; |
| import org.netbeans.modules.parsing.api.UserTask; |
| import org.netbeans.spi.java.classpath.support.ClassPathSupport; |
| import org.openide.util.Exceptions; |
| import org.openide.util.Union2; |
| import org.openide.util.WeakListeners; |
| |
| /** |
| * |
| * @author Jan Lahoda |
| */ |
| public final class ComputeImports { |
| |
| private static final String ERROR = "<error>"; |
| |
| /** Creates a new instance of JavaFixAllImports */ |
| public ComputeImports(final CompilationInfo info) { |
| this.info = info; |
| Preferences preferences = MimeLookup.getLookup(JavaTokenId.language().mimeType()).lookup(Preferences.class); |
| preferences.addPreferenceChangeListener(WeakListeners.create(PreferenceChangeListener.class, pcl, preferences)); |
| } |
| |
| private final CompilationInfo info; |
| private CompilationInfo allInfo; |
| |
| private final PreferenceChangeListener pcl = new PreferenceChangeListener() { |
| @Override |
| public void preferenceChange(PreferenceChangeEvent evt) { |
| info.putCachedValue(IMPORT_CANDIDATES_KEY, null, CacheClearPolicy.ON_CHANGE); |
| } |
| }; |
| private boolean cancelled; |
| |
| /** |
| * Candidate Elements, filtered according to visibility rules |
| */ |
| Map<String, List<Element>> candidates = new HashMap<>(); |
| |
| /** |
| * Candidate Elements, with no respect to visibility |
| */ |
| Map<String, List<Element>> notFilteredCandidates = new HashMap<>(); |
| |
| /** |
| * For each name, a possible FQNs for methods. Computed for each round |
| * of Hint processing |
| */ |
| Map<String, Set<String>> possibleMethodFQNs = new HashMap<>(); |
| |
| /** |
| * Index of possible elements of the same FQN. Computed for each round |
| * of Hint processing |
| */ |
| Map<String, List<Element>> fqn2Methods = new HashMap<>(); |
| |
| public synchronized void cancel() { |
| cancelled = true; |
| |
| if (visitor != null) |
| visitor.cancel(); |
| } |
| |
| public Set<String> getMethodFQNs(String simpleName) { |
| return possibleMethodFQNs.get(simpleName); |
| } |
| |
| private synchronized boolean isCancelled() { |
| return cancelled; |
| } |
| |
| public List<Element> getCandidates(String simpleName) { |
| return candidates.get(simpleName); |
| } |
| |
| public List<Element> getRawCandidates(String simpleName) { |
| return notFilteredCandidates.get(simpleName); |
| } |
| |
| private static final Object IMPORT_CANDIDATES_KEY = new Object(); |
| |
| public ComputeImports computeCandidatesEx() { |
| return computeCandidatesEx(Collections.emptySet()); |
| } |
| |
| private ComputeImports computeCandidatesEx(Set<String> forcedUnresolved) { |
| ComputeImports cache = (ComputeImports)info.getCachedValue(IMPORT_CANDIDATES_KEY); |
| if (cache != null) { |
| return cache; |
| } |
| boolean modules = false; |
| |
| if (info.getSourceVersion().compareTo(SourceVersion.RELEASE_9) <= 0) { |
| if (info.getClasspathInfo().getClassPath(ClasspathInfo.PathKind.SOURCE).findResource("module-info.java") != null) { |
| modules = true; |
| } |
| } |
| |
| if (modules) { |
| ClasspathInfo cpInfo = info.getClasspathInfo(); |
| ClasspathInfo extraInfo = ClasspathInfo.create( |
| ClassPathSupport.createProxyClassPath( |
| cpInfo.getClassPath(ClasspathInfo.PathKind.BOOT), |
| cpInfo.getClassPath(ClasspathInfo.PathKind.MODULE_BOOT)), |
| ClassPathSupport.createProxyClassPath( |
| cpInfo.getClassPath(ClasspathInfo.PathKind.COMPILE), |
| cpInfo.getClassPath(ClasspathInfo.PathKind.MODULE_COMPILE), |
| cpInfo.getClassPath(ClasspathInfo.PathKind.MODULE_CLASS)), |
| cpInfo.getClassPath(ClasspathInfo.PathKind.SOURCE)); |
| JavaSource src = JavaSource.create(extraInfo, info.getSnapshot().getSource().getFileObject()); |
| try { |
| src.runUserActionTask(new Task<CompilationController>() { |
| @Override |
| public void run(CompilationController parameter) throws Exception { |
| allInfo = parameter; |
| parameter.toPhase(JavaSource.Phase.RESOLVED); |
| doComputeCandidates(forcedUnresolved); |
| } |
| }, true); |
| } catch (IOException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| } else { |
| allInfo = info; |
| doComputeCandidates(forcedUnresolved); |
| } |
| info.putCachedValue(IMPORT_CANDIDATES_KEY, this, CacheClearPolicy.ON_CHANGE); |
| return this; |
| } |
| |
| public Pair<Map<String, List<Element>>, Map<String, List<Element>>> getSimpleCandidates() { |
| return new Pair<Map<String, List<Element>>, Map<String, List<Element>>>(candidates, notFilteredCandidates); |
| } |
| |
| public Pair<Map<String, List<Element>>, Map<String, List<Element>>> computeCandidates() { |
| return computeCandidates(Collections.emptySet()); |
| } |
| |
| public Pair<Map<String, List<Element>>, Map<String, List<Element>>> computeCandidates(Set<String> forcedUnresolved) { |
| return computeCandidatesEx(forcedUnresolved).getSimpleCandidates(); |
| } |
| |
| private TreeVisitorImpl visitor; |
| |
| private synchronized void setVisitor(TreeVisitorImpl visitor) { |
| this.visitor = visitor; |
| } |
| |
| private void doComputeCandidates(Set<String> forcedUnresolved) { |
| final CompilationUnitTree cut = info.getCompilationUnit(); |
| ClasspathInfo cpInfo = allInfo.getClasspathInfo(); |
| final TreeVisitorImpl v = new TreeVisitorImpl(info); |
| setVisitor(v); |
| try { |
| v.scan(cut, new HashMap<String, Object>()); |
| } finally { |
| setVisitor(null); |
| } |
| |
| Set<String> unresolvedNames = new HashSet<String>(v.unresolved); |
| |
| unresolvedNames.addAll(forcedUnresolved); |
| |
| unresolvedNames.addAll(JavadocImports.computeUnresolvedImports(info)); |
| |
| Set<String> unresolvedNonTypes = new HashSet<String>(v.unresolvedNonTypes); |
| |
| unresolvedNonTypes.addAll(forcedUnresolved); |
| |
| for (String unresolved : unresolvedNames) { |
| if (isCancelled()) |
| return; |
| |
| List<Element> classes = new ArrayList<Element>(); |
| Set<ElementHandle<TypeElement>> typeNames = cpInfo.getClassIndex().getDeclaredTypes(unresolved, NameKind.SIMPLE_NAME,EnumSet.allOf(ClassIndex.SearchScope.class)); |
| if (typeNames == null) { |
| //Canceled |
| return; |
| } |
| for (ElementHandle<TypeElement> typeName : typeNames) { |
| if (isCancelled()) |
| return; |
| TypeElement te = typeName.resolve(allInfo); |
| |
| if (te == null) { |
| Logger.getLogger(ComputeImports.class.getName()).log(Level.INFO, "Cannot resolve type element \"" + typeName + "\"."); |
| continue; |
| } |
| |
| //#122334: do not propose imports from the default package: |
| if (info.getElements().getPackageOf(te).getQualifiedName().length() != 0 && |
| !Utilities.isExcluded(te.getQualifiedName())) { |
| classes.add(te); |
| } |
| } |
| |
| if (unresolvedNonTypes.contains(unresolved)) { |
| Iterable<Symbols> simpleNames = cpInfo.getClassIndex().getDeclaredSymbols(unresolved, NameKind.SIMPLE_NAME,EnumSet.allOf(ClassIndex.SearchScope.class)); |
| |
| if (simpleNames == null) { |
| //Canceled: |
| return; |
| } |
| |
| for (final Symbols p : simpleNames) { |
| if (isCancelled()) |
| return; |
| |
| final TypeElement te = p.getEnclosingType().resolve(allInfo); |
| final Set<String> idents = p.getSymbols(); |
| if (te != null) { |
| for (Element ne : te.getEnclosedElements()) { |
| if (!ne.getModifiers().contains(Modifier.STATIC)) continue; |
| if (idents.contains(getSimpleName(ne, te))) { |
| classes.add(ne); |
| } |
| } |
| } |
| } |
| } |
| |
| candidates.put(unresolved, new ArrayList(classes)); |
| notFilteredCandidates.put(unresolved, classes); |
| } |
| |
| boolean wasChanged = true; |
| |
| while (wasChanged) { |
| if (isCancelled()) |
| return; |
| |
| wasChanged = false; |
| // reset possible FQNs, since the set of acessible stuff may have changed -> |
| // collect again |
| possibleMethodFQNs.clear(); |
| fqn2Methods.clear(); |
| |
| for (Hint hint: v.hints) { |
| wasChanged |= hint.filter(allInfo, this); |
| } |
| } |
| |
| // post processing: if some method hint was involved for a SN, we must retain ONLY |
| // such Elements, which correspond to at least 1 method reference in the text. |
| for (String sn : possibleMethodFQNs.keySet()) { |
| Set<String> fqns = possibleMethodFQNs.get(sn); |
| |
| List<Element> cands = candidates.get(sn); |
| List<Element> rawCands = notFilteredCandidates.get(sn); |
| |
| if (cands != null) { |
| for (Iterator<Element> itE = cands.iterator(); itE.hasNext(); ) { |
| Element x = itE.next(); |
| if (x.getKind() != ElementKind.METHOD) { |
| continue; |
| } |
| String fq = info.getElementUtilities().getElementName(x, true).toString(); |
| if (!fqns.contains(fq)) { |
| itE.remove(); |
| } |
| } |
| } |
| if (rawCands != null) { |
| for (Iterator<Element> itE = rawCands.iterator(); itE.hasNext(); ) { |
| Element x = itE.next(); |
| if (x.getKind() != ElementKind.METHOD) { |
| continue; |
| } |
| String fq = info.getElementUtilities().getElementName(x, true).toString(); |
| if (!fqns.contains(fq)) { |
| itE.remove(); |
| } |
| } |
| } |
| } |
| } |
| |
| public void addMethodFqn(Element el) { |
| if (el.getKind() != ElementKind.METHOD) { |
| return; |
| } |
| String fqn = info.getElementUtilities().getElementName(el, true).toString(); |
| List<Element> els = fqn2Methods.get(fqn); |
| if (els == null) { |
| els = new ArrayList<>(2); |
| fqn2Methods.put(fqn, els); |
| } |
| els.add(el); |
| String simpleName = ((ExecutableElement)el).getSimpleName().toString(); |
| Set<String> col = possibleMethodFQNs.get(simpleName); |
| if (col == null) { |
| col = new HashSet<>(3); |
| possibleMethodFQNs.put(simpleName, col); |
| } |
| col.add(fqn); |
| } |
| |
| public String displayNameForImport(@NonNull Element element) { |
| if (element.getKind().isClass() || element.getKind().isInterface()) { |
| return ((TypeElement) element).getQualifiedName().toString(); |
| } |
| |
| StringBuilder fqnSB = new StringBuilder(); |
| |
| fqnSB.append(info.getElementUtilities().getElementName(element, true)); |
| |
| if (element.getKind() == ElementKind.METHOD) { |
| String fqn = fqnSB.toString(); |
| fqnSB.append('('); |
| // check if there are no overloads, otherwise append just ellipsis: |
| Collection<Element> col = fqn2Methods.get(fqn); |
| if (col == null || col.size() == 1) { |
| boolean first = true; |
| for (VariableElement var : ((ExecutableElement) element).getParameters()) { |
| if (!first) { |
| fqnSB.append(", "); |
| } |
| fqnSB.append(info.getTypeUtilities().getTypeName(info.getTypes().erasure(var.asType()))); |
| first = false; |
| } |
| } else { |
| fqnSB.append("..."); // NOI18N |
| } |
| fqnSB.append(')'); // NOI18N |
| } |
| |
| return fqnSB.toString(); |
| } |
| |
| private static final String INIT = "<init>"; //NOI18N |
| private String getSimpleName ( |
| @NonNull final Element element, |
| @NullAllowed final Element enclosingElement) { |
| String result = element.getSimpleName().toString(); |
| if (enclosingElement != null && INIT.equals(result)) { |
| result = enclosingElement.getSimpleName().toString(); |
| } |
| return result; |
| } |
| |
| private static boolean filter(Types types, List<Element> left, List<Element> right, boolean leftReadOnly, boolean rightReadOnly) { |
| boolean changed = false; |
| Map<TypeElement, List<TypeElement>> validPairs = new HashMap<TypeElement, List<TypeElement>>(); |
| |
| for (TypeElement l : ElementFilter.typesIn(left)) { |
| List<TypeElement> valid = new ArrayList<TypeElement>(); |
| |
| for (TypeElement r : ElementFilter.typesIn(right)) { |
| TypeMirror t1 = types.erasure(l.asType()); |
| TypeMirror t2 = types.erasure(r.asType()); |
| |
| // System.err.println("t2 = " + t2); |
| // System.err.println("t1 = " + t1); |
| // System.err.println("types= " + types.getClass()); |
| // System.err.println("types.isAssignable(t2, t1) = " + types.isAssignable(t2, t1)); |
| // System.err.println("types.isSubtype(t2, t1) = " + types.isSubtype(t2, t1)); |
| // System.err.println("types.isAssignable(t1,t2) = " + types.isAssignable(t1,t2)); |
| // System.err.println("types.isSubtype(t1, t2) = " + types.isSubtype(t1, t2)); |
| if (types.isAssignable(t2, t1)) |
| valid.add(r); |
| } |
| |
| // System.err.println("l = " + l ); |
| // System.err.println("valid = " + valid ); |
| validPairs.put(l, valid); |
| } |
| |
| Set<TypeElement> validRights = new HashSet<TypeElement>(); |
| |
| for (TypeElement l : validPairs.keySet()) { |
| List<TypeElement> valid = validPairs.get(l); |
| |
| if (valid.isEmpty() && !leftReadOnly) { |
| //invalid left: |
| left.remove(l); |
| changed = true; |
| } |
| |
| validRights.addAll(valid); |
| } |
| |
| if (!rightReadOnly) |
| changed = right.retainAll(validRights) | changed; |
| |
| return changed; |
| } |
| |
| private static EnumSet<TypeKind> INVALID_TYPES = EnumSet.of(TypeKind.NULL, TypeKind.NONE, TypeKind.OTHER, TypeKind.ERROR); |
| |
| private static class TreeVisitorImpl extends CancellableTreePathScanner<Void, Map<String, Object>> { |
| |
| private final CompilationInfo info; |
| private boolean onlyTypes; |
| private Set<String> unresolved; |
| private Set<String> unresolvedNonTypes; |
| |
| private List<Hint> hints; |
| |
| public TreeVisitorImpl(CompilationInfo info) { |
| this.info = info; |
| unresolved = new HashSet<>(); |
| unresolvedNonTypes = new HashSet<>(); |
| hints = new ArrayList<>(); |
| } |
| |
| @Override |
| public Void visitMemberSelect(MemberSelectTree tree, Map<String, Object> p) { |
| if (tree.getExpression().getKind() == Kind.IDENTIFIER) { |
| p.put("request", null); |
| } |
| |
| scan(tree.getExpression(), p); |
| |
| Union2<String, DeclaredType> leftSide = (Union2<String, DeclaredType>) p.remove("result"); |
| |
| p.remove("request"); |
| |
| if (leftSide != null && leftSide.hasFirst()) { |
| String rightSide = tree.getIdentifier().toString(); |
| |
| if (ERROR.equals(rightSide)) |
| rightSide = ""; |
| |
| boolean isMethodInvocation = getCurrentPath().getParentPath().getLeaf().getKind() == Kind.METHOD_INVOCATION; |
| |
| //Ignore .class (which will not help us much): |
| if (!"class".equals(rightSide)) |
| hints.add(new EnclosedHint(leftSide.first(), rightSide, !isMethodInvocation)); |
| } |
| |
| return null; |
| } |
| |
| @Override |
| public Void visitVariable(VariableTree tree, Map<String, Object> p) { |
| scan(tree.getModifiers(), p); |
| |
| if (tree.getType() != null && tree.getType().getKind() == Kind.IDENTIFIER) { |
| p.put("request", null); |
| } |
| |
| scan(tree.getType(), p, true); |
| |
| Union2<String, DeclaredType> leftSide = (Union2<String, DeclaredType>) p.remove("result"); |
| |
| p.remove("request"); |
| |
| Union2<String, DeclaredType> rightSide = null; |
| |
| if (leftSide != null && tree.getInitializer() != null) { |
| Element el = info.getTrees().getElement(new TreePath(getCurrentPath(),tree.getInitializer())); |
| TypeMirror rightType = el != null ? el.asType() : null; |
| |
| // System.err.println("rightType = " + rightType ); |
| // System.err.println("tree.getInitializer()=" + tree.getInitializer()); |
| // System.err.println("rightType.getKind()=" + rightType.getKind()); |
| // System.err.println("INVALID_TYPES.contains(rightType.getKind())=" + INVALID_TYPES.contains(rightType.getKind())); |
| if (rightType != null && rightType.getKind() == TypeKind.DECLARED) { |
| rightSide = Union2.<String, DeclaredType>createSecond((DeclaredType) rightType); |
| } else { |
| if (tree.getInitializer().getKind() == Kind.NEW_CLASS || tree.getInitializer().getKind() == Kind.NEW_ARRAY) { |
| p.put("request", null); |
| } |
| } |
| } |
| |
| scan(tree.getInitializer(), p); |
| |
| rightSide = rightSide == null ? (Union2<String, DeclaredType>) p.remove("result") : rightSide; |
| |
| p.remove("result"); |
| |
| // System.err.println("rightSide = " + rightSide ); |
| |
| p.remove("request"); |
| |
| if (leftSide != null && rightSide != null) { |
| if (!(leftSide instanceof TypeMirror) || !(rightSide instanceof TypeMirror)) { |
| hints.add(new TypeHint(leftSide, rightSide)); |
| } |
| } |
| |
| return null; |
| } |
| |
| @Override |
| public Void visitIdentifier(IdentifierTree tree, Map<String, Object> p) { |
| super.visitIdentifier(tree, p); |
| |
| boolean methodInvocation = getCurrentPath().getParentPath() != null && getCurrentPath().getParentPath().getLeaf().getKind() == Kind.METHOD_INVOCATION; |
| |
| if (methodInvocation) { |
| MethodInvocationTree mit = (MethodInvocationTree) getCurrentPath().getParentPath().getLeaf(); |
| |
| if (mit.getMethodSelect() == tree) { |
| List<TypeMirror> params = new ArrayList<TypeMirror>(); |
| for (ExpressionTree realParam : mit.getArguments()) { |
| TypeMirror tm = info.getTrees().getTypeMirror(new TreePath(getCurrentPath().getParentPath(), realParam)); |
| if (tm != null && tm.getKind() == TypeKind.NONE && (realParam.getKind() == Tree.Kind.LAMBDA_EXPRESSION || realParam.getKind() == Kind.MEMBER_REFERENCE)) { |
| tm = info.getTypes().getNullType(); |
| } |
| params.add(tm); |
| } |
| this.hints.add(new MethodParamsHint(tree.getName().toString(), params)); |
| } |
| } |
| |
| // System.err.println("tree=" + tree); |
| final Element el = info.getTrees().getElement(getCurrentPath()); |
| if (el != null && (el.getKind().isClass() || el.getKind().isInterface() || el.getKind() == ElementKind.PACKAGE)) { |
| TypeMirror type = el.asType(); |
| String simpleName = null; |
| |
| if (type != null) { |
| if (type.getKind() == TypeKind.ERROR) { |
| boolean allowImport = true; |
| |
| if (getCurrentPath().getParentPath() != null && getCurrentPath().getParentPath().getLeaf().getKind() == Kind.ASSIGNMENT) { |
| AssignmentTree at = (AssignmentTree) getCurrentPath().getParentPath().getLeaf(); |
| |
| allowImport = at.getVariable() != tree; |
| } |
| |
| if (methodInvocation) { |
| Scope s = info.getTrees().getScope(getCurrentPath()); |
| |
| while (s != null) { |
| allowImport &= !info.getElementUtilities().getLocalMembersAndVars(s, new ElementAcceptor() { |
| @Override public boolean accept(Element e, TypeMirror type) { |
| return e.getSimpleName().contentEquals(el.getSimpleName()) && |
| (e.getKind() == ElementKind.METHOD || e.getKind() == ElementKind.CONSTRUCTOR); |
| } |
| }).iterator().hasNext(); |
| s = s.getEnclosingScope(); |
| } |
| } |
| |
| if (allowImport) { |
| simpleName = el.getSimpleName().toString(); |
| } |
| } |
| |
| if (type != null && type.getKind() == TypeKind.PACKAGE) { |
| //does the package really exists? |
| String s = ((PackageElement) el).getQualifiedName().toString(); |
| if (info.getElements().getPackageElement(s) == null) { |
| //probably situation like: |
| //Map.Entry e; |
| //where Map is not imported |
| simpleName = el.getSimpleName().toString(); |
| } |
| } |
| |
| if (simpleName == null || !SourceVersion.isIdentifier(simpleName) || SourceVersion.isKeyword(simpleName)) { |
| simpleName = null; |
| } |
| |
| if (simpleName != null) { |
| unresolved.add(simpleName); |
| |
| if (!onlyTypes) { |
| unresolvedNonTypes.add(simpleName); |
| } |
| |
| Scope currentScope = getScope(); |
| |
| hints.add(new AccessibleHint(simpleName, currentScope)); |
| |
| if (p.containsKey("request")) { |
| p.put("result", Union2.<String, DeclaredType>createFirst(simpleName)); |
| } |
| } else { |
| if (p.containsKey("request") && type.getKind() == TypeKind.DECLARED) { |
| p.put("result", Union2.<String, DeclaredType>createSecond((DeclaredType) type)); |
| } |
| } |
| } |
| } |
| |
| p.remove("request"); |
| |
| return null; |
| } |
| |
| @Override |
| public Void visitNewClass(NewClassTree node, Map<String, Object> p) { |
| filterByNotAcceptedKind(node.getIdentifier(), ElementKind.ENUM); |
| scan(node.getEnclosingExpression(), new HashMap<String, Object>()); |
| scan(node.getIdentifier(), p, true); |
| scan(node.getTypeArguments(), new HashMap<String, Object>(), true); |
| scan(node.getArguments(), new HashMap<String, Object>()); |
| scan(node.getClassBody(), new HashMap<String, Object>()); |
| return null; |
| } |
| |
| @Override |
| public Void visitMethodInvocation(MethodInvocationTree node, Map<String, Object> p) { |
| scan(node.getTypeArguments(), new HashMap<String, Object>(), true); |
| scan(node.getMethodSelect(), p); |
| scan(node.getArguments(), new HashMap<String, Object>()); |
| return null; |
| } |
| |
| @Override |
| public Void visitNewArray(NewArrayTree node, Map<String, Object> p) { |
| scan(node.getType(), p, true); |
| scan(node.getDimensions(), new HashMap<String, Object>()); |
| scan(node.getInitializers(), new HashMap<String, Object>()); |
| return null; |
| } |
| |
| @Override |
| public Void visitParameterizedType(ParameterizedTypeTree node, Map<String, Object> p) { |
| scan(node.getType(), p); |
| scan(node.getTypeArguments(), new HashMap<String, Object>()); |
| return null; |
| } |
| |
| @Override |
| public Void visitClass(ClassTree node, Map<String, Object> p) { |
| if (getCurrentPath().getParentPath().getLeaf().getKind() != Kind.NEW_CLASS) { |
| filterByAcceptedKind(node.getExtendsClause(), ElementKind.CLASS); |
| for (Tree intf : node.getImplementsClause()) { |
| filterByAcceptedKind(intf, ElementKind.INTERFACE, ElementKind.ANNOTATION_TYPE); |
| } |
| } |
| |
| scan(node.getModifiers(), p); |
| scan(node.getTypeParameters(), p, true); |
| scan(node.getExtendsClause(), p, true); |
| scan(node.getImplementsClause(), p, true); |
| scan(node.getMembers(), p); |
| |
| return null; |
| } |
| |
| @Override |
| public Void visitAnnotation(AnnotationTree node, Map<String, Object> p) { |
| filterByAcceptedKind(node.getAnnotationType(), ElementKind.ANNOTATION_TYPE); |
| scan(node.getAnnotationType(), p, true); |
| scan(node.getArguments(), p, false); |
| return null; |
| } |
| |
| @Override |
| public Void visitMethod(MethodTree node, Map<String, Object> p) { |
| scan(node.getModifiers(), p); |
| scan(node.getTypeParameters(), p, true); |
| scan(node.getReturnType(), p, true); |
| scan(node.getReceiverParameter(), p); |
| scan(node.getParameters(), p); |
| scan(node.getThrows(), p, true); |
| scan(node.getDefaultValue(), p); |
| scan(node.getBody(), p); |
| return null; |
| } |
| |
| private void scan(Iterable<? extends Tree> trees, Map<String, Object> p, boolean onlyTypes) { |
| for (Tree tree : trees) { |
| scan(tree, p, onlyTypes); |
| } |
| } |
| |
| private void scan(Tree tree, Map<String, Object> p, boolean onlyTypes) { |
| boolean oldOnlyTypes = this.onlyTypes; |
| |
| try { |
| this.onlyTypes = onlyTypes; |
| scan(tree, p); |
| } finally { |
| this.onlyTypes = oldOnlyTypes; |
| } |
| } |
| |
| private Scope topLevelScope; |
| |
| private Scope getScope() { |
| if (topLevelScope == null) { |
| topLevelScope = info.getTrees().getScope(new TreePath(getCurrentPath().getCompilationUnit())); |
| } |
| return topLevelScope; |
| } |
| |
| private void filterByAcceptedKind(Tree toFilter, ElementKind acceptedKind, ElementKind... otherAcceptedKinds) { |
| filterByKind(toFilter, EnumSet.of(acceptedKind, otherAcceptedKinds), EnumSet.noneOf(ElementKind.class)); |
| } |
| |
| private void filterByNotAcceptedKind(Tree toFilter, ElementKind notAcceptedKind, ElementKind... otherNotAcceptedKinds) { |
| filterByKind(toFilter, EnumSet.noneOf(ElementKind.class), EnumSet.of(notAcceptedKind, otherNotAcceptedKinds)); |
| } |
| |
| private void filterByKind(Tree toFilter, Set<ElementKind> acceptedKinds, Set<ElementKind> notAcceptedKinds) { |
| if (toFilter == null) return; |
| switch (toFilter.getKind()) { |
| case IDENTIFIER: |
| hints.add(new KindHint(((IdentifierTree) toFilter).getName().toString(), acceptedKinds, notAcceptedKinds)); |
| break; |
| case PARAMETERIZED_TYPE: |
| filterByKind(((ParameterizedTypeTree) toFilter).getType(), acceptedKinds, notAcceptedKinds); |
| break; |
| } |
| } |
| } |
| |
| public static interface Hint { |
| |
| public abstract boolean filter(CompilationInfo info, ComputeImports state); |
| |
| } |
| |
| public static final class TypeHint implements Hint { |
| |
| private Union2<String, DeclaredType> left; |
| private Union2<String, DeclaredType> right; |
| |
| public TypeHint(Union2<String, DeclaredType> left, Union2<String, DeclaredType> right) { |
| this.left = left; |
| this.right = right; |
| } |
| |
| public boolean filter(CompilationInfo info, ComputeImports state) { |
| Map<String, List<Element>> candidates = state.candidates; |
| |
| List<Element> left = null; |
| List<Element> right = null; |
| boolean leftReadOnly = false; |
| boolean rightReadOnly = false; |
| |
| if (this.left.hasSecond()) { |
| Element el = this.left.second().asElement(); |
| |
| //TODO do not use instanceof! |
| if (el instanceof TypeElement) { |
| left = Collections.singletonList(el); |
| leftReadOnly = true; |
| } |
| } else { |
| left = candidates.get(this.left.first()); |
| } |
| |
| if (this.right.hasSecond()) { |
| Element el = this.right.second().asElement(); |
| |
| //TODO do not use instanceof! |
| if (el instanceof TypeElement) { |
| right = Collections.singletonList(el); |
| rightReadOnly = true; |
| } |
| } else { |
| right = candidates.get(this.right.first()); |
| } |
| |
| if (left != null && right != null && !left.isEmpty() && !right.isEmpty()) { |
| return ComputeImports.filter(info.getTypes(), left, right, leftReadOnly, rightReadOnly); |
| } |
| |
| return false; |
| } |
| |
| } |
| |
| public static final class EnclosedHint implements Hint { |
| |
| private String simpleName; |
| private String methodName; |
| private boolean allowPrefix; |
| |
| public EnclosedHint(String simpleName, String methodName, boolean allowPrefix) { |
| this.simpleName = simpleName; |
| this.methodName = methodName; |
| this.allowPrefix = allowPrefix; |
| } |
| |
| public boolean filter(CompilationInfo info, ComputeImports state) { |
| Map<String, List<Element>> candidates = state.candidates; |
| |
| List<Element> cands = candidates.get(simpleName); |
| |
| if (cands == null || cands.isEmpty()) |
| return false; |
| |
| List<TypeElement> toRemove = new ArrayList<TypeElement>(); |
| |
| for (TypeElement te : ElementFilter.typesIn(cands)) { |
| boolean found = false; |
| |
| for (Element e : te.getEnclosedElements()) { |
| String simpleName = e.getSimpleName().toString(); |
| |
| if (methodName.contentEquals(simpleName)) { |
| found = true; |
| break; |
| } |
| |
| if (allowPrefix && simpleName.startsWith(methodName)) { |
| found = true; |
| break; |
| } |
| } |
| |
| if (!found) { |
| toRemove.add(te); |
| } |
| } |
| |
| return cands.removeAll(toRemove); |
| } |
| |
| } |
| |
| public static final class KindHint implements Hint { |
| |
| private String simpleName; |
| private Set<ElementKind> acceptedKinds; |
| private Set<ElementKind> notAcceptedKinds; |
| |
| public KindHint(String simpleName, Set<ElementKind> acceptedKinds, Set<ElementKind> notAcceptedKinds) { |
| this.simpleName = simpleName; |
| this.acceptedKinds = acceptedKinds; |
| this.notAcceptedKinds = notAcceptedKinds; |
| } |
| |
| public boolean filter(CompilationInfo info, ComputeImports state) { |
| Map<String, List<Element>> rawCandidates = state.notFilteredCandidates; |
| Map<String, List<Element>> candidates = state.candidates; |
| |
| // this is a hack, but if the kind does not match, we cannot offer the element even as 'not preferred'. |
| // Better enable just for annotation types, otherwise we might stop offering e.g. interfaces in place where a class could be used (?) |
| if (acceptedKinds.contains(ElementKind.ANNOTATION_TYPE) && acceptedKinds.size() == 1) { |
| doFilter(info, rawCandidates); |
| } |
| return doFilter(info,candidates); |
| } |
| |
| private boolean doFilter(CompilationInfo info, Map<String, List<Element>> candidates) { |
| List<Element> cands = candidates.get(simpleName); |
| |
| if (cands == null || cands.isEmpty()) |
| return false; |
| |
| List<TypeElement> toRemove = new ArrayList<TypeElement>(); |
| |
| for (TypeElement te : ElementFilter.typesIn(cands)) { |
| if (!acceptedKinds.isEmpty() && !acceptedKinds.contains(te.getKind())) { |
| toRemove.add(te); |
| continue; |
| } |
| if (!notAcceptedKinds.isEmpty() && notAcceptedKinds.contains(te.getKind())) { |
| toRemove.add(te); |
| continue; |
| } |
| } |
| |
| return cands.removeAll(toRemove); |
| } |
| |
| } |
| |
| public static final class AccessibleHint implements Hint { |
| |
| private String simpleName; |
| private Scope scope; |
| |
| public AccessibleHint(String simpleName, Scope scope) { |
| this.simpleName = simpleName; |
| this.scope = scope; |
| } |
| |
| public boolean filter(CompilationInfo info, ComputeImports state) { |
| Map<String, List<Element>> rawCandidates = state.notFilteredCandidates; |
| Map<String, List<Element>> candidates = state.candidates; |
| |
| List<Element> cands = rawCandidates.get(simpleName); |
| |
| if (cands == null || cands.isEmpty()) |
| return false; |
| |
| List<Element> toRemove = new ArrayList<Element>(); |
| |
| for (Element te : cands) { |
| if (te.getKind().isClass() || te.getKind().isInterface() ? !info.getTrees().isAccessible(scope, (TypeElement) te) |
| : !info.getTrees().isAccessible(scope, te, (DeclaredType) te.getEnclosingElement().asType())) { |
| toRemove.add(te); |
| } |
| } |
| |
| //remove it from the candidates too: |
| candidates.get(simpleName).removeAll(toRemove); |
| |
| return cands.removeAll(toRemove); |
| } |
| |
| } |
| |
| public static final class MethodParamsHint implements Hint { |
| |
| private final String simpleName; |
| private final List<TypeMirror> paramTypes; |
| |
| public MethodParamsHint(String simpleName, List<TypeMirror> paramTypes) { |
| this.simpleName = simpleName; |
| this.paramTypes = paramTypes; |
| } |
| |
| public boolean filter(CompilationInfo info, ComputeImports state) { |
| List<Element> rawCands = state.notFilteredCandidates.get(simpleName); |
| List<Element> cands = state.candidates.get(simpleName); |
| |
| if (rawCands == null || cands == null) { |
| return false; |
| } |
| |
| boolean modified = false; |
| boolean someMatch = false; |
| for (Element c : new ArrayList<Element>(rawCands)) { |
| if (c.getKind() != ElementKind.METHOD) { |
| rawCands.remove(c); |
| cands.remove(c); |
| modified |= true; |
| } else { |
| //XXX: varargs |
| Iterator<? extends TypeMirror> real = paramTypes.iterator(); |
| Iterator<? extends TypeMirror> formal = ((ExecutableType) c.asType()).getParameterTypes().iterator(); |
| boolean matches = true; |
| boolean inVarArgs = false; |
| TypeMirror currentFormal = null; |
| |
| while (real.hasNext() && (formal.hasNext() || inVarArgs)) { |
| TypeMirror currentReal = real.next(); |
| |
| if (!inVarArgs) |
| currentFormal = formal.next(); |
| |
| if (!info.getTypes().isAssignable(info.getTypes().erasure(currentReal), info.getTypes().erasure(currentFormal))) { |
| if (((ExecutableElement) c).isVarArgs() && !formal.hasNext() && currentFormal.getKind() == TypeKind.ARRAY) { |
| currentFormal = ((ArrayType) currentFormal).getComponentType(); |
| |
| if (!info.getTypes().isAssignable(info.getTypes().erasure(currentReal), info.getTypes().erasure(currentFormal))) { |
| matches = false; |
| break; |
| } |
| |
| inVarArgs = true; |
| } else { |
| matches = false; |
| break; |
| } |
| } |
| } |
| |
| matches &= real.hasNext() == formal.hasNext(); |
| |
| if (matches) { |
| state.addMethodFqn(c); |
| someMatch = true; |
| } |
| } |
| } |
| if (!someMatch) { |
| // sorry, no candidate matched the simple name -> remove all candidates |
| if (!rawCands.isEmpty()) { |
| cands.clear(); |
| rawCands.clear(); |
| modified = true; |
| } |
| } |
| return modified; |
| } |
| |
| } |
| |
| public static class Pair<A, B> { |
| |
| public A a; |
| public B b; |
| |
| public Pair(A a, B b) { |
| this.a = a; |
| this.b = b; |
| } |
| } |
| |
| } |