< prev index next >

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

Print this page

        

*** 77,94 **** --- 77,105 ---- import java.io.IOException; import java.net.URI; import java.nio.file.DirectoryStream; import java.nio.file.FileSystem; import java.nio.file.FileSystems; + import java.nio.file.FileVisitResult; + import java.nio.file.FileVisitor; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; + import java.nio.file.attribute.BasicFileAttributes; + import java.util.Arrays; + import java.util.Collection; import java.util.Comparator; + import java.util.HashMap; import java.util.HashSet; + import java.util.LinkedHashSet; + import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; + import java.util.concurrent.ExecutorService; + import java.util.concurrent.Executors; import java.util.function.Function; + import java.util.prefs.Preferences; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import static java.util.stream.Collectors.collectingAndThen; import static java.util.stream.Collectors.joining;
*** 97,106 **** --- 108,118 ---- import static java.util.stream.Collectors.toSet; import java.util.stream.Stream; import java.util.stream.StreamSupport; import javax.lang.model.SourceVersion; + import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.PackageElement; import javax.lang.model.element.QualifiedNameable; import javax.lang.model.element.VariableElement; import javax.lang.model.type.ArrayType;
*** 122,131 **** --- 134,147 ---- private final CompletenessAnalyzer ca; SourceCodeAnalysisImpl(JShell proc) { this.proc = proc; this.ca = new CompletenessAnalyzer(proc); + + int cpVersion = classpathVersion = 1; + + INDEXER.submit(() -> refreshIndexes(cpVersion)); } @Override public CompletionInfo analyzeCompletion(String srcInput) { MaskCommentsAndModifiers mcm = new MaskCommentsAndModifiers(srcInput, false);
*** 201,210 **** --- 217,235 ---- //TODO: would be better handled through a lexer: private final Pattern JAVA_IDENTIFIER = Pattern.compile("\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"); @Override public List<Suggestion> completionSuggestions(String code, int cursor, int[] anchor) { + suspendIndexing(); + try { + return completionSuggestionsImpl(code, cursor, anchor); + } finally { + resumeIndexing(); + } + } + + private List<Suggestion> completionSuggestionsImpl(String code, int cursor, int[] anchor) { code = code.substring(0, cursor); Matcher m = JAVA_IDENTIFIER.matcher(code); String identifier = ""; while (m.find()) { if (m.end() == code.length()) {
*** 587,705 **** .map(tk -> (Type)(tk == TypeKind.VOID ? types.getNoType(tk) : types.getPrimitiveType(tk))) .map(Type::asElement) .collect(toList()); } - private Set<String> emptyContextPackages = null; - void classpathChanged() { ! emptyContextPackages = null; ! } ! ! private Set<PackageElement> listPackages(AnalyzeTask at, String enclosingPackage) { ! Set<String> packs; ! ! if (enclosingPackage.isEmpty() && emptyContextPackages != null) { ! packs = emptyContextPackages; ! } else { ! packs = new HashSet<>(); ! listPackages(StandardLocation.PLATFORM_CLASS_PATH, enclosingPackage, packs); ! listPackages(StandardLocation.CLASS_PATH, enclosingPackage, packs); ! listPackages(StandardLocation.SOURCE_PATH, enclosingPackage, packs); ! ! if (enclosingPackage.isEmpty()) { ! emptyContextPackages = packs; } } ! return packs.stream() ! .map(pkg -> createPackageElement(at, pkg)) .collect(Collectors.toSet()); } private PackageElement createPackageElement(AnalyzeTask at, String packageName) { Names names = Names.instance(at.getContext()); Symtab syms = Symtab.instance(at.getContext()); PackageElement existing = syms.enterPackage(names.fromString(packageName)); return existing; } - private void listPackages(Location loc, String enclosing, Set<String> packs) { - Iterable<? extends Path> paths = proc.taskFactory.fileManager().getLocationAsPaths(loc); - - if (paths == null) - return ; - - for (Path p : paths) { - listPackages(p, enclosing, packs); - } - } - - private void listPackages(Path path, String enclosing, Set<String> packages) { - try { - if (path.equals(Paths.get("JRT_MARKER_FILE"))) { - FileSystem jrtfs = FileSystems.getFileSystem(URI.create("jrt:/")); - Path modules = jrtfs.getPath("modules"); - try (DirectoryStream<Path> stream = Files.newDirectoryStream(modules)) { - for (Path c : stream) { - listDirectory(c, enclosing, packages); - } - } - } else if (!Files.isDirectory(path)) { - if (Files.exists(path)) { - ClassLoader cl = SourceCodeAnalysisImpl.class.getClassLoader(); - - try (FileSystem zip = FileSystems.newFileSystem(path, cl)) { - listDirectory(zip.getRootDirectories().iterator().next(), enclosing, packages); - } - } - } else { - listDirectory(path, enclosing, packages); - } - } catch (IOException ex) { - proc.debug(ex, "SourceCodeAnalysisImpl.listPackages(" + path.toString() + ", " + enclosing + ", " + packages + ")"); - } - } - - private void listDirectory(Path path, String enclosing, Set<String> packages) throws IOException { - String separator = path.getFileSystem().getSeparator(); - Path resolved = path.resolve(enclosing.replace(".", separator)); - - if (Files.isDirectory(resolved)) { - try (DirectoryStream<Path> ds = Files.newDirectoryStream(resolved)) { - for (Path entry : ds) { - String name = pathName(entry); - - if (SourceVersion.isIdentifier(name) && - Files.isDirectory(entry) && - validPackageCandidate(entry)) { - packages.add(enclosing + (enclosing.isEmpty() ? "" : ".") + name); - } - } - } - } - } - - private boolean validPackageCandidate(Path p) throws IOException { - try (Stream<Path> dir = Files.list(p)) { - return dir.anyMatch(e -> Files.isDirectory(e) && SourceVersion.isIdentifier(pathName(e)) || - e.getFileName().toString().endsWith(".class")); - } - } - - private String pathName(Path p) { - String separator = p.getFileSystem().getSeparator(); - String name = p.getFileName().toString(); - - if (name.endsWith(separator)) //jars have '/' appended - name = name.substring(0, name.length() - separator.length()); - - return name; - } - private Element createArrayLengthSymbol(AnalyzeTask at, TypeMirror site) { Name length = Names.instance(at.getContext()).length; Type intType = Symtab.instance(at.getContext()).intType; return new VarSymbol(Flags.PUBLIC | Flags.FINAL, length, intType, ((Type) site).tsym); --- 612,653 ---- .map(tk -> (Type)(tk == TypeKind.VOID ? types.getNoType(tk) : types.getPrimitiveType(tk))) .map(Type::asElement) .collect(toList()); } void classpathChanged() { ! synchronized (currentIndexes) { ! int cpVersion = ++classpathVersion; ! INDEXER.submit(() -> refreshIndexes(cpVersion)); } } ! private Set<PackageElement> listPackages(AnalyzeTask at, String enclosingPackage) { ! synchronized (currentIndexes) { ! return currentIndexes.values() ! .stream() ! .flatMap(idx -> idx.packages.stream()) ! .filter(p -> enclosingPackage.isEmpty() || p.startsWith(enclosingPackage + ".")) ! .map(p -> { ! int dot = p.indexOf('.', enclosingPackage.length() + 1); ! return dot == (-1) ? p : p.substring(0, dot); ! }) ! .distinct() ! .map(p -> createPackageElement(at, p)) .collect(Collectors.toSet()); } + } private PackageElement createPackageElement(AnalyzeTask at, String packageName) { Names names = Names.instance(at.getContext()); Symtab syms = Symtab.instance(at.getContext()); PackageElement existing = syms.enterPackage(names.fromString(packageName)); return existing; } private Element createArrayLengthSymbol(AnalyzeTask at, TypeMirror site) { Name length = Names.instance(at.getContext()).length; Type intType = Symtab.instance(at.getContext()).intType; return new VarSymbol(Flags.PUBLIC | Flags.FINAL, length, intType, ((Type) site).tsym);
*** 963,972 **** --- 911,929 ---- return candidateConstructors; } @Override public String documentation(String code, int cursor) { + suspendIndexing(); + try { + return documentationImpl(code, cursor); + } finally { + resumeIndexing(); + } + } + + private String documentationImpl(String code, int cursor) { code = code.substring(0, cursor); if (code.trim().isEmpty()) { //TODO: comment handling code += ";"; }
*** 1072,1077 **** --- 1029,1372 ---- if (arrayType.getKind() == TypeKind.ARRAY) { return ((ArrayType)arrayType).getComponentType(); } return arrayType; } + + @Override + public String analyzeType(String code, int cursor) { + code = code.substring(0, cursor); + if (!analyzeCompletion(code).completeness.isComplete) + return null; + + if (code.trim().isEmpty()) { //TODO: comment handling + code += ";"; + } + OuterWrap codeWrap; + switch (guessKind(code)) { + case IMPORT: + case METHOD: + return null; + default: + codeWrap = wrapInClass(Wrap.methodWrap(code)); + break; + } + AnalyzeTask at = proc.taskFactory.new AnalyzeTask(codeWrap); + SourcePositions sp = at.trees().getSourcePositions(); + CompilationUnitTree topLevel = at.firstCuTree(); + TreePath tp = pathFor(topLevel, sp, codeWrap.snippetIndexToWrapIndex(code.length())); + TypeMirror type = at.trees().getTypeMirror(tp); + + if (type == null) + return null; + + switch (type.getKind()) { + case ERROR: case NONE: case OTHER: + case PACKAGE: case VOID: + return null; //not usable + case NULL: + type = at.getElements().getTypeElement("java.lang.Object").asType(); + break; + } + + return TypePrinter.printType(at, proc, type); + } + + @Override + public IndexResult getDeclaredSymbols(String code, int cursor) { + code = code.substring(0, cursor); + if (code.trim().isEmpty()) { + return new IndexResult(Collections.emptyList(), -1, true, false); + } + OuterWrap codeWrap; + switch (guessKind(code)) { + case IMPORT: + case METHOD: + return new IndexResult(Collections.emptyList(), -1, true, false); + default: + codeWrap = wrapInClass(Wrap.methodWrap(code)); + break; + } + AnalyzeTask at = proc.taskFactory.new AnalyzeTask(codeWrap); + SourcePositions sp = at.trees().getSourcePositions(); + CompilationUnitTree topLevel = at.firstCuTree(); + TreePath tp = pathFor(topLevel, sp, codeWrap.snippetIndexToWrapIndex(code.length())); + if (tp.getLeaf().getKind() != Kind.IDENTIFIER) { + return new IndexResult(Collections.emptyList(), -1, true, false); + } + Scope scope = at.trees().getScope(tp); + TypeMirror type = at.trees().getTypeMirror(tp); + Element el = at.trees().getElement(tp); + + boolean erroneous = (type.getKind() == TypeKind.ERROR && el.getKind() == ElementKind.CLASS) || + (el.getKind() == ElementKind.PACKAGE && el.getEnclosedElements().isEmpty()); + String simpleName = ((IdentifierTree) tp.getLeaf()).getName().toString(); + boolean upToDate; + List<String> result; + + synchronized (currentIndexes) { + upToDate = classpathVersion == indexVersion; + result = new ArrayList<>( + currentIndexes.values() + .stream() + .flatMap(idx -> idx.classSimpleName2FQN.getOrDefault(simpleName, + Collections.emptyList()).stream()) + .distinct() + .filter(fqn -> isAccessible(at, scope, fqn)) + .collect(Collectors.toSet()) + ); + } + + return new IndexResult(result, simpleName.length(), upToDate, !erroneous); + } + + private boolean isAccessible(AnalyzeTask at, Scope scope, String fqn) { + TypeElement type = at.getElements().getTypeElement(fqn); + if (type == null) + return false; + return at.trees().isAccessible(scope, type); + } + + private static final Map<Path, ClassIndex> PATH_TO_INDEX = new HashMap<>(); + private static final ExecutorService INDEXER = Executors.newFixedThreadPool(1, r -> { + Thread t = new Thread(r); + t.setDaemon(true); + t.setUncaughtExceptionHandler((thread, ex) -> ex.printStackTrace()); + return t; + }); + + private final Object suspendLock = new Object(); + private int suspend; + + private void waitIndexingNotSuspended() { + boolean suspendedNotified = false; + synchronized (suspendLock) { + while (suspend > 0) { + if (!suspendedNotified) { + suspendedNotified = true; + } + try { + suspendLock.wait(); + } catch (InterruptedException ex) { + } + } + } + } + + public void suspendIndexing() { + synchronized (suspendLock) { + suspend++; + } + } + + public void resumeIndexing() { + synchronized (suspendLock) { + if (--suspend == 0) { + suspendLock.notifyAll(); + } + } + } + + private final Map<Path, ClassIndex> currentIndexes = new HashMap<>(); + private int indexVersion; + private int classpathVersion; + + private void refreshIndexes(int version) { + try { + Collection<Path> paths = new ArrayList<>(); + MemoryFileManager fm = proc.taskFactory.fileManager(); + + appendPaths(fm, StandardLocation.PLATFORM_CLASS_PATH, paths); + appendPaths(fm, StandardLocation.CLASS_PATH, paths); + appendPaths(fm, StandardLocation.SOURCE_PATH, paths); + + Map<Path, ClassIndex> newIndexes = new HashMap<>(); + + for (Path p : paths) { + ClassIndex index = PATH_TO_INDEX.get(p); + if (index != null) { + newIndexes.put(p, index); + } + } + + synchronized (currentIndexes) { + //temporary setting old data: + currentIndexes.clear(); + currentIndexes.putAll(newIndexes); + } + + for (Path p : paths) { + waitIndexingNotSuspended(); + + ClassIndex index = indexForPath(p); + newIndexes.put(p, index); + } + + synchronized (currentIndexes) { + currentIndexes.clear(); + currentIndexes.putAll(newIndexes); + } + } catch (Exception ex) { + proc.debug(ex, "SourceCodeAnalysisImpl.refreshIndexes(" + version + ")"); + } finally { + synchronized (currentIndexes) { + indexVersion = version; + } + } + } + + private void appendPaths(MemoryFileManager fm, Location loc, Collection<Path> paths) { + Iterable<? extends Path> locationPaths = fm.getLocationAsPaths(loc); + if (locationPaths == null) + return ; + for (Path path : locationPaths) { + if (".".equals(path.toString())) { + //skip CWD + continue; + } + + paths.add(path); + } + } + + public ClassIndex indexForPath(Path path) { + if (isJRTMarkerFile(path)) { + FileSystem jrtfs = FileSystems.getFileSystem(URI.create("jrt:/")); + Path modules = jrtfs.getPath("modules"); + return PATH_TO_INDEX.compute(path, (p, index) -> { + try { + long lastModified = Files.getLastModifiedTime(modules).toMillis(); + if (index == null || index.timestamp != lastModified) { + try (DirectoryStream<Path> stream = Files.newDirectoryStream(modules)) { + index = doIndex(lastModified, path, stream); + } + } + return index; + } catch (IOException ex) { + proc.debug(ex, "SourceCodeAnalysisImpl.indexesForPath(" + path.toString() + ")"); + return new ClassIndex(-1, path, Collections.emptySet(), Collections.emptyMap()); + } + }); + } else if (!Files.isDirectory(path)) { + if (Files.exists(path)) { + return PATH_TO_INDEX.compute(path, (p, index) -> { + try { + long lastModified = Files.getLastModifiedTime(p).toMillis(); + if (index == null || index.timestamp != lastModified) { + ClassLoader cl = SourceCodeAnalysisImpl.class.getClassLoader(); + + try (FileSystem zip = FileSystems.newFileSystem(path, cl)) { + index = doIndex(lastModified, path, zip.getRootDirectories()); + } + } + return index; + } catch (IOException ex) { + proc.debug(ex, "SourceCodeAnalysisImpl.indexesForPath(" + path.toString() + ")"); + return new ClassIndex(-1, path, Collections.emptySet(), Collections.emptyMap()); + } + }); + } else { + return new ClassIndex(-1, path, Collections.emptySet(), Collections.emptyMap()); + } + } else { + return PATH_TO_INDEX.compute(path, (p, index) -> { + //no persistence for directories, as we cannot check timestamps: + if (index == null) { + index = doIndex(-1, path, Arrays.asList(p)); + } + return index; + }); + } + } + + static boolean isJRTMarkerFile(Path path) { + return path.equals(Paths.get("JRT_MARKER_FILE")); + } + + private ClassIndex doIndex(long timestamp, Path originalPath, Iterable<? extends Path> dirs) { + Set<String> packages = new HashSet<>(); + Map<String, Collection<String>> classSimpleName2FQN = new HashMap<>(); + + for (Path d : dirs) { + try { + Files.walkFileTree(d, new FileVisitor<Path>() { + int depth; + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + waitIndexingNotSuspended(); + if (depth++ == 0) + return FileVisitResult.CONTINUE; + String dirName = dir.getFileName().toString(); + String sep = dir.getFileSystem().getSeparator(); + dirName = dirName.endsWith(sep) ? dirName.substring(0, dirName.length() - sep.length()) + : dirName; + if (SourceVersion.isIdentifier(dirName)) + return FileVisitResult.CONTINUE; + return FileVisitResult.SKIP_SUBTREE; + } + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + waitIndexingNotSuspended(); + if (file.getFileName().toString().endsWith(".class")) { + String relativePath = d.relativize(file).toString(); + String binaryName = relativePath.substring(0, relativePath.length() - 6).replace('/', '.'); + int packageDot = binaryName.lastIndexOf('.'); + if (packageDot > (-1)) { + packages.add(binaryName.substring(0, packageDot)); + } + String typeName = binaryName.replace('$', '.'); + addClassName2Map(classSimpleName2FQN, typeName); + } + return FileVisitResult.CONTINUE; + } + @Override + public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { + return FileVisitResult.CONTINUE; + } + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + depth--; + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException ex) { + proc.debug(ex, "doIndex(" + d.toString() + ")"); + } + } + + return new ClassIndex(timestamp, originalPath, packages, classSimpleName2FQN); + } + + private static void addClassName2Map(Map<String, Collection<String>> classSimpleName2FQN, String typeName) { + int simpleNameDot = typeName.lastIndexOf('.'); + classSimpleName2FQN.computeIfAbsent(typeName.substring(simpleNameDot + 1), n -> new LinkedHashSet<>()) + .add(typeName); + } + + public static final class ClassIndex { + public final long timestamp; + public final Path forPath; + public final Set<String> packages; + public final Map<String, Collection<String>> classSimpleName2FQN; + + public ClassIndex(long timestamp, Path forPath, Set<String> packages, Map<String, Collection<String>> classSimpleName2FQN) { + this.timestamp = timestamp; + this.forPath = forPath; + this.packages = packages; + this.classSimpleName2FQN = classSimpleName2FQN; + } + + } + + public void waitBackgroundTaskFinished() throws Exception { + boolean upToDate; + synchronized (currentIndexes) { + upToDate = classpathVersion == indexVersion; + } + while (!upToDate) { + INDEXER.submit(() -> {}).get(); + synchronized (currentIndexes) { + upToDate = classpathVersion == indexVersion; + } + } + } }
< prev index next >