1 /* 2 * Copyright (c) 2014, 2019, 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 com.sun.source.tree.AssignmentTree; 29 import com.sun.source.tree.ClassTree; 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.TypeParameterTree; 43 import com.sun.source.tree.VariableTree; 44 import com.sun.source.util.SourcePositions; 45 import com.sun.source.util.TreePath; 46 import com.sun.source.util.TreePathScanner; 47 import com.sun.tools.javac.api.JavacScope; 48 import com.sun.tools.javac.code.Flags; 49 import com.sun.tools.javac.code.Symbol.CompletionFailure; 50 import com.sun.tools.javac.code.Symbol.VarSymbol; 51 import com.sun.tools.javac.code.Symtab; 52 import com.sun.tools.javac.code.Type; 53 import com.sun.tools.javac.code.Type.ClassType; 54 import jdk.internal.shellsupport.doc.JavadocHelper; 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 61 import java.util.ArrayList; 62 import java.util.Collections; 63 import java.util.Iterator; 64 import java.util.List; 65 import java.util.Objects; 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.EnumSet; 92 import java.util.HashMap; 93 import java.util.HashSet; 94 import java.util.LinkedHashSet; 95 import java.util.Map; 96 import java.util.NoSuchElementException; 97 import java.util.Set; 98 import java.util.concurrent.ExecutorService; 99 import java.util.concurrent.Executors; 100 import java.util.function.Function; 101 import java.util.regex.Matcher; 102 import java.util.regex.Pattern; 103 import java.util.stream.Collectors; 104 105 import static java.util.stream.Collectors.collectingAndThen; 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 110 import java.util.stream.Stream; 111 import java.util.stream.StreamSupport; 112 113 import javax.lang.model.SourceVersion; 114 115 import javax.lang.model.element.ExecutableElement; 116 import javax.lang.model.element.PackageElement; 117 import javax.lang.model.element.QualifiedNameable; 118 import javax.lang.model.element.TypeParameterElement; 119 import javax.lang.model.element.VariableElement; 120 import javax.lang.model.type.ArrayType; 121 import javax.lang.model.type.ExecutableType; 122 import javax.lang.model.type.TypeKind; 123 import javax.lang.model.util.ElementFilter; 124 import javax.lang.model.util.Types; 125 import javax.tools.JavaFileManager.Location; 126 import javax.tools.StandardLocation; 127 128 import jdk.jshell.ExpressionToTypeInfo.ExpressionInfo; 129 import static jdk.jshell.Util.REPL_DOESNOTMATTER_CLASS_NAME; 130 import static jdk.jshell.SourceCodeAnalysis.Completeness.DEFINITELY_INCOMPLETE; 131 import static jdk.jshell.TreeDissector.printType; 132 133 import static java.util.stream.Collectors.joining; 134 135 import javax.lang.model.type.IntersectionType; 136 137 /** 138 * The concrete implementation of SourceCodeAnalysis. 139 * @author Robert Field 140 */ 141 class SourceCodeAnalysisImpl extends SourceCodeAnalysis { 142 143 private static final Map<Path, ClassIndex> PATH_TO_INDEX = new HashMap<>(); 144 private static final ExecutorService INDEXER = Executors.newFixedThreadPool(1, r -> { 145 Thread t = new Thread(r); 146 t.setDaemon(true); 147 t.setUncaughtExceptionHandler((thread, ex) -> ex.printStackTrace()); 148 return t; 149 }); 150 151 private final JShell proc; 152 private final CompletenessAnalyzer ca; 153 private final List<AutoCloseable> closeables = new ArrayList<>(); 154 private final Map<Path, ClassIndex> currentIndexes = new HashMap<>(); 155 private int indexVersion; 156 private int classpathVersion; 157 private final Object suspendLock = new Object(); 158 private int suspend; 159 SourceCodeAnalysisImpl(JShell proc)160 SourceCodeAnalysisImpl(JShell proc) { 161 this.proc = proc; 162 this.ca = new CompletenessAnalyzer(proc); 163 164 int cpVersion = classpathVersion = 1; 165 166 INDEXER.submit(() -> refreshIndexes(cpVersion)); 167 } 168 169 @Override analyzeCompletion(String srcInput)170 public CompletionInfo analyzeCompletion(String srcInput) { 171 MaskCommentsAndModifiers mcm = new MaskCommentsAndModifiers(srcInput, false); 172 if (mcm.endsWithOpenToken()) { 173 proc.debug(DBG_COMPA, "Incomplete (open comment): %s\n", srcInput); 174 return new CompletionInfoImpl(DEFINITELY_INCOMPLETE, null, srcInput + '\n'); 175 } 176 String cleared = mcm.cleared(); 177 String trimmedInput = Util.trimEnd(cleared); 178 if (trimmedInput.isEmpty()) { 179 // Just comment or empty 180 return new CompletionInfoImpl(Completeness.EMPTY, srcInput, ""); 181 } 182 CaInfo info = ca.scan(trimmedInput); 183 Completeness status = info.status; 184 int unitEndPos = info.unitEndPos; 185 if (unitEndPos > srcInput.length()) { 186 unitEndPos = srcInput.length(); 187 } 188 int nonCommentNonWhiteLength = trimmedInput.length(); 189 String src = srcInput.substring(0, unitEndPos); 190 switch (status) { 191 case COMPLETE: { 192 if (unitEndPos == nonCommentNonWhiteLength) { 193 // The unit is the whole non-coment/white input plus semicolon 194 String compileSource = src 195 + mcm.mask().substring(nonCommentNonWhiteLength); 196 proc.debug(DBG_COMPA, "Complete: %s\n", compileSource); 197 proc.debug(DBG_COMPA, " nothing remains.\n"); 198 return new CompletionInfoImpl(status, compileSource, ""); 199 } else { 200 String remain = srcInput.substring(unitEndPos); 201 proc.debug(DBG_COMPA, "Complete: %s\n", src); 202 proc.debug(DBG_COMPA, " remaining: %s\n", remain); 203 return new CompletionInfoImpl(status, src, remain); 204 } 205 } 206 case COMPLETE_WITH_SEMI: { 207 // The unit is the whole non-coment/white input plus semicolon 208 String compileSource = src 209 + ";" 210 + mcm.mask().substring(nonCommentNonWhiteLength); 211 proc.debug(DBG_COMPA, "Complete with semi: %s\n", compileSource); 212 proc.debug(DBG_COMPA, " nothing remains.\n"); 213 return new CompletionInfoImpl(status, compileSource, ""); 214 } 215 case DEFINITELY_INCOMPLETE: 216 proc.debug(DBG_COMPA, "Incomplete: %s\n", srcInput); 217 return new CompletionInfoImpl(status, null, srcInput + '\n'); 218 case CONSIDERED_INCOMPLETE: { 219 // Since the source is potentually valid, construct the complete source 220 String compileSource = src 221 + ";" 222 + mcm.mask().substring(nonCommentNonWhiteLength); 223 proc.debug(DBG_COMPA, "Considered incomplete: %s\n", srcInput); 224 return new CompletionInfoImpl(status, compileSource, srcInput + '\n'); 225 } 226 case EMPTY: 227 proc.debug(DBG_COMPA, "Detected empty: %s\n", srcInput); 228 return new CompletionInfoImpl(status, srcInput, ""); 229 case UNKNOWN: 230 proc.debug(DBG_COMPA, "Detected error: %s\n", srcInput); 231 return new CompletionInfoImpl(status, srcInput, ""); 232 } 233 throw new InternalError(); 234 } 235 guessKind(String code)236 private Tree.Kind guessKind(String code) { 237 return proc.taskFactory.parse(code, pt -> { 238 List<? extends Tree> units = pt.units(); 239 if (units.isEmpty()) { 240 return Tree.Kind.BLOCK; 241 } 242 Tree unitTree = units.get(0); 243 proc.debug(DBG_COMPA, "Kind: %s -- %s\n", unitTree.getKind(), unitTree); 244 return unitTree.getKind(); 245 }); 246 } 247 248 //TODO: would be better handled through a lexer: 249 private final Pattern JAVA_IDENTIFIER = Pattern.compile("\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"); 250 251 @Override completionSuggestions(String code, int cursor, int[] anchor)252 public List<Suggestion> completionSuggestions(String code, int cursor, int[] anchor) { 253 suspendIndexing(); 254 try { 255 return completionSuggestionsImpl(code, cursor, anchor); 256 } catch (Throwable exc) { 257 proc.debug(exc, "Exception thrown in SourceCodeAnalysisImpl.completionSuggestions"); 258 return Collections.emptyList(); 259 } finally { 260 resumeIndexing(); 261 } 262 } 263 completionSuggestionsImpl(String code, int cursor, int[] anchor)264 private List<Suggestion> completionSuggestionsImpl(String code, int cursor, int[] anchor) { 265 code = code.substring(0, cursor); 266 Matcher m = JAVA_IDENTIFIER.matcher(code); 267 String identifier = ""; 268 while (m.find()) { 269 if (m.end() == code.length()) { 270 cursor = m.start(); 271 code = code.substring(0, cursor); 272 identifier = m.group(); 273 } 274 } 275 code = code.substring(0, cursor); 276 if (code.trim().isEmpty()) { //TODO: comment handling 277 code += ";"; 278 } 279 OuterWrap codeWrap; 280 switch (guessKind(code)) { 281 case IMPORT: 282 codeWrap = proc.outerMap.wrapImport(Wrap.simpleWrap(code + "any.any"), null); 283 break; 284 case CLASS: 285 case METHOD: 286 codeWrap = proc.outerMap.wrapInTrialClass(Wrap.classMemberWrap(code)); 287 break; 288 default: 289 codeWrap = proc.outerMap.wrapInTrialClass(Wrap.methodWrap(code)); 290 break; 291 } 292 String requiredPrefix = identifier; 293 return computeSuggestions(codeWrap, cursor, anchor).stream() 294 .filter(s -> s.continuation().startsWith(requiredPrefix) && !s.continuation().equals(REPL_DOESNOTMATTER_CLASS_NAME)) 295 .sorted(Comparator.comparing(Suggestion::continuation)) 296 .collect(collectingAndThen(toList(), Collections::unmodifiableList)); 297 } 298 computeSuggestions(OuterWrap code, int cursor, int[] anchor)299 private List<Suggestion> computeSuggestions(OuterWrap code, int cursor, int[] anchor) { 300 return proc.taskFactory.analyze(code, at -> { 301 SourcePositions sp = at.trees().getSourcePositions(); 302 CompilationUnitTree topLevel = at.firstCuTree(); 303 List<Suggestion> result = new ArrayList<>(); 304 TreePath tp = pathFor(topLevel, sp, code, cursor); 305 if (tp != null) { 306 Scope scope = at.trees().getScope(tp); 307 Predicate<Element> accessibility = createAccessibilityFilter(at, tp); 308 Predicate<Element> smartTypeFilter; 309 Predicate<Element> smartFilter; 310 Iterable<TypeMirror> targetTypes = findTargetType(at, tp); 311 if (targetTypes != null) { 312 smartTypeFilter = el -> { 313 TypeMirror resultOf = resultTypeOf(el); 314 return Util.stream(targetTypes) 315 .anyMatch(targetType -> at.getTypes().isAssignable(resultOf, targetType)); 316 }; 317 318 smartFilter = IS_CLASS.negate() 319 .and(IS_INTERFACE.negate()) 320 .and(IS_PACKAGE.negate()) 321 .and(smartTypeFilter); 322 } else { 323 smartFilter = TRUE; 324 smartTypeFilter = TRUE; 325 } 326 switch (tp.getLeaf().getKind()) { 327 case MEMBER_SELECT: { 328 MemberSelectTree mst = (MemberSelectTree)tp.getLeaf(); 329 if (mst.getIdentifier().contentEquals("*")) 330 break; 331 TreePath exprPath = new TreePath(tp, mst.getExpression()); 332 TypeMirror site = at.trees().getTypeMirror(exprPath); 333 boolean staticOnly = isStaticContext(at, exprPath); 334 ImportTree it = findImport(tp); 335 boolean isImport = it != null; 336 337 List<? extends Element> members = membersOf(at, site, staticOnly && !isImport); 338 Predicate<Element> filter = accessibility; 339 Function<Boolean, String> paren = DEFAULT_PAREN; 340 341 if (isNewClass(tp)) { // new xxx.| 342 Predicate<Element> constructorFilter = accessibility.and(IS_CONSTRUCTOR) 343 .and(el -> { 344 if (el.getEnclosingElement().getEnclosingElement().getKind() == ElementKind.CLASS) { 345 return el.getEnclosingElement().getModifiers().contains(Modifier.STATIC); 346 } 347 return true; 348 }); 349 addElements(membersOf(at, members), constructorFilter, smartFilter, result); 350 351 filter = filter.and(IS_PACKAGE); 352 } else if (isThrowsClause(tp)) { 353 staticOnly = true; 354 filter = filter.and(IS_PACKAGE.or(IS_CLASS).or(IS_INTERFACE)); 355 smartFilter = IS_PACKAGE.negate().and(smartTypeFilter); 356 } else if (isImport) { 357 paren = NO_PAREN; 358 if (!it.isStatic()) { 359 filter = filter.and(IS_PACKAGE.or(IS_CLASS).or(IS_INTERFACE)); 360 } 361 } else { 362 filter = filter.and(IS_CONSTRUCTOR.negate()); 363 } 364 365 filter = filter.and(staticOnly ? STATIC_ONLY : INSTANCE_ONLY); 366 367 addElements(members, filter, smartFilter, paren, result); 368 break; 369 } 370 case IDENTIFIER: 371 if (isNewClass(tp)) { 372 Function<Element, Iterable<? extends Element>> listEnclosed = 373 el -> el.getKind() == ElementKind.PACKAGE ? Collections.singletonList(el) 374 : el.getEnclosedElements(); 375 Predicate<Element> filter = accessibility.and(IS_CONSTRUCTOR.or(IS_PACKAGE)); 376 NewClassTree newClassTree = (NewClassTree)tp.getParentPath().getLeaf(); 377 ExpressionTree enclosingExpression = newClassTree.getEnclosingExpression(); 378 if (enclosingExpression != null) { // expr.new IDENT| 379 TypeMirror site = at.trees().getTypeMirror(new TreePath(tp, enclosingExpression)); 380 filter = filter.and(el -> el.getEnclosingElement().getKind() == ElementKind.CLASS && !el.getEnclosingElement().getModifiers().contains(Modifier.STATIC)); 381 addElements(membersOf(at, membersOf(at, site, false)), filter, smartFilter, result); 382 } else { 383 addScopeElements(at, scope, listEnclosed, filter, smartFilter, result); 384 } 385 break; 386 } 387 if (isThrowsClause(tp)) { 388 Predicate<Element> accept = accessibility.and(STATIC_ONLY) 389 .and(IS_PACKAGE.or(IS_CLASS).or(IS_INTERFACE)); 390 addScopeElements(at, scope, IDENTITY, accept, IS_PACKAGE.negate().and(smartTypeFilter), result); 391 break; 392 } 393 ImportTree it = findImport(tp); 394 if (it != null) { 395 // the context of the identifier is an import, look for 396 // package names that start with the identifier. 397 // If and when Java allows imports from the default 398 // package to the the default package which would allow 399 // JShell to change to use the default package, and that 400 // change is done, then this should use some variation 401 // of membersOf(at, at.getElements().getPackageElement("").asType(), false) 402 addElements(listPackages(at, ""), 403 it.isStatic() 404 ? STATIC_ONLY.and(accessibility) 405 : accessibility, 406 smartFilter, result); 407 } 408 break; 409 case CLASS: { 410 Predicate<Element> accept = accessibility.and(IS_TYPE); 411 addScopeElements(at, scope, IDENTITY, accept, smartFilter, result); 412 addElements(primitivesOrVoid(at), TRUE, smartFilter, result); 413 break; 414 } 415 case BLOCK: 416 case EMPTY_STATEMENT: 417 case ERRONEOUS: { 418 boolean staticOnly = ReplResolve.isStatic(((JavacScope)scope).getEnv()); 419 Predicate<Element> accept = accessibility.and(staticOnly ? STATIC_ONLY : TRUE); 420 if (isClass(tp)) { 421 ClassTree clazz = (ClassTree) tp.getParentPath().getLeaf(); 422 if (clazz.getExtendsClause() == tp.getLeaf()) { 423 accept = accept.and(IS_TYPE); 424 smartFilter = smartFilter.and(el -> el.getKind() == ElementKind.CLASS); 425 } else { 426 Predicate<Element> f = smartFilterFromList(at, tp, clazz.getImplementsClause(), tp.getLeaf()); 427 if (f != null) { 428 accept = accept.and(IS_TYPE); 429 smartFilter = f.and(el -> el.getKind() == ElementKind.INTERFACE); 430 } 431 } 432 } else if (isTypeParameter(tp)) { 433 TypeParameterTree tpt = (TypeParameterTree) tp.getParentPath().getLeaf(); 434 Predicate<Element> f = smartFilterFromList(at, tp, tpt.getBounds(), tp.getLeaf()); 435 if (f != null) { 436 accept = accept.and(IS_TYPE); 437 smartFilter = f; 438 if (!tpt.getBounds().isEmpty() && tpt.getBounds().get(0) != tp.getLeaf()) { 439 smartFilter = smartFilter.and(el -> el.getKind() == ElementKind.INTERFACE); 440 } 441 } 442 } else if (isVariable(tp)) { 443 VariableTree var = (VariableTree) tp.getParentPath().getLeaf(); 444 if (var.getType() == tp.getLeaf()) { 445 accept = accept.and(IS_TYPE); 446 } 447 } 448 449 addScopeElements(at, scope, IDENTITY, accept, smartFilter, result); 450 451 Tree parent = tp.getParentPath().getLeaf(); 452 switch (parent.getKind()) { 453 case VARIABLE: 454 accept = ((VariableTree)parent).getType() == tp.getLeaf() ? 455 IS_VOID.negate() : 456 TRUE; 457 break; 458 case PARAMETERIZED_TYPE: // TODO: JEP 218: Generics over Primitive Types 459 case TYPE_PARAMETER: 460 case CLASS: 461 case INTERFACE: 462 case ENUM: 463 accept = FALSE; 464 break; 465 default: 466 accept = TRUE; 467 break; 468 } 469 addElements(primitivesOrVoid(at), accept, smartFilter, result); 470 break; 471 } 472 } 473 } 474 anchor[0] = cursor; 475 return result; 476 }); 477 } 478 479 private static final Set<Kind> CLASS_KINDS = EnumSet.of( 480 Kind.ANNOTATION_TYPE, Kind.CLASS, Kind.ENUM, Kind.INTERFACE 481 ); 482 483 private Predicate<Element> smartFilterFromList(AnalyzeTask at, TreePath base, Collection<? extends Tree> types, Tree current) { 484 Set<Element> existingEls = new HashSet<>(); 485 486 for (Tree type : types) { 487 if (type == current) { 488 return el -> !existingEls.contains(el); 489 } 490 existingEls.add(at.trees().getElement(new TreePath(base, type))); 491 } 492 493 return null; 494 } 495 496 @Override 497 public SnippetWrapper wrapper(Snippet snippet) { 498 return new SnippetWrapper() { 499 @Override 500 public String source() { 501 return snippet.source(); 502 } 503 504 @Override 505 public String wrapped() { 506 return snippet.outerWrap().wrapped(); 507 } 508 509 @Override 510 public String fullClassName() { 511 return snippet.classFullName(); 512 } 513 514 @Override 515 public Snippet.Kind kind() { 516 return snippet.kind() == Snippet.Kind.ERRONEOUS 517 ? ((ErroneousSnippet) snippet).probableKind() 518 : snippet.kind(); 519 } 520 521 @Override 522 public int sourceToWrappedPosition(int pos) { 523 return snippet.outerWrap().snippetIndexToWrapIndex(pos); 524 } 525 526 @Override 527 public int wrappedToSourcePosition(int pos) { 528 return snippet.outerWrap().wrapIndexToSnippetIndex(pos); 529 } 530 }; 531 } 532 533 @Override 534 public List<SnippetWrapper> wrappers(String input) { 535 return proc.eval.sourceToSnippetsWithWrappers(input).stream() 536 .map(this::wrapper) 537 .collect(toList()); 538 } 539 540 @Override 541 public List<Snippet> sourceToSnippets(String input) { 542 proc.checkIfAlive(); 543 List<Snippet> snl = proc.eval.toScratchSnippets(input); 544 for (Snippet sn : snl) { 545 sn.setId(Snippet.UNASSOCIATED_ID); 546 } 547 return snl; 548 } 549 550 @Override 551 public Collection<Snippet> dependents(Snippet snippet) { 552 return proc.maps.getDependents(snippet); 553 } 554 555 private boolean isStaticContext(AnalyzeTask at, TreePath path) { 556 switch (path.getLeaf().getKind()) { 557 case ARRAY_TYPE: 558 case PRIMITIVE_TYPE: 559 return true; 560 default: 561 Element selectEl = at.trees().getElement(path); 562 return selectEl != null && (selectEl.getKind().isClass() || selectEl.getKind().isInterface() || selectEl.getKind() == ElementKind.TYPE_PARAMETER) && selectEl.asType().getKind() != TypeKind.ERROR; 563 } 564 } 565 566 private TreePath pathFor(CompilationUnitTree topLevel, SourcePositions sp, GeneralWrap wrap, int snippetEndPos) { 567 int wrapEndPos = snippetEndPos == 0 568 ? wrap.snippetIndexToWrapIndex(snippetEndPos) 569 : wrap.snippetIndexToWrapIndex(snippetEndPos - 1) + 1; 570 TreePath[] deepest = new TreePath[1]; 571 572 new TreePathScanner<Void, Void>() { 573 @Override 574 public Void scan(Tree tree, Void p) { 575 if (tree == null) 576 return null; 577 578 long start = sp.getStartPosition(topLevel, tree); 579 long end = sp.getEndPosition(topLevel, tree); 580 long prevEnd = deepest[0] != null ? sp.getEndPosition(topLevel, deepest[0].getLeaf()) : -1; 581 582 if (start <= wrapEndPos && wrapEndPos <= end && 583 (start != end || prevEnd != end || deepest[0] == null || 584 deepest[0].getParentPath().getLeaf() != getCurrentPath().getLeaf())) { 585 deepest[0] = new TreePath(getCurrentPath(), tree); 586 return super.scan(tree, p); 587 } 588 589 return null; 590 } 591 @Override 592 public Void visitErroneous(ErroneousTree node, Void p) { 593 return scan(node.getErrorTrees(), null); 594 } 595 }.scan(topLevel, null); 596 597 return deepest[0]; 598 } 599 600 private boolean isNewClass(TreePath tp) { 601 return tp.getParentPath() != null && 602 tp.getParentPath().getLeaf().getKind() == Kind.NEW_CLASS && 603 ((NewClassTree) tp.getParentPath().getLeaf()).getIdentifier() == tp.getLeaf(); 604 } 605 606 private boolean isThrowsClause(TreePath tp) { 607 Tree parent = tp.getParentPath().getLeaf(); 608 return parent.getKind() == Kind.METHOD && 609 ((MethodTree)parent).getThrows().contains(tp.getLeaf()); 610 } 611 612 private boolean isClass(TreePath tp) { 613 return tp.getParentPath() != null && 614 CLASS_KINDS.contains(tp.getParentPath().getLeaf().getKind()); 615 } 616 617 private boolean isTypeParameter(TreePath tp) { 618 return tp.getParentPath() != null && 619 tp.getParentPath().getLeaf().getKind() == Kind.TYPE_PARAMETER; 620 } 621 622 private boolean isVariable(TreePath tp) { 623 return tp.getParentPath() != null && 624 tp.getParentPath().getLeaf().getKind() == Kind.VARIABLE; 625 } 626 627 private ImportTree findImport(TreePath tp) { 628 while (tp != null && tp.getLeaf().getKind() != Kind.IMPORT) { 629 tp = tp.getParentPath(); 630 } 631 return tp != null ? (ImportTree)tp.getLeaf() : null; 632 } 633 634 private Predicate<Element> createAccessibilityFilter(AnalyzeTask at, TreePath tp) { 635 Scope scope = at.trees().getScope(tp); 636 return el -> { 637 switch (el.getKind()) { 638 case ANNOTATION_TYPE: case CLASS: case ENUM: case INTERFACE: 639 return at.trees().isAccessible(scope, (TypeElement) el); 640 case PACKAGE: 641 case EXCEPTION_PARAMETER: case PARAMETER: case LOCAL_VARIABLE: case RESOURCE_VARIABLE: 642 return true; 643 default: 644 TypeMirror type = el.getEnclosingElement().asType(); 645 if (type.getKind() == TypeKind.DECLARED) 646 return at.trees().isAccessible(scope, el, (DeclaredType) type); 647 else 648 return true; 649 } 650 }; 651 } 652 653 private final Predicate<Element> TRUE = el -> true; 654 private final Predicate<Element> FALSE = TRUE.negate(); 655 private final Predicate<Element> IS_STATIC = el -> el.getModifiers().contains(Modifier.STATIC); 656 private final Predicate<Element> IS_CONSTRUCTOR = el -> el.getKind() == ElementKind.CONSTRUCTOR; 657 private final Predicate<Element> IS_METHOD = el -> el.getKind() == ElementKind.METHOD; 658 private final Predicate<Element> IS_PACKAGE = el -> el.getKind() == ElementKind.PACKAGE; 659 private final Predicate<Element> IS_CLASS = el -> el.getKind().isClass(); 660 private final Predicate<Element> IS_INTERFACE = el -> el.getKind().isInterface(); 661 private final Predicate<Element> IS_TYPE = IS_CLASS.or(IS_INTERFACE).or(el -> el.getKind() == ElementKind.TYPE_PARAMETER); 662 private final Predicate<Element> IS_VOID = el -> el.asType().getKind() == TypeKind.VOID; 663 private final Predicate<Element> STATIC_ONLY = el -> { 664 ElementKind kind = el.getKind(); 665 Element encl = el.getEnclosingElement(); 666 ElementKind enclKind = encl != null ? encl.getKind() : ElementKind.OTHER; 667 668 return IS_STATIC.or(IS_PACKAGE).or(IS_CLASS).or(IS_INTERFACE).test(el) || IS_PACKAGE.test(encl) || 669 (kind == ElementKind.TYPE_PARAMETER && !enclKind.isClass() && !enclKind.isInterface()); 670 }; 671 private final Predicate<Element> INSTANCE_ONLY = el -> { 672 Element encl = el.getEnclosingElement(); 673 674 return IS_STATIC.or(IS_CLASS).or(IS_INTERFACE).negate().test(el) || 675 IS_PACKAGE.test(encl); 676 }; 677 private final Function<Element, Iterable<? extends Element>> IDENTITY = Collections::singletonList; 678 private final Function<Boolean, String> DEFAULT_PAREN = hasParams -> hasParams ? "(" : "()"; 679 private final Function<Boolean, String> NO_PAREN = hasParams -> ""; 680 681 private void addElements(Iterable<? extends Element> elements, Predicate<Element> accept, Predicate<Element> smart, List<Suggestion> result) { 682 addElements(elements, accept, smart, DEFAULT_PAREN, result); 683 } 684 private void addElements(Iterable<? extends Element> elements, Predicate<Element> accept, Predicate<Element> smart, Function<Boolean, String> paren, List<Suggestion> result) { 685 Set<String> hasParams = Util.stream(elements) 686 .filter(accept) 687 .filter(IS_CONSTRUCTOR.or(IS_METHOD)) 688 .filter(c -> !((ExecutableElement)c).getParameters().isEmpty()) 689 .map(this::simpleName) 690 .collect(toSet()); 691 692 for (Element c : elements) { 693 if (!accept.test(c)) 694 continue; 695 if (c.getKind() == ElementKind.METHOD && 696 c.getSimpleName().contentEquals(Util.DOIT_METHOD_NAME) && 697 ((ExecutableElement) c).getParameters().isEmpty()) { 698 continue; 699 } 700 String simpleName = simpleName(c); 701 switch (c.getKind()) { 702 case CONSTRUCTOR: 703 case METHOD: 704 // add trailing open or matched parenthesis, as approriate 705 simpleName += paren.apply(hasParams.contains(simpleName)); 706 break; 707 case PACKAGE: 708 // add trailing dot to package names 709 simpleName += "."; 710 break; 711 } 712 result.add(new SuggestionImpl(simpleName, smart.test(c))); 713 } 714 } 715 716 private String simpleName(Element el) { 717 return el.getKind() == ElementKind.CONSTRUCTOR ? el.getEnclosingElement().getSimpleName().toString() 718 : el.getSimpleName().toString(); 719 } 720 721 private List<? extends Element> membersOf(AnalyzeTask at, TypeMirror site, boolean shouldGenerateDotClassItem) { 722 if (site == null) 723 return Collections.emptyList(); 724 725 switch (site.getKind()) { 726 case INTERSECTION: { 727 List<Element> result = new ArrayList<>(); 728 for (TypeMirror bound : ((IntersectionType) site).getBounds()) { 729 result.addAll(membersOf(at, bound, shouldGenerateDotClassItem)); 730 } 731 return result; 732 } 733 case DECLARED: { 734 TypeElement element = (TypeElement) at.getTypes().asElement(site); 735 List<Element> result = new ArrayList<>(); 736 result.addAll(at.getElements().getAllMembers(element)); 737 if (shouldGenerateDotClassItem) { 738 result.add(createDotClassSymbol(at, site)); 739 } 740 result.removeIf(el -> el.getKind() == ElementKind.STATIC_INIT); 741 return result; 742 } 743 case ERROR: { 744 //try current qualified name as a package: 745 TypeElement typeElement = (TypeElement) at.getTypes().asElement(site); 746 Element enclosingElement = typeElement.getEnclosingElement(); 747 String parentPackageName = enclosingElement instanceof QualifiedNameable ? 748 ((QualifiedNameable)enclosingElement).getQualifiedName().toString() : 749 ""; 750 Set<PackageElement> packages = listPackages(at, parentPackageName); 751 return packages.stream() 752 .filter(p -> p.getQualifiedName().equals(typeElement.getQualifiedName())) 753 .findAny() 754 .map(p -> membersOf(at, p.asType(), false)) 755 .orElse(Collections.emptyList()); 756 } 757 case PACKAGE: { 758 String packageName = site.toString()/*XXX*/; 759 List<Element> result = new ArrayList<>(); 760 result.addAll(getEnclosedElements(at.getElements().getPackageElement(packageName))); 761 result.addAll(listPackages(at, packageName)); 762 return result; 763 } 764 case BOOLEAN: case BYTE: case SHORT: case CHAR: 765 case INT: case FLOAT: case LONG: case DOUBLE: 766 case VOID: { 767 return shouldGenerateDotClassItem ? 768 Collections.singletonList(createDotClassSymbol(at, site)) : 769 Collections.emptyList(); 770 } 771 case ARRAY: { 772 List<Element> result = new ArrayList<>(); 773 result.add(createArrayLengthSymbol(at, site)); 774 if (shouldGenerateDotClassItem) 775 result.add(createDotClassSymbol(at, site)); 776 return result; 777 } 778 default: 779 return Collections.emptyList(); 780 } 781 } 782 783 private List<? extends Element> membersOf(AnalyzeTask at, List<? extends Element> elements) { 784 return elements.stream() 785 .flatMap(e -> membersOf(at, e.asType(), true).stream()) 786 .collect(toList()); 787 } 788 789 private List<? extends Element> getEnclosedElements(PackageElement packageEl) { 790 if (packageEl == null) { 791 return Collections.emptyList(); 792 } 793 //workaround for: JDK-8024687 794 while (true) { 795 try { 796 return packageEl.getEnclosedElements() 797 .stream() 798 .filter(el -> el.asType() != null) 799 .filter(el -> el.asType().getKind() != TypeKind.ERROR) 800 .collect(toList()); 801 } catch (CompletionFailure cf) { 802 //ignore... 803 } 804 } 805 } 806 807 private List<? extends Element> primitivesOrVoid(AnalyzeTask at) { 808 Types types = at.getTypes(); 809 return Stream.of( 810 TypeKind.BOOLEAN, TypeKind.BYTE, TypeKind.CHAR, 811 TypeKind.DOUBLE, TypeKind.FLOAT, TypeKind.INT, 812 TypeKind.LONG, TypeKind.SHORT, TypeKind.VOID) 813 .map(tk -> (Type)(tk == TypeKind.VOID ? types.getNoType(tk) : types.getPrimitiveType(tk))) 814 .map(Type::asElement) 815 .collect(toList()); 816 } 817 818 void classpathChanged() { 819 synchronized (currentIndexes) { 820 int cpVersion = ++classpathVersion; 821 822 INDEXER.submit(() -> refreshIndexes(cpVersion)); 823 } 824 } 825 826 private Set<PackageElement> listPackages(AnalyzeTask at, String enclosingPackage) { 827 synchronized (currentIndexes) { 828 return currentIndexes.values() 829 .stream() 830 .flatMap(idx -> idx.packages.stream()) 831 .filter(p -> enclosingPackage.isEmpty() || p.startsWith(enclosingPackage + ".")) 832 .map(p -> { 833 int dot = p.indexOf('.', enclosingPackage.length() + 1); 834 return dot == (-1) ? p : p.substring(0, dot); 835 }) 836 .distinct() 837 .map(p -> createPackageElement(at, p)) 838 .collect(Collectors.toSet()); 839 } 840 } 841 842 private PackageElement createPackageElement(AnalyzeTask at, String packageName) { 843 Names names = Names.instance(at.getContext()); 844 Symtab syms = Symtab.instance(at.getContext()); 845 PackageElement existing = syms.enterPackage(syms.unnamedModule, names.fromString(packageName)); 846 847 return existing; 848 } 849 850 private Element createArrayLengthSymbol(AnalyzeTask at, TypeMirror site) { 851 Name length = Names.instance(at.getContext()).length; 852 Type intType = Symtab.instance(at.getContext()).intType; 853 854 return new VarSymbol(Flags.PUBLIC | Flags.FINAL, length, intType, ((Type) site).tsym); 855 } 856 857 private Element createDotClassSymbol(AnalyzeTask at, TypeMirror site) { 858 Name _class = Names.instance(at.getContext())._class; 859 Type classType = Symtab.instance(at.getContext()).classType; 860 Type erasedSite = (Type)at.getTypes().erasure(site); 861 classType = new ClassType(classType.getEnclosingType(), com.sun.tools.javac.util.List.of(erasedSite), classType.asElement()); 862 863 return new VarSymbol(Flags.PUBLIC | Flags.STATIC | Flags.FINAL, _class, classType, erasedSite.tsym); 864 } 865 866 private Iterable<? extends Element> scopeContent(AnalyzeTask at, Scope scope, Function<Element, Iterable<? extends Element>> elementConvertor) { 867 Iterable<Scope> scopeIterable = () -> new Iterator<Scope>() { 868 private Scope currentScope = scope; 869 @Override 870 public boolean hasNext() { 871 return currentScope != null; 872 } 873 @Override 874 public Scope next() { 875 if (!hasNext()) 876 throw new NoSuchElementException(); 877 try { 878 return currentScope; 879 } finally { 880 currentScope = currentScope.getEnclosingScope(); 881 } 882 } 883 }; 884 @SuppressWarnings("unchecked") 885 List<Element> result = Util.stream(scopeIterable) 886 .flatMap(this::localElements) 887 .flatMap(el -> Util.stream((Iterable<Element>)elementConvertor.apply(el))) 888 .collect(toCollection(ArrayList :: new)); 889 result.addAll(listPackages(at, "")); 890 return result; 891 } 892 893 private Stream<Element> localElements(Scope scope) { 894 //workaround for: JDK-8024687 895 Iterable<Element> elementsIt = () -> new Iterator<Element>() { 896 Iterator<? extends Element> it = scope.getLocalElements().iterator(); 897 @Override 898 public boolean hasNext() { 899 while (true) { 900 try { 901 return it.hasNext(); 902 } catch (CompletionFailure cf) { 903 //ignore... 904 } 905 } 906 } 907 @Override 908 public Element next() { 909 while (true) { 910 try { 911 return it.next(); 912 } catch (CompletionFailure cf) { 913 //ignore... 914 } 915 } 916 } 917 }; 918 Stream<Element> elements = Util.stream(elementsIt); 919 920 if (scope.getEnclosingScope() != null && 921 scope.getEnclosingClass() != scope.getEnclosingScope().getEnclosingClass()) { 922 elements = Stream.concat(elements, scope.getEnclosingClass().getEnclosedElements().stream()); 923 } 924 925 return elements; 926 } 927 928 @SuppressWarnings("fallthrough") 929 private Iterable<TypeMirror> findTargetType(AnalyzeTask at, TreePath forPath) { 930 if (forPath.getParentPath() == null) 931 return null; 932 933 Tree current = forPath.getLeaf(); 934 935 switch (forPath.getParentPath().getLeaf().getKind()) { 936 case ASSIGNMENT: { 937 AssignmentTree tree = (AssignmentTree) forPath.getParentPath().getLeaf(); 938 if (tree.getExpression() == current) 939 return Collections.singletonList(at.trees().getTypeMirror(new TreePath(forPath.getParentPath(), tree.getVariable()))); 940 break; 941 } 942 case VARIABLE: { 943 VariableTree tree = (VariableTree) forPath.getParentPath().getLeaf(); 944 if (tree.getInitializer()== current) 945 return Collections.singletonList(at.trees().getTypeMirror(forPath.getParentPath())); 946 break; 947 } 948 case ERRONEOUS: 949 return findTargetType(at, forPath.getParentPath()); 950 case NEW_CLASS: { 951 NewClassTree nct = (NewClassTree) forPath.getParentPath().getLeaf(); 952 List<TypeMirror> actuals = computeActualInvocationTypes(at, nct.getArguments(), forPath); 953 954 if (actuals != null) { 955 Iterable<Pair<ExecutableElement, ExecutableType>> candidateConstructors = newClassCandidates(at, forPath.getParentPath()); 956 957 return computeSmartTypesForExecutableType(at, candidateConstructors, actuals); 958 } else { 959 return findTargetType(at, forPath.getParentPath()); 960 } 961 } 962 case METHOD: 963 if (!isThrowsClause(forPath)) { 964 break; 965 } 966 // fall through 967 case THROW: 968 return Collections.singletonList(at.getElements().getTypeElement("java.lang.Throwable").asType()); 969 case METHOD_INVOCATION: { 970 MethodInvocationTree mit = (MethodInvocationTree) forPath.getParentPath().getLeaf(); 971 List<TypeMirror> actuals = computeActualInvocationTypes(at, mit.getArguments(), forPath); 972 973 if (actuals == null) 974 return null; 975 976 Iterable<Pair<ExecutableElement, ExecutableType>> candidateMethods = methodCandidates(at, forPath.getParentPath()); 977 978 return computeSmartTypesForExecutableType(at, candidateMethods, actuals); 979 } 980 } 981 982 return null; 983 } 984 985 private List<TypeMirror> computeActualInvocationTypes(AnalyzeTask at, List<? extends ExpressionTree> arguments, TreePath currentArgument) { 986 if (currentArgument == null) 987 return null; 988 989 int paramIndex = arguments.indexOf(currentArgument.getLeaf()); 990 991 if (paramIndex == (-1)) 992 return null; 993 994 List<TypeMirror> actuals = new ArrayList<>(); 995 996 for (ExpressionTree arg : arguments.subList(0, paramIndex)) { 997 actuals.add(at.trees().getTypeMirror(new TreePath(currentArgument.getParentPath(), arg))); 998 } 999 1000 return actuals; 1001 } 1002 1003 private List<Pair<ExecutableElement, ExecutableType>> filterExecutableTypesByArguments(AnalyzeTask at, Iterable<Pair<ExecutableElement, ExecutableType>> candidateMethods, List<TypeMirror> precedingActualTypes) { 1004 List<Pair<ExecutableElement, ExecutableType>> candidate = new ArrayList<>(); 1005 int paramIndex = precedingActualTypes.size(); 1006 1007 OUTER: 1008 for (Pair<ExecutableElement, ExecutableType> method : candidateMethods) { 1009 boolean varargInvocation = paramIndex >= method.snd.getParameterTypes().size(); 1010 1011 for (int i = 0; i < paramIndex; i++) { 1012 TypeMirror actual = precedingActualTypes.get(i); 1013 1014 if (this.parameterType(method.fst, method.snd, i, !varargInvocation) 1015 .noneMatch(formal -> at.getTypes().isAssignable(actual, formal))) { 1016 continue OUTER; 1017 } 1018 } 1019 candidate.add(method); 1020 } 1021 1022 return candidate; 1023 } 1024 1025 private Stream<TypeMirror> parameterType(ExecutableElement method, ExecutableType methodType, int paramIndex, boolean allowVarArgsArray) { 1026 int paramCount = methodType.getParameterTypes().size(); 1027 if (paramIndex >= paramCount && !method.isVarArgs()) 1028 return Stream.empty(); 1029 if (paramIndex < paramCount - 1 || !method.isVarArgs()) 1030 return Stream.of(methodType.getParameterTypes().get(paramIndex)); 1031 TypeMirror varargType = methodType.getParameterTypes().get(paramCount - 1); 1032 TypeMirror elemenType = ((ArrayType) varargType).getComponentType(); 1033 if (paramIndex >= paramCount || !allowVarArgsArray) 1034 return Stream.of(elemenType); 1035 return Stream.of(varargType, elemenType); 1036 } 1037 1038 private List<TypeMirror> computeSmartTypesForExecutableType(AnalyzeTask at, Iterable<Pair<ExecutableElement, ExecutableType>> candidateMethods, List<TypeMirror> precedingActualTypes) { 1039 List<TypeMirror> candidate = new ArrayList<>(); 1040 int paramIndex = precedingActualTypes.size(); 1041 1042 this.filterExecutableTypesByArguments(at, candidateMethods, precedingActualTypes) 1043 .stream() 1044 .flatMap(method -> parameterType(method.fst, method.snd, paramIndex, true)) 1045 .forEach(candidate::add); 1046 1047 return candidate; 1048 } 1049 1050 1051 private TypeMirror resultTypeOf(Element el) { 1052 //TODO: should reflect the type of site! 1053 switch (el.getKind()) { 1054 case METHOD: 1055 return ((ExecutableElement) el).getReturnType(); 1056 case CONSTRUCTOR: 1057 case INSTANCE_INIT: case STATIC_INIT: //TODO: should be filtered out 1058 return el.getEnclosingElement().asType(); 1059 default: 1060 return el.asType(); 1061 } 1062 } 1063 1064 private void addScopeElements(AnalyzeTask at, Scope scope, Function<Element, Iterable<? extends Element>> elementConvertor, Predicate<Element> filter, Predicate<Element> smartFilter, List<Suggestion> result) { 1065 addElements(scopeContent(at, scope, elementConvertor), filter, smartFilter, result); 1066 } 1067 1068 private Iterable<Pair<ExecutableElement, ExecutableType>> methodCandidates(AnalyzeTask at, TreePath invocation) { 1069 MethodInvocationTree mit = (MethodInvocationTree) invocation.getLeaf(); 1070 ExpressionTree select = mit.getMethodSelect(); 1071 List<Pair<ExecutableElement, ExecutableType>> result = new ArrayList<>(); 1072 Predicate<Element> accessibility = createAccessibilityFilter(at, invocation); 1073 1074 switch (select.getKind()) { 1075 case MEMBER_SELECT: 1076 MemberSelectTree mst = (MemberSelectTree) select; 1077 TreePath tp = new TreePath(new TreePath(invocation, select), mst.getExpression()); 1078 TypeMirror site = at.trees().getTypeMirror(tp); 1079 1080 if (site == null || site.getKind() != TypeKind.DECLARED) 1081 break; 1082 1083 Element siteEl = at.getTypes().asElement(site); 1084 1085 if (siteEl == null) 1086 break; 1087 1088 if (isStaticContext(at, tp)) { 1089 accessibility = accessibility.and(STATIC_ONLY); 1090 } 1091 1092 for (ExecutableElement ee : ElementFilter.methodsIn(membersOf(at, siteEl.asType(), false))) { 1093 if (ee.getSimpleName().contentEquals(mst.getIdentifier())) { 1094 if (accessibility.test(ee)) { 1095 result.add(Pair.of(ee, (ExecutableType) at.getTypes().asMemberOf((DeclaredType) site, ee))); 1096 } 1097 } 1098 } 1099 break; 1100 case IDENTIFIER: 1101 IdentifierTree it = (IdentifierTree) select; 1102 for (ExecutableElement ee : ElementFilter.methodsIn(scopeContent(at, at.trees().getScope(invocation), IDENTITY))) { 1103 if (ee.getSimpleName().contentEquals(it.getName())) { 1104 if (accessibility.test(ee)) { 1105 result.add(Pair.of(ee, (ExecutableType) ee.asType())); //XXX: proper site 1106 } 1107 } 1108 } 1109 break; 1110 default: 1111 break; 1112 } 1113 1114 return result; 1115 } 1116 1117 private Iterable<Pair<ExecutableElement, ExecutableType>> newClassCandidates(AnalyzeTask at, TreePath newClassPath) { 1118 NewClassTree nct = (NewClassTree) newClassPath.getLeaf(); 1119 Element type = at.trees().getElement(new TreePath(newClassPath.getParentPath(), nct.getIdentifier())); 1120 TypeMirror targetType = at.trees().getTypeMirror(newClassPath); 1121 if (targetType == null || targetType.getKind() != TypeKind.DECLARED) { 1122 Iterable<TypeMirror> targetTypes = findTargetType(at, newClassPath); 1123 if (targetTypes == null) 1124 targetTypes = Collections.emptyList(); 1125 targetType = 1126 StreamSupport.stream(targetTypes.spliterator(), false) 1127 .filter(t -> at.getTypes().asElement(t) == type) 1128 .findAny() 1129 .orElse(at.getTypes().erasure(type.asType())); 1130 } 1131 List<Pair<ExecutableElement, ExecutableType>> candidateConstructors = new ArrayList<>(); 1132 Predicate<Element> accessibility = createAccessibilityFilter(at, newClassPath); 1133 1134 if (targetType != null && 1135 targetType.getKind() == TypeKind.DECLARED && 1136 type != null && 1137 (type.getKind().isClass() || type.getKind().isInterface())) { 1138 for (ExecutableElement constr : ElementFilter.constructorsIn(type.getEnclosedElements())) { 1139 if (accessibility.test(constr)) { 1140 ExecutableType constrType = 1141 (ExecutableType) at.getTypes().asMemberOf((DeclaredType) targetType, constr); 1142 candidateConstructors.add(Pair.of(constr, constrType)); 1143 } 1144 } 1145 } 1146 1147 return candidateConstructors; 1148 } 1149 1150 @Override 1151 public List<Documentation> documentation(String code, int cursor, boolean computeJavadoc) { 1152 suspendIndexing(); 1153 try { 1154 return documentationImpl(code, cursor, computeJavadoc); 1155 } catch (Throwable exc) { 1156 proc.debug(exc, "Exception thrown in SourceCodeAnalysisImpl.documentation"); 1157 return Collections.emptyList(); 1158 } finally { 1159 resumeIndexing(); 1160 } 1161 } 1162 1163 //tweaked by tests to disable reading parameter names from classfiles so that tests using 1164 //JDK's classes are stable for both release and fastdebug builds: 1165 private final String[] keepParameterNames = new String[] { 1166 "-parameters" 1167 }; 1168 1169 private List<Documentation> documentationImpl(String code, int cursor, boolean computeJavadoc) { 1170 code = code.substring(0, cursor); 1171 if (code.trim().isEmpty()) { //TODO: comment handling 1172 code += ";"; 1173 } 1174 1175 if (guessKind(code) == Kind.IMPORT) 1176 return Collections.emptyList(); 1177 1178 OuterWrap codeWrap = proc.outerMap.wrapInTrialClass(Wrap.methodWrap(code)); 1179 return proc.taskFactory.analyze(codeWrap, List.of(keepParameterNames), at -> { 1180 SourcePositions sp = at.trees().getSourcePositions(); 1181 CompilationUnitTree topLevel = at.firstCuTree(); 1182 TreePath tp = pathFor(topLevel, sp, codeWrap, cursor); 1183 1184 if (tp == null) 1185 return Collections.emptyList(); 1186 1187 TreePath prevPath = null; 1188 while (tp != null && tp.getLeaf().getKind() != Kind.METHOD_INVOCATION && 1189 tp.getLeaf().getKind() != Kind.NEW_CLASS && tp.getLeaf().getKind() != Kind.IDENTIFIER && 1190 tp.getLeaf().getKind() != Kind.MEMBER_SELECT) { 1191 prevPath = tp; 1192 tp = tp.getParentPath(); 1193 } 1194 1195 if (tp == null) 1196 return Collections.emptyList(); 1197 1198 Stream<Element> elements; 1199 Iterable<Pair<ExecutableElement, ExecutableType>> candidates; 1200 List<? extends ExpressionTree> arguments; 1201 1202 if (tp.getLeaf().getKind() == Kind.METHOD_INVOCATION || tp.getLeaf().getKind() == Kind.NEW_CLASS) { 1203 if (tp.getLeaf().getKind() == Kind.METHOD_INVOCATION) { 1204 MethodInvocationTree mit = (MethodInvocationTree) tp.getLeaf(); 1205 candidates = methodCandidates(at, tp); 1206 arguments = mit.getArguments(); 1207 } else { 1208 NewClassTree nct = (NewClassTree) tp.getLeaf(); 1209 candidates = newClassCandidates(at, tp); 1210 arguments = nct.getArguments(); 1211 } 1212 1213 if (!isEmptyArgumentsContext(arguments)) { 1214 List<TypeMirror> actuals = computeActualInvocationTypes(at, arguments, prevPath); 1215 List<TypeMirror> fullActuals = actuals != null ? actuals : Collections.emptyList(); 1216 1217 candidates = 1218 this.filterExecutableTypesByArguments(at, candidates, fullActuals) 1219 .stream() 1220 .filter(method -> parameterType(method.fst, method.snd, fullActuals.size(), true).findAny().isPresent()) 1221 .collect(Collectors.toList()); 1222 } 1223 1224 elements = Util.stream(candidates).map(method -> method.fst); 1225 } else if (tp.getLeaf().getKind() == Kind.IDENTIFIER || tp.getLeaf().getKind() == Kind.MEMBER_SELECT) { 1226 Element el = at.trees().getElement(tp); 1227 1228 if (el == null || 1229 el.asType().getKind() == TypeKind.ERROR || 1230 (el.getKind() == ElementKind.PACKAGE && el.getEnclosedElements().isEmpty())) { 1231 //erroneous element: 1232 return Collections.emptyList(); 1233 } 1234 1235 Predicate<Element> accessibility = createAccessibilityFilter(at, tp); 1236 1237 if (!accessibility.test(el)) { 1238 //not accessible 1239 return Collections.emptyList(); 1240 } 1241 1242 elements = Stream.of(el); 1243 } else { 1244 return Collections.emptyList(); 1245 } 1246 1247 List<Documentation> result = Collections.emptyList(); 1248 1249 try (JavadocHelper helper = JavadocHelper.create(at.task, findSources())) { 1250 result = elements.map(el -> constructDocumentation(at, helper, el, computeJavadoc)) 1251 .filter(Objects::nonNull) 1252 .collect(Collectors.toList()); 1253 } catch (IOException ex) { 1254 proc.debug(ex, "JavadocHelper.close()"); 1255 } 1256 1257 return result; 1258 }); 1259 } 1260 1261 private Documentation constructDocumentation(AnalyzeTask at, JavadocHelper helper, Element el, boolean computeJavadoc) { 1262 String javadoc = null; 1263 try { 1264 if (hasSyntheticParameterNames(el)) { 1265 el = helper.getSourceElement(el); 1266 } 1267 if (computeJavadoc) { 1268 javadoc = helper.getResolvedDocComment(el); 1269 } 1270 } catch (IOException ex) { 1271 proc.debug(ex, "SourceCodeAnalysisImpl.element2String(..., " + el + ")"); 1272 } 1273 String signature = Util.expunge(elementHeader(at, el, !hasSyntheticParameterNames(el), true)); 1274 return new DocumentationImpl(signature, javadoc); 1275 } 1276 1277 public void close() { 1278 for (AutoCloseable closeable : closeables) { 1279 try { 1280 closeable.close(); 1281 } catch (Exception ex) { 1282 proc.debug(ex, "SourceCodeAnalysisImpl.close()"); 1283 } 1284 } 1285 } 1286 1287 private static final class DocumentationImpl implements Documentation { 1288 1289 private final String signature; 1290 private final String javadoc; 1291 1292 public DocumentationImpl(String signature, String javadoc) { 1293 this.signature = signature; 1294 this.javadoc = javadoc; 1295 } 1296 1297 @Override 1298 public String signature() { 1299 return signature; 1300 } 1301 1302 @Override 1303 public String javadoc() { 1304 return javadoc; 1305 } 1306 1307 } 1308 1309 private boolean isEmptyArgumentsContext(List<? extends ExpressionTree> arguments) { 1310 if (arguments.size() == 1) { 1311 Tree firstArgument = arguments.get(0); 1312 return firstArgument.getKind() == Kind.ERRONEOUS; 1313 } 1314 return false; 1315 } 1316 1317 private boolean hasSyntheticParameterNames(Element el) { 1318 if (el.getKind() != ElementKind.CONSTRUCTOR && el.getKind() != ElementKind.METHOD) 1319 return false; 1320 1321 ExecutableElement ee = (ExecutableElement) el; 1322 1323 if (ee.getParameters().isEmpty()) 1324 return false; 1325 1326 return ee.getParameters() 1327 .stream() 1328 .allMatch(param -> param.getSimpleName().toString().startsWith("arg")); 1329 } 1330 1331 private static List<Path> availableSourcesOverride; //for tests 1332 private List<Path> availableSources; 1333 1334 private List<Path> findSources() { 1335 if (availableSources != null) { 1336 return availableSources; 1337 } 1338 if (availableSourcesOverride != null) { 1339 return availableSources = availableSourcesOverride; 1340 } 1341 List<Path> result = new ArrayList<>(); 1342 Path home = Paths.get(System.getProperty("java.home")); 1343 Path srcZip = home.resolve("lib").resolve("src.zip"); 1344 if (!Files.isReadable(srcZip)) 1345 srcZip = home.getParent().resolve("src.zip"); 1346 if (Files.isReadable(srcZip)) { 1347 boolean keepOpen = false; 1348 FileSystem zipFO = null; 1349 1350 try { 1351 URI uri = URI.create("jar:" + srcZip.toUri()); 1352 zipFO = FileSystems.newFileSystem(uri, Collections.emptyMap()); 1353 Path root = zipFO.getRootDirectories().iterator().next(); 1354 1355 if (Files.exists(root.resolve("java/lang/Object.java".replace("/", zipFO.getSeparator())))) { 1356 //non-modular format: 1357 result.add(srcZip); 1358 } else if (Files.exists(root.resolve("java.base/java/lang/Object.java".replace("/", zipFO.getSeparator())))) { 1359 //modular format: 1360 try (DirectoryStream<Path> ds = Files.newDirectoryStream(root)) { 1361 for (Path p : ds) { 1362 if (Files.isDirectory(p)) { 1363 result.add(p); 1364 } 1365 } 1366 } 1367 1368 keepOpen = true; 1369 } 1370 } catch (IOException ex) { 1371 proc.debug(ex, "SourceCodeAnalysisImpl.findSources()"); 1372 } finally { 1373 if (zipFO != null) { 1374 if (keepOpen) { 1375 closeables.add(zipFO); 1376 } else { 1377 try { 1378 zipFO.close(); 1379 } catch (IOException ex) { 1380 proc.debug(ex, "SourceCodeAnalysisImpl.findSources()"); 1381 } 1382 } 1383 } 1384 } 1385 } 1386 return availableSources = result; 1387 } 1388 1389 private String elementHeader(AnalyzeTask at, Element el, boolean includeParameterNames, boolean useFQN) { 1390 switch (el.getKind()) { 1391 case ANNOTATION_TYPE: case CLASS: case ENUM: case INTERFACE: { 1392 TypeElement type = (TypeElement)el; 1393 String fullname = type.getQualifiedName().toString(); 1394 Element pkg = at.getElements().getPackageOf(el); 1395 String name = pkg == null || useFQN ? fullname : 1396 proc.maps.fullClassNameAndPackageToClass(fullname, ((PackageElement)pkg).getQualifiedName().toString()); 1397 1398 return name + typeParametersOpt(at, type.getTypeParameters(), includeParameterNames); 1399 } 1400 case TYPE_PARAMETER: { 1401 TypeParameterElement tp = (TypeParameterElement)el; 1402 String name = tp.getSimpleName().toString(); 1403 1404 List<? extends TypeMirror> bounds = tp.getBounds(); 1405 boolean boundIsObject = bounds.isEmpty() || 1406 bounds.size() == 1 && at.getTypes().isSameType(bounds.get(0), Symtab.instance(at.getContext()).objectType); 1407 1408 return boundIsObject 1409 ? name 1410 : name + " extends " + bounds.stream() 1411 .map(bound -> printType(at, proc, bound)) 1412 .collect(joining(" & ")); 1413 } 1414 case FIELD: 1415 return appendDot(elementHeader(at, el.getEnclosingElement(), includeParameterNames, false)) + el.getSimpleName() + ":" + el.asType(); 1416 case ENUM_CONSTANT: 1417 return appendDot(elementHeader(at, el.getEnclosingElement(), includeParameterNames, false)) + el.getSimpleName(); 1418 case EXCEPTION_PARAMETER: case LOCAL_VARIABLE: case PARAMETER: case RESOURCE_VARIABLE: 1419 return el.getSimpleName() + ":" + el.asType(); 1420 case CONSTRUCTOR: case METHOD: { 1421 StringBuilder header = new StringBuilder(); 1422 1423 boolean isMethod = el.getKind() == ElementKind.METHOD; 1424 ExecutableElement method = (ExecutableElement) el; 1425 1426 if (isMethod) { 1427 // return type 1428 header.append(printType(at, proc, method.getReturnType())).append(" "); 1429 } else { 1430 // type parameters for the constructor 1431 String typeParameters = typeParametersOpt(at, method.getTypeParameters(), includeParameterNames); 1432 if (!typeParameters.isEmpty()) { 1433 header.append(typeParameters).append(" "); 1434 } 1435 } 1436 1437 // receiver type 1438 String clazz = elementHeader(at, el.getEnclosingElement(), includeParameterNames, false); 1439 header.append(clazz); 1440 1441 if (isMethod) { 1442 //method name with type parameters 1443 (clazz.isEmpty() ? header : header.append(".")) 1444 .append(typeParametersOpt(at, method.getTypeParameters(), includeParameterNames)) 1445 .append(el.getSimpleName()); 1446 } 1447 1448 // arguments 1449 header.append("("); 1450 String sep = ""; 1451 for (Iterator<? extends VariableElement> i = method.getParameters().iterator(); i.hasNext();) { 1452 VariableElement p = i.next(); 1453 header.append(sep); 1454 if (!i.hasNext() && method.isVarArgs()) { 1455 header.append(printType(at, proc, unwrapArrayType(p.asType()))).append("..."); 1456 } else { 1457 header.append(printType(at, proc, p.asType())); 1458 } 1459 if (includeParameterNames) { 1460 header.append(" "); 1461 header.append(p.getSimpleName()); 1462 } 1463 sep = ", "; 1464 } 1465 header.append(")"); 1466 1467 // throws 1468 List<? extends TypeMirror> thrownTypes = method.getThrownTypes(); 1469 if (!thrownTypes.isEmpty()) { 1470 header.append(" throws ") 1471 .append(thrownTypes.stream() 1472 .map(type -> printType(at, proc, type)) 1473 .collect(joining(", "))); 1474 } 1475 return header.toString(); 1476 } 1477 default: 1478 return el.toString(); 1479 } 1480 } 1481 private String appendDot(String fqn) { 1482 return fqn.isEmpty() ? fqn : fqn + "."; 1483 } 1484 private TypeMirror unwrapArrayType(TypeMirror arrayType) { 1485 if (arrayType.getKind() == TypeKind.ARRAY) { 1486 return ((ArrayType)arrayType).getComponentType(); 1487 } 1488 return arrayType; 1489 } 1490 private String typeParametersOpt(AnalyzeTask at, List<? extends TypeParameterElement> typeParameters, boolean includeParameterNames) { 1491 return typeParameters.isEmpty() ? "" 1492 : typeParameters.stream() 1493 .map(tp -> elementHeader(at, tp, includeParameterNames, false)) 1494 .collect(joining(", ", "<", ">")); 1495 } 1496 1497 @Override 1498 public String analyzeType(String code, int cursor) { 1499 switch (guessKind(code)) { 1500 case IMPORT: case METHOD: case CLASS: case ENUM: 1501 case INTERFACE: case ANNOTATION_TYPE: case VARIABLE: 1502 return null; 1503 default: 1504 break; 1505 } 1506 ExpressionInfo ei = ExpressionToTypeInfo.expressionInfo(code, proc); 1507 return (ei == null || !ei.isNonVoid) 1508 ? null 1509 : ei.typeName; 1510 } 1511 1512 @Override 1513 public QualifiedNames listQualifiedNames(String code, int cursor) { 1514 String codeFin = code.substring(0, cursor); 1515 if (codeFin.trim().isEmpty()) { 1516 return new QualifiedNames(Collections.emptyList(), -1, true, false); 1517 } 1518 OuterWrap codeWrap; 1519 switch (guessKind(codeFin)) { 1520 case IMPORT: 1521 return new QualifiedNames(Collections.emptyList(), -1, true, false); 1522 case METHOD: 1523 codeWrap = proc.outerMap.wrapInTrialClass(Wrap.classMemberWrap(codeFin)); 1524 break; 1525 default: 1526 codeWrap = proc.outerMap.wrapInTrialClass(Wrap.methodWrap(codeFin)); 1527 break; 1528 } 1529 return proc.taskFactory.analyze(codeWrap, at -> { 1530 SourcePositions sp = at.trees().getSourcePositions(); 1531 CompilationUnitTree topLevel = at.firstCuTree(); 1532 TreePath tp = pathFor(topLevel, sp, codeWrap, codeFin.length()); 1533 if (tp.getLeaf().getKind() != Kind.IDENTIFIER) { 1534 return new QualifiedNames(Collections.emptyList(), -1, true, false); 1535 } 1536 Scope scope = at.trees().getScope(tp); 1537 TypeMirror type = at.trees().getTypeMirror(tp); 1538 Element el = at.trees().getElement(tp); 1539 1540 boolean erroneous = (type.getKind() == TypeKind.ERROR && el.getKind() == ElementKind.CLASS) || 1541 (el.getKind() == ElementKind.PACKAGE && el.getEnclosedElements().isEmpty()); 1542 String simpleName = ((IdentifierTree) tp.getLeaf()).getName().toString(); 1543 boolean upToDate; 1544 List<String> result; 1545 1546 synchronized (currentIndexes) { 1547 upToDate = classpathVersion == indexVersion; 1548 result = currentIndexes.values() 1549 .stream() 1550 .flatMap(idx -> idx.classSimpleName2FQN.getOrDefault(simpleName, 1551 Collections.emptyList()).stream()) 1552 .distinct() 1553 .filter(fqn -> isAccessible(at, scope, fqn)) 1554 .sorted() 1555 .collect(Collectors.toList()); 1556 } 1557 1558 return new QualifiedNames(result, simpleName.length(), upToDate, !erroneous); 1559 }); 1560 } 1561 1562 private boolean isAccessible(AnalyzeTask at, Scope scope, String fqn) { 1563 TypeElement type = at.getElements().getTypeElement(fqn); 1564 if (type == null) 1565 return false; 1566 return at.trees().isAccessible(scope, type); 1567 } 1568 1569 //-------------------- 1570 // classpath indexing: 1571 //-------------------- 1572 1573 //the indexing can be suspended when a more important task is running: 1574 private void waitIndexingNotSuspended() { 1575 boolean suspendedNotified = false; 1576 synchronized (suspendLock) { 1577 while (suspend > 0) { 1578 if (!suspendedNotified) { 1579 suspendedNotified = true; 1580 } 1581 try { 1582 suspendLock.wait(); 1583 } catch (InterruptedException ex) { 1584 } 1585 } 1586 } 1587 } 1588 1589 public void suspendIndexing() { 1590 synchronized (suspendLock) { 1591 suspend++; 1592 } 1593 } 1594 1595 public void resumeIndexing() { 1596 synchronized (suspendLock) { 1597 if (--suspend == 0) { 1598 suspendLock.notifyAll(); 1599 } 1600 } 1601 } 1602 1603 //update indexes, either initially or after a classpath change: 1604 private void refreshIndexes(int version) { 1605 try { 1606 Collection<Path> paths = new ArrayList<>(); 1607 MemoryFileManager fm = proc.taskFactory.fileManager(); 1608 1609 appendPaths(fm, StandardLocation.PLATFORM_CLASS_PATH, paths); 1610 appendPaths(fm, StandardLocation.CLASS_PATH, paths); 1611 appendPaths(fm, StandardLocation.SOURCE_PATH, paths); 1612 1613 Map<Path, ClassIndex> newIndexes = new HashMap<>(); 1614 1615 //setup existing/last known data: 1616 for (Path p : paths) { 1617 ClassIndex index = PATH_TO_INDEX.get(p); 1618 if (index != null) { 1619 newIndexes.put(p, index); 1620 } 1621 } 1622 1623 synchronized (currentIndexes) { 1624 //temporary setting old data: 1625 currentIndexes.clear(); 1626 currentIndexes.putAll(newIndexes); 1627 } 1628 1629 //update/compute the indexes if needed: 1630 for (Path p : paths) { 1631 waitIndexingNotSuspended(); 1632 1633 ClassIndex index = indexForPath(p); 1634 newIndexes.put(p, index); 1635 } 1636 1637 synchronized (currentIndexes) { 1638 currentIndexes.clear(); 1639 currentIndexes.putAll(newIndexes); 1640 } 1641 } catch (Exception ex) { 1642 proc.debug(ex, "SourceCodeAnalysisImpl.refreshIndexes(" + version + ")"); 1643 } finally { 1644 synchronized (currentIndexes) { 1645 indexVersion = version; 1646 } 1647 } 1648 } 1649 1650 private void appendPaths(MemoryFileManager fm, Location loc, Collection<Path> paths) { 1651 Iterable<? extends Path> locationPaths = fm.getLocationAsPaths(loc); 1652 if (locationPaths == null) 1653 return ; 1654 for (Path path : locationPaths) { 1655 if (".".equals(path.toString())) { 1656 //skip CWD 1657 continue; 1658 } 1659 1660 paths.add(path); 1661 } 1662 } 1663 1664 //create/update index a given JavaFileManager entry (which may be a JDK installation, a jar/zip file or a directory): 1665 //if an index exists for the given entry, the existing index is kept unless the timestamp is modified 1666 private ClassIndex indexForPath(Path path) { 1667 if (isJRTMarkerFile(path)) { 1668 FileSystem jrtfs = FileSystems.getFileSystem(URI.create("jrt:/")); 1669 Path modules = jrtfs.getPath("modules"); 1670 return PATH_TO_INDEX.compute(path, (p, index) -> { 1671 try { 1672 long lastModified = Files.getLastModifiedTime(modules).toMillis(); 1673 if (index == null || index.timestamp != lastModified) { 1674 try (DirectoryStream<Path> stream = Files.newDirectoryStream(modules)) { 1675 index = doIndex(lastModified, path, stream); 1676 } 1677 } 1678 return index; 1679 } catch (IOException ex) { 1680 proc.debug(ex, "SourceCodeAnalysisImpl.indexesForPath(" + path.toString() + ")"); 1681 return new ClassIndex(-1, path, Collections.emptySet(), Collections.emptyMap()); 1682 } 1683 }); 1684 } else if (!Files.isDirectory(path)) { 1685 if (Files.exists(path)) { 1686 return PATH_TO_INDEX.compute(path, (p, index) -> { 1687 try { 1688 long lastModified = Files.getLastModifiedTime(p).toMillis(); 1689 if (index == null || index.timestamp != lastModified) { 1690 ClassLoader cl = SourceCodeAnalysisImpl.class.getClassLoader(); 1691 1692 try (FileSystem zip = FileSystems.newFileSystem(path, cl)) { 1693 index = doIndex(lastModified, path, zip.getRootDirectories()); 1694 } 1695 } 1696 return index; 1697 } catch (IOException ex) { 1698 proc.debug(ex, "SourceCodeAnalysisImpl.indexesForPath(" + path.toString() + ")"); 1699 return new ClassIndex(-1, path, Collections.emptySet(), Collections.emptyMap()); 1700 } 1701 }); 1702 } else { 1703 return new ClassIndex(-1, path, Collections.emptySet(), Collections.emptyMap()); 1704 } 1705 } else { 1706 return PATH_TO_INDEX.compute(path, (p, index) -> { 1707 //no persistence for directories, as we cannot check timestamps: 1708 if (index == null) { 1709 index = doIndex(-1, path, Arrays.asList(p)); 1710 } 1711 return index; 1712 }); 1713 } 1714 } 1715 1716 static boolean isJRTMarkerFile(Path path) { 1717 return path.equals(Paths.get(System.getProperty("java.home"), "lib", "modules")); 1718 } 1719 1720 //create an index based on the content of the given dirs; the original JavaFileManager entry is originalPath. 1721 private ClassIndex doIndex(long timestamp, Path originalPath, Iterable<? extends Path> dirs) { 1722 Set<String> packages = new HashSet<>(); 1723 Map<String, Collection<String>> classSimpleName2FQN = new HashMap<>(); 1724 1725 for (Path d : dirs) { 1726 try { 1727 Files.walkFileTree(d, new FileVisitor<Path>() { 1728 int depth; 1729 @Override 1730 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { 1731 waitIndexingNotSuspended(); 1732 if (depth++ == 0) 1733 return FileVisitResult.CONTINUE; 1734 String dirName = dir.getFileName().toString(); 1735 String sep = dir.getFileSystem().getSeparator(); 1736 dirName = dirName.endsWith(sep) ? dirName.substring(0, dirName.length() - sep.length()) 1737 : dirName; 1738 if (SourceVersion.isIdentifier(dirName)) 1739 return FileVisitResult.CONTINUE; 1740 return FileVisitResult.SKIP_SUBTREE; 1741 } 1742 @Override 1743 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 1744 waitIndexingNotSuspended(); 1745 if (file.getFileName().toString().endsWith(".class")) { 1746 String relativePath = d.relativize(file).toString(); 1747 String binaryName = relativePath.substring(0, relativePath.length() - 6).replace('/', '.'); 1748 int packageDot = binaryName.lastIndexOf('.'); 1749 if (packageDot > (-1)) { 1750 packages.add(binaryName.substring(0, packageDot)); 1751 } 1752 String typeName = binaryName.replace('$', '.'); 1753 addClassName2Map(classSimpleName2FQN, typeName); 1754 } 1755 return FileVisitResult.CONTINUE; 1756 } 1757 @Override 1758 public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { 1759 return FileVisitResult.CONTINUE; 1760 } 1761 @Override 1762 public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { 1763 depth--; 1764 return FileVisitResult.CONTINUE; 1765 } 1766 }); 1767 } catch (IOException ex) { 1768 proc.debug(ex, "doIndex(" + d.toString() + ")"); 1769 } 1770 } 1771 1772 return new ClassIndex(timestamp, originalPath, packages, classSimpleName2FQN); 1773 } 1774 1775 private static void addClassName2Map(Map<String, Collection<String>> classSimpleName2FQN, String typeName) { 1776 int simpleNameDot = typeName.lastIndexOf('.'); 1777 classSimpleName2FQN.computeIfAbsent(typeName.substring(simpleNameDot + 1), n -> new LinkedHashSet<>()) 1778 .add(typeName); 1779 } 1780 1781 //holder for indexed data about a given path 1782 public static final class ClassIndex { 1783 public final long timestamp; 1784 public final Path forPath; 1785 public final Set<String> packages; 1786 public final Map<String, Collection<String>> classSimpleName2FQN; 1787 1788 public ClassIndex(long timestamp, Path forPath, Set<String> packages, Map<String, Collection<String>> classSimpleName2FQN) { 1789 this.timestamp = timestamp; 1790 this.forPath = forPath; 1791 this.packages = packages; 1792 this.classSimpleName2FQN = classSimpleName2FQN; 1793 } 1794 1795 } 1796 1797 //for tests, to be able to wait until the indexing finishes: 1798 public void waitBackgroundTaskFinished() throws Exception { 1799 boolean upToDate; 1800 synchronized (currentIndexes) { 1801 upToDate = classpathVersion == indexVersion; 1802 } 1803 while (!upToDate) { 1804 INDEXER.submit(() -> {}).get(); 1805 synchronized (currentIndexes) { 1806 upToDate = classpathVersion == indexVersion; 1807 } 1808 } 1809 } 1810 1811 /** 1812 * A candidate for continuation of the given user's input. 1813 */ 1814 private static class SuggestionImpl implements Suggestion { 1815 1816 private final String continuation; 1817 private final boolean matchesType; 1818 1819 /** 1820 * Create a {@code Suggestion} instance. 1821 * 1822 * @param continuation a candidate continuation of the user's input 1823 * @param matchesType does the candidate match the target type 1824 */ 1825 public SuggestionImpl(String continuation, boolean matchesType) { 1826 this.continuation = continuation; 1827 this.matchesType = matchesType; 1828 } 1829 1830 /** 1831 * The candidate continuation of the given user's input. 1832 * 1833 * @return the continuation string 1834 */ 1835 @Override 1836 public String continuation() { 1837 return continuation; 1838 } 1839 1840 /** 1841 * Indicates whether input continuation matches the target type and is thus 1842 * more likely to be the desired continuation. A matching continuation is 1843 * preferred. 1844 * 1845 * @return {@code true} if this suggested continuation matches the 1846 * target type; otherwise {@code false} 1847 */ 1848 @Override 1849 public boolean matchesType() { 1850 return matchesType; 1851 } 1852 } 1853 1854 /** 1855 * The result of {@code analyzeCompletion(String input)}. 1856 * Describes the completeness and position of the first snippet in the given input. 1857 */ 1858 private static class CompletionInfoImpl implements CompletionInfo { 1859 1860 private final Completeness completeness; 1861 private final String source; 1862 private final String remaining; 1863 1864 CompletionInfoImpl(Completeness completeness, String source, String remaining) { 1865 this.completeness = completeness; 1866 this.source = source; 1867 this.remaining = remaining; 1868 } 1869 1870 /** 1871 * The analyzed completeness of the input. 1872 * 1873 * @return an enum describing the completeness of the input string. 1874 */ 1875 @Override 1876 public Completeness completeness() { 1877 return completeness; 1878 } 1879 1880 /** 1881 * Input remaining after the complete part of the source. 1882 * 1883 * @return the portion of the input string that remains after the 1884 * complete Snippet 1885 */ 1886 @Override 1887 public String remaining() { 1888 return remaining; 1889 } 1890 1891 /** 1892 * Source code for the first Snippet of code input. For example, first 1893 * statement, or first method declaration. Trailing semicolons will be 1894 * added, as needed. 1895 * 1896 * @return the source of the first encountered Snippet 1897 */ 1898 @Override 1899 public String source() { 1900 return source; 1901 } 1902 } 1903 1904 } 1905