8190698: jjs tool of jdk.scripting.nashorn.shell module should not statically depend on java.desktop
authorsundar
Fri, 03 Nov 2017 19:53:09 +0530
changeset 47492 560fab171dc7
parent 47491 7e83ed8fa882
child 47493 843c071258a6
child 47518 783d04ecccc3
8190698: jjs tool of jdk.scripting.nashorn.shell module should not statically depend on java.desktop Reviewed-by: jlaskey, hannesw
src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Console.java
src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/EditObject.java
src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java
src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/NashornCompleter.java
src/jdk.scripting.nashorn.shell/share/classes/module-info.java
test/nashorn/script/basic/JDK-8190698.js
--- a/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Console.java	Fri Nov 03 02:21:58 2017 +0000
+++ b/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Console.java	Fri Nov 03 19:53:09 2017 +0530
@@ -25,7 +25,6 @@
 
 package jdk.nashorn.tools.jjs;
 
-import java.awt.event.ActionListener;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
@@ -69,7 +68,7 @@
         });
         in.addCompleter(completer);
         Runtime.getRuntime().addShutdownHook(new Thread((Runnable)this::saveHistory));
-        bind(DOCUMENTATION_SHORTCUT, (ActionListener)evt -> showDocumentation(docHelper));
+        bind(DOCUMENTATION_SHORTCUT, (Runnable) ()->showDocumentation(docHelper));
         try {
             Signal.handle(new Signal("CONT"), new Handler() {
                 @Override public void handle(Signal sig) {
--- a/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/EditObject.java	Fri Nov 03 02:21:58 2017 +0000
+++ b/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/EditObject.java	Fri Nov 03 19:53:09 2017 +0530
@@ -117,7 +117,7 @@
         final SaveHandler saveHandler = new SaveHandler(initText);
         if (editor != null && !editor.isEmpty()) {
             ExternalEditor.edit(editor, errorHandler, initText, saveHandler, console);
-        } else if (! Main.HEADLESS) {
+        } else {
             try {
                 ServiceLoader<BuildInEditorProvider> sl
                         = ServiceLoader.load(BuildInEditorProvider.class);
@@ -136,8 +136,6 @@
             } catch (RuntimeException ex) {
                 errorHandler.accept(Main.getMessage("jjs.err.cant.launch.editor"));
             }
-        } else {
-            errorHandler.accept(Main.getMessage("no.editor"));
         }
         return UNDEFINED;
     }
--- a/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java	Fri Nov 03 02:21:58 2017 +0000
+++ b/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java	Fri Nov 03 19:53:09 2017 +0530
@@ -27,8 +27,6 @@
 
 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
 
-import java.awt.Desktop;
-import java.awt.GraphicsEnvironment;
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.InputStream;
@@ -65,7 +63,6 @@
     private static final String DOC_PROPERTY_NAME = "__doc__";
 
     static final boolean DEBUG = Boolean.getBoolean("nashorn.jjs.debug");
-    static final boolean HEADLESS = GraphicsEnvironment.isHeadless();
 
     // file where history is persisted.
     private static final File HIST_FILE = new File(new File(System.getProperty("user.home")), ".jjs.history");
@@ -120,7 +117,49 @@
         final Global oldGlobal = Context.getGlobal();
         final boolean globalChanged = (oldGlobal != global);
         final PropertiesHelper propsHelper = new PropertiesHelper(context);
-        final NashornCompleter completer = new NashornCompleter(context, global, this, propsHelper);
+
+        if (globalChanged) {
+            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>");
+
+        // 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 NashornCompleter completer = new NashornCompleter(context, global, this, propsHelper, fileChooserFunc);
+
+        // 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>");
 
         try (final Console in = new Console(System.in, System.out, HIST_FILE, completer,
                 str -> {
@@ -128,14 +167,14 @@
                         final Object res = context.eval(global, str, global, "<shell>");
                         if (res != null && res != UNDEFINED) {
                             // Special case Java types: show the javadoc for the class.
-                            if (NativeJava.isType(UNDEFINED, res)) {
+                            if (!isHeadless && NativeJava.isType(UNDEFINED, res)) {
                                 final String typeName = NativeJava.typeName(UNDEFINED, res).toString();
                                 final String url = typeName.replace('.', '/').replace('$', '.') + ".html";
-                                openBrowserForJavadoc(url);
-                            } else if (res instanceof NativeJavaPackage) {
+                                openBrowserForJavadoc(browseFunc, url);
+                            } else if (!isHeadless && res instanceof NativeJavaPackage) {
                                 final String pkgName = ((NativeJavaPackage)res).getName();
                                 final String url = pkgName.replace('.', '/') + "/package-summary.html";
-                                openBrowserForJavadoc(url);
+                                openBrowserForJavadoc(browseFunc, url);
                             } else if (res instanceof ScriptObject) {
                                 final ScriptObject sobj = (ScriptObject)res;
                                 if (sobj.has(DOC_PROPERTY_NAME)) {
@@ -153,10 +192,6 @@
                      return null;
                 })) {
 
-            if (globalChanged) {
-                Context.setGlobal(global);
-            }
-
             global.addShellBuiltins();
 
             // redefine readLine to use jline Console's readLine!
@@ -282,11 +317,10 @@
     }
 
     private static String JAVADOC_BASE = "https://docs.oracle.com/javase/9/docs/api/";
-
-    private static void openBrowserForJavadoc(String relativeUrl) {
+    private static void openBrowserForJavadoc(ScriptFunction browse, String relativeUrl) {
         try {
             final URI uri = new URI(JAVADOC_BASE + relativeUrl);
-            Desktop.getDesktop().browse(uri);
+            ScriptRuntime.apply(browse, null, uri);
         } catch (Exception ignored) {
         }
     }
--- a/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/NashornCompleter.java	Fri Nov 03 02:21:58 2017 +0000
+++ b/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/NashornCompleter.java	Fri Nov 03 19:53:09 2017 +0530
@@ -29,12 +29,8 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.FutureTask;
+import java.util.Objects;
 import java.util.regex.Pattern;
-import javax.swing.JFileChooser;
-import javax.swing.filechooser.FileNameExtensionFilter;
-import javax.swing.SwingUtilities;
 import jdk.internal.jline.console.completer.Completer;
 import jdk.internal.jline.console.UserInterruptException;
 import jdk.nashorn.api.tree.AssignmentTree;
@@ -60,6 +56,7 @@
 import jdk.nashorn.internal.runtime.ECMAException;
 import jdk.nashorn.internal.runtime.Context;
 import jdk.nashorn.internal.runtime.ScriptEnvironment;
+import jdk.nashorn.internal.runtime.ScriptFunction;
 import jdk.nashorn.internal.runtime.ScriptRuntime;
 
 /**
@@ -72,16 +69,19 @@
     private final ScriptEnvironment env;
     private final PartialParser partialParser;
     private final PropertiesHelper propsHelper;
+    private final ScriptFunction fileChooserFunc;
     private final Parser parser;
     private static final boolean BACKSLASH_FILE_SEPARATOR = File.separatorChar == '\\';
 
     NashornCompleter(final Context context, final Global global,
-            final PartialParser partialParser, final PropertiesHelper propsHelper) {
+            final PartialParser partialParser, final PropertiesHelper propsHelper,
+            final ScriptFunction fileChooserFunc) {
         this.context = context;
         this.global = global;
         this.env = context.getEnv();
         this.partialParser = partialParser;
         this.propsHelper = propsHelper;
+        this.fileChooserFunc = fileChooserFunc;
         this.parser = createParser(env);
     }
 
@@ -236,8 +236,9 @@
 
         final ExpressionTree topExpr = getTopLevelExpression(parser, completeExpr);
         if (topExpr == null) {
-            // special case for load call that looks like "load(" with optional whitespaces
-            if (LOAD_CALL.matcher(test).matches()) {
+            // Special case for load call that looks like "load(" with optional whitespaces.
+            // If we have a fileChooserFunc then call it, so that the user can select a file.
+            if (fileChooserFunc != null && LOAD_CALL.matcher(test).matches()) {
                 String name = readFileName(context.getErr());
                 if (name != null) {
                     // handle '\' file separator
@@ -269,26 +270,11 @@
     // Internals only below this point
 
     // read file name from the user using by showing a swing file chooser diablog
-    private static String readFileName(final PrintWriter err) {
-        // if running on AWT Headless mode, don't attempt swing dialog box!
-        if (Main.HEADLESS) {
-            return null;
-        }
-
-        final FutureTask<String> fileChooserTask = new FutureTask<String>(() -> {
-            // show a file chooser dialog box
-            final JFileChooser chooser = new JFileChooser();
-            chooser.setFileFilter(new FileNameExtensionFilter("JavaScript Files", "js"));
-            final int retVal = chooser.showOpenDialog(null);
-            return retVal == JFileChooser.APPROVE_OPTION ?
-                chooser.getSelectedFile().getAbsolutePath() : null;
-        });
-
-        SwingUtilities.invokeLater(fileChooserTask);
-
+    private String readFileName(final PrintWriter err) {
         try {
-            return fileChooserTask.get();
-        } catch (final ExecutionException | InterruptedException e) {
+            final Object res = ScriptRuntime.apply(fileChooserFunc, null);
+            return res instanceof String? (String)res : null;
+        } catch (final Exception e) {
             err.println(e);
             if (Main.DEBUG) {
                 e.printStackTrace();
--- a/src/jdk.scripting.nashorn.shell/share/classes/module-info.java	Fri Nov 03 02:21:58 2017 +0000
+++ b/src/jdk.scripting.nashorn.shell/share/classes/module-info.java	Fri Nov 03 19:53:09 2017 +0530
@@ -39,7 +39,6 @@
  */
 module jdk.scripting.nashorn.shell {
     requires java.compiler;
-    requires java.desktop;
     requires jdk.internal.le;
     requires jdk.scripting.nashorn;
     requires jdk.internal.ed;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/nashorn/script/basic/JDK-8190698.js	Fri Nov 03 19:53:09 2017 +0530
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ *
+ * 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.
+ */
+
+/**
+ * JDK-8190698: jjs tool of jdk.scripting.nashorn.shell module should not statically depend on java.desktop
+ *
+ * @test
+ * @run
+ */
+
+var optJjsMod = java.lang.ModuleLayer.boot().findModule("jdk.scripting.nashorn.shell");
+
+// make sure that the module exists!
+Assert.assertTrue(optJjsMod.isPresent());
+
+// jdk.scripting.nashorn.shell should not have java.desktop dependency
+var javaDesktopDependency = optJjsMod.get().
+        descriptor.requires().
+        stream().
+        filter(function(mod) { return mod.name() == "java.desktop" }).
+        findFirst();
+
+Assert.assertTrue(!javaDesktopDependency.isPresent());