langtools/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java
changeset 41865 3ef02797070d
parent 41527 e8f487b79e24
child 42258 a1aafd5ea6ec
--- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java	Tue Nov 01 14:47:07 2016 -0700
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java	Wed Nov 02 07:38:37 2016 +0100
@@ -42,19 +42,17 @@
 import com.sun.source.tree.Tree.Kind;
 import com.sun.source.tree.TypeParameterTree;
 import com.sun.source.tree.VariableTree;
-import com.sun.source.util.JavacTask;
 import com.sun.source.util.SourcePositions;
 import com.sun.source.util.TreePath;
 import com.sun.source.util.TreePathScanner;
-import com.sun.source.util.Trees;
 import com.sun.tools.javac.api.JavacScope;
-import com.sun.tools.javac.api.JavacTaskImpl;
 import com.sun.tools.javac.code.Flags;
 import com.sun.tools.javac.code.Symbol.CompletionFailure;
 import com.sun.tools.javac.code.Symbol.VarSymbol;
 import com.sun.tools.javac.code.Symtab;
 import com.sun.tools.javac.code.Type;
 import com.sun.tools.javac.code.Type.ClassType;
+import jdk.internal.shellsupport.doc.JavadocHelper;
 import com.sun.tools.javac.util.Name;
 import com.sun.tools.javac.util.Names;
 import com.sun.tools.javac.util.Pair;
@@ -105,6 +103,7 @@
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 import static java.util.stream.Collectors.collectingAndThen;
+import static java.util.stream.Collectors.joining;
 import static java.util.stream.Collectors.toCollection;
 import static java.util.stream.Collectors.toList;
 import static java.util.stream.Collectors.toSet;
@@ -123,15 +122,10 @@
 import javax.lang.model.type.TypeKind;
 import javax.lang.model.util.ElementFilter;
 import javax.lang.model.util.Types;
-import javax.tools.JavaCompiler;
 import javax.tools.JavaFileManager.Location;
-import javax.tools.JavaFileObject;
-import javax.tools.StandardJavaFileManager;
 import javax.tools.StandardLocation;
-import javax.tools.ToolProvider;
 
 import static jdk.jshell.Util.REPL_DOESNOTMATTER_CLASS_NAME;
-import static java.util.stream.Collectors.joining;
 import static jdk.jshell.SourceCodeAnalysis.Completeness.DEFINITELY_INCOMPLETE;
 import static jdk.jshell.TreeDissector.printType;
 
@@ -151,6 +145,7 @@
 
     private final JShell proc;
     private final CompletenessAnalyzer ca;
+    private final List<AutoCloseable> closeables = new ArrayList<>();
     private final Map<Path, ClassIndex> currentIndexes = new HashMap<>();
     private int indexVersion;
     private int classpathVersion;
@@ -1097,10 +1092,10 @@
     }
 
     @Override
-    public String documentation(String code, int cursor) {
+    public List<Documentation> documentation(String code, int cursor, boolean computeJavadoc) {
         suspendIndexing();
         try {
-            return documentationImpl(code, cursor);
+            return documentationImpl(code, cursor, computeJavadoc);
         } finally {
             resumeIndexing();
         }
@@ -1112,14 +1107,14 @@
         "-parameters"
     };
 
-    private String documentationImpl(String code, int cursor) {
+    private List<Documentation> documentationImpl(String code, int cursor, boolean computeJavadoc) {
         code = code.substring(0, cursor);
         if (code.trim().isEmpty()) { //TODO: comment handling
             code += ";";
         }
 
         if (guessKind(code) == Kind.IMPORT)
-            return null;
+            return Collections.<Documentation>emptyList();
 
         OuterWrap codeWrap = proc.outerMap.wrapInTrialClass(Wrap.methodWrap(code));
         AnalyzeTask at = proc.taskFactory.new AnalyzeTask(codeWrap, keepParameterNames);
@@ -1128,46 +1123,120 @@
         TreePath tp = pathFor(topLevel, sp, codeWrap.snippetIndexToWrapIndex(cursor));
 
         if (tp == null)
-            return null;
+            return Collections.<Documentation>emptyList();
 
         TreePath prevPath = null;
-        while (tp != null && tp.getLeaf().getKind() != Kind.METHOD_INVOCATION && tp.getLeaf().getKind() != Kind.NEW_CLASS) {
+        while (tp != null && tp.getLeaf().getKind() != Kind.METHOD_INVOCATION &&
+               tp.getLeaf().getKind() != Kind.NEW_CLASS && tp.getLeaf().getKind() != Kind.IDENTIFIER &&
+               tp.getLeaf().getKind() != Kind.MEMBER_SELECT) {
             prevPath = tp;
             tp = tp.getParentPath();
         }
 
         if (tp == null)
-            return null;
+            return Collections.<Documentation>emptyList();
 
+        Stream<Element> elements;
         Iterable<Pair<ExecutableElement, ExecutableType>> candidates;
         List<? extends ExpressionTree> arguments;
 
-        if (tp.getLeaf().getKind() == Kind.METHOD_INVOCATION) {
-            MethodInvocationTree mit = (MethodInvocationTree) tp.getLeaf();
-            candidates = methodCandidates(at, tp);
-            arguments = mit.getArguments();
+        if (tp.getLeaf().getKind() == Kind.METHOD_INVOCATION || tp.getLeaf().getKind() == Kind.NEW_CLASS) {
+            if (tp.getLeaf().getKind() == Kind.METHOD_INVOCATION) {
+                MethodInvocationTree mit = (MethodInvocationTree) tp.getLeaf();
+                candidates = methodCandidates(at, tp);
+                arguments = mit.getArguments();
+            } else {
+                NewClassTree nct = (NewClassTree) tp.getLeaf();
+                candidates = newClassCandidates(at, tp);
+                arguments = nct.getArguments();
+            }
+
+            if (!isEmptyArgumentsContext(arguments)) {
+                List<TypeMirror> actuals = computeActualInvocationTypes(at, arguments, prevPath);
+                List<TypeMirror> fullActuals = actuals != null ? actuals : Collections.emptyList();
+
+                candidates =
+                        this.filterExecutableTypesByArguments(at, candidates, fullActuals)
+                            .stream()
+                            .filter(method -> parameterType(method.fst, method.snd, fullActuals.size(), true).findAny().isPresent())
+                            .collect(Collectors.toList());
+            }
+
+            elements = Util.stream(candidates).map(method -> method.fst);
+        } else if (tp.getLeaf().getKind() == Kind.IDENTIFIER || tp.getLeaf().getKind() == Kind.MEMBER_SELECT) {
+            Element el = at.trees().getElement(tp);
+
+            if (el == null ||
+                el.asType().getKind() == TypeKind.ERROR ||
+                (el.getKind() == ElementKind.PACKAGE && el.getEnclosedElements().isEmpty())) {
+                //erroneous element:
+                return Collections.<Documentation>emptyList();
+            }
+
+            elements = Stream.of(el);
         } else {
-            NewClassTree nct = (NewClassTree) tp.getLeaf();
-            candidates = newClassCandidates(at, tp);
-            arguments = nct.getArguments();
+            return Collections.<Documentation>emptyList();
+        }
+
+        List<Documentation> result = Collections.emptyList();
+
+        try (JavadocHelper helper = JavadocHelper.create(at.task, findSources())) {
+            result = elements.map(el -> constructDocumentation(at, helper, el, computeJavadoc))
+                             .filter(r -> r != null)
+                             .collect(Collectors.toList());
+        } catch (IOException ex) {
+            proc.debug(ex, "JavadocHelper.close()");
         }
 
-        if (!isEmptyArgumentsContext(arguments)) {
-            List<TypeMirror> actuals = computeActualInvocationTypes(at, arguments, prevPath);
-            List<TypeMirror> fullActuals = actuals != null ? actuals : Collections.emptyList();
+        return result;
+    }
+
+    private Documentation constructDocumentation(AnalyzeTask at, JavadocHelper helper, Element el, boolean computeJavadoc) {
+        String javadoc = null;
+        try {
+            if (hasSyntheticParameterNames(el)) {
+                el = helper.getSourceElement(el);
+            }
+            if (computeJavadoc) {
+                javadoc = helper.getResolvedDocComment(el);
+            }
+        } catch (IOException ex) {
+            proc.debug(ex, "SourceCodeAnalysisImpl.element2String(..., " + el + ")");
+        }
+        String signature = Util.expunge(elementHeader(at, el, !hasSyntheticParameterNames(el), true));
+        return new DocumentationImpl(signature,  javadoc);
+    }
 
-            candidates =
-                    this.filterExecutableTypesByArguments(at, candidates, fullActuals)
-                        .stream()
-                        .filter(method -> parameterType(method.fst, method.snd, fullActuals.size(), true).findAny().isPresent())
-                        .collect(Collectors.toList());
+    public void close() {
+        for (AutoCloseable closeable : closeables) {
+            try {
+                closeable.close();
+            } catch (Exception ex) {
+                proc.debug(ex, "SourceCodeAnalysisImpl.close()");
+            }
+        }
+    }
+
+    private static final class DocumentationImpl implements Documentation {
+
+        private final String signature;
+        private final String javadoc;
+
+        public DocumentationImpl(String signature, String javadoc) {
+            this.signature = signature;
+            this.javadoc = javadoc;
         }
 
-        try (SourceCache sourceCache = new SourceCache(at)) {
-            return Util.stream(candidates)
-                    .map(method -> Util.expunge(element2String(sourceCache, method.fst)))
-                    .collect(joining("\n"));
+        @Override
+        public String signature() {
+            return signature;
         }
+
+        @Override
+        public String javadoc() {
+            return javadoc;
+        }
+
     }
 
     private boolean isEmptyArgumentsContext(List<? extends ExpressionTree> arguments) {
@@ -1178,18 +1247,6 @@
         return false;
     }
 
-    private String element2String(SourceCache sourceCache, Element el) {
-        try {
-            if (hasSyntheticParameterNames(el)) {
-                el = sourceCache.getSourceMethod(el);
-            }
-        } catch (IOException ex) {
-            proc.debug(ex, "SourceCodeAnalysisImpl.element2String(..., " + el + ")");
-        }
-
-        return Util.expunge(elementHeader(sourceCache.originalTask, el, !hasSyntheticParameterNames(el)));
-    }
-
     private boolean hasSyntheticParameterNames(Element el) {
         if (el.getKind() != ElementKind.CONSTRUCTOR && el.getKind() != ElementKind.METHOD)
             return false;
@@ -1204,119 +1261,6 @@
                  .allMatch(param -> param.getSimpleName().toString().startsWith("arg"));
     }
 
-    private final class SourceCache implements AutoCloseable {
-        private final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
-        private final Map<String, Map<String, Element>> topLevelName2Signature2Method = new HashMap<>();
-        private final AnalyzeTask originalTask;
-        private final StandardJavaFileManager fm;
-
-        public SourceCache(AnalyzeTask originalTask) {
-            this.originalTask = originalTask;
-            List<Path> sources = findSources();
-            if (sources.iterator().hasNext()) {
-                StandardJavaFileManager fm = compiler.getStandardFileManager(null, null, null);
-                try {
-                    fm.setLocationFromPaths(StandardLocation.SOURCE_PATH, sources);
-                } catch (IOException ex) {
-                    proc.debug(ex, "SourceCodeAnalysisImpl.SourceCache.<init>(...)");
-                    try {
-                        fm.close();
-                    } catch (IOException closeEx) {
-                        proc.debug(closeEx, "SourceCodeAnalysisImpl.SourceCache.close()");
-                    }
-                    fm = null;
-                }
-                this.fm = fm;
-            } else {
-                //don't waste time if there are no sources
-                this.fm = null;
-            }
-        }
-
-        public Element getSourceMethod(Element method) throws IOException {
-            if (fm == null)
-                return method;
-
-            TypeElement type = topLevelType(method);
-
-            if (type == null)
-                return method;
-
-            String binaryName = originalTask.task.getElements().getBinaryName(type).toString();
-
-            Map<String, Element> cache = topLevelName2Signature2Method.get(binaryName);
-
-            if (cache == null) {
-                topLevelName2Signature2Method.put(binaryName, cache = createMethodCache(binaryName));
-            }
-
-            String handle = elementHeader(originalTask, method, false);
-
-            return cache.getOrDefault(handle, method);
-        }
-
-        private TypeElement topLevelType(Element el) {
-            while (el != null && el.getEnclosingElement().getKind() != ElementKind.PACKAGE) {
-                el = el.getEnclosingElement();
-            }
-
-            return el != null && (el.getKind().isClass() || el.getKind().isInterface()) ? (TypeElement) el : null;
-        }
-
-        private Map<String, Element> createMethodCache(String binaryName) throws IOException {
-            Pair<JavacTask, CompilationUnitTree> source = findSource(binaryName);
-
-            if (source == null)
-                return Collections.emptyMap();
-
-            Map<String, Element> signature2Method = new HashMap<>();
-            Trees trees = Trees.instance(source.fst);
-
-            new TreePathScanner<Void, Void>() {
-                @Override
-                public Void visitMethod(MethodTree node, Void p) {
-                    Element currentMethod = trees.getElement(getCurrentPath());
-
-                    if (currentMethod != null) {
-                        signature2Method.put(elementHeader(originalTask, currentMethod, false), currentMethod);
-                    }
-
-                    return null;
-                }
-            }.scan(source.snd, null);
-
-            return signature2Method;
-        }
-
-        private Pair<JavacTask, CompilationUnitTree> findSource(String binaryName) throws IOException {
-            JavaFileObject jfo = fm.getJavaFileForInput(StandardLocation.SOURCE_PATH,
-                                                        binaryName,
-                                                        JavaFileObject.Kind.SOURCE);
-
-            if (jfo == null)
-                return null;
-
-            List<JavaFileObject> jfos = Arrays.asList(jfo);
-            JavacTaskImpl task = (JavacTaskImpl) compiler.getTask(null, fm, d -> {}, null, null, jfos);
-            Iterable<? extends CompilationUnitTree> cuts = task.parse();
-
-            task.enter();
-
-            return Pair.of(task, cuts.iterator().next());
-        }
-
-        @Override
-        public void close() {
-            try {
-                if (fm != null) {
-                    fm.close();
-                }
-            } catch (IOException ex) {
-                proc.debug(ex, "SourceCodeAnalysisImpl.SourceCache.close()");
-            }
-        }
-    }
-
     private List<Path> availableSources;
 
     private List<Path> findSources() {
@@ -1328,25 +1272,59 @@
         Path srcZip = home.resolve("src.zip");
         if (!Files.isReadable(srcZip))
             srcZip = home.getParent().resolve("src.zip");
-        if (Files.isReadable(srcZip))
-            result.add(srcZip);
+        if (Files.isReadable(srcZip)) {
+            boolean keepOpen = false;
+            FileSystem zipFO = null;
+
+            try {
+                URI uri = URI.create("jar:" + srcZip.toUri());
+                zipFO = FileSystems.newFileSystem(uri, Collections.emptyMap());
+                Path root = zipFO.getRootDirectories().iterator().next();
+
+                if (Files.exists(root.resolve("java/lang/Object.java".replace("/", zipFO.getSeparator())))) {
+                    //non-modular format:
+                    result.add(srcZip);
+                } else if (Files.exists(root.resolve("java.base/java/lang/Object.java".replace("/", zipFO.getSeparator())))) {
+                    //modular format:
+                    try (DirectoryStream<Path> ds = Files.newDirectoryStream(root)) {
+                        for (Path p : ds) {
+                            if (Files.isDirectory(p)) {
+                                result.add(p);
+                            }
+                        }
+                    }
+
+                    keepOpen = true;
+                }
+            } catch (IOException ex) {
+                proc.debug(ex, "SourceCodeAnalysisImpl.findSources()");
+            } finally {
+                if (zipFO != null) {
+                    if (keepOpen) {
+                        closeables.add(zipFO);
+                    } else {
+                        try {
+                            zipFO.close();
+                        } catch (IOException ex) {
+                            proc.debug(ex, "SourceCodeAnalysisImpl.findSources()");
+                        }
+                    }
+                }
+            }
+        }
         return availableSources = result;
     }
 
-    private String elementHeader(AnalyzeTask at, Element el) {
-        return elementHeader(at, el, true);
-    }
-
-    private String elementHeader(AnalyzeTask at, Element el, boolean includeParameterNames) {
+    private String elementHeader(AnalyzeTask at, Element el, boolean includeParameterNames, boolean useFQN) {
         switch (el.getKind()) {
             case ANNOTATION_TYPE: case CLASS: case ENUM: case INTERFACE: {
                 TypeElement type = (TypeElement)el;
                 String fullname = type.getQualifiedName().toString();
                 Element pkg = at.getElements().getPackageOf(el);
-                String name = pkg == null ? fullname :
+                String name = pkg == null || useFQN ? fullname :
                         proc.maps.fullClassNameAndPackageToClass(fullname, ((PackageElement)pkg).getQualifiedName().toString());
 
-                return name + typeParametersOpt(at, type.getTypeParameters());
+                return name + typeParametersOpt(at, type.getTypeParameters(), includeParameterNames);
             }
             case TYPE_PARAMETER: {
                 TypeParameterElement tp = (TypeParameterElement)el;
@@ -1363,9 +1341,9 @@
                                 .collect(joining(" & "));
             }
             case FIELD:
-                return elementHeader(at, el.getEnclosingElement()) + "." + el.getSimpleName() + ":" + el.asType();
+                return elementHeader(at, el.getEnclosingElement(), includeParameterNames, false) + "." + el.getSimpleName() + ":" + el.asType();
             case ENUM_CONSTANT:
-                return elementHeader(at, el.getEnclosingElement()) + "." + el.getSimpleName();
+                return elementHeader(at, el.getEnclosingElement(), includeParameterNames, false) + "." + el.getSimpleName();
             case EXCEPTION_PARAMETER: case LOCAL_VARIABLE: case PARAMETER: case RESOURCE_VARIABLE:
                 return el.getSimpleName() + ":" + el.asType();
             case CONSTRUCTOR: case METHOD: {
@@ -1379,20 +1357,20 @@
                     header.append(printType(at, proc, method.getReturnType())).append(" ");
                 } else {
                     // type parameters for the constructor
-                    String typeParameters = typeParametersOpt(at, method.getTypeParameters());
+                    String typeParameters = typeParametersOpt(at, method.getTypeParameters(), includeParameterNames);
                     if (!typeParameters.isEmpty()) {
                         header.append(typeParameters).append(" ");
                     }
                 }
 
                 // receiver type
-                String clazz = elementHeader(at, el.getEnclosingElement());
+                String clazz = elementHeader(at, el.getEnclosingElement(), includeParameterNames, false);
                 header.append(clazz);
 
                 if (isMethod) {
                     //method name with type parameters
                     (clazz.isEmpty() ? header : header.append("."))
-                            .append(typeParametersOpt(at, method.getTypeParameters()))
+                            .append(typeParametersOpt(at, method.getTypeParameters(), includeParameterNames))
                             .append(el.getSimpleName());
                 }
 
@@ -1435,10 +1413,10 @@
         }
         return arrayType;
     }
-    private String typeParametersOpt(AnalyzeTask at, List<? extends TypeParameterElement> typeParameters) {
+    private String typeParametersOpt(AnalyzeTask at, List<? extends TypeParameterElement> typeParameters, boolean includeParameterNames) {
         return typeParameters.isEmpty() ? ""
                 : typeParameters.stream()
-                        .map(tp -> elementHeader(at, tp))
+                        .map(tp -> elementHeader(at, tp, includeParameterNames, false))
                         .collect(joining(", ", "<", ">"));
     }