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