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) { 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: 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 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; 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 } | 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.FileVisitResult; 83 import java.nio.file.FileVisitor; 84 import java.nio.file.Files; 85 import java.nio.file.Path; 86 import java.nio.file.Paths; 87 import java.nio.file.attribute.BasicFileAttributes; 88 import java.util.Arrays; 89 import java.util.Collection; 90 import java.util.Comparator; 91 import java.util.HashMap; 92 import java.util.HashSet; 93 import java.util.LinkedHashSet; 94 import java.util.Map; 95 import java.util.NoSuchElementException; 96 import java.util.Set; 97 import java.util.concurrent.ExecutorService; 98 import java.util.concurrent.Executors; 99 import java.util.function.Function; 100 import java.util.prefs.Preferences; 101 import java.util.regex.Matcher; 102 import java.util.regex.Pattern; 103 import java.util.stream.Collectors; 104 import static java.util.stream.Collectors.collectingAndThen; 105 import static java.util.stream.Collectors.joining; 106 import static java.util.stream.Collectors.toCollection; 107 import static java.util.stream.Collectors.toList; 108 import static java.util.stream.Collectors.toSet; 109 import java.util.stream.Stream; 110 import java.util.stream.StreamSupport; 111 112 import javax.lang.model.SourceVersion; 113 114 import javax.lang.model.element.ExecutableElement; 115 import javax.lang.model.element.PackageElement; 116 import javax.lang.model.element.QualifiedNameable; 117 import javax.lang.model.element.VariableElement; 118 import javax.lang.model.type.ArrayType; 119 import javax.lang.model.type.ExecutableType; 120 import javax.lang.model.type.TypeKind; 121 import javax.lang.model.util.ElementFilter; 122 import javax.lang.model.util.Types; 123 import javax.tools.JavaFileManager.Location; 124 import javax.tools.StandardLocation; 125 126 import static jdk.jshell.Util.REPL_DOESNOTMATTER_CLASS_NAME; 127 128 /** 129 * The concrete implementation of SourceCodeAnalysis. 130 * @author Robert Field 131 */ 132 class SourceCodeAnalysisImpl extends SourceCodeAnalysis { 133 private final JShell proc; 134 private final CompletenessAnalyzer ca; 135 136 SourceCodeAnalysisImpl(JShell proc) { 137 this.proc = proc; 138 this.ca = new CompletenessAnalyzer(proc); 139 140 int cpVersion = classpathVersion = 1; 141 142 INDEXER.submit(() -> refreshIndexes(cpVersion)); 143 } 144 145 @Override 146 public CompletionInfo analyzeCompletion(String srcInput) { 147 MaskCommentsAndModifiers mcm = new MaskCommentsAndModifiers(srcInput, false); 148 String cleared = mcm.cleared(); 149 String trimmedInput = Util.trimEnd(cleared); 150 if (trimmedInput.isEmpty()) { 151 // Just comment or empty 152 return new CompletionInfo(Completeness.EMPTY, srcInput.length(), srcInput, ""); 153 } 154 CaInfo info = ca.scan(trimmedInput); 155 Completeness status = info.status; 156 int unitEndPos = info.unitEndPos; 157 if (unitEndPos > srcInput.length()) { 158 unitEndPos = srcInput.length(); 159 } 160 int nonCommentNonWhiteLength = trimmedInput.length(); 161 String src = srcInput.substring(0, unitEndPos); 162 switch (status) { 202 String imports = proc.maps.packageAndImportsExcept(null, null); 203 return OuterWrap.wrapInClass(proc.maps.packageName(), REPL_DOESNOTMATTER_CLASS_NAME, imports, "", guts); 204 } 205 206 private Tree.Kind guessKind(String code) { 207 ParseTask pt = proc.taskFactory.new ParseTask(code); 208 List<? extends Tree> units = pt.units(); 209 if (units.isEmpty()) { 210 return Tree.Kind.BLOCK; 211 } 212 Tree unitTree = units.get(0); 213 proc.debug(DBG_COMPA, "Kind: %s -- %s\n", unitTree.getKind(), unitTree); 214 return unitTree.getKind(); 215 } 216 217 //TODO: would be better handled through a lexer: 218 private final Pattern JAVA_IDENTIFIER = Pattern.compile("\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"); 219 220 @Override 221 public List<Suggestion> completionSuggestions(String code, int cursor, int[] anchor) { 222 suspendIndexing(); 223 try { 224 return completionSuggestionsImpl(code, cursor, anchor); 225 } finally { 226 resumeIndexing(); 227 } 228 } 229 230 private List<Suggestion> completionSuggestionsImpl(String code, int cursor, int[] anchor) { 231 code = code.substring(0, cursor); 232 Matcher m = JAVA_IDENTIFIER.matcher(code); 233 String identifier = ""; 234 while (m.find()) { 235 if (m.end() == code.length()) { 236 cursor = m.start(); 237 code = code.substring(0, cursor); 238 identifier = m.group(); 239 } 240 } 241 code = code.substring(0, cursor); 242 if (code.trim().isEmpty()) { //TODO: comment handling 243 code += ";"; 244 } 245 OuterWrap codeWrap; 246 switch (guessKind(code)) { 247 case IMPORT: 248 codeWrap = OuterWrap.wrapImport(null, Wrap.importWrap(code + "any.any")); 249 break; 250 case METHOD: 597 .filter(el -> el.asType() != null) 598 .filter(el -> el.asType().getKind() != TypeKind.ERROR) 599 .collect(toList()); 600 } catch (CompletionFailure cf) { 601 //ignore... 602 } 603 } 604 } 605 606 private List<? extends Element> primitivesOrVoid(AnalyzeTask at) { 607 Types types = at.getTypes(); 608 return Stream.of( 609 TypeKind.BOOLEAN, TypeKind.BYTE, TypeKind.CHAR, 610 TypeKind.DOUBLE, TypeKind.FLOAT, TypeKind.INT, 611 TypeKind.LONG, TypeKind.SHORT, TypeKind.VOID) 612 .map(tk -> (Type)(tk == TypeKind.VOID ? types.getNoType(tk) : types.getPrimitiveType(tk))) 613 .map(Type::asElement) 614 .collect(toList()); 615 } 616 617 void classpathChanged() { 618 synchronized (currentIndexes) { 619 int cpVersion = ++classpathVersion; 620 621 INDEXER.submit(() -> refreshIndexes(cpVersion)); 622 } 623 } 624 625 private Set<PackageElement> listPackages(AnalyzeTask at, String enclosingPackage) { 626 synchronized (currentIndexes) { 627 return currentIndexes.values() 628 .stream() 629 .flatMap(idx -> idx.packages.stream()) 630 .filter(p -> enclosingPackage.isEmpty() || p.startsWith(enclosingPackage + ".")) 631 .map(p -> { 632 int dot = p.indexOf('.', enclosingPackage.length() + 1); 633 return dot == (-1) ? p : p.substring(0, dot); 634 }) 635 .distinct() 636 .map(p -> createPackageElement(at, p)) 637 .collect(Collectors.toSet()); 638 } 639 } 640 641 private PackageElement createPackageElement(AnalyzeTask at, String packageName) { 642 Names names = Names.instance(at.getContext()); 643 Symtab syms = Symtab.instance(at.getContext()); 644 PackageElement existing = syms.enterPackage(names.fromString(packageName)); 645 646 return existing; 647 } 648 649 private Element createArrayLengthSymbol(AnalyzeTask at, TypeMirror site) { 650 Name length = Names.instance(at.getContext()).length; 651 Type intType = Symtab.instance(at.getContext()).intType; 652 653 return new VarSymbol(Flags.PUBLIC | Flags.FINAL, length, intType, ((Type) site).tsym); 654 } 655 656 private Element createDotClassSymbol(AnalyzeTask at, TypeMirror site) { 657 Name _class = Names.instance(at.getContext())._class; 658 Type classType = Symtab.instance(at.getContext()).classType; 659 Type erasedSite = (Type)at.getTypes().erasure(site); 660 classType = new ClassType(classType.getEnclosingType(), com.sun.tools.javac.util.List.of(erasedSite), classType.asElement()); 661 662 return new VarSymbol(Flags.PUBLIC | Flags.STATIC | Flags.FINAL, _class, classType, erasedSite.tsym); 663 } 664 665 private Iterable<? extends Element> scopeContent(AnalyzeTask at, Scope scope, Function<Element, Iterable<? extends Element>> elementConvertor) { 666 Iterable<Scope> scopeIterable = () -> new Iterator<Scope>() { 667 private Scope currentScope = scope; 668 @Override 896 Predicate<Element> accessibility = createAccessibilityFilter(at, newClassPath); 897 898 if (targetType != null && 899 targetType.getKind() == TypeKind.DECLARED && 900 type != null && 901 (type.getKind().isClass() || type.getKind().isInterface())) { 902 for (ExecutableElement constr : ElementFilter.constructorsIn(type.getEnclosedElements())) { 903 if (accessibility.test(constr)) { 904 ExecutableType constrType = 905 (ExecutableType) at.getTypes().asMemberOf((DeclaredType) targetType, constr); 906 candidateConstructors.add(Pair.of(constr, constrType)); 907 } 908 } 909 } 910 911 return candidateConstructors; 912 } 913 914 @Override 915 public String documentation(String code, int cursor) { 916 suspendIndexing(); 917 try { 918 return documentationImpl(code, cursor); 919 } finally { 920 resumeIndexing(); 921 } 922 } 923 924 private String documentationImpl(String code, int cursor) { 925 code = code.substring(0, cursor); 926 if (code.trim().isEmpty()) { //TODO: comment handling 927 code += ";"; 928 } 929 930 if (guessKind(code) == Kind.IMPORT) 931 return null; 932 933 OuterWrap codeWrap = wrapInClass(Wrap.methodWrap(code)); 934 AnalyzeTask at = proc.taskFactory.new AnalyzeTask(codeWrap); 935 SourcePositions sp = at.trees().getSourcePositions(); 936 CompilationUnitTree topLevel = at.firstCuTree(); 937 TreePath tp = pathFor(topLevel, sp, codeWrap.snippetIndexToWrapIndex(cursor)); 938 939 if (tp == null) 940 return null; 941 942 TreePath prevPath = null; 943 while (tp != null && tp.getLeaf().getKind() != Kind.METHOD_INVOCATION && tp.getLeaf().getKind() != Kind.NEW_CLASS) { 944 prevPath = tp; 1014 1015 } else { 1016 header.append(p.asType()); 1017 } 1018 header.append(" "); 1019 header.append(p.getSimpleName()); 1020 sep = ", "; 1021 } 1022 header.append(")"); 1023 return header.toString(); 1024 default: 1025 return el.toString(); 1026 } 1027 } 1028 private TypeMirror unwrapArrayType(TypeMirror arrayType) { 1029 if (arrayType.getKind() == TypeKind.ARRAY) { 1030 return ((ArrayType)arrayType).getComponentType(); 1031 } 1032 return arrayType; 1033 } 1034 1035 @Override 1036 public String analyzeType(String code, int cursor) { 1037 code = code.substring(0, cursor); 1038 if (!analyzeCompletion(code).completeness.isComplete) 1039 return null; 1040 1041 if (code.trim().isEmpty()) { //TODO: comment handling 1042 code += ";"; 1043 } 1044 OuterWrap codeWrap; 1045 switch (guessKind(code)) { 1046 case IMPORT: 1047 case METHOD: 1048 return null; 1049 default: 1050 codeWrap = wrapInClass(Wrap.methodWrap(code)); 1051 break; 1052 } 1053 AnalyzeTask at = proc.taskFactory.new AnalyzeTask(codeWrap); 1054 SourcePositions sp = at.trees().getSourcePositions(); 1055 CompilationUnitTree topLevel = at.firstCuTree(); 1056 TreePath tp = pathFor(topLevel, sp, codeWrap.snippetIndexToWrapIndex(code.length())); 1057 TypeMirror type = at.trees().getTypeMirror(tp); 1058 1059 if (type == null) 1060 return null; 1061 1062 switch (type.getKind()) { 1063 case ERROR: case NONE: case OTHER: 1064 case PACKAGE: case VOID: 1065 return null; //not usable 1066 case NULL: 1067 type = at.getElements().getTypeElement("java.lang.Object").asType(); 1068 break; 1069 } 1070 1071 return TypePrinter.printType(at, proc, type); 1072 } 1073 1074 @Override 1075 public IndexResult getDeclaredSymbols(String code, int cursor) { 1076 code = code.substring(0, cursor); 1077 if (code.trim().isEmpty()) { 1078 return new IndexResult(Collections.emptyList(), -1, true, false); 1079 } 1080 OuterWrap codeWrap; 1081 switch (guessKind(code)) { 1082 case IMPORT: 1083 case METHOD: 1084 return new IndexResult(Collections.emptyList(), -1, true, false); 1085 default: 1086 codeWrap = wrapInClass(Wrap.methodWrap(code)); 1087 break; 1088 } 1089 AnalyzeTask at = proc.taskFactory.new AnalyzeTask(codeWrap); 1090 SourcePositions sp = at.trees().getSourcePositions(); 1091 CompilationUnitTree topLevel = at.firstCuTree(); 1092 TreePath tp = pathFor(topLevel, sp, codeWrap.snippetIndexToWrapIndex(code.length())); 1093 if (tp.getLeaf().getKind() != Kind.IDENTIFIER) { 1094 return new IndexResult(Collections.emptyList(), -1, true, false); 1095 } 1096 Scope scope = at.trees().getScope(tp); 1097 TypeMirror type = at.trees().getTypeMirror(tp); 1098 Element el = at.trees().getElement(tp); 1099 1100 boolean erroneous = (type.getKind() == TypeKind.ERROR && el.getKind() == ElementKind.CLASS) || 1101 (el.getKind() == ElementKind.PACKAGE && el.getEnclosedElements().isEmpty()); 1102 String simpleName = ((IdentifierTree) tp.getLeaf()).getName().toString(); 1103 boolean upToDate; 1104 List<String> result; 1105 1106 synchronized (currentIndexes) { 1107 upToDate = classpathVersion == indexVersion; 1108 result = new ArrayList<>( 1109 currentIndexes.values() 1110 .stream() 1111 .flatMap(idx -> idx.classSimpleName2FQN.getOrDefault(simpleName, 1112 Collections.emptyList()).stream()) 1113 .distinct() 1114 .filter(fqn -> isAccessible(at, scope, fqn)) 1115 .collect(Collectors.toSet()) 1116 ); 1117 } 1118 1119 return new IndexResult(result, simpleName.length(), upToDate, !erroneous); 1120 } 1121 1122 private boolean isAccessible(AnalyzeTask at, Scope scope, String fqn) { 1123 TypeElement type = at.getElements().getTypeElement(fqn); 1124 if (type == null) 1125 return false; 1126 return at.trees().isAccessible(scope, type); 1127 } 1128 1129 private static final Map<Path, ClassIndex> PATH_TO_INDEX = new HashMap<>(); 1130 private static final ExecutorService INDEXER = Executors.newFixedThreadPool(1, r -> { 1131 Thread t = new Thread(r); 1132 t.setDaemon(true); 1133 t.setUncaughtExceptionHandler((thread, ex) -> ex.printStackTrace()); 1134 return t; 1135 }); 1136 1137 private final Object suspendLock = new Object(); 1138 private int suspend; 1139 1140 private void waitIndexingNotSuspended() { 1141 boolean suspendedNotified = false; 1142 synchronized (suspendLock) { 1143 while (suspend > 0) { 1144 if (!suspendedNotified) { 1145 suspendedNotified = true; 1146 } 1147 try { 1148 suspendLock.wait(); 1149 } catch (InterruptedException ex) { 1150 } 1151 } 1152 } 1153 } 1154 1155 public void suspendIndexing() { 1156 synchronized (suspendLock) { 1157 suspend++; 1158 } 1159 } 1160 1161 public void resumeIndexing() { 1162 synchronized (suspendLock) { 1163 if (--suspend == 0) { 1164 suspendLock.notifyAll(); 1165 } 1166 } 1167 } 1168 1169 private final Map<Path, ClassIndex> currentIndexes = new HashMap<>(); 1170 private int indexVersion; 1171 private int classpathVersion; 1172 1173 private void refreshIndexes(int version) { 1174 try { 1175 Collection<Path> paths = new ArrayList<>(); 1176 MemoryFileManager fm = proc.taskFactory.fileManager(); 1177 1178 appendPaths(fm, StandardLocation.PLATFORM_CLASS_PATH, paths); 1179 appendPaths(fm, StandardLocation.CLASS_PATH, paths); 1180 appendPaths(fm, StandardLocation.SOURCE_PATH, paths); 1181 1182 Map<Path, ClassIndex> newIndexes = new HashMap<>(); 1183 1184 for (Path p : paths) { 1185 ClassIndex index = PATH_TO_INDEX.get(p); 1186 if (index != null) { 1187 newIndexes.put(p, index); 1188 } 1189 } 1190 1191 synchronized (currentIndexes) { 1192 //temporary setting old data: 1193 currentIndexes.clear(); 1194 currentIndexes.putAll(newIndexes); 1195 } 1196 1197 for (Path p : paths) { 1198 waitIndexingNotSuspended(); 1199 1200 ClassIndex index = indexForPath(p); 1201 newIndexes.put(p, index); 1202 } 1203 1204 synchronized (currentIndexes) { 1205 currentIndexes.clear(); 1206 currentIndexes.putAll(newIndexes); 1207 } 1208 } catch (Exception ex) { 1209 proc.debug(ex, "SourceCodeAnalysisImpl.refreshIndexes(" + version + ")"); 1210 } finally { 1211 synchronized (currentIndexes) { 1212 indexVersion = version; 1213 } 1214 } 1215 } 1216 1217 private void appendPaths(MemoryFileManager fm, Location loc, Collection<Path> paths) { 1218 Iterable<? extends Path> locationPaths = fm.getLocationAsPaths(loc); 1219 if (locationPaths == null) 1220 return ; 1221 for (Path path : locationPaths) { 1222 if (".".equals(path.toString())) { 1223 //skip CWD 1224 continue; 1225 } 1226 1227 paths.add(path); 1228 } 1229 } 1230 1231 public ClassIndex indexForPath(Path path) { 1232 if (isJRTMarkerFile(path)) { 1233 FileSystem jrtfs = FileSystems.getFileSystem(URI.create("jrt:/")); 1234 Path modules = jrtfs.getPath("modules"); 1235 return PATH_TO_INDEX.compute(path, (p, index) -> { 1236 try { 1237 long lastModified = Files.getLastModifiedTime(modules).toMillis(); 1238 if (index == null || index.timestamp != lastModified) { 1239 try (DirectoryStream<Path> stream = Files.newDirectoryStream(modules)) { 1240 index = doIndex(lastModified, path, stream); 1241 } 1242 } 1243 return index; 1244 } catch (IOException ex) { 1245 proc.debug(ex, "SourceCodeAnalysisImpl.indexesForPath(" + path.toString() + ")"); 1246 return new ClassIndex(-1, path, Collections.emptySet(), Collections.emptyMap()); 1247 } 1248 }); 1249 } else if (!Files.isDirectory(path)) { 1250 if (Files.exists(path)) { 1251 return PATH_TO_INDEX.compute(path, (p, index) -> { 1252 try { 1253 long lastModified = Files.getLastModifiedTime(p).toMillis(); 1254 if (index == null || index.timestamp != lastModified) { 1255 ClassLoader cl = SourceCodeAnalysisImpl.class.getClassLoader(); 1256 1257 try (FileSystem zip = FileSystems.newFileSystem(path, cl)) { 1258 index = doIndex(lastModified, path, zip.getRootDirectories()); 1259 } 1260 } 1261 return index; 1262 } catch (IOException ex) { 1263 proc.debug(ex, "SourceCodeAnalysisImpl.indexesForPath(" + path.toString() + ")"); 1264 return new ClassIndex(-1, path, Collections.emptySet(), Collections.emptyMap()); 1265 } 1266 }); 1267 } else { 1268 return new ClassIndex(-1, path, Collections.emptySet(), Collections.emptyMap()); 1269 } 1270 } else { 1271 return PATH_TO_INDEX.compute(path, (p, index) -> { 1272 //no persistence for directories, as we cannot check timestamps: 1273 if (index == null) { 1274 index = doIndex(-1, path, Arrays.asList(p)); 1275 } 1276 return index; 1277 }); 1278 } 1279 } 1280 1281 static boolean isJRTMarkerFile(Path path) { 1282 return path.equals(Paths.get("JRT_MARKER_FILE")); 1283 } 1284 1285 private ClassIndex doIndex(long timestamp, Path originalPath, Iterable<? extends Path> dirs) { 1286 Set<String> packages = new HashSet<>(); 1287 Map<String, Collection<String>> classSimpleName2FQN = new HashMap<>(); 1288 1289 for (Path d : dirs) { 1290 try { 1291 Files.walkFileTree(d, new FileVisitor<Path>() { 1292 int depth; 1293 @Override 1294 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { 1295 waitIndexingNotSuspended(); 1296 if (depth++ == 0) 1297 return FileVisitResult.CONTINUE; 1298 String dirName = dir.getFileName().toString(); 1299 String sep = dir.getFileSystem().getSeparator(); 1300 dirName = dirName.endsWith(sep) ? dirName.substring(0, dirName.length() - sep.length()) 1301 : dirName; 1302 if (SourceVersion.isIdentifier(dirName)) 1303 return FileVisitResult.CONTINUE; 1304 return FileVisitResult.SKIP_SUBTREE; 1305 } 1306 @Override 1307 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 1308 waitIndexingNotSuspended(); 1309 if (file.getFileName().toString().endsWith(".class")) { 1310 String relativePath = d.relativize(file).toString(); 1311 String binaryName = relativePath.substring(0, relativePath.length() - 6).replace('/', '.'); 1312 int packageDot = binaryName.lastIndexOf('.'); 1313 if (packageDot > (-1)) { 1314 packages.add(binaryName.substring(0, packageDot)); 1315 } 1316 String typeName = binaryName.replace('$', '.'); 1317 addClassName2Map(classSimpleName2FQN, typeName); 1318 } 1319 return FileVisitResult.CONTINUE; 1320 } 1321 @Override 1322 public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { 1323 return FileVisitResult.CONTINUE; 1324 } 1325 @Override 1326 public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { 1327 depth--; 1328 return FileVisitResult.CONTINUE; 1329 } 1330 }); 1331 } catch (IOException ex) { 1332 proc.debug(ex, "doIndex(" + d.toString() + ")"); 1333 } 1334 } 1335 1336 return new ClassIndex(timestamp, originalPath, packages, classSimpleName2FQN); 1337 } 1338 1339 private static void addClassName2Map(Map<String, Collection<String>> classSimpleName2FQN, String typeName) { 1340 int simpleNameDot = typeName.lastIndexOf('.'); 1341 classSimpleName2FQN.computeIfAbsent(typeName.substring(simpleNameDot + 1), n -> new LinkedHashSet<>()) 1342 .add(typeName); 1343 } 1344 1345 public static final class ClassIndex { 1346 public final long timestamp; 1347 public final Path forPath; 1348 public final Set<String> packages; 1349 public final Map<String, Collection<String>> classSimpleName2FQN; 1350 1351 public ClassIndex(long timestamp, Path forPath, Set<String> packages, Map<String, Collection<String>> classSimpleName2FQN) { 1352 this.timestamp = timestamp; 1353 this.forPath = forPath; 1354 this.packages = packages; 1355 this.classSimpleName2FQN = classSimpleName2FQN; 1356 } 1357 1358 } 1359 1360 public void waitBackgroundTaskFinished() throws Exception { 1361 boolean upToDate; 1362 synchronized (currentIndexes) { 1363 upToDate = classpathVersion == indexVersion; 1364 } 1365 while (!upToDate) { 1366 INDEXER.submit(() -> {}).get(); 1367 synchronized (currentIndexes) { 1368 upToDate = classpathVersion == indexVersion; 1369 } 1370 } 1371 } 1372 } |