1 /* 2 * Copyright (c) 2014, 2015, 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 26 package jdk.jshell; 27 28 import jdk.jshell.SourceCodeAnalysis.Completeness; 29 import com.sun.source.tree.AssignmentTree; 30 import com.sun.source.tree.CompilationUnitTree; 31 import com.sun.source.tree.ErroneousTree; 32 import com.sun.source.tree.ExpressionTree; 33 import com.sun.source.tree.IdentifierTree; 34 import com.sun.source.tree.ImportTree; 35 import com.sun.source.tree.MemberSelectTree; 36 import com.sun.source.tree.MethodInvocationTree; 37 import com.sun.source.tree.MethodTree; 38 import com.sun.source.tree.NewClassTree; 39 import com.sun.source.tree.Scope; 40 import com.sun.source.tree.Tree; 41 import com.sun.source.tree.Tree.Kind; 42 import com.sun.source.tree.VariableTree; 43 import com.sun.source.util.SourcePositions; 44 import com.sun.source.util.TreePath; 45 import com.sun.source.util.TreePathScanner; 46 import com.sun.tools.javac.api.JavacScope; 47 import com.sun.tools.javac.code.Flags; 48 import com.sun.tools.javac.code.Symbol.CompletionFailure; 49 import com.sun.tools.javac.code.Symbol.VarSymbol; 50 import com.sun.tools.javac.code.Symtab; 51 import com.sun.tools.javac.code.Type; 52 import com.sun.tools.javac.code.Type.ClassType; 53 import com.sun.tools.javac.util.DefinedBy; 54 import com.sun.tools.javac.util.DefinedBy.Api; 55 import com.sun.tools.javac.util.Name; 56 import com.sun.tools.javac.util.Names; 57 import com.sun.tools.javac.util.Pair; 58 import jdk.jshell.CompletenessAnalyzer.CaInfo; 59 import jdk.jshell.TaskFactory.AnalyzeTask; 60 import jdk.jshell.TaskFactory.ParseTask; 61 62 import java.util.ArrayList; 63 import java.util.Collections; 64 import java.util.Iterator; 65 import java.util.List; 66 import java.util.function.Predicate; 67 68 import javax.lang.model.element.Element; 69 import javax.lang.model.element.ElementKind; 70 import javax.lang.model.element.Modifier; 71 import javax.lang.model.element.TypeElement; 72 import javax.lang.model.type.DeclaredType; 73 import javax.lang.model.type.TypeMirror; 74 75 import static jdk.internal.jshell.debug.InternalDebugControl.DBG_COMPA; 76 77 import java.io.IOException; 78 import java.net.URI; 79 import java.nio.file.DirectoryStream; 80 import java.nio.file.FileSystem; 81 import java.nio.file.FileSystems; 82 import java.nio.file.Files; 83 import java.nio.file.Path; 84 import java.nio.file.Paths; 85 import java.util.Comparator; 86 import java.util.HashSet; 87 import java.util.NoSuchElementException; 88 import java.util.Set; 89 import java.util.function.Function; 90 import java.util.regex.Matcher; 91 import java.util.regex.Pattern; 92 import java.util.stream.Collectors; 93 import static java.util.stream.Collectors.collectingAndThen; 94 import static java.util.stream.Collectors.joining; 95 import static java.util.stream.Collectors.toCollection; 96 import static java.util.stream.Collectors.toList; 97 import static java.util.stream.Collectors.toSet; 98 import java.util.stream.Stream; 99 import java.util.stream.StreamSupport; 100 101 import javax.lang.model.SourceVersion; 102 import javax.lang.model.element.ExecutableElement; 103 import javax.lang.model.element.PackageElement; 104 import javax.lang.model.element.QualifiedNameable; 105 import javax.lang.model.element.VariableElement; 106 import javax.lang.model.type.ArrayType; 107 import javax.lang.model.type.ExecutableType; 108 import javax.lang.model.type.TypeKind; 109 import javax.lang.model.util.ElementFilter; 110 import javax.lang.model.util.Types; 111 import javax.tools.JavaFileManager.Location; 112 import javax.tools.StandardLocation; 113 114 import static jdk.jshell.Util.REPL_DOESNOTMATTER_CLASS_NAME; 115 116 /** 117 * The concrete implementation of SourceCodeAnalysis. 118 * @author Robert Field 119 */ 120 class SourceCodeAnalysisImpl extends SourceCodeAnalysis { 121 private final JShell proc; 122 private final CompletenessAnalyzer ca; 123 124 SourceCodeAnalysisImpl(JShell proc) { 125 this.proc = proc; 126 this.ca = new CompletenessAnalyzer(proc); 127 } 128 129 @Override 130 public CompletionInfo analyzeCompletion(String srcInput) { 131 MaskCommentsAndModifiers mcm = new MaskCommentsAndModifiers(srcInput, false); 132 String cleared = mcm.cleared(); 133 String trimmedInput = Util.trimEnd(cleared); 134 if (trimmedInput.isEmpty()) { 135 // Just comment or empty 136 return new CompletionInfo(Completeness.EMPTY, srcInput.length(), srcInput, ""); 137 } 138 CaInfo info = ca.scan(trimmedInput); 139 Completeness status = info.status; 140 int unitEndPos = info.unitEndPos; 141 if (unitEndPos > srcInput.length()) { 142 unitEndPos = srcInput.length(); 143 } 144 int nonCommentNonWhiteLength = trimmedInput.length(); 145 String src = srcInput.substring(0, unitEndPos); 146 switch (status) { 147 case COMPLETE: 148 if (unitEndPos == nonCommentNonWhiteLength) { 149 // The unit is the whole non-coment/white input plus semicolon 150 String compileSource = src 151 + mcm.mask().substring(nonCommentNonWhiteLength); 152 proc.debug(DBG_COMPA, "Complete: %s\n", compileSource); 153 proc.debug(DBG_COMPA, " nothing remains.\n"); 154 return new CompletionInfo(status, unitEndPos, compileSource, ""); 155 } else { 156 String remain = srcInput.substring(unitEndPos); 157 proc.debug(DBG_COMPA, "Complete: %s\n", src); 158 proc.debug(DBG_COMPA, " remaining: %s\n", remain); 159 return new CompletionInfo(status, unitEndPos, src, remain); 160 } 161 case COMPLETE_WITH_SEMI: 162 // The unit is the whole non-coment/white input plus semicolon 163 String compileSource = src 164 + ";" 165 + mcm.mask().substring(nonCommentNonWhiteLength); 166 proc.debug(DBG_COMPA, "Complete with semi: %s\n", compileSource); 167 proc.debug(DBG_COMPA, " nothing remains.\n"); 168 return new CompletionInfo(status, unitEndPos, compileSource, ""); 169 case DEFINITELY_INCOMPLETE: 170 proc.debug(DBG_COMPA, "Incomplete: %s\n", srcInput); 171 return new CompletionInfo(status, unitEndPos, null, srcInput + '\n'); 172 case CONSIDERED_INCOMPLETE: 173 proc.debug(DBG_COMPA, "Considered incomplete: %s\n", srcInput); 174 return new CompletionInfo(status, unitEndPos, null, srcInput + '\n'); 175 case EMPTY: 176 proc.debug(DBG_COMPA, "Detected empty: %s\n", srcInput); 177 return new CompletionInfo(status, unitEndPos, srcInput, ""); 178 case UNKNOWN: 179 proc.debug(DBG_COMPA, "Detected error: %s\n", srcInput); 180 return new CompletionInfo(status, unitEndPos, srcInput, ""); 181 } 182 throw new InternalError(); 183 } 184 185 private OuterWrap wrapInClass(Wrap guts) { 186 String imports = proc.maps.packageAndImportsExcept(null, null); 187 return OuterWrap.wrapInClass(proc.maps.packageName(), REPL_DOESNOTMATTER_CLASS_NAME, imports, "", guts); 188 } 189 190 private Tree.Kind guessKind(String code) { 191 ParseTask pt = proc.taskFactory.new ParseTask(code); 192 List<? extends Tree> units = pt.units(); 193 if (units.isEmpty()) { 194 return Tree.Kind.BLOCK; 195 } 196 Tree unitTree = units.get(0); 197 proc.debug(DBG_COMPA, "Kind: %s -- %s\n", unitTree.getKind(), unitTree); 198 return unitTree.getKind(); 199 } 200 201 //TODO: would be better handled through a lexer: 202 private final Pattern JAVA_IDENTIFIER = Pattern.compile("\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"); 203 204 @Override 205 public List<Suggestion> completionSuggestions(String code, int cursor, int[] anchor) { 206 code = code.substring(0, cursor); 207 Matcher m = JAVA_IDENTIFIER.matcher(code); 208 String identifier = ""; 209 while (m.find()) { 210 if (m.end() == code.length()) { 211 cursor = m.start(); 212 code = code.substring(0, cursor); 213 identifier = m.group(); 214 } 215 } 216 code = code.substring(0, cursor); 217 if (code.trim().isEmpty()) { //TODO: comment handling 218 code += ";"; 219 } 220 OuterWrap codeWrap; 221 switch (guessKind(code)) { 222 case IMPORT: 223 codeWrap = OuterWrap.wrapImport(null, Wrap.importWrap(code + "any.any")); 224 break; 225 case METHOD: 226 codeWrap = wrapInClass(Wrap.classMemberWrap(code)); 227 break; 228 default: 229 codeWrap = wrapInClass(Wrap.methodWrap(code)); 230 break; 231 } 232 String requiredPrefix = identifier; 233 return computeSuggestions(codeWrap, cursor, anchor).stream() 234 .filter(s -> s.continuation.startsWith(requiredPrefix) && !s.continuation.equals(REPL_DOESNOTMATTER_CLASS_NAME)) 235 .sorted(Comparator.comparing(s -> s.continuation)) 236 .collect(collectingAndThen(toList(), Collections::unmodifiableList)); 237 } 238 239 private List<Suggestion> computeSuggestions(OuterWrap code, int cursor, int[] anchor) { 240 AnalyzeTask at = proc.taskFactory.new AnalyzeTask(code); 241 SourcePositions sp = at.trees().getSourcePositions(); 242 CompilationUnitTree topLevel = at.firstCuTree(); 243 List<Suggestion> result = new ArrayList<>(); 244 TreePath tp = pathFor(topLevel, sp, code.snippetIndexToWrapIndex(cursor)); 245 if (tp != null) { 246 Scope scope = at.trees().getScope(tp); 247 Predicate<Element> accessibility = createAccessibilityFilter(at, tp); 248 Predicate<Element> smartTypeFilter; 249 Predicate<Element> smartFilter; 250 Iterable<TypeMirror> targetTypes = findTargetType(at, tp); 251 if (targetTypes != null) { 252 smartTypeFilter = el -> { 253 TypeMirror resultOf = resultTypeOf(el); 254 return Util.stream(targetTypes) 255 .anyMatch(targetType -> at.getTypes().isAssignable(resultOf, targetType)); 256 }; 257 258 smartFilter = IS_CLASS.negate() 259 .and(IS_INTERFACE.negate()) 260 .and(IS_PACKAGE.negate()) 261 .and(smartTypeFilter); 262 } else { 263 smartFilter = TRUE; 264 smartTypeFilter = TRUE; 265 } 266 switch (tp.getLeaf().getKind()) { 267 case MEMBER_SELECT: { 268 MemberSelectTree mst = (MemberSelectTree)tp.getLeaf(); 269 if (mst.getIdentifier().contentEquals("*")) 270 break; 271 TreePath exprPath = new TreePath(tp, mst.getExpression()); 272 TypeMirror site = at.trees().getTypeMirror(exprPath); 273 boolean staticOnly = isStaticContext(at, exprPath); 274 ImportTree it = findImport(tp); 275 boolean isImport = it != null; 276 277 List<? extends Element> members = membersOf(at, site, staticOnly && !isImport); 278 Predicate<Element> filter = accessibility; 279 Function<Boolean, String> paren = DEFAULT_PAREN; 280 281 if (isNewClass(tp)) { // new xxx.| 282 Predicate<Element> constructorFilter = accessibility.and(IS_CONSTRUCTOR) 283 .and(el -> { 284 if (el.getEnclosingElement().getEnclosingElement().getKind() == ElementKind.CLASS) { 285 return el.getEnclosingElement().getModifiers().contains(Modifier.STATIC); 286 } 287 return true; 288 }); 289 addElements(membersOf(at, members), constructorFilter, smartFilter, result); 290 291 filter = filter.and(IS_PACKAGE); 292 } else if (isThrowsClause(tp)) { 293 staticOnly = true; 294 filter = filter.and(IS_PACKAGE.or(IS_CLASS).or(IS_INTERFACE)); 295 smartFilter = IS_PACKAGE.negate().and(smartTypeFilter); 296 } else if (isImport) { 297 paren = NO_PAREN; 298 if (!it.isStatic()) { 299 filter = filter.and(IS_PACKAGE.or(IS_CLASS).or(IS_INTERFACE)); 300 } 301 } else { 302 filter = filter.and(IS_CONSTRUCTOR.negate()); 303 } 304 305 filter = filter.and(staticOnly ? STATIC_ONLY : INSTANCE_ONLY); 306 307 addElements(members, filter, smartFilter, paren, result); 308 break; 309 } 310 case IDENTIFIER: 311 if (isNewClass(tp)) { 312 Function<Element, Iterable<? extends Element>> listEnclosed = 313 el -> el.getKind() == ElementKind.PACKAGE ? Collections.singletonList(el) 314 : el.getEnclosedElements(); 315 Predicate<Element> filter = accessibility.and(IS_CONSTRUCTOR.or(IS_PACKAGE)); 316 NewClassTree newClassTree = (NewClassTree)tp.getParentPath().getLeaf(); 317 ExpressionTree enclosingExpression = newClassTree.getEnclosingExpression(); 318 if (enclosingExpression != null) { // expr.new IDENT| 319 TypeMirror site = at.trees().getTypeMirror(new TreePath(tp, enclosingExpression)); 320 filter = filter.and(el -> el.getEnclosingElement().getKind() == ElementKind.CLASS && !el.getEnclosingElement().getModifiers().contains(Modifier.STATIC)); 321 addElements(membersOf(at, membersOf(at, site, false)), filter, smartFilter, result); 322 } else { 323 addScopeElements(at, scope, listEnclosed, filter, smartFilter, result); 324 } 325 break; 326 } 327 if (isThrowsClause(tp)) { 328 Predicate<Element> accept = accessibility.and(STATIC_ONLY) 329 .and(IS_PACKAGE.or(IS_CLASS).or(IS_INTERFACE)); 330 addScopeElements(at, scope, IDENTITY, accept, IS_PACKAGE.negate().and(smartTypeFilter), result); 331 break; 332 } 333 ImportTree it = findImport(tp); 334 if (it != null) { 335 addElements(membersOf(at, at.getElements().getPackageElement("").asType(), false), it.isStatic() ? STATIC_ONLY.and(accessibility) : accessibility, smartFilter, result); 336 } 337 break; 338 case ERRONEOUS: 339 case EMPTY_STATEMENT: { 340 boolean staticOnly = ReplResolve.isStatic(((JavacScope)scope).getEnv()); 341 Predicate<Element> accept = accessibility.and(staticOnly ? STATIC_ONLY : TRUE); 342 addScopeElements(at, scope, IDENTITY, accept, smartFilter, result); 343 344 Tree parent = tp.getParentPath().getLeaf(); 345 switch (parent.getKind()) { 346 case VARIABLE: 347 accept = ((VariableTree)parent).getType() == tp.getLeaf() ? 348 IS_VOID.negate() : 349 TRUE; 350 break; 351 case PARAMETERIZED_TYPE: // TODO: JEP 218: Generics over Primitive Types 352 case TYPE_PARAMETER: 353 case CLASS: 354 case INTERFACE: 355 case ENUM: 356 accept = FALSE; 357 break; 358 default: 359 accept = TRUE; 360 break; 361 } 362 addElements(primitivesOrVoid(at), accept, smartFilter, result); 363 break; 364 } 365 } 366 } 367 anchor[0] = cursor; 368 return result; 369 } 370 371 private boolean isStaticContext(AnalyzeTask at, TreePath path) { 372 switch (path.getLeaf().getKind()) { 373 case ARRAY_TYPE: 374 case PRIMITIVE_TYPE: 375 return true; 376 default: 377 Element selectEl = at.trees().getElement(path); 378 return selectEl != null && (selectEl.getKind().isClass() || selectEl.getKind().isInterface() || selectEl.getKind() == ElementKind.TYPE_PARAMETER) && selectEl.asType().getKind() != TypeKind.ERROR; 379 } 380 } 381 382 private TreePath pathFor(CompilationUnitTree topLevel, SourcePositions sp, int pos) { 383 TreePath[] deepest = new TreePath[1]; 384 385 new TreePathScanner<Void, Void>() { 386 @Override @DefinedBy(Api.COMPILER_TREE) 387 public Void scan(Tree tree, Void p) { 388 if (tree == null) 389 return null; 390 391 long start = sp.getStartPosition(topLevel, tree); 392 long end = sp.getEndPosition(topLevel, tree); 393 394 if (start <= pos && pos <= end) { 395 deepest[0] = new TreePath(getCurrentPath(), tree); 396 return super.scan(tree, p); 397 } 398 399 return null; 400 } 401 @Override @DefinedBy(Api.COMPILER_TREE) 402 public Void visitErroneous(ErroneousTree node, Void p) { 403 return scan(node.getErrorTrees(), null); 404 } 405 }.scan(topLevel, null); 406 407 return deepest[0]; 408 } 409 410 private boolean isNewClass(TreePath tp) { 411 return tp.getParentPath() != null && 412 tp.getParentPath().getLeaf().getKind() == Kind.NEW_CLASS && 413 ((NewClassTree) tp.getParentPath().getLeaf()).getIdentifier() == tp.getLeaf(); 414 } 415 416 private boolean isThrowsClause(TreePath tp) { 417 Tree parent = tp.getParentPath().getLeaf(); 418 return parent.getKind() == Kind.METHOD && 419 ((MethodTree)parent).getThrows().contains(tp.getLeaf()); 420 } 421 422 private ImportTree findImport(TreePath tp) { 423 while (tp != null && tp.getLeaf().getKind() != Kind.IMPORT) { 424 tp = tp.getParentPath(); 425 } 426 return tp != null ? (ImportTree)tp.getLeaf() : null; 427 } 428 429 private Predicate<Element> createAccessibilityFilter(AnalyzeTask at, TreePath tp) { 430 Scope scope = at.trees().getScope(tp); 431 return el -> { 432 switch (el.getKind()) { 433 case ANNOTATION_TYPE: case CLASS: case ENUM: case INTERFACE: 434 return at.trees().isAccessible(scope, (TypeElement) el); 435 case PACKAGE: 436 case EXCEPTION_PARAMETER: case PARAMETER: case LOCAL_VARIABLE: case RESOURCE_VARIABLE: 437 return true; 438 default: 439 TypeMirror type = el.getEnclosingElement().asType(); 440 if (type.getKind() == TypeKind.DECLARED) 441 return at.trees().isAccessible(scope, el, (DeclaredType) type); 442 else 443 return true; 444 } 445 }; 446 } 447 448 private final Predicate<Element> TRUE = el -> true; 449 private final Predicate<Element> FALSE = TRUE.negate(); 450 private final Predicate<Element> IS_STATIC = el -> el.getModifiers().contains(Modifier.STATIC); 451 private final Predicate<Element> IS_CONSTRUCTOR = el -> el.getKind() == ElementKind.CONSTRUCTOR; 452 private final Predicate<Element> IS_METHOD = el -> el.getKind() == ElementKind.METHOD; 453 private final Predicate<Element> IS_PACKAGE = el -> el.getKind() == ElementKind.PACKAGE; 454 private final Predicate<Element> IS_CLASS = el -> el.getKind().isClass(); 455 private final Predicate<Element> IS_INTERFACE = el -> el.getKind().isInterface(); 456 private final Predicate<Element> IS_VOID = el -> el.asType().getKind() == TypeKind.VOID; 457 private final Predicate<Element> STATIC_ONLY = el -> { 458 ElementKind kind = el.getKind(); 459 Element encl = el.getEnclosingElement(); 460 ElementKind enclKind = encl != null ? encl.getKind() : ElementKind.OTHER; 461 462 return IS_STATIC.or(IS_PACKAGE).or(IS_CLASS).or(IS_INTERFACE).test(el) || IS_PACKAGE.test(encl) || 463 (kind == ElementKind.TYPE_PARAMETER && !enclKind.isClass() && !enclKind.isInterface()); 464 }; 465 private final Predicate<Element> INSTANCE_ONLY = el -> { 466 Element encl = el.getEnclosingElement(); 467 468 return IS_STATIC.or(IS_CLASS).or(IS_INTERFACE).negate().test(el) || 469 IS_PACKAGE.test(encl); 470 }; 471 private final Function<Element, Iterable<? extends Element>> IDENTITY = el -> Collections.singletonList(el); 472 private final Function<Boolean, String> DEFAULT_PAREN = hasParams -> hasParams ? "(" : "()"; 473 private final Function<Boolean, String> NO_PAREN = hasParams -> ""; 474 475 private void addElements(Iterable<? extends Element> elements, Predicate<Element> accept, Predicate<Element> smart, List<Suggestion> result) { 476 addElements(elements, accept, smart, DEFAULT_PAREN, result); 477 } 478 private void addElements(Iterable<? extends Element> elements, Predicate<Element> accept, Predicate<Element> smart, Function<Boolean, String> paren, List<Suggestion> result) { 479 Set<String> hasParams = Util.stream(elements) 480 .filter(accept) 481 .filter(IS_CONSTRUCTOR.or(IS_METHOD)) 482 .filter(c -> !((ExecutableElement)c).getParameters().isEmpty()) 483 .map(this::simpleName) 484 .collect(toSet()); 485 486 for (Element c : elements) { 487 if (!accept.test(c)) 488 continue; 489 String simpleName = simpleName(c); 490 if (c.getKind() == ElementKind.CONSTRUCTOR || c.getKind() == ElementKind.METHOD) { 491 simpleName += paren.apply(hasParams.contains(simpleName)); 492 } 493 result.add(new Suggestion(simpleName, smart.test(c))); 494 } 495 } 496 497 private String simpleName(Element el) { 498 return el.getKind() == ElementKind.CONSTRUCTOR ? el.getEnclosingElement().getSimpleName().toString() 499 : el.getSimpleName().toString(); 500 } 501 502 private List<? extends Element> membersOf(AnalyzeTask at, TypeMirror site, boolean shouldGenerateDotClassItem) { 503 if (site == null) 504 return Collections.emptyList(); 505 506 switch (site.getKind()) { 507 case DECLARED: { 508 TypeElement element = (TypeElement) at.getTypes().asElement(site); 509 List<Element> result = new ArrayList<>(); 510 result.addAll(at.getElements().getAllMembers(element)); 511 if (shouldGenerateDotClassItem) { 512 result.add(createDotClassSymbol(at, site)); 513 } 514 result.removeIf(el -> el.getKind() == ElementKind.STATIC_INIT); 515 return result; 516 } 517 case ERROR: { 518 //try current qualified name as a package: 519 TypeElement typeElement = (TypeElement) at.getTypes().asElement(site); 520 Element enclosingElement = typeElement.getEnclosingElement(); 521 String parentPackageName = enclosingElement instanceof QualifiedNameable ? 522 ((QualifiedNameable)enclosingElement).getQualifiedName().toString() : 523 ""; 524 Set<PackageElement> packages = listPackages(at, parentPackageName); 525 return packages.stream() 526 .filter(p -> p.getQualifiedName().equals(typeElement.getQualifiedName())) 527 .findAny() 528 .map(p -> membersOf(at, p.asType(), false)) 529 .orElse(Collections.emptyList()); 530 } 531 case PACKAGE: { 532 String packageName = site.toString()/*XXX*/; 533 List<Element> result = new ArrayList<>(); 534 result.addAll(getEnclosedElements(at.getElements().getPackageElement(packageName))); 535 result.addAll(listPackages(at, packageName)); 536 return result; 537 } 538 case BOOLEAN: case BYTE: case SHORT: case CHAR: 539 case INT: case FLOAT: case LONG: case DOUBLE: 540 case VOID: { 541 return shouldGenerateDotClassItem ? 542 Collections.singletonList(createDotClassSymbol(at, site)) : 543 Collections.emptyList(); 544 } 545 case ARRAY: { 546 List<Element> result = new ArrayList<>(); 547 result.add(createArrayLengthSymbol(at, site)); 548 if (shouldGenerateDotClassItem) 549 result.add(createDotClassSymbol(at, site)); 550 return result; 551 } 552 default: 553 return Collections.emptyList(); 554 } 555 } 556 557 private List<? extends Element> membersOf(AnalyzeTask at, List<? extends Element> elements) { 558 return elements.stream() 559 .flatMap(e -> membersOf(at, e.asType(), true).stream()) 560 .collect(toList()); 561 } 562 563 private List<? extends Element> getEnclosedElements(PackageElement packageEl) { 564 if (packageEl == null) { 565 return Collections.emptyList(); 566 } 567 //workaround for: JDK-8024687 568 while (true) { 569 try { 570 return packageEl.getEnclosedElements() 571 .stream() 572 .filter(el -> el.asType() != null) 573 .filter(el -> el.asType().getKind() != TypeKind.ERROR) 574 .collect(toList()); 575 } catch (CompletionFailure cf) { 576 //ignore... 577 } 578 } 579 } 580 581 private List<? extends Element> primitivesOrVoid(AnalyzeTask at) { 582 Types types = at.getTypes(); 583 return Stream.of( 584 TypeKind.BOOLEAN, TypeKind.BYTE, TypeKind.CHAR, 585 TypeKind.DOUBLE, TypeKind.FLOAT, TypeKind.INT, 586 TypeKind.LONG, TypeKind.SHORT, TypeKind.VOID) 587 .map(tk -> (Type)(tk == TypeKind.VOID ? types.getNoType(tk) : types.getPrimitiveType(tk))) 588 .map(Type::asElement) 589 .collect(toList()); 590 } 591 592 private Set<String> emptyContextPackages = null; 593 594 void classpathChanged() { 595 emptyContextPackages = null; 596 } 597 598 private Set<PackageElement> listPackages(AnalyzeTask at, String enclosingPackage) { 599 Set<String> packs; 600 601 if (enclosingPackage.isEmpty() && emptyContextPackages != null) { 602 packs = emptyContextPackages; 603 } else { 604 packs = new HashSet<>(); 605 606 listPackages(StandardLocation.PLATFORM_CLASS_PATH, enclosingPackage, packs); 607 listPackages(StandardLocation.CLASS_PATH, enclosingPackage, packs); 608 listPackages(StandardLocation.SOURCE_PATH, enclosingPackage, packs); 609 610 if (enclosingPackage.isEmpty()) { 611 emptyContextPackages = packs; 612 } 613 } 614 615 return packs.stream() 616 .map(pkg -> createPackageElement(at, pkg)) 617 .collect(Collectors.toSet()); 618 } 619 620 private PackageElement createPackageElement(AnalyzeTask at, String packageName) { 621 Names names = Names.instance(at.getContext()); 622 Symtab syms = Symtab.instance(at.getContext()); 623 PackageElement existing = syms.enterPackage(names.fromString(packageName)); 624 625 return existing; 626 } 627 628 private void listPackages(Location loc, String enclosing, Set<String> packs) { 629 Iterable<? extends Path> paths = proc.taskFactory.fileManager().getLocationAsPaths(loc); 630 631 if (paths == null) 632 return ; 633 634 for (Path p : paths) { 635 listPackages(p, enclosing, packs); 636 } 637 } 638 639 private void listPackages(Path path, String enclosing, Set<String> packages) { 640 try { 641 if (path.equals(Paths.get("JRT_MARKER_FILE"))) { 642 FileSystem jrtfs = FileSystems.getFileSystem(URI.create("jrt:/")); 643 Path modules = jrtfs.getPath("modules"); 644 try (DirectoryStream<Path> stream = Files.newDirectoryStream(modules)) { 645 for (Path c : stream) { 646 listDirectory(c, enclosing, packages); 647 } 648 } 649 } else if (!Files.isDirectory(path)) { 650 if (Files.exists(path)) { 651 ClassLoader cl = SourceCodeAnalysisImpl.class.getClassLoader(); 652 653 try (FileSystem zip = FileSystems.newFileSystem(path, cl)) { 654 listDirectory(zip.getRootDirectories().iterator().next(), enclosing, packages); 655 } 656 } 657 } else { 658 listDirectory(path, enclosing, packages); 659 } 660 } catch (IOException ex) { 661 proc.debug(ex, "SourceCodeAnalysisImpl.listPackages(" + path.toString() + ", " + enclosing + ", " + packages + ")"); 662 } 663 } 664 665 private void listDirectory(Path path, String enclosing, Set<String> packages) throws IOException { 666 String separator = path.getFileSystem().getSeparator(); 667 Path resolved = path.resolve(enclosing.replace(".", separator)); 668 669 if (Files.isDirectory(resolved)) { 670 try (DirectoryStream<Path> ds = Files.newDirectoryStream(resolved)) { 671 for (Path entry : ds) { 672 String name = pathName(entry); 673 674 if (SourceVersion.isIdentifier(name) && 675 Files.isDirectory(entry) && 676 validPackageCandidate(entry)) { 677 packages.add(enclosing + (enclosing.isEmpty() ? "" : ".") + name); 678 } 679 } 680 } 681 } 682 } 683 684 private boolean validPackageCandidate(Path p) throws IOException { 685 try (Stream<Path> dir = Files.list(p)) { 686 return dir.anyMatch(e -> Files.isDirectory(e) && SourceVersion.isIdentifier(pathName(e)) || 687 e.getFileName().toString().endsWith(".class")); 688 } 689 } 690 691 private String pathName(Path p) { 692 String separator = p.getFileSystem().getSeparator(); 693 String name = p.getFileName().toString(); 694 695 if (name.endsWith(separator)) //jars have '/' appended 696 name = name.substring(0, name.length() - separator.length()); 697 698 return name; 699 } 700 701 private Element createArrayLengthSymbol(AnalyzeTask at, TypeMirror site) { 702 Name length = Names.instance(at.getContext()).length; 703 Type intType = Symtab.instance(at.getContext()).intType; 704 705 return new VarSymbol(Flags.PUBLIC | Flags.FINAL, length, intType, ((Type) site).tsym); 706 } 707 708 private Element createDotClassSymbol(AnalyzeTask at, TypeMirror site) { 709 Name _class = Names.instance(at.getContext())._class; 710 Type classType = Symtab.instance(at.getContext()).classType; 711 Type erasedSite = (Type)at.getTypes().erasure(site); 712 classType = new ClassType(classType.getEnclosingType(), com.sun.tools.javac.util.List.of(erasedSite), classType.asElement()); 713 714 return new VarSymbol(Flags.PUBLIC | Flags.STATIC | Flags.FINAL, _class, classType, erasedSite.tsym); 715 } 716 717 private Iterable<? extends Element> scopeContent(AnalyzeTask at, Scope scope, Function<Element, Iterable<? extends Element>> elementConvertor) { 718 Iterable<Scope> scopeIterable = () -> new Iterator<Scope>() { 719 private Scope currentScope = scope; 720 @Override 721 public boolean hasNext() { 722 return currentScope != null; 723 } 724 @Override 725 public Scope next() { 726 if (!hasNext()) 727 throw new NoSuchElementException(); 728 try { 729 return currentScope; 730 } finally { 731 currentScope = currentScope.getEnclosingScope(); 732 } 733 } 734 }; 735 @SuppressWarnings("unchecked") 736 List<Element> result = Util.stream(scopeIterable) 737 .flatMap(s -> Util.stream((Iterable<Element>)s.getLocalElements())) 738 .flatMap(el -> Util.stream((Iterable<Element>)elementConvertor.apply(el))) 739 .collect(toCollection(ArrayList :: new)); 740 result.addAll(listPackages(at, "")); 741 return result; 742 } 743 744 @SuppressWarnings("fallthrough") 745 private Iterable<TypeMirror> findTargetType(AnalyzeTask at, TreePath forPath) { 746 if (forPath.getParentPath() == null) 747 return null; 748 749 Tree current = forPath.getLeaf(); 750 751 switch (forPath.getParentPath().getLeaf().getKind()) { 752 case ASSIGNMENT: { 753 AssignmentTree tree = (AssignmentTree) forPath.getParentPath().getLeaf(); 754 if (tree.getExpression() == current) 755 return Collections.singletonList(at.trees().getTypeMirror(new TreePath(forPath.getParentPath(), tree.getVariable()))); 756 break; 757 } 758 case VARIABLE: { 759 VariableTree tree = (VariableTree) forPath.getParentPath().getLeaf(); 760 if (tree.getInitializer()== current) 761 return Collections.singletonList(at.trees().getTypeMirror(forPath.getParentPath())); 762 break; 763 } 764 case ERRONEOUS: 765 return findTargetType(at, forPath.getParentPath()); 766 case NEW_CLASS: { 767 NewClassTree nct = (NewClassTree) forPath.getParentPath().getLeaf(); 768 List<TypeMirror> actuals = computeActualInvocationTypes(at, nct.getArguments(), forPath); 769 770 if (actuals != null) { 771 Iterable<Pair<ExecutableElement, ExecutableType>> candidateConstructors = newClassCandidates(at, forPath.getParentPath()); 772 773 return computeSmartTypesForExecutableType(at, candidateConstructors, actuals); 774 } else { 775 return findTargetType(at, forPath.getParentPath()); 776 } 777 } 778 case METHOD: 779 if (!isThrowsClause(forPath)) { 780 break; 781 } 782 // fall through 783 case THROW: 784 return Collections.singletonList(at.getElements().getTypeElement("java.lang.Throwable").asType()); 785 case METHOD_INVOCATION: { 786 MethodInvocationTree mit = (MethodInvocationTree) forPath.getParentPath().getLeaf(); 787 List<TypeMirror> actuals = computeActualInvocationTypes(at, mit.getArguments(), forPath); 788 789 if (actuals == null) 790 return null; 791 792 Iterable<Pair<ExecutableElement, ExecutableType>> candidateMethods = methodCandidates(at, forPath.getParentPath()); 793 794 return computeSmartTypesForExecutableType(at, candidateMethods, actuals); 795 } 796 } 797 798 return null; 799 } 800 801 private List<TypeMirror> computeActualInvocationTypes(AnalyzeTask at, List<? extends ExpressionTree> arguments, TreePath currentArgument) { 802 if (currentArgument == null) 803 return null; 804 805 int paramIndex = arguments.indexOf(currentArgument.getLeaf()); 806 807 if (paramIndex == (-1)) 808 return null; 809 810 List<TypeMirror> actuals = new ArrayList<>(); 811 812 for (ExpressionTree arg : arguments.subList(0, paramIndex)) { 813 actuals.add(at.trees().getTypeMirror(new TreePath(currentArgument.getParentPath(), arg))); 814 } 815 816 return actuals; 817 } 818 819 private List<Pair<ExecutableElement, ExecutableType>> filterExecutableTypesByArguments(AnalyzeTask at, Iterable<Pair<ExecutableElement, ExecutableType>> candidateMethods, List<TypeMirror> precedingActualTypes) { 820 List<Pair<ExecutableElement, ExecutableType>> candidate = new ArrayList<>(); 821 int paramIndex = precedingActualTypes.size(); 822 823 OUTER: 824 for (Pair<ExecutableElement, ExecutableType> method : candidateMethods) { 825 boolean varargInvocation = paramIndex >= method.snd.getParameterTypes().size(); 826 827 for (int i = 0; i < paramIndex; i++) { 828 TypeMirror actual = precedingActualTypes.get(i); 829 830 if (this.parameterType(method.fst, method.snd, i, !varargInvocation) 831 .noneMatch(formal -> at.getTypes().isAssignable(actual, formal))) { 832 continue OUTER; 833 } 834 } 835 candidate.add(method); 836 } 837 838 return candidate; 839 } 840 841 private Stream<TypeMirror> parameterType(ExecutableElement method, ExecutableType methodType, int paramIndex, boolean allowVarArgsArray) { 842 int paramCount = methodType.getParameterTypes().size(); 843 if (paramIndex >= paramCount && !method.isVarArgs()) 844 return Stream.empty(); 845 if (paramIndex < paramCount - 1 || !method.isVarArgs()) 846 return Stream.of(methodType.getParameterTypes().get(paramIndex)); 847 TypeMirror varargType = methodType.getParameterTypes().get(paramCount - 1); 848 TypeMirror elemenType = ((ArrayType) varargType).getComponentType(); 849 if (paramIndex >= paramCount || !allowVarArgsArray) 850 return Stream.of(elemenType); 851 return Stream.of(varargType, elemenType); 852 } 853 854 private List<TypeMirror> computeSmartTypesForExecutableType(AnalyzeTask at, Iterable<Pair<ExecutableElement, ExecutableType>> candidateMethods, List<TypeMirror> precedingActualTypes) { 855 List<TypeMirror> candidate = new ArrayList<>(); 856 int paramIndex = precedingActualTypes.size(); 857 858 this.filterExecutableTypesByArguments(at, candidateMethods, precedingActualTypes) 859 .stream() 860 .flatMap(method -> parameterType(method.fst, method.snd, paramIndex, true)) 861 .forEach(candidate::add); 862 863 return candidate; 864 } 865 866 867 private TypeMirror resultTypeOf(Element el) { 868 //TODO: should reflect the type of site! 869 switch (el.getKind()) { 870 case METHOD: 871 return ((ExecutableElement) el).getReturnType(); 872 case CONSTRUCTOR: 873 case INSTANCE_INIT: case STATIC_INIT: //TODO: should be filtered out 874 return el.getEnclosingElement().asType(); 875 default: 876 return el.asType(); 877 } 878 } 879 880 private void addScopeElements(AnalyzeTask at, Scope scope, Function<Element, Iterable<? extends Element>> elementConvertor, Predicate<Element> filter, Predicate<Element> smartFilter, List<Suggestion> result) { 881 addElements(scopeContent(at, scope, elementConvertor), filter, smartFilter, result); 882 } 883 884 private Iterable<Pair<ExecutableElement, ExecutableType>> methodCandidates(AnalyzeTask at, TreePath invocation) { 885 MethodInvocationTree mit = (MethodInvocationTree) invocation.getLeaf(); 886 ExpressionTree select = mit.getMethodSelect(); 887 List<Pair<ExecutableElement, ExecutableType>> result = new ArrayList<>(); 888 Predicate<Element> accessibility = createAccessibilityFilter(at, invocation); 889 890 switch (select.getKind()) { 891 case MEMBER_SELECT: 892 MemberSelectTree mst = (MemberSelectTree) select; 893 TreePath tp = new TreePath(new TreePath(invocation, select), mst.getExpression()); 894 TypeMirror site = at.trees().getTypeMirror(tp); 895 896 if (site == null || site.getKind() != TypeKind.DECLARED) 897 break; 898 899 Element siteEl = at.getTypes().asElement(site); 900 901 if (siteEl == null) 902 break; 903 904 if (isStaticContext(at, tp)) { 905 accessibility = accessibility.and(STATIC_ONLY); 906 } 907 908 for (ExecutableElement ee : ElementFilter.methodsIn(membersOf(at, siteEl.asType(), false))) { 909 if (ee.getSimpleName().contentEquals(mst.getIdentifier())) { 910 if (accessibility.test(ee)) { 911 result.add(Pair.of(ee, (ExecutableType) at.getTypes().asMemberOf((DeclaredType) site, ee))); 912 } 913 } 914 } 915 break; 916 case IDENTIFIER: 917 IdentifierTree it = (IdentifierTree) select; 918 for (ExecutableElement ee : ElementFilter.methodsIn(scopeContent(at, at.trees().getScope(invocation), IDENTITY))) { 919 if (ee.getSimpleName().contentEquals(it.getName())) { 920 if (accessibility.test(ee)) { 921 result.add(Pair.of(ee, (ExecutableType) ee.asType())); //XXX: proper site 922 } 923 } 924 } 925 break; 926 default: 927 break; 928 } 929 930 return result; 931 } 932 933 private Iterable<Pair<ExecutableElement, ExecutableType>> newClassCandidates(AnalyzeTask at, TreePath newClassPath) { 934 NewClassTree nct = (NewClassTree) newClassPath.getLeaf(); 935 Element type = at.trees().getElement(new TreePath(newClassPath.getParentPath(), nct.getIdentifier())); 936 TypeMirror targetType = at.trees().getTypeMirror(newClassPath); 937 if (targetType == null || targetType.getKind() != TypeKind.DECLARED) { 938 Iterable<TypeMirror> targetTypes = findTargetType(at, newClassPath); 939 if (targetTypes == null) 940 targetTypes = Collections.emptyList(); 941 targetType = 942 StreamSupport.stream(targetTypes.spliterator(), false) 943 .filter(t -> at.getTypes().asElement(t) == type) 944 .findAny() 945 .orElse(at.getTypes().erasure(type.asType())); 946 } 947 List<Pair<ExecutableElement, ExecutableType>> candidateConstructors = new ArrayList<>(); 948 Predicate<Element> accessibility = createAccessibilityFilter(at, newClassPath); 949 950 if (targetType != null && 951 targetType.getKind() == TypeKind.DECLARED && 952 type != null && 953 (type.getKind().isClass() || type.getKind().isInterface())) { 954 for (ExecutableElement constr : ElementFilter.constructorsIn(type.getEnclosedElements())) { 955 if (accessibility.test(constr)) { 956 ExecutableType constrType = 957 (ExecutableType) at.getTypes().asMemberOf((DeclaredType) targetType, constr); 958 candidateConstructors.add(Pair.of(constr, constrType)); 959 } 960 } 961 } 962 963 return candidateConstructors; 964 } 965 966 @Override 967 public String documentation(String code, int cursor) { 968 code = code.substring(0, cursor); 969 if (code.trim().isEmpty()) { //TODO: comment handling 970 code += ";"; 971 } 972 973 if (guessKind(code) == Kind.IMPORT) 974 return null; 975 976 OuterWrap codeWrap = wrapInClass(Wrap.methodWrap(code)); 977 AnalyzeTask at = proc.taskFactory.new AnalyzeTask(codeWrap); 978 SourcePositions sp = at.trees().getSourcePositions(); 979 CompilationUnitTree topLevel = at.firstCuTree(); 980 TreePath tp = pathFor(topLevel, sp, codeWrap.snippetIndexToWrapIndex(cursor)); 981 982 if (tp == null) 983 return null; 984 985 TreePath prevPath = null; 986 while (tp != null && tp.getLeaf().getKind() != Kind.METHOD_INVOCATION && tp.getLeaf().getKind() != Kind.NEW_CLASS) { 987 prevPath = tp; 988 tp = tp.getParentPath(); 989 } 990 991 if (tp == null) 992 return null; 993 994 Iterable<Pair<ExecutableElement, ExecutableType>> candidates; 995 List<? extends ExpressionTree> arguments; 996 997 if (tp.getLeaf().getKind() == Kind.METHOD_INVOCATION) { 998 MethodInvocationTree mit = (MethodInvocationTree) tp.getLeaf(); 999 candidates = methodCandidates(at, tp); 1000 arguments = mit.getArguments(); 1001 } else { 1002 NewClassTree nct = (NewClassTree) tp.getLeaf(); 1003 candidates = newClassCandidates(at, tp); 1004 arguments = nct.getArguments(); 1005 } 1006 1007 if (!isEmptyArgumentsContext(arguments)) { 1008 List<TypeMirror> actuals = computeActualInvocationTypes(at, arguments, prevPath); 1009 List<TypeMirror> fullActuals = actuals != null ? actuals : Collections.emptyList(); 1010 1011 candidates = 1012 this.filterExecutableTypesByArguments(at, candidates, fullActuals) 1013 .stream() 1014 .filter(method -> parameterType(method.fst, method.snd, fullActuals.size(), true).findAny().isPresent()) 1015 .collect(Collectors.toList()); 1016 } 1017 1018 return Util.stream(candidates) 1019 .map(method -> Util.expunge(element2String(method.fst))) 1020 .collect(joining("\n")); 1021 } 1022 1023 private boolean isEmptyArgumentsContext(List<? extends ExpressionTree> arguments) { 1024 if (arguments.size() == 1) { 1025 Tree firstArgument = arguments.get(0); 1026 return firstArgument.getKind() == Kind.ERRONEOUS; 1027 } 1028 return false; 1029 } 1030 1031 private String element2String(Element el) { 1032 switch (el.getKind()) { 1033 case ANNOTATION_TYPE: case CLASS: case ENUM: case INTERFACE: 1034 return ((TypeElement) el).getQualifiedName().toString(); 1035 case FIELD: 1036 return element2String(el.getEnclosingElement()) + "." + el.getSimpleName() + ":" + el.asType(); 1037 case ENUM_CONSTANT: 1038 return element2String(el.getEnclosingElement()) + "." + el.getSimpleName(); 1039 case EXCEPTION_PARAMETER: case LOCAL_VARIABLE: case PARAMETER: case RESOURCE_VARIABLE: 1040 return el.getSimpleName() + ":" + el.asType(); 1041 case CONSTRUCTOR: case METHOD: 1042 StringBuilder header = new StringBuilder(); 1043 header.append(element2String(el.getEnclosingElement())); 1044 if (el.getKind() == ElementKind.METHOD) { 1045 header.append("."); 1046 header.append(el.getSimpleName()); 1047 } 1048 header.append("("); 1049 String sep = ""; 1050 ExecutableElement method = (ExecutableElement) el; 1051 for (Iterator<? extends VariableElement> i = method.getParameters().iterator(); i.hasNext();) { 1052 VariableElement p = i.next(); 1053 header.append(sep); 1054 if (!i.hasNext() && method.isVarArgs()) { 1055 header.append(unwrapArrayType(p.asType())); 1056 header.append("..."); 1057 1058 } else { 1059 header.append(p.asType()); 1060 } 1061 header.append(" "); 1062 header.append(p.getSimpleName()); 1063 sep = ", "; 1064 } 1065 header.append(")"); 1066 return header.toString(); 1067 default: 1068 return el.toString(); 1069 } 1070 } 1071 private TypeMirror unwrapArrayType(TypeMirror arrayType) { 1072 if (arrayType.getKind() == TypeKind.ARRAY) { 1073 return ((ArrayType)arrayType).getComponentType(); 1074 } 1075 return arrayType; 1076 } 1077 }