# HG changeset patch # User lana # Date 1440706950 25200 # Node ID 61b401b23fc28208930977d46b690423911173c6 # Parent 0a68f2d8e06f8897a1dfea158285e290bf783038# Parent 6263188b48dead9c5e267097fbce9b77ba4569ee Merge diff -r 0a68f2d8e06f -r 61b401b23fc2 samples/classes.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/samples/classes.js Thu Aug 27 13:22:30 2015 -0700 @@ -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 -r 0a68f2d8e06f -r 61b401b23fc2 src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Console.java --- a/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Console.java Thu Aug 27 12:59:56 2015 -0700 +++ b/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Console.java Thu Aug 27 13:22:30 2015 -0700 @@ -34,6 +34,11 @@ 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 @@ 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 @@ 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 -r 0a68f2d8e06f -r 61b401b23fc2 src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/EditObject.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/EditObject.java Thu Aug 27 13:22:30 2015 -0700 @@ -0,0 +1,130 @@ +/* + * 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.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; +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 static final Set props; + static { + final HashSet s = new HashSet<>(); + s.add("editor"); + props = Collections.unmodifiableSet(s); + } + + private final Console console; + private final Consumer errorHandler; + private final Consumer evaluator; + private String editor; + + EditObject(final Console console, final Consumer errorHandler, + final Consumer evaluator) { + this.console = console; + this.errorHandler = errorHandler; + this.evaluator = evaluator; + } + + @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 Set keySet() { + return props; + } + + @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 = value != null && value != UNDEFINED? 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 if (! Main.HEADLESS) { + EditPad.edit(errorHandler, initText, saveHandler); + } else { + errorHandler.accept(Main.getMessage("no.editor")); + } + return UNDEFINED; + } + + @Override + public boolean isFunction() { + return true; + } +} diff -r 0a68f2d8e06f -r 61b401b23fc2 src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/EditPad.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/EditPad.java Thu Aug 27 13:22:30 2015 -0700 @@ -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 -r 0a68f2d8e06f -r 61b401b23fc2 src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/ExternalEditor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/ExternalEditor.java Thu Aug 27 13:22:30 2015 -0700 @@ -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 -r 0a68f2d8e06f -r 61b401b23fc2 src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/HistoryObject.java --- a/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/HistoryObject.java Thu Aug 27 12:59:56 2015 -0700 +++ b/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/HistoryObject.java Thu Aug 27 13:22:30 2015 -0700 @@ -25,16 +25,23 @@ package jdk.nashorn.tools.jjs; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; import java.io.IOException; +import java.io.PrintWriter; import java.util.Collections; import java.util.HashSet; import java.util.Set; +import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Supplier; import jdk.internal.jline.console.history.FileHistory; import jdk.internal.jline.console.history.History; import jdk.nashorn.api.scripting.AbstractJSObject; import jdk.nashorn.api.scripting.JSObject; import jdk.nashorn.internal.runtime.JSType; +import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; /* @@ -46,15 +53,51 @@ final HashSet s = new HashSet<>(); s.add("clear"); s.add("forEach"); + s.add("load"); s.add("print"); + s.add("save"); s.add("size"); + s.add("toString"); props = Collections.unmodifiableSet(s); } private final FileHistory hist; + private final PrintWriter err; + private final Consumer evaluator; - HistoryObject(final FileHistory hist) { + HistoryObject(final FileHistory hist, final PrintWriter err, + final Consumer evaluator) { this.hist = hist; + this.err = err; + this.evaluator = evaluator; + } + + @Override + public boolean isFunction() { + return true; + } + + @Override + public Object call(final Object thiz, final Object... args) { + if (args.length > 0) { + int index = JSType.toInteger(args[0]); + if (index < 0) { + index += (hist.size() - 1); + } else { + index--; + } + + if (index >= 0 && index < (hist.size() - 1)) { + final CharSequence src = hist.get(index); + hist.replace(src); + err.println(src); + evaluator.accept(src.toString()); + } else { + hist.removeLast(); + err.println("no history entry @ " + (index + 1)); + } + } + return UNDEFINED; } @Override @@ -64,10 +107,16 @@ return (Runnable)hist::clear; case "forEach": return (Function)this::iterate; + case "load": + return (Consumer)this::load; case "print": return (Runnable)this::print; + case "save": + return (Consumer)this::save; case "size": return hist.size(); + case "toString": + return (Supplier)this::toString; } return UNDEFINED; } @@ -82,7 +131,11 @@ @Override public String toString() { - return "[object history]"; + final StringBuilder buf = new StringBuilder(); + for (History.Entry e : hist) { + buf.append(e.value()).append('\n'); + } + return buf.toString(); } @Override @@ -90,9 +143,32 @@ return props; } + private void save(final Object obj) { + final File file = getFile(obj); + try (final PrintWriter pw = new PrintWriter(file)) { + for (History.Entry e : hist) { + pw.println(e.value()); + } + } catch (final IOException exp) { + throw new RuntimeException(exp); + } + } + + private void load(final Object obj) { + final File file = getFile(obj); + String item = null; + try (final BufferedReader r = new BufferedReader(new FileReader(file))) { + while ((item = r.readLine()) != null) { + hist.add(item); + } + } catch (final IOException exp) { + throw new RuntimeException(exp); + } + } + private void print() { for (History.Entry e : hist) { - System.out.println(e.value()); + System.out.printf("%3d %s\n", e.index() + 1, e.value()); } } @@ -104,4 +180,17 @@ } return UNDEFINED; } + + private static File getFile(final Object obj) { + File file = null; + if (obj instanceof String) { + file = new File((String)obj); + } else if (obj instanceof File) { + file = (File)obj; + } else { + throw typeError("not.a.file", JSType.toString(obj)); + } + + return file; + } } diff -r 0a68f2d8e06f -r 61b401b23fc2 src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java --- a/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java Thu Aug 27 12:59:56 2015 -0700 +++ b/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java Thu Aug 27 13:22:30 2015 -0700 @@ -25,6 +25,7 @@ package jdk.nashorn.tools.jjs; +import java.awt.GraphicsEnvironment; import java.io.BufferedReader; import java.io.File; import java.io.InputStream; @@ -32,12 +33,14 @@ import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; +import java.util.function.Consumer; import jdk.internal.jline.console.completer.Completer; import jdk.internal.jline.console.UserInterruptException; 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; @@ -48,6 +51,9 @@ public final class Main extends Shell { private Main() {} + 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"); @@ -96,10 +102,12 @@ protected int readEvalPrint(final Context context, final Global global) { final ScriptEnvironment env = context.getEnv(); final String prompt = bundle.getString("shell.prompt"); + final String prompt2 = bundle.getString("shell.prompt2"); 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 NashornCompleter completer = new NashornCompleter(context, global, this, propsHelper); try (final Console in = new Console(System.in, System.out, HIST_FILE, completer)) { if (globalChanged) { @@ -107,8 +115,30 @@ } global.addShellBuiltins(); - // expose history object for reflecting on command line history - global.put("history", new HistoryObject(in.getHistory()), false); + + if (System.getSecurityManager() == null) { + final Consumer evaluator = 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); + } + } + }; + + // expose history object for reflecting on command line history + global.addOwnProperty("history", Property.NOT_ENUMERABLE, new HistoryObject(in.getHistory(), err, evaluator)); + + // 'edit' command + global.addOwnProperty("edit", Property.NOT_ENUMERABLE, new EditObject(in, err::println, evaluator)); + } while (true) { String source = ""; @@ -133,10 +163,25 @@ if (res != ScriptRuntime.UNDEFINED) { err.println(JSType.toString(res)); } - } catch (final Exception e) { - err.println(e); - if (env._dump_on_error) { - e.printStackTrace(err); + } catch (final Exception exp) { + // Is this a ECMAScript SyntaxError at last column (of the single line)? + // If so, it is because parser expected more input but got EOF. Try to + // to more lines from the user (multiline edit support). + + if (completer.isSyntaxErrorAt(exp, 1, source.length())) { + final String fullSrc = completer.readMoreLines(source, exp, in, prompt2, err); + + // check if we succeeded in getting complete code. + if (fullSrc != null && !fullSrc.isEmpty()) { + evalImpl(context, global, fullSrc, err, env._dump_on_error); + } // else ignore, error reported already by 'completer.readMoreLines' + } else { + + // can't read more lines to have parseable/complete code. + err.println(exp); + if (env._dump_on_error) { + exp.printStackTrace(err); + } } } } @@ -149,8 +194,34 @@ if (globalChanged) { Context.setGlobal(oldGlobal); } + try { + propsHelper.close(); + } catch (final Exception exp) { + if (DEBUG) { + exp.printStackTrace(); + } + } } return SUCCESS; } + + static String getMessage(final String id) { + return bundle.getString(id); + } + + 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); + } + } + } } diff -r 0a68f2d8e06f -r 61b401b23fc2 src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/NashornCompleter.java --- a/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/NashornCompleter.java Thu Aug 27 12:59:56 2015 -0700 +++ b/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/NashornCompleter.java Thu Aug 27 13:22:30 2015 -0700 @@ -25,9 +25,18 @@ package jdk.nashorn.tools.jjs; +import java.io.File; +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.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; import jdk.nashorn.api.tree.BinaryTree; import jdk.nashorn.api.tree.CompilationUnitTree; @@ -46,28 +55,143 @@ import jdk.nashorn.api.tree.Parser; import jdk.nashorn.api.scripting.NashornException; import jdk.nashorn.tools.PartialParser; +import jdk.nashorn.internal.objects.NativeSyntaxError; import jdk.nashorn.internal.objects.Global; +import jdk.nashorn.internal.runtime.ECMAException; import jdk.nashorn.internal.runtime.Context; +import jdk.nashorn.internal.runtime.ScriptEnvironment; import jdk.nashorn.internal.runtime.ScriptRuntime; -// A simple source completer for nashorn +/** + * A simple source completer for nashorn. Handles code completion for + * expressions as well as handles incomplete single line code. + */ final class NashornCompleter implements Completer { private final Context context; private final Global global; + private final ScriptEnvironment env; private final PartialParser partialParser; + private final PropertiesHelper propsHelper; private final Parser parser; + private static final boolean BACKSLASH_FILE_SEPARATOR = File.separatorChar == '\\'; - 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.env = context.getEnv(); this.partialParser = partialParser; - this.parser = Parser.create(); + this.propsHelper = propsHelper; + this.parser = createParser(env); + } + + + /** + * Is this a ECMAScript SyntaxError thrown for parse issue at the given line and column? + * + * @param exp Throwable to check + * @param line line number to check + * @param column column number to check + * + * @return true if the given Throwable is a ECMAScript SyntaxError at given line, column + */ + boolean isSyntaxErrorAt(final Throwable exp, final int line, final int column) { + if (exp instanceof ECMAException) { + final ECMAException eexp = (ECMAException)exp; + if (eexp.getThrown() instanceof NativeSyntaxError) { + return isParseErrorAt(eexp.getCause(), line, column); + } + } + + return false; + } + + /** + * Is this a parse error at the given line and column? + * + * @param exp Throwable to check + * @param line line number to check + * @param column column number to check + * + * @return true if the given Throwable is a parser error at given line, column + */ + boolean isParseErrorAt(final Throwable exp, final int line, final int column) { + if (exp instanceof NashornException) { + final NashornException nexp = (NashornException)exp; + return nexp.getLineNumber() == line && nexp.getColumnNumber() == column; + } + return false; + } + + + /** + * Read more lines of code if we got SyntaxError at EOF and we can it fine by + * by reading more lines of code from the user. This is used for multiline editing. + * + * @param firstLine First line of code from the user + * @param exp Exception thrown by evaluting first line code + * @param in Console to get read more lines from the user + * @param prompt Prompt to be printed to read more lines from the user + * @param err PrintWriter to print any errors in the proecess of reading + * + * @return Complete code read from the user including the first line. This is null + * if any error or the user discarded multiline editing by Ctrl-C. + */ + String readMoreLines(final String firstLine, final Exception exp, final Console in, + final String prompt, final PrintWriter err) { + int line = 1; + final StringBuilder buf = new StringBuilder(firstLine); + while (true) { + buf.append('\n'); + String curLine = null; + try { + curLine = in.readLine(prompt); + buf.append(curLine); + line++; + } catch (final Throwable th) { + if (th instanceof UserInterruptException) { + // Ctrl-C from user - discard the whole thing silently! + return null; + } else { + // print anything else -- but still discard the code + err.println(th); + if (env._dump_on_error) { + th.printStackTrace(err); + } + return null; + } + } + + final String allLines = buf.toString(); + try { + parser.parse("", allLines, null); + } catch (final Exception pexp) { + // Do we have a parse error at the end of current line? + // If so, read more lines from the console. + if (isParseErrorAt(pexp, line, curLine.length())) { + continue; + } else { + // print anything else and bail out! + err.println(pexp); + if (env._dump_on_error) { + pexp.printStackTrace(err); + } + return null; + } + } + + // We have complete parseable code! + return buf.toString(); + } } // Pattern to match a unfinished member selection expression. object part and "." // but property name missing pattern. private static final Pattern SELECT_PROP_MISSING = Pattern.compile(".*\\.\\s*"); + // Pattern to match load call + private static final Pattern LOAD_CALL = Pattern.compile("\\s*load\\s*\\(\\s*"); + @Override public int complete(final String test, final int cursor, final List result) { // check that cursor is at the end of test string. Do not complete in the middle! @@ -96,6 +220,19 @@ 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()) { + String name = readFileName(context.getErr()); + if (name != null) { + // handle '\' file separator + if (BACKSLASH_FILE_SEPARATOR) { + name = name.replace("\\", "\\\\"); + } + result.add("\"" + name + "\")"); + return cursor + name.length() + 3; + } + } + // did not parse to be a top level expression, no suggestions! return cursor; } @@ -113,6 +250,38 @@ } } + // 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 fileChooserTask = new FutureTask(() -> { + // 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); + + try { + return fileChooserTask.get(); + } catch (final ExecutionException | InterruptedException e) { + err.println(e); + if (Main.DEBUG) { + e.printStackTrace(); + } + } + return null; + } + + // fill properties of the incomplete member expression private int completeMemberSelect(final String exprStr, final int cursor, final List result, final MemberSelectTree select, final boolean endsWithDot) { final ExpressionTree objExpr = select.getExpression(); @@ -122,19 +291,22 @@ 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(); } } @@ -142,10 +314,11 @@ return cursor; } + // fill properties for the given (partial) identifer 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(); } @@ -169,6 +342,7 @@ return null; } + // get the right most expreesion of the given expression private Tree getRightMostExpression(final ExpressionTree expr) { return expr.accept(new SimpleTreeVisitorES5_1() { @Override @@ -228,4 +402,27 @@ } }, null); } + + // create a Parser instance that uses compatible command line options of the + // current ScriptEnvironment being used for REPL. + private static Parser createParser(final ScriptEnvironment env) { + final List args = new ArrayList<>(); + if (env._const_as_var) { + args.add("--const-as-var"); + } + + if (env._no_syntax_extensions) { + args.add("-nse"); + } + + if (env._scripting) { + args.add("-scripting"); + } + + if (env._strict) { + args.add("-strict"); + } + + return Parser.create(args.toArray(new String[0])); + } } diff -r 0a68f2d8e06f -r 61b401b23fc2 src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/PackagesHelper.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/PackagesHelper.java Thu Aug 27 13:22:30 2015 -0700 @@ -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 -r 0a68f2d8e06f -r 61b401b23fc2 src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/PropertiesHelper.java --- a/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/PropertiesHelper.java Thu Aug 27 12:59:56 2015 -0700 +++ b/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/PropertiesHelper.java Thu Aug 27 13:22:30 2015 -0700 @@ -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.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 @@ * 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 @@ 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 @@ return props; } + // any other Java object final Class clazz = obj.getClass(); if (propsCache.containsKey(clazz)) { return propsCache.get(clazz); @@ -94,8 +138,14 @@ 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 -r 0a68f2d8e06f -r 61b401b23fc2 src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java --- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java Thu Aug 27 12:59:56 2015 -0700 +++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java Thu Aug 27 13:22:30 2015 -0700 @@ -249,7 +249,7 @@ private final Set 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