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