< prev index next >

src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java

Print this page




  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 }
< prev index next >