< 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 >