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