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