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