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