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