1 19 package org.netbeans.modules.java.editor.imports; 20 21 import com.sun.source.tree.IdentifierTree; 22 import com.sun.source.tree.MemberSelectTree; 23 import com.sun.source.tree.Scope; 24 import com.sun.source.tree.Tree.Kind; 25 import com.sun.source.tree.VariableTree; 26 import com.sun.source.util.TreePath; 27 import com.sun.source.util.TreePathScanner; 28 import java.awt.Dialog ; 29 import java.io.IOException ; 30 import java.util.ArrayList ; 31 import java.util.Collections ; 32 import java.util.EnumSet ; 33 import java.util.HashMap ; 34 import java.util.HashSet ; 35 import java.util.List ; 36 import java.util.Map ; 37 import java.util.Set ; 38 import java.util.logging.Level ; 39 import java.util.logging.Logger ; 40 import javax.lang.model.element.Element; 41 import javax.lang.model.element.ElementKind; 42 import javax.lang.model.element.PackageElement; 43 import javax.lang.model.element.TypeElement; 44 import javax.lang.model.type.DeclaredType; 45 import javax.lang.model.type.TypeKind; 46 import javax.lang.model.type.TypeMirror; 47 import javax.lang.model.util.Types; 48 import javax.swing.text.BadLocationException ; 49 import org.netbeans.api.java.source.CancellableTask; 50 import org.netbeans.api.java.source.ClassIndex; 51 import org.netbeans.api.java.source.CompilationController; 52 import org.netbeans.api.java.source.CompilationInfo; 53 import org.netbeans.api.java.source.JavaSource; 54 import org.netbeans.api.java.source.JavaSource.Phase; 55 import org.netbeans.api.java.source.SourceUtils; 56 import org.netbeans.api.java.source.ClassIndex.NameKind; 57 import org.netbeans.api.java.source.ElementHandle; 58 import org.netbeans.api.java.source.support.CancellableTreePathScanner; 59 import org.openide.DialogDescriptor; 60 import org.openide.DialogDisplayer; 61 import org.openide.ErrorManager; 62 import org.openide.filesystems.FileObject; 63 import org.openide.util.Union2; 64 65 69 public class ComputeImports { 70 71 private static final String ERROR = "<error>"; 72 73 74 public ComputeImports() { 75 } 76 77 private boolean cancelled; 78 79 public synchronized void cancel() { 80 cancelled = true; 81 82 if (visitor != null) 83 visitor.cancel(); 84 } 85 86 private synchronized boolean isCancelled() { 87 return cancelled; 88 } 89 90 public Pair<Map <String , List <TypeElement>>, Map <String , List <TypeElement>>> computeCandidates(CompilationInfo info) { 91 return computeCandidates(info, Collections.<String >emptySet()); 92 } 93 94 private TreeVisitorImpl visitor; 95 96 private synchronized void setVisitor(TreeVisitorImpl visitor) { 97 this.visitor = visitor; 98 } 99 100 Pair<Map <String , List <TypeElement>>, Map <String , List <TypeElement>>> computeCandidates(CompilationInfo info, Set <String > forcedUnresolved) { 101 Map <String , List <TypeElement>> candidates = new HashMap <String , List <TypeElement>>(); 102 Map <String , List <TypeElement>> notFilteredCandidates = new HashMap <String , List <TypeElement>>(); 103 TreeVisitorImpl v = new TreeVisitorImpl(info); 104 105 setVisitor(v); 106 107 v.scan(info.getCompilationUnit(), new HashMap <String , Object >()); 108 109 setVisitor(null); 110 111 Set <String > unresolvedNames = new HashSet <String >(v.unresolved); 112 113 unresolvedNames.addAll(forcedUnresolved); 114 115 for (String unresolved : unresolvedNames) { 116 if (isCancelled()) 117 return new Pair(Collections.emptyMap(), Collections.emptyMap()); 118 119 List <TypeElement> classes = new ArrayList <TypeElement>(); 120 121 for (ElementHandle<TypeElement> typeNames : info.getJavaSource().getClasspathInfo().getClassIndex().getDeclaredTypes(unresolved, NameKind.SIMPLE_NAME,EnumSet.allOf(ClassIndex.SearchScope.class))) { 122 TypeElement te = info.getElements().getTypeElement(typeNames.getQualifiedName()); 123 124 if (te == null) { 125 Logger.getLogger(ComputeImports.class.getName()).log(Level.INFO, "Cannot resolve type element \"" + typeNames + "\"."); 126 continue; 127 } 128 129 classes.add(te); 130 } 131 132 candidates.put(unresolved, new ArrayList (classes)); 133 notFilteredCandidates.put(unresolved, classes); 134 } 135 136 boolean wasChanged = true; 137 138 while (wasChanged) { 139 if (isCancelled()) 140 return new Pair(Collections.emptyMap(), Collections.emptyMap()); 141 142 wasChanged = false; 143 144 for (Hint hint: v.hints) { 145 wasChanged |= hint.filter(info, notFilteredCandidates, candidates); 146 } 147 } 148 149 return new Pair<Map <String , List <TypeElement>>, Map <String , List <TypeElement>>>(candidates, notFilteredCandidates); 150 } 151 152 private static boolean filter(Types types, List <TypeElement> left, List <TypeElement> right, boolean leftReadOnly, boolean rightReadOnly) { 153 boolean changed = false; 154 Map <TypeElement, List <TypeElement>> validPairs = new HashMap <TypeElement, List <TypeElement>>(); 155 156 for (TypeElement l : left) { 157 List <TypeElement> valid = new ArrayList <TypeElement>(); 158 159 for (TypeElement r : right) { 160 TypeMirror t1 = types.erasure(l.asType()); 161 TypeMirror t2 = types.erasure(r.asType()); 162 163 if (types.isAssignable(t2, t1)) 171 valid.add(r); 172 } 173 174 validPairs.put(l, valid); 177 } 178 179 Set <TypeElement> validRights = new HashSet <TypeElement>(); 180 181 for (TypeElement l : validPairs.keySet()) { 182 List <TypeElement> valid = validPairs.get(l); 183 184 if (valid.isEmpty() && !leftReadOnly) { 185 left.remove(l); 187 changed = true; 188 } 189 190 validRights.addAll(valid); 191 } 192 193 if (!rightReadOnly) 194 changed = right.retainAll(validRights) | changed; 195 196 return changed; 197 } 198 199 private static EnumSet <TypeKind> INVALID_TYPES = EnumSet.of(TypeKind.NULL, TypeKind.NONE, TypeKind.OTHER, TypeKind.ERROR); 200 201 private static class TreeVisitorImpl extends CancellableTreePathScanner<Void , Map <String , Object >> { 202 203 private CompilationInfo info; 204 private Set <String > unresolved; 205 206 private List <Hint> hints; 207 208 public TreeVisitorImpl(CompilationInfo info) { 209 this.info = info; 210 unresolved = new HashSet <String >(); 211 hints = new ArrayList <Hint>(); 212 } 213 214 @Override 215 public Void visitMemberSelect(MemberSelectTree tree, Map <String , Object > p) { 216 if (tree.getExpression().getKind() == Kind.IDENTIFIER) { 217 p.put("request", null); 218 } 219 220 scan(tree.getExpression(), p); 221 222 Union2<String , DeclaredType> leftSide = (Union2<String , DeclaredType>) p.remove("result"); 223 224 p.remove("request"); 225 226 if (leftSide != null && leftSide.hasFirst()) { 227 String rightSide = tree.getIdentifier().toString(); 228 229 if (ERROR.equals(rightSide)) 230 rightSide = ""; 231 232 boolean isMethodInvocation = getCurrentPath().getParentPath().getLeaf().getKind() == Kind.METHOD_INVOCATION; 233 234 if (!"class".equals(rightSide)) 236 hints.add(new EnclosedHint(leftSide.first(), rightSide, !isMethodInvocation)); 237 } 238 239 return null; 240 } 241 242 @Override 243 public Void visitVariable(VariableTree tree, Map <String , Object > p) { 244 scan(tree.getModifiers(), p); 245 246 if (tree.getType().getKind() == Kind.IDENTIFIER) { 247 p.put("request", null); 248 } 249 250 scan(tree.getType(), p); 251 252 Union2<String , DeclaredType> leftSide = (Union2<String , DeclaredType>) p.remove("result"); 253 254 p.remove("request"); 255 256 Union2<String , DeclaredType> rightSide = null; 257 258 if (leftSide != null && tree.getInitializer() != null) { 259 Element el = info.getTrees().getElement(new TreePath(getCurrentPath(),tree.getInitializer())); 260 TypeMirror rightType = el != null ? el.asType() : null; 261 262 if (rightType != null && rightType.getKind() == TypeKind.DECLARED) { 267 rightSide = Union2.<String , DeclaredType>createSecond((DeclaredType) rightType); 268 } else { 269 if (tree.getInitializer().getKind() == Kind.NEW_CLASS || tree.getInitializer().getKind() == Kind.NEW_ARRAY) { 270 p.put("request", null); 271 } 272 } 273 } 274 275 scan(tree.getInitializer(), p); 276 277 rightSide = rightSide == null ? (Union2<String , DeclaredType>) p.remove("result") : rightSide; 278 279 p.remove("result"); 280 281 283 p.remove("request"); 284 285 if (leftSide != null && rightSide != null) { 286 if (!(leftSide instanceof TypeMirror) || !(rightSide instanceof TypeMirror)) { 287 hints.add(new TypeHint(leftSide, rightSide)); 288 } 289 } 290 291 return null; 292 } 293 294 @Override 295 public Void visitIdentifier(IdentifierTree tree, Map <String , Object > p) { 296 super.visitIdentifier(tree, p); 297 298 Element el = info.getTrees().getElement(getCurrentPath()); 300 if (el != null && (el.getKind().isClass() || el.getKind().isInterface() || el.getKind() == ElementKind.PACKAGE)) { 301 TypeMirror type = el.asType(); 302 String simpleName = null; 303 304 if (type.getKind() == TypeKind.ERROR) { 305 simpleName = el.getSimpleName().toString(); 306 } 307 308 if (type.getKind() == TypeKind.PACKAGE) { 309 String s = ((PackageElement) el).getQualifiedName().toString(); 311 if (info.getElements().getPackageElement(s) == null) { 312 simpleName = el.getSimpleName().toString(); 316 } 317 } 318 319 if (ERROR.equals(simpleName)) { 320 simpleName = null; 321 } 322 323 if (simpleName != null) { 324 unresolved.add(simpleName); 325 326 Scope currentScope = info.getTrees().getScope(getCurrentPath()); 327 328 hints.add(new AccessibleHint(simpleName, currentScope)); 329 330 if (p.containsKey("request")) { 331 p.put("result", Union2.<String , DeclaredType>createFirst(simpleName)); 332 } 333 } else { 334 if (p.containsKey("request") && type.getKind() == TypeKind.DECLARED) { 335 p.put("result", Union2.<String , DeclaredType>createSecond((DeclaredType) type)); 336 } 337 } 338 } 339 340 p.remove("request"); 341 342 return null; 343 } 344 345 } 346 347 public static interface Hint { 348 349 public abstract boolean filter(CompilationInfo info, Map <String , List <TypeElement>> rawCandidates, Map <String , List <TypeElement>> candidates); 350 351 } 352 353 public static final class TypeHint implements Hint { 354 355 private Union2<String , DeclaredType> left; 356 private Union2<String , DeclaredType> right; 357 358 public TypeHint(Union2<String , DeclaredType> left, Union2<String , DeclaredType> right) { 359 this.left = left; 360 this.right = right; 361 } 362 363 public boolean filter(CompilationInfo info, Map <String , List <TypeElement>> rawCandidates, Map <String , List <TypeElement>> candidates) { 364 List <TypeElement> left = null; 365 List <TypeElement> right = null; 366 boolean leftReadOnly = false; 367 boolean rightReadOnly = false; 368 369 if (this.left.hasSecond()) { 370 Element el = this.left.second().asElement(); 371 372 if (el instanceof TypeElement) { 374 left = Collections.singletonList((TypeElement) el); 375 leftReadOnly = true; 376 } 377 } else { 378 left = candidates.get(this.left.first()); 379 } 380 381 if (this.right.hasSecond()) { 382 Element el = this.right.second().asElement(); 383 384 if (el instanceof TypeElement) { 386 right = Collections.singletonList((TypeElement) el); 387 rightReadOnly = true; 388 } 389 } else { 390 right = candidates.get(this.right.first()); 391 } 392 393 if (left != null && right != null && !left.isEmpty() && !right.isEmpty()) { 394 return ComputeImports.filter(info.getTypes(), left, right, leftReadOnly, rightReadOnly); 395 } 396 397 return false; 398 } 399 400 } 401 402 public static final class EnclosedHint implements Hint { 403 404 private String simpleName; 405 private String methodName; 406 private boolean allowPrefix; 407 408 public EnclosedHint(String simpleName, String methodName, boolean allowPrefix) { 409 this.simpleName = simpleName; 410 this.methodName = methodName; 411 this.allowPrefix = allowPrefix; 412 } 413 414 public boolean filter(CompilationInfo info, Map <String , List <TypeElement>> rawCandidates, Map <String , List <TypeElement>> candidates) { 415 List <TypeElement> cands = candidates.get(simpleName); 416 417 if (cands == null || cands.isEmpty()) 418 return false; 419 420 List <TypeElement> toRemove = new ArrayList <TypeElement>(); 421 422 for (TypeElement te : cands) { 423 boolean found = false; 424 425 for (Element e : te.getEnclosedElements()) { 426 String simpleName = e.getSimpleName().toString(); 427 428 if (methodName.contentEquals(simpleName)) { 429 found = true; 430 break; 431 } 432 433 if (allowPrefix && simpleName.startsWith(methodName)) { 434 found = true; 435 break; 436 } 437 } 438 439 if (!found) { 440 toRemove.add(te); 441 } 442 } 443 444 return cands.removeAll(toRemove); 445 } 446 447 } 448 449 public static final class AccessibleHint implements Hint { 450 451 private String simpleName; 452 private Scope scope; 453 454 public AccessibleHint(String simpleName, Scope scope) { 455 this.simpleName = simpleName; 456 this.scope = scope; 457 } 458 459 public boolean filter(CompilationInfo info, Map <String , List <TypeElement>> rawCandidates, Map <String , List <TypeElement>> candidates) { 460 List <TypeElement> cands = candidates.get(simpleName); 461 462 if (cands == null || cands.isEmpty()) 463 return false; 464 465 List <TypeElement> toRemove = new ArrayList <TypeElement>(); 466 467 for (TypeElement te : cands) { 468 if (!info.getTrees().isAccessible(scope, te)) { 469 toRemove.add(te); 470 } 471 } 472 473 rawCandidates.get(simpleName).removeAll(toRemove); 475 476 return cands.removeAll(toRemove); 477 } 478 479 } 480 481 public static class Pair<A, B> { 482 483 public A a; 484 public B b; 485 486 public Pair(A a, B b) { 487 this.a = a; 488 this.b = b; 489 } 490 } 491 492 } 493 | Popular Tags |