8190795: jjs should show javadoc for java methods on shift-tab
authorsundar
Mon, 06 Nov 2017 22:05:53 +0530
changeset 47704 38aa08d2ec6c
parent 47703 dbfac941197a
child 47705 a6f8cacdef93
8190795: jjs should show javadoc for java methods on shift-tab Reviewed-by: hannesw, jlaskey
make/CompileJavaModules.gmk
make/nashorn/build.xml
src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java
src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/resources/jjs.js
--- a/make/CompileJavaModules.gmk	Mon Nov 06 14:10:39 2017 +0100
+++ b/make/CompileJavaModules.gmk	Mon Nov 06 22:05:53 2017 +0530
@@ -393,6 +393,10 @@
 
 ################################################################################
 
+jdk.scripting.nashorn.shell_COPY += .js .properties
+
+################################################################################
+
 jdk.rmic_SETUP := GENERATE_JDKBYTECODE_NOWARNINGS
 jdk.rmic_CLEAN += .properties
 
--- a/make/nashorn/build.xml	Mon Nov 06 14:10:39 2017 +0100
+++ b/make/nashorn/build.xml	Mon Nov 06 22:05:53 2017 +0530
@@ -227,6 +227,9 @@
     </copy>
     <copy file="${dynalink.module.src.dir}/jdk/dynalink/support/messages.properties" todir="${dynalink.module.classes.dir}/jdk/dynalink/support"/>
     <copy file="${nashorn.module.src.dir}/jdk/nashorn/internal/codegen/anchor.properties" todir="${nashorn.module.classes.dir}/jdk/nashorn/internal/codegen"/>
+    <copy todir="${nashorn.shell.module.classes.dir}/jdk/nashorn/tools/jjs/resources">
+       <fileset dir="${nashorn.shell.module.src.dir}/jdk/nashorn/tools/jjs/resources/"/>
+    </copy>
 
     <echo message="version_string=${nashorn.fullversion}" file="${nashorn.module.classes.dir}/jdk/nashorn/internal/runtime/resources/version.properties"/>
     <echo file="${nashorn.module.classes.dir}/jdk/nashorn/internal/runtime/resources/version.properties" append="true">${line.separator}</echo>
--- a/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java	Mon Nov 06 14:10:39 2017 +0100
+++ b/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java	Mon Nov 06 22:05:53 2017 +0530
@@ -36,6 +36,8 @@
 import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.net.URI;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
 import java.util.concurrent.Callable;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -52,6 +54,7 @@
 import jdk.nashorn.internal.runtime.ScriptingFunctions;
 import jdk.nashorn.internal.runtime.ScriptObject;
 import jdk.nashorn.internal.runtime.ScriptRuntime;
+import jdk.nashorn.internal.runtime.Source;
 import jdk.nashorn.tools.Shell;
 
 /**
@@ -122,44 +125,18 @@
             Context.setGlobal(global);
         }
 
-        // Check if java.desktop module is available and we're running in non-headless mode.
-        // We access AWT via script to avoid direct dependency on java.desktop module.
-        final boolean isHeadless = (boolean) context.eval(global,
-            "(function() { \n" +
-            "    var env = java.awt.GraphicsEnvironment; \n" +
-            "    return env && typeof env.isHeadless == 'function'? \n" +
-            "        env.isHeadless() : true; \n" +
-            "})()",
-            global, "<headless-check>");
+        // jjs.js is read and evaluated. The result of the evaluation is an "exports" object. This is done
+        // to avoid polluting javascript global scope. These are internal funtions are retrieved from the
+        // 'exports' object and used from here.
+        final ScriptObject jjsObj = (ScriptObject)context.eval(global, readJJSScript(), global, "<jjs.js>");
 
-        // Function that shows a JFileChooser dialog and returns the file name chosen (if chosen).
-        // We access swing from script to avoid direct dependency on java.desktop module.
-        final ScriptFunction fileChooserFunc = isHeadless? null : (ScriptFunction) context.eval(global,
-            "(function() { \n" +
-            "    var ExtensionFilter = javax.swing.filechooser.FileNameExtensionFilter; \n" +
-            "    var JFileChooser = javax.swing.JFileChooser; \n" +
-            "    function run() { \n" +
-            "        var chooser = new JFileChooser(); \n" +
-            "        chooser.fileFilter = new ExtensionFilter('JavaScript Files', 'js'); \n" +
-            "        var retVal = chooser.showOpenDialog(null);  \n" +
-            "        return retVal == JFileChooser.APPROVE_OPTION ?  \n" +
-            "            chooser.selectedFile.absolutePath : null; \n" +
-            "    }; \n" +
-            "    var fileChooserTask = new java.util.concurrent.FutureTask(run); \n" +
-            "    javax.swing.SwingUtilities.invokeLater(fileChooserTask); \n" +
-            "    return fileChooserTask.get(); \n" +
-            "})",
-            global, "<file-chooser>");
+        final boolean isHeadless = (boolean) ScriptRuntime.apply((ScriptFunction) jjsObj.get("isHeadless"), null);
+        final ScriptFunction fileChooserFunc = isHeadless? null : (ScriptFunction) jjsObj.get("chooseFile");
 
         final NashornCompleter completer = new NashornCompleter(context, global, this, propsHelper, fileChooserFunc);
+        final ScriptFunction browseFunc = isHeadless? null : (ScriptFunction) jjsObj.get("browse");
 
-        // Function that opens up the desktop browser application with the given URI.
-        // We access AWT from script to avoid direct dependency on java.desktop module.
-        final ScriptFunction browseFunc = isHeadless? null : (ScriptFunction) context.eval(global,
-            "(function(uri) { \n" +
-            "    java.awt.Desktop.desktop.browse(uri); \n" +
-            "})",
-            global, "<browse>");
+        final ScriptFunction javadoc = (ScriptFunction) jjsObj.get("javadoc");
 
         try (final Console in = new Console(System.in, System.out, HIST_FILE, completer,
                 str -> {
@@ -175,6 +152,9 @@
                                 final String pkgName = ((NativeJavaPackage)res).getName();
                                 final String url = pkgName.replace('.', '/') + "/package-summary.html";
                                 openBrowserForJavadoc(browseFunc, url);
+                            } else if (NativeJava.isJavaMethod(UNDEFINED, res)) {
+                                ScriptRuntime.apply(javadoc, UNDEFINED, res);
+                                return ""; // javadoc function already prints javadoc
                             } else if (res instanceof ScriptObject) {
                                 final ScriptObject sobj = (ScriptObject)res;
                                 if (sobj.has(DOC_PROPERTY_NAME)) {
@@ -324,4 +304,22 @@
         } catch (Exception ignored) {
         }
     }
+
+    private static String readJJSScript() {
+        return AccessController.doPrivileged(
+            new PrivilegedAction<String>() {
+                @Override
+                public String run() {
+                    try {
+                        final InputStream resStream = Main.class.getResourceAsStream("resources/jjs.js");
+                        if (resStream == null) {
+                            throw new RuntimeException("resources/jjs.js is missing!");
+                        }
+                        return new String(Source.readFully(resStream));
+                    } catch (final IOException exp) {
+                        throw new RuntimeException(exp);
+                    }
+                }
+            });
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/resources/jjs.js	Mon Nov 06 22:05:53 2017 +0530
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+(function () {
+
+// Check if java.desktop module is available and we're running in non-headless mode.
+// We access AWT via script to avoid direct dependency on java.desktop module.
+function isHeadless() {
+    var GraphicsEnvironment = java.awt.GraphicsEnvironment;
+    return Java.isType(GraphicsEnvironment)? GraphicsEnvironment.isHeadless() : true;
+}
+
+
+// Function that shows a JFileChooser dialog and returns the file name chosen (if chosen).
+// We access swing from script to avoid direct dependency on java.desktop module.
+function chooseFile() {
+    var JFileChooser = javax.swing.JFileChooser;
+    if (!Java.isType(JFileChooser)) {
+        return null;
+    }
+
+    var ExtensionFilter = javax.swing.filechooser.FileNameExtensionFilter;
+    function run() {
+        var chooser = new JFileChooser();
+        chooser.fileFilter = new ExtensionFilter('JavaScript Files', 'js');
+        var retVal = chooser.showOpenDialog(null);
+        return retVal == JFileChooser.APPROVE_OPTION ?
+            chooser.selectedFile.absolutePath : null;
+    }
+
+    var FutureTask = java.util.concurrent.FutureTask;
+    var fileChooserTask = new FutureTask(run);
+    javax.swing.SwingUtilities.invokeLater(fileChooserTask);
+
+    return fileChooserTask.get();
+}
+
+// Function that opens up the desktop browser application with the given URI.
+// We access AWT from script to avoid direct dependency on java.desktop module.
+function browse(uri) {
+    var Desktop = java.awt.Desktop;
+    if (Java.isType(Desktop)) {
+        Desktop.desktop.browse(uri);
+    }
+}
+
+function printDoc(list) {
+    list.forEach(function(doc) {
+        print();
+        print(doc.signature());
+        print();
+        print(doc.javadoc());
+    });
+}
+
+var JShell = null;
+var jshell = null;
+
+function javadoc(obj) {
+    var str = String(obj);
+    if (!JShell) {
+        // first time - resolve JShell class
+        JShell = Packages.jdk.jshell.JShell;
+        // if JShell class is available, create an instance
+        jshell = Java.isType(JShell)? JShell.create() : null;
+    }
+
+    if (!jshell) {
+        // we don't have jshell. Just print the default!
+        return print(str);
+    }
+
+    /*
+     * A java method object's String representation looks something like this:
+     *
+     * For an overloaded method:
+     *
+     *   [jdk.dynalink.beans.OverloadedDynamicMethod
+     *      String java.lang.System.getProperty(String,String)
+     *      String java.lang.System.getProperty(String)
+     *    ]
+     *
+     * For a non-overloaded method:
+     *
+     *  [jdk.dynalink.beans.SimpleDynamicMethod void java.lang.System.exit(int)]
+     *
+     * jshell expects "java.lang.System.getProperty(" or "java.lang.System.exit("
+     * to retrieve the javadoc comment(s) for the method.
+     */
+    var javaCode = str.split(" ")[2]; // stuff after second whitespace char
+    javaCode = javaCode.substring(0, javaCode.indexOf('(') + 1); // strip argument types
+
+    try {
+        var analysis = jshell.sourceCodeAnalysis();
+        var docList = analysis.documentation(javaCode, javaCode.length, true);
+        if (!docList.isEmpty()) {
+            return printDoc(docList);
+        }
+
+        /*
+         * May be the method is a Java instance method. In such a case, jshell expects
+         * a valid starting portion of an instance method call expression. We cast null
+         * to Java object and call method on it. i.e., We pass something like this:
+         *
+         *  "((java.io.PrintStream)null).println("
+         */
+        var javaType = javaCode.substring(0, javaCode.lastIndexOf('.'));
+        javaCode = "((" + javaType + ")null)" + javaCode.substring(javaCode.lastIndexOf('.'));
+        docList = analysis.documentation(javaCode, javaCode.length, true);
+        if (!docList.isEmpty()) {
+            return printDoc(docList);
+        }
+    } catch (e) {
+    }
+    print(str);
+}
+
+return {
+    isHeadless: isHeadless,
+    chooseFile: chooseFile,
+    browse: browse,
+    javadoc: javadoc
+};
+
+})();