SourceCodeAnalysisImpl.java revision 3294:9adfb22ff08f
1/* 2 * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26package jdk.jshell; 27 28import jdk.jshell.SourceCodeAnalysis.Completeness; 29import com.sun.source.tree.AssignmentTree; 30import com.sun.source.tree.CompilationUnitTree; 31import com.sun.source.tree.ErroneousTree; 32import com.sun.source.tree.ExpressionTree; 33import com.sun.source.tree.IdentifierTree; 34import com.sun.source.tree.ImportTree; 35import com.sun.source.tree.MemberSelectTree; 36import com.sun.source.tree.MethodInvocationTree; 37import com.sun.source.tree.MethodTree; 38import com.sun.source.tree.NewClassTree; 39import com.sun.source.tree.Scope; 40import com.sun.source.tree.Tree; 41import com.sun.source.tree.Tree.Kind; 42import com.sun.source.tree.VariableTree; 43import com.sun.source.util.SourcePositions; 44import com.sun.source.util.TreePath; 45import com.sun.source.util.TreePathScanner; 46import com.sun.tools.javac.api.JavacScope; 47import com.sun.tools.javac.code.Flags; 48import com.sun.tools.javac.code.Symbol.CompletionFailure; 49import com.sun.tools.javac.code.Symbol.VarSymbol; 50import com.sun.tools.javac.code.Symtab; 51import com.sun.tools.javac.code.Type; 52import com.sun.tools.javac.code.Type.ClassType; 53import com.sun.tools.javac.util.DefinedBy; 54import com.sun.tools.javac.util.DefinedBy.Api; 55import com.sun.tools.javac.util.Name; 56import com.sun.tools.javac.util.Names; 57import com.sun.tools.javac.util.Pair; 58import jdk.jshell.CompletenessAnalyzer.CaInfo; 59import jdk.jshell.TaskFactory.AnalyzeTask; 60import jdk.jshell.TaskFactory.ParseTask; 61 62import java.util.ArrayList; 63import java.util.Collections; 64import java.util.Iterator; 65import java.util.List; 66import java.util.function.Predicate; 67 68import javax.lang.model.element.Element; 69import javax.lang.model.element.ElementKind; 70import javax.lang.model.element.Modifier; 71import javax.lang.model.element.TypeElement; 72import javax.lang.model.type.DeclaredType; 73import javax.lang.model.type.TypeMirror; 74 75import static jdk.internal.jshell.debug.InternalDebugControl.DBG_COMPA; 76 77import java.io.IOException; 78import java.net.URI; 79import java.nio.file.DirectoryStream; 80import java.nio.file.FileSystem; 81import java.nio.file.FileSystems; 82import java.nio.file.FileVisitResult; 83import java.nio.file.FileVisitor; 84import java.nio.file.Files; 85import java.nio.file.Path; 86import java.nio.file.Paths; 87import java.nio.file.attribute.BasicFileAttributes; 88import java.util.Arrays; 89import java.util.Collection; 90import java.util.Comparator; 91import java.util.HashMap; 92import java.util.HashSet; 93import java.util.LinkedHashSet; 94import java.util.Map; 95import java.util.NoSuchElementException; 96import java.util.Set; 97import java.util.concurrent.ExecutorService; 98import java.util.concurrent.Executors; 99import java.util.function.Function; 100import java.util.regex.Matcher; 101import java.util.regex.Pattern; 102import java.util.stream.Collectors; 103import static java.util.stream.Collectors.collectingAndThen; 104import static java.util.stream.Collectors.joining; 105import static java.util.stream.Collectors.toCollection; 106import static java.util.stream.Collectors.toList; 107import static java.util.stream.Collectors.toSet; 108import java.util.stream.Stream; 109import java.util.stream.StreamSupport; 110 111import javax.lang.model.SourceVersion; 112 113import javax.lang.model.element.ExecutableElement; 114import javax.lang.model.element.PackageElement; 115import javax.lang.model.element.QualifiedNameable; 116import javax.lang.model.element.VariableElement; 117import javax.lang.model.type.ArrayType; 118import javax.lang.model.type.ExecutableType; 119import javax.lang.model.type.TypeKind; 120import javax.lang.model.util.ElementFilter; 121import javax.lang.model.util.Types; 122import javax.tools.JavaFileManager.Location; 123import javax.tools.StandardLocation; 124 125import static jdk.jshell.Util.REPL_DOESNOTMATTER_CLASS_NAME; 126 127/** 128 * The concrete implementation of SourceCodeAnalysis. 129 * @author Robert Field 130 */ 131class SourceCodeAnalysisImpl extends SourceCodeAnalysis { 132 133 private static final Map<Path, ClassIndex> PATH_TO_INDEX = new HashMap<>(); 134 private static final ExecutorService INDEXER = Executors.newFixedThreadPool(1, r -> { 135 Thread t = new Thread(r); 136 t.setDaemon(true); 137 t.setUncaughtExceptionHandler((thread, ex) -> ex.printStackTrace()); 138 return t; 139 }); 140 141 private final JShell proc; 142 private final CompletenessAnalyzer ca; 143 private final Map<Path, ClassIndex> currentIndexes = new HashMap<>(); 144 private int indexVersion; 145 private int classpathVersion; 146 private final Object suspendLock = new Object(); 147 private int suspend; 148 149 SourceCodeAnalysisImpl(JShell proc) { 150 this.proc = proc; 151 this.ca = new CompletenessAnalyzer(proc); 152 153 int cpVersion = classpathVersion = 1; 154 155 INDEXER.submit(() -> refreshIndexes(cpVersion)); 156 } 157 158 @Override 159 public CompletionInfo analyzeCompletion(String srcInput) { 160 MaskCommentsAndModifiers mcm = new MaskCommentsAndModifiers(srcInput, false); 161 String cleared = mcm.cleared(); 162 String trimmedInput = Util.trimEnd(cleared); 163 if (trimmedInput.isEmpty()) { 164 // Just comment or empty 165 return new CompletionInfo(Completeness.EMPTY, srcInput.length(), srcInput, ""); 166 } 167 CaInfo info = ca.scan(trimmedInput); 168 Completeness status = info.status; 169 int unitEndPos = info.unitEndPos; 170 if (unitEndPos > srcInput.length()) { 171 unitEndPos = srcInput.length(); 172 } 173 int nonCommentNonWhiteLength = trimmedInput.length(); 174 String src = srcInput.substring(0, unitEndPos); 175 switch (status) { 176 case COMPLETE: 177 if (unitEndPos == nonCommentNonWhiteLength) { 178 // The unit is the whole non-coment/white input plus semicolon 179 String compileSource = src 180 + mcm.mask().substring(nonCommentNonWhiteLength); 181 proc.debug(DBG_COMPA, "Complete: %s\n", compileSource); 182 proc.debug(DBG_COMPA, " nothing remains.\n"); 183 return new CompletionInfo(status, unitEndPos, compileSource, ""); 184 } else { 185 String remain = srcInput.substring(unitEndPos); 186 proc.debug(DBG_COMPA, "Complete: %s\n", src); 187 proc.debug(DBG_COMPA, " remaining: %s\n", remain); 188 return new CompletionInfo(status, unitEndPos, src, remain); 189 } 190 case COMPLETE_WITH_SEMI: 191 // The unit is the whole non-coment/white input plus semicolon 192 String compileSource = src 193 + ";" 194 + mcm.mask().substring(nonCommentNonWhiteLength); 195 proc.debug(DBG_COMPA, "Complete with semi: %s\n", compileSource); 196 proc.debug(DBG_COMPA, " nothing remains.\n"); 197 return new CompletionInfo(status, unitEndPos, compileSource, ""); 198 case DEFINITELY_INCOMPLETE: 199 proc.debug(DBG_COMPA, "Incomplete: %s\n", srcInput); 200 return new CompletionInfo(status, unitEndPos, null, srcInput + '\n'); 201 case CONSIDERED_INCOMPLETE: 202 proc.debug(DBG_COMPA, "Considered incomplete: %s\n", srcInput); 203 return new CompletionInfo(status, unitEndPos, null, srcInput + '\n'); 204 case EMPTY: 205 proc.debug(DBG_COMPA, "Detected empty: %s\n", srcInput); 206 return new CompletionInfo(status, unitEndPos, srcInput, ""); 207 case UNKNOWN: 208 proc.debug(DBG_COMPA, "Detected error: %s\n", srcInput); 209 return new CompletionInfo(status, unitEndPos, srcInput, ""); 210 } 211 throw new InternalError(); 212 } 213 214 private OuterWrap wrapInClass(Wrap guts) { 215 String imports = proc.maps.packageAndImportsExcept(null, null); 216 return OuterWrap.wrapInClass(proc.maps.packageName(), REPL_DOESNOTMATTER_CLASS_NAME, imports, "", guts); 217 } 218 219 private Tree.Kind guessKind(String code) { 220 ParseTask pt = proc.taskFactory.new ParseTask(code); 221 List<? extends Tree> units = pt.units(); 222 if (units.isEmpty()) { 223 return Tree.Kind.BLOCK; 224 } 225 Tree unitTree = units.get(0); 226 proc.debug(DBG_COMPA, "Kind: %s -- %s\n", unitTree.getKind(), unitTree); 227 return unitTree.getKind(); 228 } 229 230 //TODO: would be better handled through a lexer: 231 private final Pattern JAVA_IDENTIFIER = Pattern.compile("\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"); 232 233 @Override 234 public List<Suggestion> completionSuggestions(String code, int cursor, int[] anchor) { 235 suspendIndexing(); 236 try { 237 return completionSuggestionsImpl(code, cursor, anchor); 238 } finally { 239 resumeIndexing(); 240 } 241 } 242 243 private List<Suggestion> completionSuggestionsImpl(String code, int cursor, int[] anchor) { 244 code = code.substring(0, cursor); 245 Matcher m = JAVA_IDENTIFIER.matcher(code); 246 String identifier = ""; 247 while (m.find()) { 248 if (m.end() == code.length()) { 249 cursor = m.start(); 250 code = code.substring(0, cursor); 251 identifier = m.group(); 252 } 253 } 254 code = code.substring(0, cursor); 255 if (code.trim().isEmpty()) { //TODO: comment handling 256 code += ";"; 257 } 258 OuterWrap codeWrap; 259 switch (guessKind(code)) { 260 case IMPORT: 261 codeWrap = OuterWrap.wrapImport(null, Wrap.importWrap(code + "any.any")); 262 break; 263 case METHOD: 264 codeWrap = wrapInClass(Wrap.classMemberWrap(code)); 265 break; 266 default: 267 codeWrap = wrapInClass(Wrap.methodWrap(code)); 268 break; 269 } 270 String requiredPrefix = identifier; 271 return computeSuggestions(codeWrap, cursor, anchor).stream() 272 .filter(s -> s.continuation.startsWith(requiredPrefix) && !s.continuation.equals(REPL_DOESNOTMATTER_CLASS_NAME)) 273 .sorted(Comparator.comparing(s -> s.continuation)) 274 .collect(collectingAndThen(toList(), Collections::unmodifiableList)); 275 } 276 277 private List<Suggestion> computeSuggestions(OuterWrap code, int cursor, int[] anchor) { 278 AnalyzeTask at = proc.taskFactory.new AnalyzeTask(code); 279 SourcePositions sp = at.trees().getSourcePositions(); 280 CompilationUnitTree topLevel = at.firstCuTree(); 281 List<Suggestion> result = new ArrayList<>(); 282 TreePath tp = pathFor(topLevel, sp, code.snippetIndexToWrapIndex(cursor)); 283 if (tp != null) { 284 Scope scope = at.trees().getScope(tp); 285 Predicate<Element> accessibility = createAccessibilityFilter(at, tp); 286 Predicate<Element> smartTypeFilter; 287 Predicate<Element> smartFilter; 288 Iterable<TypeMirror> targetTypes = findTargetType(at, tp); 289 if (targetTypes != null) { 290 smartTypeFilter = el -> { 291 TypeMirror resultOf = resultTypeOf(el); 292 return Util.stream(targetTypes) 293 .anyMatch(targetType -> at.getTypes().isAssignable(resultOf, targetType)); 294 }; 295 296 smartFilter = IS_CLASS.negate() 297 .and(IS_INTERFACE.negate()) 298 .and(IS_PACKAGE.negate()) 299 .and(smartTypeFilter); 300 } else { 301 smartFilter = TRUE; 302 smartTypeFilter = TRUE; 303 } 304 switch (tp.getLeaf().getKind()) { 305 case MEMBER_SELECT: { 306 MemberSelectTree mst = (MemberSelectTree)tp.getLeaf(); 307 if (mst.getIdentifier().contentEquals("*")) 308 break; 309 TreePath exprPath = new TreePath(tp, mst.getExpression()); 310 TypeMirror site = at.trees().getTypeMirror(exprPath); 311 boolean staticOnly = isStaticContext(at, exprPath); 312 ImportTree it = findImport(tp); 313 boolean isImport = it != null; 314 315 List<? extends Element> members = membersOf(at, site, staticOnly && !isImport); 316 Predicate<Element> filter = accessibility; 317 Function<Boolean, String> paren = DEFAULT_PAREN; 318 319 if (isNewClass(tp)) { // new xxx.| 320 Predicate<Element> constructorFilter = accessibility.and(IS_CONSTRUCTOR) 321 .and(el -> { 322 if (el.getEnclosingElement().getEnclosingElement().getKind() == ElementKind.CLASS) { 323 return el.getEnclosingElement().getModifiers().contains(Modifier.STATIC); 324 } 325 return true; 326 }); 327 addElements(membersOf(at, members), constructorFilter, smartFilter, result); 328 329 filter = filter.and(IS_PACKAGE); 330 } else if (isThrowsClause(tp)) { 331 staticOnly = true; 332 filter = filter.and(IS_PACKAGE.or(IS_CLASS).or(IS_INTERFACE)); 333 smartFilter = IS_PACKAGE.negate().and(smartTypeFilter); 334 } else if (isImport) { 335 paren = NO_PAREN; 336 if (!it.isStatic()) { 337 filter = filter.and(IS_PACKAGE.or(IS_CLASS).or(IS_INTERFACE)); 338 } 339 } else { 340 filter = filter.and(IS_CONSTRUCTOR.negate()); 341 } 342 343 filter = filter.and(staticOnly ? STATIC_ONLY : INSTANCE_ONLY); 344 345 addElements(members, filter, smartFilter, paren, result); 346 break; 347 } 348 case IDENTIFIER: 349 if (isNewClass(tp)) { 350 Function<Element, Iterable<? extends Element>> listEnclosed = 351 el -> el.getKind() == ElementKind.PACKAGE ? Collections.singletonList(el) 352 : el.getEnclosedElements(); 353 Predicate<Element> filter = accessibility.and(IS_CONSTRUCTOR.or(IS_PACKAGE)); 354 NewClassTree newClassTree = (NewClassTree)tp.getParentPath().getLeaf(); 355 ExpressionTree enclosingExpression = newClassTree.getEnclosingExpression(); 356 if (enclosingExpression != null) { // expr.new IDENT| 357 TypeMirror site = at.trees().getTypeMirror(new TreePath(tp, enclosingExpression)); 358 filter = filter.and(el -> el.getEnclosingElement().getKind() == ElementKind.CLASS && !el.getEnclosingElement().getModifiers().contains(Modifier.STATIC)); 359 addElements(membersOf(at, membersOf(at, site, false)), filter, smartFilter, result); 360 } else { 361 addScopeElements(at, scope, listEnclosed, filter, smartFilter, result); 362 } 363 break; 364 } 365 if (isThrowsClause(tp)) { 366 Predicate<Element> accept = accessibility.and(STATIC_ONLY) 367 .and(IS_PACKAGE.or(IS_CLASS).or(IS_INTERFACE)); 368 addScopeElements(at, scope, IDENTITY, accept, IS_PACKAGE.negate().and(smartTypeFilter), result); 369 break; 370 } 371 ImportTree it = findImport(tp); 372 if (it != null) { 373 addElements(membersOf(at, at.getElements().getPackageElement("").asType(), false), it.isStatic() ? STATIC_ONLY.and(accessibility) : accessibility, smartFilter, result); 374 } 375 break; 376 case ERRONEOUS: 377 case EMPTY_STATEMENT: { 378 boolean staticOnly = ReplResolve.isStatic(((JavacScope)scope).getEnv()); 379 Predicate<Element> accept = accessibility.and(staticOnly ? STATIC_ONLY : TRUE); 380 addScopeElements(at, scope, IDENTITY, accept, smartFilter, result); 381 382 Tree parent = tp.getParentPath().getLeaf(); 383 switch (parent.getKind()) { 384 case VARIABLE: 385 accept = ((VariableTree)parent).getType() == tp.getLeaf() ? 386 IS_VOID.negate() : 387 TRUE; 388 break; 389 case PARAMETERIZED_TYPE: // TODO: JEP 218: Generics over Primitive Types 390 case TYPE_PARAMETER: 391 case CLASS: 392 case INTERFACE: 393 case ENUM: 394 accept = FALSE; 395 break; 396 default: 397 accept = TRUE; 398 break; 399 } 400 addElements(primitivesOrVoid(at), accept, smartFilter, result); 401 break; 402 } 403 } 404 } 405 anchor[0] = cursor; 406 return result; 407 } 408 409 private boolean isStaticContext(AnalyzeTask at, TreePath path) { 410 switch (path.getLeaf().getKind()) { 411 case ARRAY_TYPE: 412 case PRIMITIVE_TYPE: 413 return true; 414 default: 415 Element selectEl = at.trees().getElement(path); 416 return selectEl != null && (selectEl.getKind().isClass() || selectEl.getKind().isInterface() || selectEl.getKind() == ElementKind.TYPE_PARAMETER) && selectEl.asType().getKind() != TypeKind.ERROR; 417 } 418 } 419 420 private TreePath pathFor(CompilationUnitTree topLevel, SourcePositions sp, int pos) { 421 TreePath[] deepest = new TreePath[1]; 422 423 new TreePathScanner<Void, Void>() { 424 @Override @DefinedBy(Api.COMPILER_TREE) 425 public Void scan(Tree tree, Void p) { 426 if (tree == null) 427 return null; 428 429 long start = sp.getStartPosition(topLevel, tree); 430 long end = sp.getEndPosition(topLevel, tree); 431 long prevEnd = deepest[0] != null ? sp.getEndPosition(topLevel, deepest[0].getLeaf()) : -1; 432 433 if (start <= pos && pos <= end && 434 (start != end || prevEnd != end || deepest[0] == null || 435 deepest[0].getParentPath().getLeaf() != getCurrentPath().getLeaf())) { 436 deepest[0] = new TreePath(getCurrentPath(), tree); 437 return super.scan(tree, p); 438 } 439 440 return null; 441 } 442 @Override @DefinedBy(Api.COMPILER_TREE) 443 public Void visitErroneous(ErroneousTree node, Void p) { 444 return scan(node.getErrorTrees(), null); 445 } 446 }.scan(topLevel, null); 447 448 return deepest[0]; 449 } 450 451 private boolean isNewClass(TreePath tp) { 452 return tp.getParentPath() != null && 453 tp.getParentPath().getLeaf().getKind() == Kind.NEW_CLASS && 454 ((NewClassTree) tp.getParentPath().getLeaf()).getIdentifier() == tp.getLeaf(); 455 } 456 457 private boolean isThrowsClause(TreePath tp) { 458 Tree parent = tp.getParentPath().getLeaf(); 459 return parent.getKind() == Kind.METHOD && 460 ((MethodTree)parent).getThrows().contains(tp.getLeaf()); 461 } 462 463 private ImportTree findImport(TreePath tp) { 464 while (tp != null && tp.getLeaf().getKind() != Kind.IMPORT) { 465 tp = tp.getParentPath(); 466 } 467 return tp != null ? (ImportTree)tp.getLeaf() : null; 468 } 469 470 private Predicate<Element> createAccessibilityFilter(AnalyzeTask at, TreePath tp) { 471 Scope scope = at.trees().getScope(tp); 472 return el -> { 473 switch (el.getKind()) { 474 case ANNOTATION_TYPE: case CLASS: case ENUM: case INTERFACE: 475 return at.trees().isAccessible(scope, (TypeElement) el); 476 case PACKAGE: 477 case EXCEPTION_PARAMETER: case PARAMETER: case LOCAL_VARIABLE: case RESOURCE_VARIABLE: 478 return true; 479 default: 480 TypeMirror type = el.getEnclosingElement().asType(); 481 if (type.getKind() == TypeKind.DECLARED) 482 return at.trees().isAccessible(scope, el, (DeclaredType) type); 483 else 484 return true; 485 } 486 }; 487 } 488 489 private final Predicate<Element> TRUE = el -> true; 490 private final Predicate<Element> FALSE = TRUE.negate(); 491 private final Predicate<Element> IS_STATIC = el -> el.getModifiers().contains(Modifier.STATIC); 492 private final Predicate<Element> IS_CONSTRUCTOR = el -> el.getKind() == ElementKind.CONSTRUCTOR; 493 private final Predicate<Element> IS_METHOD = el -> el.getKind() == ElementKind.METHOD; 494 private final Predicate<Element> IS_PACKAGE = el -> el.getKind() == ElementKind.PACKAGE; 495 private final Predicate<Element> IS_CLASS = el -> el.getKind().isClass(); 496 private final Predicate<Element> IS_INTERFACE = el -> el.getKind().isInterface(); 497 private final Predicate<Element> IS_VOID = el -> el.asType().getKind() == TypeKind.VOID; 498 private final Predicate<Element> STATIC_ONLY = el -> { 499 ElementKind kind = el.getKind(); 500 Element encl = el.getEnclosingElement(); 501 ElementKind enclKind = encl != null ? encl.getKind() : ElementKind.OTHER; 502 503 return IS_STATIC.or(IS_PACKAGE).or(IS_CLASS).or(IS_INTERFACE).test(el) || IS_PACKAGE.test(encl) || 504 (kind == ElementKind.TYPE_PARAMETER && !enclKind.isClass() && !enclKind.isInterface()); 505 }; 506 private final Predicate<Element> INSTANCE_ONLY = el -> { 507 Element encl = el.getEnclosingElement(); 508 509 return IS_STATIC.or(IS_CLASS).or(IS_INTERFACE).negate().test(el) || 510 IS_PACKAGE.test(encl); 511 }; 512 private final Function<Element, Iterable<? extends Element>> IDENTITY = el -> Collections.singletonList(el); 513 private final Function<Boolean, String> DEFAULT_PAREN = hasParams -> hasParams ? "(" : "()"; 514 private final Function<Boolean, String> NO_PAREN = hasParams -> ""; 515 516 private void addElements(Iterable<? extends Element> elements, Predicate<Element> accept, Predicate<Element> smart, List<Suggestion> result) { 517 addElements(elements, accept, smart, DEFAULT_PAREN, result); 518 } 519 private void addElements(Iterable<? extends Element> elements, Predicate<Element> accept, Predicate<Element> smart, Function<Boolean, String> paren, List<Suggestion> result) { 520 Set<String> hasParams = Util.stream(elements) 521 .filter(accept) 522 .filter(IS_CONSTRUCTOR.or(IS_METHOD)) 523 .filter(c -> !((ExecutableElement)c).getParameters().isEmpty()) 524 .map(this::simpleName) 525 .collect(toSet()); 526 527 for (Element c : elements) { 528 if (!accept.test(c)) 529 continue; 530 String simpleName = simpleName(c); 531 if (c.getKind() == ElementKind.CONSTRUCTOR || c.getKind() == ElementKind.METHOD) { 532 simpleName += paren.apply(hasParams.contains(simpleName)); 533 } 534 result.add(new Suggestion(simpleName, smart.test(c))); 535 } 536 } 537 538 private String simpleName(Element el) { 539 return el.getKind() == ElementKind.CONSTRUCTOR ? el.getEnclosingElement().getSimpleName().toString() 540 : el.getSimpleName().toString(); 541 } 542 543 private List<? extends Element> membersOf(AnalyzeTask at, TypeMirror site, boolean shouldGenerateDotClassItem) { 544 if (site == null) 545 return Collections.emptyList(); 546 547 switch (site.getKind()) { 548 case DECLARED: { 549 TypeElement element = (TypeElement) at.getTypes().asElement(site); 550 List<Element> result = new ArrayList<>(); 551 result.addAll(at.getElements().getAllMembers(element)); 552 if (shouldGenerateDotClassItem) { 553 result.add(createDotClassSymbol(at, site)); 554 } 555 result.removeIf(el -> el.getKind() == ElementKind.STATIC_INIT); 556 return result; 557 } 558 case ERROR: { 559 //try current qualified name as a package: 560 TypeElement typeElement = (TypeElement) at.getTypes().asElement(site); 561 Element enclosingElement = typeElement.getEnclosingElement(); 562 String parentPackageName = enclosingElement instanceof QualifiedNameable ? 563 ((QualifiedNameable)enclosingElement).getQualifiedName().toString() : 564 ""; 565 Set<PackageElement> packages = listPackages(at, parentPackageName); 566 return packages.stream() 567 .filter(p -> p.getQualifiedName().equals(typeElement.getQualifiedName())) 568 .findAny() 569 .map(p -> membersOf(at, p.asType(), false)) 570 .orElse(Collections.emptyList()); 571 } 572 case PACKAGE: { 573 String packageName = site.toString()/*XXX*/; 574 List<Element> result = new ArrayList<>(); 575 result.addAll(getEnclosedElements(at.getElements().getPackageElement(packageName))); 576 result.addAll(listPackages(at, packageName)); 577 return result; 578 } 579 case BOOLEAN: case BYTE: case SHORT: case CHAR: 580 case INT: case FLOAT: case LONG: case DOUBLE: 581 case VOID: { 582 return shouldGenerateDotClassItem ? 583 Collections.singletonList(createDotClassSymbol(at, site)) : 584 Collections.emptyList(); 585 } 586 case ARRAY: { 587 List<Element> result = new ArrayList<>(); 588 result.add(createArrayLengthSymbol(at, site)); 589 if (shouldGenerateDotClassItem) 590 result.add(createDotClassSymbol(at, site)); 591 return result; 592 } 593 default: 594 return Collections.emptyList(); 595 } 596 } 597 598 private List<? extends Element> membersOf(AnalyzeTask at, List<? extends Element> elements) { 599 return elements.stream() 600 .flatMap(e -> membersOf(at, e.asType(), true).stream()) 601 .collect(toList()); 602 } 603 604 private List<? extends Element> getEnclosedElements(PackageElement packageEl) { 605 if (packageEl == null) { 606 return Collections.emptyList(); 607 } 608 //workaround for: JDK-8024687 609 while (true) { 610 try { 611 return packageEl.getEnclosedElements() 612 .stream() 613 .filter(el -> el.asType() != null) 614 .filter(el -> el.asType().getKind() != TypeKind.ERROR) 615 .collect(toList()); 616 } catch (CompletionFailure cf) { 617 //ignore... 618 } 619 } 620 } 621 622 private List<? extends Element> primitivesOrVoid(AnalyzeTask at) { 623 Types types = at.getTypes(); 624 return Stream.of( 625 TypeKind.BOOLEAN, TypeKind.BYTE, TypeKind.CHAR, 626 TypeKind.DOUBLE, TypeKind.FLOAT, TypeKind.INT, 627 TypeKind.LONG, TypeKind.SHORT, TypeKind.VOID) 628 .map(tk -> (Type)(tk == TypeKind.VOID ? types.getNoType(tk) : types.getPrimitiveType(tk))) 629 .map(Type::asElement) 630 .collect(toList()); 631 } 632 633 void classpathChanged() { 634 synchronized (currentIndexes) { 635 int cpVersion = ++classpathVersion; 636 637 INDEXER.submit(() -> refreshIndexes(cpVersion)); 638 } 639 } 640 641 private Set<PackageElement> listPackages(AnalyzeTask at, String enclosingPackage) { 642 synchronized (currentIndexes) { 643 return currentIndexes.values() 644 .stream() 645 .flatMap(idx -> idx.packages.stream()) 646 .filter(p -> enclosingPackage.isEmpty() || p.startsWith(enclosingPackage + ".")) 647 .map(p -> { 648 int dot = p.indexOf('.', enclosingPackage.length() + 1); 649 return dot == (-1) ? p : p.substring(0, dot); 650 }) 651 .distinct() 652 .map(p -> createPackageElement(at, p)) 653 .collect(Collectors.toSet()); 654 } 655 } 656 657 private PackageElement createPackageElement(AnalyzeTask at, String packageName) { 658 Names names = Names.instance(at.getContext()); 659 Symtab syms = Symtab.instance(at.getContext()); 660 PackageElement existing = syms.enterPackage(syms.unnamedModule, names.fromString(packageName)); 661 662 return existing; 663 } 664 665 private Element createArrayLengthSymbol(AnalyzeTask at, TypeMirror site) { 666 Name length = Names.instance(at.getContext()).length; 667 Type intType = Symtab.instance(at.getContext()).intType; 668 669 return new VarSymbol(Flags.PUBLIC | Flags.FINAL, length, intType, ((Type) site).tsym); 670 } 671 672 private Element createDotClassSymbol(AnalyzeTask at, TypeMirror site) { 673 Name _class = Names.instance(at.getContext())._class; 674 Type classType = Symtab.instance(at.getContext()).classType; 675 Type erasedSite = (Type)at.getTypes().erasure(site); 676 classType = new ClassType(classType.getEnclosingType(), com.sun.tools.javac.util.List.of(erasedSite), classType.asElement()); 677 678 return new VarSymbol(Flags.PUBLIC | Flags.STATIC | Flags.FINAL, _class, classType, erasedSite.tsym); 679 } 680 681 private Iterable<? extends Element> scopeContent(AnalyzeTask at, Scope scope, Function<Element, Iterable<? extends Element>> elementConvertor) { 682 Iterable<Scope> scopeIterable = () -> new Iterator<Scope>() { 683 private Scope currentScope = scope; 684 @Override 685 public boolean hasNext() { 686 return currentScope != null; 687 } 688 @Override 689 public Scope next() { 690 if (!hasNext()) 691 throw new NoSuchElementException(); 692 try { 693 return currentScope; 694 } finally { 695 currentScope = currentScope.getEnclosingScope(); 696 } 697 } 698 }; 699 @SuppressWarnings("unchecked") 700 List<Element> result = Util.stream(scopeIterable) 701 .flatMap(s -> Util.stream((Iterable<Element>)s.getLocalElements())) 702 .flatMap(el -> Util.stream((Iterable<Element>)elementConvertor.apply(el))) 703 .collect(toCollection(ArrayList :: new)); 704 result.addAll(listPackages(at, "")); 705 return result; 706 } 707 708 @SuppressWarnings("fallthrough") 709 private Iterable<TypeMirror> findTargetType(AnalyzeTask at, TreePath forPath) { 710 if (forPath.getParentPath() == null) 711 return null; 712 713 Tree current = forPath.getLeaf(); 714 715 switch (forPath.getParentPath().getLeaf().getKind()) { 716 case ASSIGNMENT: { 717 AssignmentTree tree = (AssignmentTree) forPath.getParentPath().getLeaf(); 718 if (tree.getExpression() == current) 719 return Collections.singletonList(at.trees().getTypeMirror(new TreePath(forPath.getParentPath(), tree.getVariable()))); 720 break; 721 } 722 case VARIABLE: { 723 VariableTree tree = (VariableTree) forPath.getParentPath().getLeaf(); 724 if (tree.getInitializer()== current) 725 return Collections.singletonList(at.trees().getTypeMirror(forPath.getParentPath())); 726 break; 727 } 728 case ERRONEOUS: 729 return findTargetType(at, forPath.getParentPath()); 730 case NEW_CLASS: { 731 NewClassTree nct = (NewClassTree) forPath.getParentPath().getLeaf(); 732 List<TypeMirror> actuals = computeActualInvocationTypes(at, nct.getArguments(), forPath); 733 734 if (actuals != null) { 735 Iterable<Pair<ExecutableElement, ExecutableType>> candidateConstructors = newClassCandidates(at, forPath.getParentPath()); 736 737 return computeSmartTypesForExecutableType(at, candidateConstructors, actuals); 738 } else { 739 return findTargetType(at, forPath.getParentPath()); 740 } 741 } 742 case METHOD: 743 if (!isThrowsClause(forPath)) { 744 break; 745 } 746 // fall through 747 case THROW: 748 return Collections.singletonList(at.getElements().getTypeElement("java.lang.Throwable").asType()); 749 case METHOD_INVOCATION: { 750 MethodInvocationTree mit = (MethodInvocationTree) forPath.getParentPath().getLeaf(); 751 List<TypeMirror> actuals = computeActualInvocationTypes(at, mit.getArguments(), forPath); 752 753 if (actuals == null) 754 return null; 755 756 Iterable<Pair<ExecutableElement, ExecutableType>> candidateMethods = methodCandidates(at, forPath.getParentPath()); 757 758 return computeSmartTypesForExecutableType(at, candidateMethods, actuals); 759 } 760 } 761 762 return null; 763 } 764 765 private List<TypeMirror> computeActualInvocationTypes(AnalyzeTask at, List<? extends ExpressionTree> arguments, TreePath currentArgument) { 766 if (currentArgument == null) 767 return null; 768 769 int paramIndex = arguments.indexOf(currentArgument.getLeaf()); 770 771 if (paramIndex == (-1)) 772 return null; 773 774 List<TypeMirror> actuals = new ArrayList<>(); 775 776 for (ExpressionTree arg : arguments.subList(0, paramIndex)) { 777 actuals.add(at.trees().getTypeMirror(new TreePath(currentArgument.getParentPath(), arg))); 778 } 779 780 return actuals; 781 } 782 783 private List<Pair<ExecutableElement, ExecutableType>> filterExecutableTypesByArguments(AnalyzeTask at, Iterable<Pair<ExecutableElement, ExecutableType>> candidateMethods, List<TypeMirror> precedingActualTypes) { 784 List<Pair<ExecutableElement, ExecutableType>> candidate = new ArrayList<>(); 785 int paramIndex = precedingActualTypes.size(); 786 787 OUTER: 788 for (Pair<ExecutableElement, ExecutableType> method : candidateMethods) { 789 boolean varargInvocation = paramIndex >= method.snd.getParameterTypes().size(); 790 791 for (int i = 0; i < paramIndex; i++) { 792 TypeMirror actual = precedingActualTypes.get(i); 793 794 if (this.parameterType(method.fst, method.snd, i, !varargInvocation) 795 .noneMatch(formal -> at.getTypes().isAssignable(actual, formal))) { 796 continue OUTER; 797 } 798 } 799 candidate.add(method); 800 } 801 802 return candidate; 803 } 804 805 private Stream<TypeMirror> parameterType(ExecutableElement method, ExecutableType methodType, int paramIndex, boolean allowVarArgsArray) { 806 int paramCount = methodType.getParameterTypes().size(); 807 if (paramIndex >= paramCount && !method.isVarArgs()) 808 return Stream.empty(); 809 if (paramIndex < paramCount - 1 || !method.isVarArgs()) 810 return Stream.of(methodType.getParameterTypes().get(paramIndex)); 811 TypeMirror varargType = methodType.getParameterTypes().get(paramCount - 1); 812 TypeMirror elemenType = ((ArrayType) varargType).getComponentType(); 813 if (paramIndex >= paramCount || !allowVarArgsArray) 814 return Stream.of(elemenType); 815 return Stream.of(varargType, elemenType); 816 } 817 818 private List<TypeMirror> computeSmartTypesForExecutableType(AnalyzeTask at, Iterable<Pair<ExecutableElement, ExecutableType>> candidateMethods, List<TypeMirror> precedingActualTypes) { 819 List<TypeMirror> candidate = new ArrayList<>(); 820 int paramIndex = precedingActualTypes.size(); 821 822 this.filterExecutableTypesByArguments(at, candidateMethods, precedingActualTypes) 823 .stream() 824 .flatMap(method -> parameterType(method.fst, method.snd, paramIndex, true)) 825 .forEach(candidate::add); 826 827 return candidate; 828 } 829 830 831 private TypeMirror resultTypeOf(Element el) { 832 //TODO: should reflect the type of site! 833 switch (el.getKind()) { 834 case METHOD: 835 return ((ExecutableElement) el).getReturnType(); 836 case CONSTRUCTOR: 837 case INSTANCE_INIT: case STATIC_INIT: //TODO: should be filtered out 838 return el.getEnclosingElement().asType(); 839 default: 840 return el.asType(); 841 } 842 } 843 844 private void addScopeElements(AnalyzeTask at, Scope scope, Function<Element, Iterable<? extends Element>> elementConvertor, Predicate<Element> filter, Predicate<Element> smartFilter, List<Suggestion> result) { 845 addElements(scopeContent(at, scope, elementConvertor), filter, smartFilter, result); 846 } 847 848 private Iterable<Pair<ExecutableElement, ExecutableType>> methodCandidates(AnalyzeTask at, TreePath invocation) { 849 MethodInvocationTree mit = (MethodInvocationTree) invocation.getLeaf(); 850 ExpressionTree select = mit.getMethodSelect(); 851 List<Pair<ExecutableElement, ExecutableType>> result = new ArrayList<>(); 852 Predicate<Element> accessibility = createAccessibilityFilter(at, invocation); 853 854 switch (select.getKind()) { 855 case MEMBER_SELECT: 856 MemberSelectTree mst = (MemberSelectTree) select; 857 TreePath tp = new TreePath(new TreePath(invocation, select), mst.getExpression()); 858 TypeMirror site = at.trees().getTypeMirror(tp); 859 860 if (site == null || site.getKind() != TypeKind.DECLARED) 861 break; 862 863 Element siteEl = at.getTypes().asElement(site); 864 865 if (siteEl == null) 866 break; 867 868 if (isStaticContext(at, tp)) { 869 accessibility = accessibility.and(STATIC_ONLY); 870 } 871 872 for (ExecutableElement ee : ElementFilter.methodsIn(membersOf(at, siteEl.asType(), false))) { 873 if (ee.getSimpleName().contentEquals(mst.getIdentifier())) { 874 if (accessibility.test(ee)) { 875 result.add(Pair.of(ee, (ExecutableType) at.getTypes().asMemberOf((DeclaredType) site, ee))); 876 } 877 } 878 } 879 break; 880 case IDENTIFIER: 881 IdentifierTree it = (IdentifierTree) select; 882 for (ExecutableElement ee : ElementFilter.methodsIn(scopeContent(at, at.trees().getScope(invocation), IDENTITY))) { 883 if (ee.getSimpleName().contentEquals(it.getName())) { 884 if (accessibility.test(ee)) { 885 result.add(Pair.of(ee, (ExecutableType) ee.asType())); //XXX: proper site 886 } 887 } 888 } 889 break; 890 default: 891 break; 892 } 893 894 return result; 895 } 896 897 private Iterable<Pair<ExecutableElement, ExecutableType>> newClassCandidates(AnalyzeTask at, TreePath newClassPath) { 898 NewClassTree nct = (NewClassTree) newClassPath.getLeaf(); 899 Element type = at.trees().getElement(new TreePath(newClassPath.getParentPath(), nct.getIdentifier())); 900 TypeMirror targetType = at.trees().getTypeMirror(newClassPath); 901 if (targetType == null || targetType.getKind() != TypeKind.DECLARED) { 902 Iterable<TypeMirror> targetTypes = findTargetType(at, newClassPath); 903 if (targetTypes == null) 904 targetTypes = Collections.emptyList(); 905 targetType = 906 StreamSupport.stream(targetTypes.spliterator(), false) 907 .filter(t -> at.getTypes().asElement(t) == type) 908 .findAny() 909 .orElse(at.getTypes().erasure(type.asType())); 910 } 911 List<Pair<ExecutableElement, ExecutableType>> candidateConstructors = new ArrayList<>(); 912 Predicate<Element> accessibility = createAccessibilityFilter(at, newClassPath); 913 914 if (targetType != null && 915 targetType.getKind() == TypeKind.DECLARED && 916 type != null && 917 (type.getKind().isClass() || type.getKind().isInterface())) { 918 for (ExecutableElement constr : ElementFilter.constructorsIn(type.getEnclosedElements())) { 919 if (accessibility.test(constr)) { 920 ExecutableType constrType = 921 (ExecutableType) at.getTypes().asMemberOf((DeclaredType) targetType, constr); 922 candidateConstructors.add(Pair.of(constr, constrType)); 923 } 924 } 925 } 926 927 return candidateConstructors; 928 } 929 930 @Override 931 public String documentation(String code, int cursor) { 932 suspendIndexing(); 933 try { 934 return documentationImpl(code, cursor); 935 } finally { 936 resumeIndexing(); 937 } 938 } 939 940 private String documentationImpl(String code, int cursor) { 941 code = code.substring(0, cursor); 942 if (code.trim().isEmpty()) { //TODO: comment handling 943 code += ";"; 944 } 945 946 if (guessKind(code) == Kind.IMPORT) 947 return null; 948 949 OuterWrap codeWrap = wrapInClass(Wrap.methodWrap(code)); 950 AnalyzeTask at = proc.taskFactory.new AnalyzeTask(codeWrap); 951 SourcePositions sp = at.trees().getSourcePositions(); 952 CompilationUnitTree topLevel = at.firstCuTree(); 953 TreePath tp = pathFor(topLevel, sp, codeWrap.snippetIndexToWrapIndex(cursor)); 954 955 if (tp == null) 956 return null; 957 958 TreePath prevPath = null; 959 while (tp != null && tp.getLeaf().getKind() != Kind.METHOD_INVOCATION && tp.getLeaf().getKind() != Kind.NEW_CLASS) { 960 prevPath = tp; 961 tp = tp.getParentPath(); 962 } 963 964 if (tp == null) 965 return null; 966 967 Iterable<Pair<ExecutableElement, ExecutableType>> candidates; 968 List<? extends ExpressionTree> arguments; 969 970 if (tp.getLeaf().getKind() == Kind.METHOD_INVOCATION) { 971 MethodInvocationTree mit = (MethodInvocationTree) tp.getLeaf(); 972 candidates = methodCandidates(at, tp); 973 arguments = mit.getArguments(); 974 } else { 975 NewClassTree nct = (NewClassTree) tp.getLeaf(); 976 candidates = newClassCandidates(at, tp); 977 arguments = nct.getArguments(); 978 } 979 980 if (!isEmptyArgumentsContext(arguments)) { 981 List<TypeMirror> actuals = computeActualInvocationTypes(at, arguments, prevPath); 982 List<TypeMirror> fullActuals = actuals != null ? actuals : Collections.emptyList(); 983 984 candidates = 985 this.filterExecutableTypesByArguments(at, candidates, fullActuals) 986 .stream() 987 .filter(method -> parameterType(method.fst, method.snd, fullActuals.size(), true).findAny().isPresent()) 988 .collect(Collectors.toList()); 989 } 990 991 return Util.stream(candidates) 992 .map(method -> Util.expunge(element2String(method.fst))) 993 .collect(joining("\n")); 994 } 995 996 private boolean isEmptyArgumentsContext(List<? extends ExpressionTree> arguments) { 997 if (arguments.size() == 1) { 998 Tree firstArgument = arguments.get(0); 999 return firstArgument.getKind() == Kind.ERRONEOUS; 1000 } 1001 return false; 1002 } 1003 1004 private String element2String(Element el) { 1005 switch (el.getKind()) { 1006 case ANNOTATION_TYPE: case CLASS: case ENUM: case INTERFACE: 1007 return ((TypeElement) el).getQualifiedName().toString(); 1008 case FIELD: 1009 return element2String(el.getEnclosingElement()) + "." + el.getSimpleName() + ":" + el.asType(); 1010 case ENUM_CONSTANT: 1011 return element2String(el.getEnclosingElement()) + "." + el.getSimpleName(); 1012 case EXCEPTION_PARAMETER: case LOCAL_VARIABLE: case PARAMETER: case RESOURCE_VARIABLE: 1013 return el.getSimpleName() + ":" + el.asType(); 1014 case CONSTRUCTOR: case METHOD: 1015 StringBuilder header = new StringBuilder(); 1016 header.append(element2String(el.getEnclosingElement())); 1017 if (el.getKind() == ElementKind.METHOD) { 1018 header.append("."); 1019 header.append(el.getSimpleName()); 1020 } 1021 header.append("("); 1022 String sep = ""; 1023 ExecutableElement method = (ExecutableElement) el; 1024 for (Iterator<? extends VariableElement> i = method.getParameters().iterator(); i.hasNext();) { 1025 VariableElement p = i.next(); 1026 header.append(sep); 1027 if (!i.hasNext() && method.isVarArgs()) { 1028 header.append(unwrapArrayType(p.asType())); 1029 header.append("..."); 1030 1031 } else { 1032 header.append(p.asType()); 1033 } 1034 header.append(" "); 1035 header.append(p.getSimpleName()); 1036 sep = ", "; 1037 } 1038 header.append(")"); 1039 return header.toString(); 1040 default: 1041 return el.toString(); 1042 } 1043 } 1044 private TypeMirror unwrapArrayType(TypeMirror arrayType) { 1045 if (arrayType.getKind() == TypeKind.ARRAY) { 1046 return ((ArrayType)arrayType).getComponentType(); 1047 } 1048 return arrayType; 1049 } 1050 1051 @Override 1052 public String analyzeType(String code, int cursor) { 1053 code = code.substring(0, cursor); 1054 CompletionInfo completionInfo = analyzeCompletion(code); 1055 if (!completionInfo.completeness.isComplete) 1056 return null; 1057 if (completionInfo.completeness == Completeness.COMPLETE_WITH_SEMI) { 1058 code += ";"; 1059 } 1060 1061 OuterWrap codeWrap; 1062 switch (guessKind(code)) { 1063 case IMPORT: case METHOD: case CLASS: case ENUM: 1064 case INTERFACE: case ANNOTATION_TYPE: case VARIABLE: 1065 return null; 1066 default: 1067 codeWrap = wrapInClass(Wrap.methodWrap(code)); 1068 break; 1069 } 1070 AnalyzeTask at = proc.taskFactory.new AnalyzeTask(codeWrap); 1071 SourcePositions sp = at.trees().getSourcePositions(); 1072 CompilationUnitTree topLevel = at.firstCuTree(); 1073 int pos = codeWrap.snippetIndexToWrapIndex(code.length()); 1074 TreePath tp = pathFor(topLevel, sp, pos); 1075 while (ExpressionTree.class.isAssignableFrom(tp.getParentPath().getLeaf().getKind().asInterface()) && 1076 tp.getParentPath().getLeaf().getKind() != Kind.ERRONEOUS && 1077 tp.getParentPath().getParentPath() != null) 1078 tp = tp.getParentPath(); 1079 TypeMirror type = at.trees().getTypeMirror(tp); 1080 1081 if (type == null) 1082 return null; 1083 1084 switch (type.getKind()) { 1085 case ERROR: case NONE: case OTHER: 1086 case PACKAGE: case VOID: 1087 return null; //not usable 1088 case NULL: 1089 type = at.getElements().getTypeElement("java.lang.Object").asType(); 1090 break; 1091 } 1092 1093 return TreeDissector.printType(at, proc, type); 1094 } 1095 1096 @Override 1097 public QualifiedNames listQualifiedNames(String code, int cursor) { 1098 code = code.substring(0, cursor); 1099 if (code.trim().isEmpty()) { 1100 return new QualifiedNames(Collections.emptyList(), -1, true, false); 1101 } 1102 OuterWrap codeWrap; 1103 switch (guessKind(code)) { 1104 case IMPORT: 1105 return new QualifiedNames(Collections.emptyList(), -1, true, false); 1106 case METHOD: 1107 codeWrap = wrapInClass(Wrap.classMemberWrap(code)); 1108 break; 1109 default: 1110 codeWrap = wrapInClass(Wrap.methodWrap(code)); 1111 break; 1112 } 1113 AnalyzeTask at = proc.taskFactory.new AnalyzeTask(codeWrap); 1114 SourcePositions sp = at.trees().getSourcePositions(); 1115 CompilationUnitTree topLevel = at.firstCuTree(); 1116 TreePath tp = pathFor(topLevel, sp, codeWrap.snippetIndexToWrapIndex(code.length())); 1117 if (tp.getLeaf().getKind() != Kind.IDENTIFIER) { 1118 return new QualifiedNames(Collections.emptyList(), -1, true, false); 1119 } 1120 Scope scope = at.trees().getScope(tp); 1121 TypeMirror type = at.trees().getTypeMirror(tp); 1122 Element el = at.trees().getElement(tp); 1123 1124 boolean erroneous = (type.getKind() == TypeKind.ERROR && el.getKind() == ElementKind.CLASS) || 1125 (el.getKind() == ElementKind.PACKAGE && el.getEnclosedElements().isEmpty()); 1126 String simpleName = ((IdentifierTree) tp.getLeaf()).getName().toString(); 1127 boolean upToDate; 1128 List<String> result; 1129 1130 synchronized (currentIndexes) { 1131 upToDate = classpathVersion == indexVersion; 1132 result = currentIndexes.values() 1133 .stream() 1134 .flatMap(idx -> idx.classSimpleName2FQN.getOrDefault(simpleName, 1135 Collections.emptyList()).stream()) 1136 .distinct() 1137 .filter(fqn -> isAccessible(at, scope, fqn)) 1138 .sorted() 1139 .collect(Collectors.toList()); 1140 } 1141 1142 return new QualifiedNames(result, simpleName.length(), upToDate, !erroneous); 1143 } 1144 1145 private boolean isAccessible(AnalyzeTask at, Scope scope, String fqn) { 1146 TypeElement type = at.getElements().getTypeElement(fqn); 1147 if (type == null) 1148 return false; 1149 return at.trees().isAccessible(scope, type); 1150 } 1151 1152 //-------------------- 1153 // classpath indexing: 1154 //-------------------- 1155 1156 //the indexing can be suspended when a more important task is running: 1157 private void waitIndexingNotSuspended() { 1158 boolean suspendedNotified = false; 1159 synchronized (suspendLock) { 1160 while (suspend > 0) { 1161 if (!suspendedNotified) { 1162 suspendedNotified = true; 1163 } 1164 try { 1165 suspendLock.wait(); 1166 } catch (InterruptedException ex) { 1167 } 1168 } 1169 } 1170 } 1171 1172 public void suspendIndexing() { 1173 synchronized (suspendLock) { 1174 suspend++; 1175 } 1176 } 1177 1178 public void resumeIndexing() { 1179 synchronized (suspendLock) { 1180 if (--suspend == 0) { 1181 suspendLock.notifyAll(); 1182 } 1183 } 1184 } 1185 1186 //update indexes, either initially or after a classpath change: 1187 private void refreshIndexes(int version) { 1188 try { 1189 Collection<Path> paths = new ArrayList<>(); 1190 MemoryFileManager fm = proc.taskFactory.fileManager(); 1191 1192 appendPaths(fm, StandardLocation.PLATFORM_CLASS_PATH, paths); 1193 appendPaths(fm, StandardLocation.CLASS_PATH, paths); 1194 appendPaths(fm, StandardLocation.SOURCE_PATH, paths); 1195 1196 Map<Path, ClassIndex> newIndexes = new HashMap<>(); 1197 1198 //setup existing/last known data: 1199 for (Path p : paths) { 1200 ClassIndex index = PATH_TO_INDEX.get(p); 1201 if (index != null) { 1202 newIndexes.put(p, index); 1203 } 1204 } 1205 1206 synchronized (currentIndexes) { 1207 //temporary setting old data: 1208 currentIndexes.clear(); 1209 currentIndexes.putAll(newIndexes); 1210 } 1211 1212 //update/compute the indexes if needed: 1213 for (Path p : paths) { 1214 waitIndexingNotSuspended(); 1215 1216 ClassIndex index = indexForPath(p); 1217 newIndexes.put(p, index); 1218 } 1219 1220 synchronized (currentIndexes) { 1221 currentIndexes.clear(); 1222 currentIndexes.putAll(newIndexes); 1223 } 1224 } catch (Exception ex) { 1225 proc.debug(ex, "SourceCodeAnalysisImpl.refreshIndexes(" + version + ")"); 1226 } finally { 1227 synchronized (currentIndexes) { 1228 indexVersion = version; 1229 } 1230 } 1231 } 1232 1233 private void appendPaths(MemoryFileManager fm, Location loc, Collection<Path> paths) { 1234 Iterable<? extends Path> locationPaths = fm.getLocationAsPaths(loc); 1235 if (locationPaths == null) 1236 return ; 1237 for (Path path : locationPaths) { 1238 if (".".equals(path.toString())) { 1239 //skip CWD 1240 continue; 1241 } 1242 1243 paths.add(path); 1244 } 1245 } 1246 1247 //create/update index a given JavaFileManager entry (which may be a JDK installation, a jar/zip file or a directory): 1248 //if an index exists for the given entry, the existing index is kept unless the timestamp is modified 1249 private ClassIndex indexForPath(Path path) { 1250 if (isJRTMarkerFile(path)) { 1251 FileSystem jrtfs = FileSystems.getFileSystem(URI.create("jrt:/")); 1252 Path modules = jrtfs.getPath("modules"); 1253 return PATH_TO_INDEX.compute(path, (p, index) -> { 1254 try { 1255 long lastModified = Files.getLastModifiedTime(modules).toMillis(); 1256 if (index == null || index.timestamp != lastModified) { 1257 try (DirectoryStream<Path> stream = Files.newDirectoryStream(modules)) { 1258 index = doIndex(lastModified, path, stream); 1259 } 1260 } 1261 return index; 1262 } catch (IOException ex) { 1263 proc.debug(ex, "SourceCodeAnalysisImpl.indexesForPath(" + path.toString() + ")"); 1264 return new ClassIndex(-1, path, Collections.emptySet(), Collections.emptyMap()); 1265 } 1266 }); 1267 } else if (!Files.isDirectory(path)) { 1268 if (Files.exists(path)) { 1269 return PATH_TO_INDEX.compute(path, (p, index) -> { 1270 try { 1271 long lastModified = Files.getLastModifiedTime(p).toMillis(); 1272 if (index == null || index.timestamp != lastModified) { 1273 ClassLoader cl = SourceCodeAnalysisImpl.class.getClassLoader(); 1274 1275 try (FileSystem zip = FileSystems.newFileSystem(path, cl)) { 1276 index = doIndex(lastModified, path, zip.getRootDirectories()); 1277 } 1278 } 1279 return index; 1280 } catch (IOException ex) { 1281 proc.debug(ex, "SourceCodeAnalysisImpl.indexesForPath(" + path.toString() + ")"); 1282 return new ClassIndex(-1, path, Collections.emptySet(), Collections.emptyMap()); 1283 } 1284 }); 1285 } else { 1286 return new ClassIndex(-1, path, Collections.emptySet(), Collections.emptyMap()); 1287 } 1288 } else { 1289 return PATH_TO_INDEX.compute(path, (p, index) -> { 1290 //no persistence for directories, as we cannot check timestamps: 1291 if (index == null) { 1292 index = doIndex(-1, path, Arrays.asList(p)); 1293 } 1294 return index; 1295 }); 1296 } 1297 } 1298 1299 static boolean isJRTMarkerFile(Path path) { 1300 return path.equals(Paths.get(System.getProperty("java.home"), "lib", "modules")); 1301 } 1302 1303 //create an index based on the content of the given dirs; the original JavaFileManager entry is originalPath. 1304 private ClassIndex doIndex(long timestamp, Path originalPath, Iterable<? extends Path> dirs) { 1305 Set<String> packages = new HashSet<>(); 1306 Map<String, Collection<String>> classSimpleName2FQN = new HashMap<>(); 1307 1308 for (Path d : dirs) { 1309 try { 1310 Files.walkFileTree(d, new FileVisitor<Path>() { 1311 int depth; 1312 @Override 1313 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { 1314 waitIndexingNotSuspended(); 1315 if (depth++ == 0) 1316 return FileVisitResult.CONTINUE; 1317 String dirName = dir.getFileName().toString(); 1318 String sep = dir.getFileSystem().getSeparator(); 1319 dirName = dirName.endsWith(sep) ? dirName.substring(0, dirName.length() - sep.length()) 1320 : dirName; 1321 if (SourceVersion.isIdentifier(dirName)) 1322 return FileVisitResult.CONTINUE; 1323 return FileVisitResult.SKIP_SUBTREE; 1324 } 1325 @Override 1326 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 1327 waitIndexingNotSuspended(); 1328 if (file.getFileName().toString().endsWith(".class")) { 1329 String relativePath = d.relativize(file).toString(); 1330 String binaryName = relativePath.substring(0, relativePath.length() - 6).replace('/', '.'); 1331 int packageDot = binaryName.lastIndexOf('.'); 1332 if (packageDot > (-1)) { 1333 packages.add(binaryName.substring(0, packageDot)); 1334 } 1335 String typeName = binaryName.replace('$', '.'); 1336 addClassName2Map(classSimpleName2FQN, typeName); 1337 } 1338 return FileVisitResult.CONTINUE; 1339 } 1340 @Override 1341 public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { 1342 return FileVisitResult.CONTINUE; 1343 } 1344 @Override 1345 public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { 1346 depth--; 1347 return FileVisitResult.CONTINUE; 1348 } 1349 }); 1350 } catch (IOException ex) { 1351 proc.debug(ex, "doIndex(" + d.toString() + ")"); 1352 } 1353 } 1354 1355 return new ClassIndex(timestamp, originalPath, packages, classSimpleName2FQN); 1356 } 1357 1358 private static void addClassName2Map(Map<String, Collection<String>> classSimpleName2FQN, String typeName) { 1359 int simpleNameDot = typeName.lastIndexOf('.'); 1360 classSimpleName2FQN.computeIfAbsent(typeName.substring(simpleNameDot + 1), n -> new LinkedHashSet<>()) 1361 .add(typeName); 1362 } 1363 1364 //holder for indexed data about a given path 1365 public static final class ClassIndex { 1366 public final long timestamp; 1367 public final Path forPath; 1368 public final Set<String> packages; 1369 public final Map<String, Collection<String>> classSimpleName2FQN; 1370 1371 public ClassIndex(long timestamp, Path forPath, Set<String> packages, Map<String, Collection<String>> classSimpleName2FQN) { 1372 this.timestamp = timestamp; 1373 this.forPath = forPath; 1374 this.packages = packages; 1375 this.classSimpleName2FQN = classSimpleName2FQN; 1376 } 1377 1378 } 1379 1380 //for tests, to be able to wait until the indexing finishes: 1381 public void waitBackgroundTaskFinished() throws Exception { 1382 boolean upToDate; 1383 synchronized (currentIndexes) { 1384 upToDate = classpathVersion == indexVersion; 1385 } 1386 while (!upToDate) { 1387 INDEXER.submit(() -> {}).get(); 1388 synchronized (currentIndexes) { 1389 upToDate = classpathVersion == indexVersion; 1390 } 1391 } 1392 } 1393} 1394