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