From 321ce034fcd0bb7f4ddf2ca0e42ab99096f0bd4f Mon Sep 17 00:00:00 2001 From: Athijegannathan Sundararajan Date: Fri, 21 Aug 2015 18:01:23 +0530 Subject: [PATCH 01/12] 8133948: Add 'edit' function to allow external editing of scripts Reviewed-by: attila, hannesw, jlahoda --- .../jdk/nashorn/tools/jjs/Console.java | 63 ++++++++ .../jdk/nashorn/tools/jjs/EditObject.java | 113 +++++++++++++ .../jdk/nashorn/tools/jjs/EditPad.java | 136 ++++++++++++++++ .../jdk/nashorn/tools/jjs/ExternalEditor.java | 152 ++++++++++++++++++ .../classes/jdk/nashorn/tools/jjs/Main.java | 53 ++++-- 5 files changed, 504 insertions(+), 13 deletions(-) create mode 100644 nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/EditObject.java create mode 100644 nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/EditPad.java create mode 100644 nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/ExternalEditor.java diff --git a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Console.java b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Console.java index 3ecee8c7d11..484f1c239c7 100644 --- a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Console.java +++ b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Console.java @@ -34,6 +34,11 @@ import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; +import jdk.internal.jline.NoInterruptUnixTerminal; +import jdk.internal.jline.Terminal; +import jdk.internal.jline.TerminalFactory; +import jdk.internal.jline.TerminalFactory.Flavor; +import jdk.internal.jline.WindowsTerminal; import jdk.internal.jline.console.ConsoleReader; import jdk.internal.jline.console.completer.Completer; import jdk.internal.jline.console.history.FileHistory; @@ -45,6 +50,8 @@ class Console implements AutoCloseable { Console(final InputStream cmdin, final PrintStream cmdout, final File historyFile, final Completer completer) throws IOException { in = new ConsoleReader(cmdin, cmdout); + TerminalFactory.registerFlavor(Flavor.WINDOWS, JJSWindowsTerminal :: new); + TerminalFactory.registerFlavor(Flavor.UNIX, JJSUnixTerminal :: new); in.setExpandEvents(false); in.setHandleUserInterrupt(true); in.setBellEnabled(true); @@ -71,4 +78,60 @@ class Console implements AutoCloseable { FileHistory getHistory() { return (FileHistory) in.getHistory(); } + + boolean terminalEditorRunning() { + Terminal terminal = in.getTerminal(); + if (terminal instanceof JJSUnixTerminal) { + return ((JJSUnixTerminal) terminal).isRaw(); + } + return false; + } + + void suspend() { + try { + in.getTerminal().restore(); + } catch (Exception ex) { + throw new IllegalStateException(ex); + } + } + + void resume() { + try { + in.getTerminal().init(); + } catch (Exception ex) { + throw new IllegalStateException(ex); + } + } + + static final class JJSUnixTerminal extends NoInterruptUnixTerminal { + JJSUnixTerminal() throws Exception { + } + + boolean isRaw() { + try { + return getSettings().get("-a").contains("-icanon"); + } catch (IOException | InterruptedException ex) { + return false; + } + } + + @Override + public void disableInterruptCharacter() { + } + + @Override + public void enableInterruptCharacter() { + } + } + + static final class JJSWindowsTerminal extends WindowsTerminal { + public JJSWindowsTerminal() throws Exception { + } + + @Override + public void init() throws Exception { + super.init(); + setAnsiSupported(false); + } + } } diff --git a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/EditObject.java b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/EditObject.java new file mode 100644 index 00000000000..5551478ad0b --- /dev/null +++ b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/EditObject.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2015, 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. + */ + +package jdk.nashorn.tools.jjs; + +import java.util.function.Consumer; +import jdk.nashorn.api.scripting.AbstractJSObject; +import jdk.nashorn.internal.runtime.JSType; +import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; + +/* + * "edit" top level script function which shows an external Window + * for editing and evaluating scripts from it. + */ +final class EditObject extends AbstractJSObject { + private final Consumer errorHandler; + private final Consumer evaluator; + private final Console console; + private String editor; + + EditObject(final Consumer errorHandler, final Consumer evaluator, + final Console console) { + this.errorHandler = errorHandler; + this.evaluator = evaluator; + this.console = console; + } + + @Override + public Object getDefaultValue(final Class hint) { + if (hint == String.class) { + return toString(); + } + return UNDEFINED; + } + + @Override + public String toString() { + return "function edit() { [native code] }"; + } + + @Override + public Object getMember(final String name) { + if (name.equals("editor")) { + return editor; + } + return UNDEFINED; + } + + @Override + public void setMember(final String name, final Object value) { + if (name.equals("editor")) { + this.editor = JSType.toString(value); + } + } + + // called whenever user 'saves' script in editor + class SaveHandler implements Consumer { + private String lastStr; // last seen code + + SaveHandler(final String str) { + this.lastStr = str; + } + + @Override + public void accept(final String str) { + // ignore repeated save of the same code! + if (! str.equals(lastStr)) { + this.lastStr = str; + // evaluate the new code + evaluator.accept(str); + } + } + } + + @Override + public Object call(final Object thiz, final Object... args) { + final String initText = args.length > 0? JSType.toString(args[0]) : ""; + final SaveHandler saveHandler = new SaveHandler(initText); + if (editor != null && !editor.isEmpty()) { + ExternalEditor.edit(editor, errorHandler, initText, saveHandler, console); + } else { + EditPad.edit(errorHandler, initText, saveHandler); + } + return UNDEFINED; + } + + @Override + public boolean isFunction() { + return true; + } +} diff --git a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/EditPad.java b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/EditPad.java new file mode 100644 index 00000000000..9dbe2a89cfc --- /dev/null +++ b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/EditPad.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2015, 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. + */ + +package jdk.nashorn.tools.jjs; + +import java.awt.BorderLayout; +import java.awt.FlowLayout; +import java.awt.event.KeyEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.function.Consumer; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.SwingUtilities; + +/** + * A minimal Swing editor as a fallback when the user does not specify an + * external editor. + */ +final class EditPad extends JFrame implements Runnable { + private static final long serialVersionUID = 1; + private final Consumer errorHandler; + private final String initialText; + private final boolean[] closeLock; + private final Consumer saveHandler; + + EditPad(Consumer errorHandler, String initialText, + boolean[] closeLock, Consumer saveHandler) { + super("Edit Pad (Experimental)"); + this.errorHandler = errorHandler; + this.initialText = initialText; + this.closeLock = closeLock; + this.saveHandler = saveHandler; + } + + @Override + public void run() { + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + EditPad.this.dispose(); + notifyClose(); + } + }); + setLocationRelativeTo(null); + setLayout(new BorderLayout()); + JTextArea textArea = new JTextArea(initialText); + add(new JScrollPane(textArea), BorderLayout.CENTER); + add(buttons(textArea), BorderLayout.SOUTH); + + setSize(800, 600); + setVisible(true); + } + + private JPanel buttons(JTextArea textArea) { + FlowLayout flow = new FlowLayout(); + flow.setHgap(35); + JPanel buttons = new JPanel(flow); + JButton cancel = new JButton("Cancel"); + cancel.setMnemonic(KeyEvent.VK_C); + JButton accept = new JButton("Accept"); + accept.setMnemonic(KeyEvent.VK_A); + JButton exit = new JButton("Exit"); + exit.setMnemonic(KeyEvent.VK_X); + buttons.add(cancel); + buttons.add(accept); + buttons.add(exit); + + cancel.addActionListener(e -> { + close(); + }); + accept.addActionListener(e -> { + saveHandler.accept(textArea.getText()); + }); + exit.addActionListener(e -> { + saveHandler.accept(textArea.getText()); + close(); + }); + + return buttons; + } + + private void close() { + setVisible(false); + dispose(); + notifyClose(); + } + + private void notifyClose() { + synchronized (closeLock) { + closeLock[0] = true; + closeLock.notify(); + } + } + + static void edit(Consumer errorHandler, String initialText, + Consumer saveHandler) { + boolean[] closeLock = new boolean[1]; + SwingUtilities.invokeLater( + new EditPad(errorHandler, initialText, closeLock, saveHandler)); + synchronized (closeLock) { + while (!closeLock[0]) { + try { + closeLock.wait(); + } catch (InterruptedException ex) { + // ignore and loop + } + } + } + } +} diff --git a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/ExternalEditor.java b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/ExternalEditor.java new file mode 100644 index 00000000000..d67add1f740 --- /dev/null +++ b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/ExternalEditor.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2015, 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. + */ + +package jdk.nashorn.tools.jjs; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.ClosedWatchServiceException; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; +import java.util.List; +import java.util.function.Consumer; +import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE; +import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE; +import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; + +final class ExternalEditor { + private final Consumer errorHandler; + private final Consumer saveHandler; + private final Console input; + + private WatchService watcher; + private Thread watchedThread; + private Path dir; + private Path tmpfile; + + ExternalEditor(Consumer errorHandler, Consumer saveHandler, Console input) { + this.errorHandler = errorHandler; + this.saveHandler = saveHandler; + this.input = input; + } + + private void edit(String cmd, String initialText) { + try { + setupWatch(initialText); + launch(cmd); + } catch (IOException ex) { + errorHandler.accept(ex.getMessage()); + } + } + + /** + * Creates a WatchService and registers the given directory + */ + private void setupWatch(String initialText) throws IOException { + this.watcher = FileSystems.getDefault().newWatchService(); + this.dir = Files.createTempDirectory("REPL"); + this.tmpfile = Files.createTempFile(dir, null, ".js"); + Files.write(tmpfile, initialText.getBytes(Charset.forName("UTF-8"))); + dir.register(watcher, + ENTRY_CREATE, + ENTRY_DELETE, + ENTRY_MODIFY); + watchedThread = new Thread(() -> { + for (;;) { + WatchKey key; + try { + key = watcher.take(); + } catch (ClosedWatchServiceException ex) { + break; + } catch (InterruptedException ex) { + continue; // tolerate an intrupt + } + + if (!key.pollEvents().isEmpty()) { + if (!input.terminalEditorRunning()) { + saveFile(); + } + } + + boolean valid = key.reset(); + if (!valid) { + errorHandler.accept("Invalid key"); + break; + } + } + }); + watchedThread.start(); + } + + private void launch(String cmd) throws IOException { + ProcessBuilder pb = new ProcessBuilder(cmd, tmpfile.toString()); + pb = pb.inheritIO(); + + try { + input.suspend(); + Process process = pb.start(); + process.waitFor(); + } catch (IOException ex) { + errorHandler.accept("process IO failure: " + ex.getMessage()); + } catch (InterruptedException ex) { + errorHandler.accept("process interrupt: " + ex.getMessage()); + } finally { + try { + watcher.close(); + watchedThread.join(); //so that saveFile() is finished. + saveFile(); + } catch (InterruptedException ex) { + errorHandler.accept("process interrupt: " + ex.getMessage()); + } finally { + input.resume(); + } + } + } + + private void saveFile() { + List lines; + try { + lines = Files.readAllLines(tmpfile); + } catch (IOException ex) { + errorHandler.accept("Failure read edit file: " + ex.getMessage()); + return ; + } + StringBuilder sb = new StringBuilder(); + for (String ln : lines) { + sb.append(ln); + sb.append('\n'); + } + saveHandler.accept(sb.toString()); + } + + static void edit(String cmd, Consumer errorHandler, String initialText, + Consumer saveHandler, Console input) { + ExternalEditor ed = new ExternalEditor(errorHandler, saveHandler, input); + ed.edit(cmd, initialText); + } +} diff --git a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java index e0edd4dad5d..e556a686099 100644 --- a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java +++ b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java @@ -38,6 +38,7 @@ import jdk.nashorn.api.scripting.NashornException; import jdk.nashorn.internal.objects.Global; import jdk.nashorn.internal.runtime.Context; import jdk.nashorn.internal.runtime.JSType; +import jdk.nashorn.internal.runtime.Property; import jdk.nashorn.internal.runtime.ScriptEnvironment; import jdk.nashorn.internal.runtime.ScriptRuntime; import jdk.nashorn.tools.Shell; @@ -107,8 +108,29 @@ public final class Main extends Shell { } global.addShellBuiltins(); - // expose history object for reflecting on command line history - global.put("history", new HistoryObject(in.getHistory()), false); + + if (System.getSecurityManager() == null) { + // expose history object for reflecting on command line history + global.addOwnProperty("history", Property.NOT_ENUMERABLE, new HistoryObject(in.getHistory())); + + // 'edit' command + global.addOwnProperty("edit", Property.NOT_ENUMERABLE, new EditObject(err::println, + str -> { + // could be called from different thread (GUI), we need to handle Context set/reset + final Global _oldGlobal = Context.getGlobal(); + final boolean _globalChanged = (oldGlobal != global); + if (_globalChanged) { + Context.setGlobal(global); + } + try { + evalImpl(context, global, str, err, env._dump_on_error); + } finally { + if (_globalChanged) { + Context.setGlobal(_oldGlobal); + } + } + }, in)); + } while (true) { String source = ""; @@ -128,17 +150,7 @@ public final class Main extends Shell { continue; } - try { - final Object res = context.eval(global, source, global, ""); - if (res != ScriptRuntime.UNDEFINED) { - err.println(JSType.toString(res)); - } - } catch (final Exception e) { - err.println(e); - if (env._dump_on_error) { - e.printStackTrace(err); - } - } + evalImpl(context, global, source, err, env._dump_on_error); } } catch (final Exception e) { err.println(e); @@ -153,4 +165,19 @@ public final class Main extends Shell { return SUCCESS; } + + private void evalImpl(final Context context, final Global global, final String source, + final PrintWriter err, final boolean doe) { + try { + final Object res = context.eval(global, source, global, ""); + if (res != ScriptRuntime.UNDEFINED) { + err.println(JSType.toString(res)); + } + } catch (final Exception e) { + err.println(e); + if (doe) { + e.printStackTrace(err); + } + } + } } From 4470a2eefaa4ed7ff04d2d6398219fb3456c0062 Mon Sep 17 00:00:00 2001 From: Athijegannathan Sundararajan Date: Sun, 23 Aug 2015 10:02:14 +0530 Subject: [PATCH 02/12] 8134255: Implement tab-completion for java package prefixes and package names Reviewed-by: attila, mhaupt --- nashorn/samples/classes.js | 47 +++++ .../jdk/nashorn/tools/jjs/EditObject.java | 15 ++ .../classes/jdk/nashorn/tools/jjs/Main.java | 12 +- .../nashorn/tools/jjs/NashornCompleter.java | 18 +- .../jdk/nashorn/tools/jjs/PackagesHelper.java | 171 ++++++++++++++++++ .../nashorn/tools/jjs/PropertiesHelper.java | 64 ++++++- .../internal/runtime/ScriptEnvironment.java | 4 + 7 files changed, 317 insertions(+), 14 deletions(-) create mode 100644 nashorn/samples/classes.js create mode 100644 nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/PackagesHelper.java diff --git a/nashorn/samples/classes.js b/nashorn/samples/classes.js new file mode 100644 index 00000000000..24f74d9174b --- /dev/null +++ b/nashorn/samples/classes.js @@ -0,0 +1,47 @@ +// Usage: jjs classes.js [ -- ] + +/* + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of Oracle nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// print all Java classes of the given package and its subpackages. +var pkg = arguments.length > 0? arguments[0] : "java.lang"; + +with (new JavaImporter(javax.tools, java.util)) { + var compiler = ToolProvider.systemJavaCompiler; + var fm = compiler.getStandardFileManager(null, null, null); + var kinds = EnumSet.of(JavaFileObject.Kind.CLASS); + var loc = StandardLocation.PLATFORM_CLASS_PATH; + var itr = fm.list(loc, pkg, kinds, true).iterator(); + while(itr.hasNext()) { + print(fm.inferBinaryName(loc, itr.next())); + } + fm.close(); +} diff --git a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/EditObject.java b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/EditObject.java index 5551478ad0b..e130f10c909 100644 --- a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/EditObject.java +++ b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/EditObject.java @@ -25,6 +25,9 @@ package jdk.nashorn.tools.jjs; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; import java.util.function.Consumer; import jdk.nashorn.api.scripting.AbstractJSObject; import jdk.nashorn.internal.runtime.JSType; @@ -35,6 +38,13 @@ import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; * for editing and evaluating scripts from it. */ final class EditObject extends AbstractJSObject { + private static final Set props; + static { + final HashSet s = new HashSet<>(); + s.add("editor"); + props = Collections.unmodifiableSet(s); + } + private final Consumer errorHandler; private final Consumer evaluator; private final Console console; @@ -60,6 +70,11 @@ final class EditObject extends AbstractJSObject { return "function edit() { [native code] }"; } + @Override + public Set keySet() { + return props; + } + @Override public Object getMember(final String name) { if (name.equals("editor")) { diff --git a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java index e556a686099..3582b40c285 100644 --- a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java +++ b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java @@ -49,6 +49,8 @@ import jdk.nashorn.tools.Shell; public final class Main extends Shell { private Main() {} + static final boolean DEBUG = Boolean.getBoolean("nashorn.jjs.debug"); + // file where history is persisted. private static final File HIST_FILE = new File(new File(System.getProperty("user.home")), ".jjs.history"); @@ -100,7 +102,8 @@ public final class Main extends Shell { final PrintWriter err = context.getErr(); final Global oldGlobal = Context.getGlobal(); final boolean globalChanged = (oldGlobal != global); - final Completer completer = new NashornCompleter(context, global, this); + final PropertiesHelper propsHelper = new PropertiesHelper(env._classpath); + final Completer completer = new NashornCompleter(context, global, this, propsHelper); try (final Console in = new Console(System.in, System.out, HIST_FILE, completer)) { if (globalChanged) { @@ -161,6 +164,13 @@ public final class Main extends Shell { if (globalChanged) { Context.setGlobal(oldGlobal); } + try { + propsHelper.close(); + } catch (final Exception exp) { + if (DEBUG) { + exp.printStackTrace(); + } + } } return SUCCESS; diff --git a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/NashornCompleter.java b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/NashornCompleter.java index 5a676da4d89..566cd60cb7c 100644 --- a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/NashornCompleter.java +++ b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/NashornCompleter.java @@ -55,12 +55,15 @@ final class NashornCompleter implements Completer { private final Context context; private final Global global; private final PartialParser partialParser; + private final PropertiesHelper propsHelper; private final Parser parser; - NashornCompleter(final Context context, final Global global, final PartialParser partialParser) { + NashornCompleter(final Context context, final Global global, + final PartialParser partialParser, final PropertiesHelper propsHelper) { this.context = context; this.global = global; this.partialParser = partialParser; + this.propsHelper = propsHelper; this.parser = Parser.create(); } @@ -122,19 +125,22 @@ final class NashornCompleter implements Completer { Object obj = null; try { obj = context.eval(global, objExprCode, global, ""); - } catch (Exception ignored) { - // throw the exception - this is during tab-completion + } catch (Exception exp) { + // throw away the exception - this is during tab-completion + if (Main.DEBUG) { + exp.printStackTrace(); + } } if (obj != null && obj != ScriptRuntime.UNDEFINED) { if (endsWithDot) { // no user specified "prefix". List all properties of the object - result.addAll(PropertiesHelper.getProperties(obj)); + result.addAll(propsHelper.getProperties(obj)); return cursor; } else { // list of properties matching the user specified prefix final String prefix = select.getIdentifier(); - result.addAll(PropertiesHelper.getProperties(obj, prefix)); + result.addAll(propsHelper.getProperties(obj, prefix)); return cursor - prefix.length(); } } @@ -145,7 +151,7 @@ final class NashornCompleter implements Completer { private int completeIdentifier(final String test, final int cursor, final List result, final IdentifierTree ident) { final String name = ident.getName(); - result.addAll(PropertiesHelper.getProperties(global, name)); + result.addAll(propsHelper.getProperties(global, name)); return cursor - name.length(); } diff --git a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/PackagesHelper.java b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/PackagesHelper.java new file mode 100644 index 00000000000..5b73f504d5c --- /dev/null +++ b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/PackagesHelper.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2015, 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. + */ + +package jdk.nashorn.tools.jjs; + +import java.io.IOException; +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; +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; + +/** + * A helper class to compute properties of a Java package object. Properties of + * package object are (simple) top level class names in that java package and + * immediate subpackages of that package. + */ +final class PackagesHelper { + // JavaCompiler may be null on certain platforms (eg. JRE) + private static final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + + /** + * Is Java package properties helper available? + * + * @return true if package properties support is available + */ + static boolean isAvailable() { + return compiler != null; + } + + private final StandardJavaFileManager fm; + private final Set fileKinds; + + /** + * Construct a new PackagesHelper. + * + * @param classPath Class path to compute properties of java package objects + */ + PackagesHelper(final String classPath) throws IOException { + assert isAvailable() : "no java compiler found!"; + + fm = compiler.getStandardFileManager(null, null, null); + fileKinds = EnumSet.of(JavaFileObject.Kind.CLASS); + + if (classPath != null && !classPath.isEmpty()) { + fm.setLocation(StandardLocation.CLASS_PATH, getFiles(classPath)); + } else { + // no classpath set. Make sure that it is empty and not any default like "." + fm.setLocation(StandardLocation.CLASS_PATH, Collections.emptyList()); + } + } + + // LRU cache for java package properties lists + private final LinkedHashMap> propsCache = + new LinkedHashMap<>(32, 0.75f, true) { + private static final int CACHE_SIZE = 100; + private static final long serialVersionUID = 1; + + @Override + protected boolean removeEldestEntry(final Map.Entry> eldest) { + return size() > CACHE_SIZE; + } + }; + + /** + * Return the list of properties of the given Java package or package prefix + * + * @param pkg Java package name or package prefix name + * @return the list of properties of the given Java package or package prefix + */ + List getPackageProperties(final String pkg) { + // check the cache first + if (propsCache.containsKey(pkg)) { + return propsCache.get(pkg); + } + + try { + // make sorted list of properties + final List props = new ArrayList<>(listPackage(pkg)); + Collections.sort(props); + propsCache.put(pkg, props); + return props; + } catch (final IOException exp) { + if (Main.DEBUG) { + exp.printStackTrace(); + } + return Collections.emptyList(); + } + } + + public void close() throws IOException { + fm.close(); + } + + private Set listPackage(final String pkg) throws IOException { + final Set props = new HashSet<>(); + listPackage(StandardLocation.PLATFORM_CLASS_PATH, pkg, props); + listPackage(StandardLocation.CLASS_PATH, pkg, props); + return props; + } + + private void listPackage(final Location loc, final String pkg, final Set props) + throws IOException { + for (JavaFileObject file : fm.list(loc, pkg, fileKinds, true)) { + final String binaryName = fm.inferBinaryName(loc, file); + // does not start with the given package prefix + if (!binaryName.startsWith(pkg + ".")) { + continue; + } + + final int nextDot = binaryName.indexOf('.', pkg.length() + 1); + final int start = pkg.length() + 1; + + if (nextDot != -1) { + // subpackage - eg. "regex" for "java.util" + props.add(binaryName.substring(start, nextDot)); + } else { + // class - filter out nested, inner, anonymous, local classes. + // Dynalink supported public nested classes as properties of + // StaticClass object anyway. We don't want to expose those + // "$" internal names as properties of package object. + + final String clsName = binaryName.substring(start); + if (clsName.indexOf('$') == -1) { + props.add(clsName); + } + } + } + } + + // return list of File objects for the given class path + private static List getFiles(final String classPath) { + return Stream.of(classPath.split(File.pathSeparator)) + .map(File::new) + .collect(Collectors.toList()); + } +} diff --git a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/PropertiesHelper.java b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/PropertiesHelper.java index aada9223bf9..c37e1f46fc1 100644 --- a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/PropertiesHelper.java +++ b/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/PropertiesHelper.java @@ -25,6 +25,7 @@ package jdk.nashorn.tools.jjs; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -32,6 +33,7 @@ import java.util.List; import java.util.WeakHashMap; import java.util.stream.Collectors; import jdk.nashorn.internal.runtime.JSType; +import jdk.nashorn.internal.runtime.NativeJavaPackage; import jdk.nashorn.internal.runtime.PropertyMap; import jdk.nashorn.internal.runtime.ScriptObject; import jdk.nashorn.internal.runtime.ScriptRuntime; @@ -41,19 +43,59 @@ import jdk.nashorn.internal.objects.NativeJava; * A helper class to get properties of a given object for source code completion. */ final class PropertiesHelper { - private PropertiesHelper() {} - + // Java package properties helper, may be null + private PackagesHelper pkgsHelper; // cached properties list - private static final WeakHashMap> propsCache = new WeakHashMap<>(); + private final WeakHashMap> propsCache = new WeakHashMap<>(); - // returns the list of properties of the given object - static List getProperties(final Object obj) { + /** + * Construct a new PropertiesHelper. + * + * @param classPath Class path to compute properties of java package objects + */ + PropertiesHelper(final String classPath) { + if (PackagesHelper.isAvailable()) { + try { + this.pkgsHelper = new PackagesHelper(classPath); + } catch (final IOException exp) { + if (Main.DEBUG) { + exp.printStackTrace(); + } + this.pkgsHelper = null; + } + } + } + + void close() throws Exception { + propsCache.clear(); + pkgsHelper.close(); + } + + /** + * returns the list of properties of the given object. + * + * @param obj object whose property list is returned + * @return the list of properties of the given object + */ + List getProperties(final Object obj) { assert obj != null && obj != ScriptRuntime.UNDEFINED; + // wrap JS primitives as objects before gettting properties if (JSType.isPrimitive(obj)) { return getProperties(JSType.toScriptObject(obj)); } + // Handle Java package prefix case first. Should do it before checking + // for its super class ScriptObject! + if (obj instanceof NativeJavaPackage) { + if (pkgsHelper != null) { + return pkgsHelper.getPackageProperties(((NativeJavaPackage)obj).getName()); + } else { + return Collections.emptyList(); + } + } + + // script object - all inherited and non-enumerable, non-index properties if (obj instanceof ScriptObject) { final ScriptObject sobj = (ScriptObject)obj; final PropertyMap pmap = sobj.getMap(); @@ -71,6 +113,7 @@ final class PropertiesHelper { return props; } + // java class case - don't refer to StaticClass directly if (NativeJava.isType(ScriptRuntime.UNDEFINED, obj)) { if (propsCache.containsKey(obj)) { return propsCache.get(obj); @@ -82,6 +125,7 @@ final class PropertiesHelper { return props; } + // any other Java object final Class clazz = obj.getClass(); if (propsCache.containsKey(clazz)) { return propsCache.get(clazz); @@ -94,8 +138,14 @@ final class PropertiesHelper { return props; } - // returns the list of properties of the given object that start with the given prefix - static List getProperties(final Object obj, final String prefix) { + /** + * Returns the list of properties of the given object that start with the given prefix. + * + * @param obj object whose property list is returned + * @param prefix property prefix to be matched + * @return the list of properties of the given object + */ + List getProperties(final Object obj, final String prefix) { assert prefix != null && !prefix.isEmpty(); return getProperties(obj).stream() .filter(s -> s.startsWith(prefix)) diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptEnvironment.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptEnvironment.java index 99cef1201ad..f573fdc7a06 100644 --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptEnvironment.java +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptEnvironment.java @@ -61,6 +61,9 @@ public final class ScriptEnvironment { /** Size of the per-global Class cache size */ public final int _class_cache_size; + /** -classpath value. */ + public final String _classpath; + /** Only compile script, do not run it or generate other ScriptObjects */ public final boolean _compile_only; @@ -220,6 +223,7 @@ public final class ScriptEnvironment { this.options = options; _class_cache_size = options.getInteger("class.cache.size"); + _classpath = options.getString("classpath"); _compile_only = options.getBoolean("compile.only"); _const_as_var = options.getBoolean("const.as.var"); _debug_lines = options.getBoolean("debug.lines"); From b63af33cd201cd46cdc6507a487510b6b4e8347d Mon Sep 17 00:00:00 2001 From: Attila Szegedi Date: Mon, 24 Aug 2015 09:11:46 +0200 Subject: [PATCH 03/12] 8134150: Make Timing both threadsafe and efficient Reviewed-by: jlaskey, sundar --- .../jdk/nashorn/internal/runtime/Timing.java | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/Timing.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/Timing.java index e1d464d9267..2cc52577733 100644 --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/Timing.java +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/Timing.java @@ -28,12 +28,14 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.LongAdder; +import java.util.function.Function; import java.util.function.Supplier; - import jdk.nashorn.internal.codegen.CompileUnit; import jdk.nashorn.internal.runtime.logging.DebugLogger; import jdk.nashorn.internal.runtime.logging.Loggable; @@ -156,11 +158,12 @@ public final class Timing implements Loggable { } final class TimeSupplier implements Supplier { - private final Map timings; - - TimeSupplier() { - timings = new LinkedHashMap<>(); - } + private final Map timings = new ConcurrentHashMap<>(); + private final LinkedBlockingQueue orderedTimingNames = new LinkedBlockingQueue<>(); + private final Function newTimingCreator = s -> { + orderedTimingNames.add(s); + return new LongAdder(); + }; String[] getStrings() { final List strs = new ArrayList<>(); @@ -184,26 +187,26 @@ public final class Timing implements Loggable { int maxKeyLength = 0; int maxValueLength = 0; - for (final Map.Entry entry : timings.entrySet()) { + for (final Map.Entry entry : timings.entrySet()) { maxKeyLength = Math.max(maxKeyLength, entry.getKey().length()); - maxValueLength = Math.max(maxValueLength, toMillisPrint(entry.getValue()).length()); + maxValueLength = Math.max(maxValueLength, toMillisPrint(entry.getValue().longValue()).length()); } maxKeyLength++; final StringBuilder sb = new StringBuilder(); sb.append("Accumulated compilation phase timings:\n\n"); - for (final Map.Entry entry : timings.entrySet()) { + for (final String timingName: orderedTimingNames) { int len; len = sb.length(); - sb.append(entry.getKey()); + sb.append(timingName); len = sb.length() - len; while (len++ < maxKeyLength) { sb.append(' '); } - final Long duration = entry.getValue(); + final long duration = timings.get(timingName).longValue(); final String strDuration = toMillisPrint(duration); len = strDuration.length(); for (int i = 0; i < maxValueLength - len; i++) { @@ -233,11 +236,7 @@ public final class Timing implements Loggable { } private void accumulateTime(final String module, final long duration) { - Long accumulatedTime = timings.get(module); - if (accumulatedTime == null) { - accumulatedTime = 0L; - } - timings.put(module, accumulatedTime + duration); + timings.computeIfAbsent(module, newTimingCreator).add(duration); } } } From 373f5906d40484cbb6ecd85d86ba194798fc64b9 Mon Sep 17 00:00:00 2001 From: Attila Szegedi Date: Mon, 24 Aug 2015 09:12:35 +0200 Subject: [PATCH 04/12] 8133785: SharedScopeCall should be enabled for non-optimistic call sites in optimistic compilation Reviewed-by: hannesw, lagergren --- .../internal/codegen/CodeGenerator.java | 43 +++++++++---------- .../internal/runtime/ScriptEnvironment.java | 19 +++++++- .../runtime/options/OptionTemplate.java | 4 +- .../internal/runtime/options/Options.java | 22 ++++++++-- .../runtime/resources/Messages.properties | 2 + nashorn/test/script/basic/JDK-8053905.js | 1 + nashorn/test/script/basic/JDK-8058561.js | 2 + .../test/script/basic/JDK-8078612_eager_1a.js | 1 + .../test/script/basic/JDK-8078612_eager_1b.js | 1 + .../test/script/basic/JDK-8078612_eager_2a.js | 1 + .../test/script/basic/JDK-8078612_eager_2b.js | 1 + 11 files changed, 69 insertions(+), 28 deletions(-) diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java index 468683a1fa6..a9c45907e3e 100644 --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java @@ -249,7 +249,7 @@ final class CodeGenerator extends NodeOperatorVisitor emittedMethods = new HashSet<>(); // Function Id -> ContinuationInfo. Used by compilation of rest-of function only. - private final Map fnIdToContinuationInfo = new HashMap<>(); + private ContinuationInfo continuationInfo; private final Deque