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