--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/samples/classes.js Thu Aug 27 13:22:30 2015 -0700
@@ -0,0 +1,47 @@
+// Usage: jjs classes.js [ -- <java_package_name > ]
+
+/*
+ * 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();
+}
--- a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Console.java Thu Aug 27 12:59:56 2015 -0700
+++ b/nashorn/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);
+ }
+ }
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/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<String> props;
+ static {
+ final HashSet<String> s = new HashSet<>();
+ s.add("editor");
+ props = Collections.unmodifiableSet(s);
+ }
+
+ private final Console console;
+ private final Consumer<String> errorHandler;
+ private final Consumer<String> evaluator;
+ private String editor;
+
+ EditObject(final Console console, final Consumer<String> errorHandler,
+ final Consumer<String> 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<String> 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<String> {
+ 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;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/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<String> errorHandler;
+ private final String initialText;
+ private final boolean[] closeLock;
+ private final Consumer<String> saveHandler;
+
+ EditPad(Consumer<String> errorHandler, String initialText,
+ boolean[] closeLock, Consumer<String> 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<String> errorHandler, String initialText,
+ Consumer<String> 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
+ }
+ }
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/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<String> errorHandler;
+ private final Consumer<String> saveHandler;
+ private final Console input;
+
+ private WatchService watcher;
+ private Thread watchedThread;
+ private Path dir;
+ private Path tmpfile;
+
+ ExternalEditor(Consumer<String> errorHandler, Consumer<String> 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<String> 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<String> errorHandler, String initialText,
+ Consumer<String> saveHandler, Console input) {
+ ExternalEditor ed = new ExternalEditor(errorHandler, saveHandler, input);
+ ed.edit(cmd, initialText);
+ }
+}
--- a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/HistoryObject.java Thu Aug 27 12:59:56 2015 -0700
+++ b/nashorn/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<String> 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<String> evaluator;
- HistoryObject(final FileHistory hist) {
+ HistoryObject(final FileHistory hist, final PrintWriter err,
+ final Consumer<String> 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<JSObject, Object>)this::iterate;
+ case "load":
+ return (Consumer<Object>)this::load;
case "print":
return (Runnable)this::print;
+ case "save":
+ return (Consumer<Object>)this::save;
case "size":
return hist.size();
+ case "toString":
+ return (Supplier<String>)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;
+ }
}
--- a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/Main.java Thu Aug 27 12:59:56 2015 -0700
+++ b/nashorn/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<String> 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, "<shell>");
+ if (res != ScriptRuntime.UNDEFINED) {
+ err.println(JSType.toString(res));
+ }
+ } catch (final Exception e) {
+ err.println(e);
+ if (doe) {
+ e.printStackTrace(err);
+ }
+ }
+ }
}
--- a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/NashornCompleter.java Thu Aug 27 12:59:56 2015 -0700
+++ b/nashorn/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("<shell>", 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<CharSequence> 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<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);
+
+ 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<CharSequence> 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, "<suggestions>");
- } 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<CharSequence> 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<Tree, Void>() {
@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<String> 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]));
+ }
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/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<JavaFileObject.Kind> 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.<File>emptyList());
+ }
+ }
+
+ // LRU cache for java package properties lists
+ private final LinkedHashMap<String, List<String>> propsCache =
+ new LinkedHashMap<String, List<String>>(32, 0.75f, true) {
+ private static final int CACHE_SIZE = 100;
+ private static final long serialVersionUID = 1;
+
+ @Override
+ protected boolean removeEldestEntry(final Map.Entry<String, List<String>> 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<String> getPackageProperties(final String pkg) {
+ // check the cache first
+ if (propsCache.containsKey(pkg)) {
+ return propsCache.get(pkg);
+ }
+
+ try {
+ // make sorted list of properties
+ final List<String> 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.<String>emptyList();
+ }
+ }
+
+ public void close() throws IOException {
+ fm.close();
+ }
+
+ private Set<String> listPackage(final String pkg) throws IOException {
+ final Set<String> 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<String> 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<File> getFiles(final String classPath) {
+ return Stream.of(classPath.split(File.pathSeparator))
+ .map(File::new)
+ .collect(Collectors.toList());
+ }
+}
--- a/nashorn/src/jdk.scripting.nashorn.shell/share/classes/jdk/nashorn/tools/jjs/PropertiesHelper.java Thu Aug 27 12:59:56 2015 -0700
+++ b/nashorn/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<Object, List<String>> propsCache = new WeakHashMap<>();
+ private final WeakHashMap<Object, List<String>> propsCache = new WeakHashMap<>();
- // returns the list of properties of the given object
- static List<String> 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<String> 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.<String>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<String> 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<String> getProperties(final Object obj, final String prefix) {
assert prefix != null && !prefix.isEmpty();
return getProperties(obj).stream()
.filter(s -> s.startsWith(prefix))
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java Thu Aug 27 12:59:56 2015 -0700
+++ b/nashorn/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<String> emittedMethods = new HashSet<>();
// Function Id -> ContinuationInfo. Used by compilation of rest-of function only.
- private final Map<Integer, ContinuationInfo> fnIdToContinuationInfo = new HashMap<>();
+ private ContinuationInfo continuationInfo;
private final Deque<Label> scopeEntryLabels = new ArrayDeque<>();
@@ -349,11 +349,20 @@
final int flags = getScopeCallSiteFlags(symbol);
if (isFastScope(symbol)) {
// Only generate shared scope getter for fast-scope symbols so we know we can dial in correct scope.
- if (symbol.getUseCount() > SharedScopeCall.FAST_SCOPE_GET_THRESHOLD && !isOptimisticOrRestOf()) {
- method.loadCompilerConstant(SCOPE);
- // As shared scope vars are only used in non-optimistic compilation, we switch from using TypeBounds to
+ if (symbol.getUseCount() > SharedScopeCall.FAST_SCOPE_GET_THRESHOLD && !identNode.isOptimistic()) {
+ // As shared scope vars are only used with non-optimistic identifiers, we switch from using TypeBounds to
// just a single definitive type, resultBounds.widest.
- loadSharedScopeVar(resultBounds.widest, symbol, flags);
+ new OptimisticOperation(identNode, TypeBounds.OBJECT) {
+ @Override
+ void loadStack() {
+ method.loadCompilerConstant(SCOPE);
+ }
+
+ @Override
+ void consumeStack() {
+ loadSharedScopeVar(resultBounds.widest, symbol, flags);
+ }
+ }.emit();
} else {
new LoadFastScopeVar(identNode, resultBounds, flags).emit();
}
@@ -384,10 +393,6 @@
return continuationEntryPoints != null;
}
- private boolean isOptimisticOrRestOf() {
- return useOptimisticTypes() || isRestOf();
- }
-
private boolean isCurrentContinuationEntryPoint(final int programPoint) {
return isRestOf() && getCurrentContinuationEntryPoint() == programPoint;
}
@@ -464,12 +469,8 @@
}
private MethodEmitter loadSharedScopeVar(final Type valueType, final Symbol symbol, final int flags) {
- assert !isOptimisticOrRestOf();
- if (isFastScope(symbol)) {
- method.load(getScopeProtoDepth(lc.getCurrentBlock(), symbol));
- } else {
- method.load(-1);
- }
+ assert isFastScope(symbol);
+ method.load(getScopeProtoDepth(lc.getCurrentBlock(), symbol));
return lc.getScopeGet(unit, symbol, valueType, flags).generateInvoke(method);
}
@@ -1573,7 +1574,7 @@
} else if (useCount <= SharedScopeCall.FAST_SCOPE_CALL_THRESHOLD
|| !isFastScope(symbol) && useCount <= SharedScopeCall.SLOW_SCOPE_CALL_THRESHOLD
|| CodeGenerator.this.lc.inDynamicScope()
- || isOptimisticOrRestOf()) {
+ || callNode.isOptimistic()) {
scopeCall(node, flags);
} else {
sharedScopeCall(node, flags);
@@ -2070,8 +2071,6 @@
@Override
public boolean enterFunctionNode(final FunctionNode functionNode) {
- final int fnId = functionNode.getId();
-
if (skipFunction(functionNode)) {
// In case we are not generating code for the function, we must create or retrieve the function object and
// load it on the stack here.
@@ -2109,9 +2108,9 @@
method.begin();
if (isRestOf()) {
- final ContinuationInfo ci = new ContinuationInfo();
- fnIdToContinuationInfo.put(fnId, ci);
- method.gotoLoopStart(ci.getHandlerLabel());
+ assert continuationInfo == null;
+ continuationInfo = new ContinuationInfo();
+ method.gotoLoopStart(continuationInfo.getHandlerLabel());
}
}
@@ -5308,7 +5307,7 @@
}
private ContinuationInfo getContinuationInfo() {
- return fnIdToContinuationInfo.get(lc.getCurrentFunction().getId());
+ return continuationInfo;
}
private void generateContinuationHandler() {
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/parser/Lexer.java Thu Aug 27 12:59:56 2015 -0700
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/parser/Lexer.java Thu Aug 27 13:22:30 2015 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 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
@@ -1451,9 +1451,22 @@
skip(3);
}
- // Scan identifier.
+ // Scan identifier. It might be quoted, indicating that no string editing should take place.
+ final char quoteChar = ch0;
+ final boolean noStringEditing = quoteChar == '"' || quoteChar == '\'';
+ if (noStringEditing) {
+ skip(1);
+ }
final int identStart = position;
final int identLength = scanIdentifier();
+ if (noStringEditing) {
+ if (ch0 != quoteChar) {
+ error(Lexer.message("here.non.matching.delimiter"), last, position, position);
+ restoreState(saved);
+ return false;
+ }
+ skip(1);
+ }
// Check for identifier.
if (identLength == 0) {
@@ -1523,7 +1536,7 @@
}
// Edit string if appropriate.
- if (scripting && !stringState.isEmpty()) {
+ if (!noStringEditing && !stringState.isEmpty()) {
editString(STRING, stringState);
} else {
// Add here string.
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptEnvironment.java Thu Aug 27 12:59:56 2015 -0700
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptEnvironment.java Thu Aug 27 13:22:30 2015 -0700
@@ -46,6 +46,11 @@
* and output and error writers, top level Namespace etc.
*/
public final class ScriptEnvironment {
+ // Primarily intended to be used in test environments so that eager compilation tests work without an
+ // error when tested with optimistic compilation.
+ private static final boolean ALLOW_EAGER_COMPILATION_SILENT_OVERRIDE = Options.getBooleanProperty(
+ "nashorn.options.allowEagerCompilationSilentOverride", false);
+
/** Output writer for this environment */
private final PrintWriter out;
@@ -61,6 +66,9 @@
/** 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 +228,7 @@
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");
@@ -237,8 +246,20 @@
}
_fx = options.getBoolean("fx");
_global_per_engine = options.getBoolean("global.per.engine");
- _lazy_compilation = options.getBoolean("lazy.compilation");
_optimistic_types = options.getBoolean("optimistic.types");
+ final boolean lazy_compilation = options.getBoolean("lazy.compilation");
+ if (!lazy_compilation && _optimistic_types) {
+ if (!ALLOW_EAGER_COMPILATION_SILENT_OVERRIDE) {
+ throw new IllegalStateException(
+ ECMAErrors.getMessage(
+ "config.error.eagerCompilationConflictsWithOptimisticTypes",
+ options.getOptionTemplateByKey("lazy.compilation").getName(),
+ options.getOptionTemplateByKey("optimistic.types").getName()));
+ }
+ _lazy_compilation = true;
+ } else {
+ _lazy_compilation = lazy_compilation;
+ }
_loader_per_compile = options.getBoolean("loader.per.compile");
_no_java = options.getBoolean("no.java");
_no_syntax_extensions = options.getBoolean("no.syntax.extensions");
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/Timing.java Thu Aug 27 12:59:56 2015 -0700
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/Timing.java Thu Aug 27 13:22:30 2015 -0700
@@ -28,12 +28,14 @@
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 @@
}
final class TimeSupplier implements Supplier<String> {
- private final Map<String, Long> timings;
-
- TimeSupplier() {
- timings = new LinkedHashMap<>();
- }
+ private final Map<String, LongAdder> timings = new ConcurrentHashMap<>();
+ private final LinkedBlockingQueue<String> orderedTimingNames = new LinkedBlockingQueue<>();
+ private final Function<String, LongAdder> newTimingCreator = s -> {
+ orderedTimingNames.add(s);
+ return new LongAdder();
+ };
String[] getStrings() {
final List<String> strs = new ArrayList<>();
@@ -184,26 +187,26 @@
int maxKeyLength = 0;
int maxValueLength = 0;
- for (final Map.Entry<String, Long> entry : timings.entrySet()) {
+ for (final Map.Entry<String, LongAdder> 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<String, Long> 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 @@
}
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);
}
}
}
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/UserAccessorProperty.java Thu Aug 27 12:59:56 2015 -0700
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/UserAccessorProperty.java Thu Aug 27 13:22:30 2015 -0700
@@ -24,7 +24,6 @@
*/
package jdk.nashorn.internal.runtime;
-
import static jdk.nashorn.internal.lookup.Lookup.MH;
import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
@@ -34,6 +33,7 @@
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
+import java.util.concurrent.Callable;
import jdk.nashorn.internal.lookup.Lookup;
import jdk.nashorn.internal.runtime.linker.Bootstrap;
import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor;
@@ -80,6 +80,15 @@
private final static MethodHandle INVOKE_LONG_SETTER = findOwnMH_S("invokeLongSetter", void.class, Accessors.class, MethodHandle.class, String.class, Object.class, long.class);
private final static MethodHandle INVOKE_NUMBER_SETTER = findOwnMH_S("invokeNumberSetter", void.class, Accessors.class, MethodHandle.class, String.class, Object.class, double.class);
+ private static final Object OBJECT_GETTER_INVOKER_KEY = new Object();
+ private static MethodHandle getObjectGetterInvoker() {
+ return Context.getGlobal().getDynamicInvoker(OBJECT_GETTER_INVOKER_KEY, new Callable<MethodHandle>() {
+ @Override
+ public MethodHandle call() throws Exception {
+ return getINVOKE_UA_GETTER(Object.class, INVALID_PROGRAM_POINT);
+ }
+ });
+ }
static MethodHandle getINVOKE_UA_GETTER(final Class<?> returnType, final int programPoint) {
if (UnwarrantedOptimismException.isValid(programPoint)) {
@@ -90,6 +99,16 @@
}
}
+ private static final Object OBJECT_SETTER_INVOKER_KEY = new Object();
+ private static MethodHandle getObjectSetterInvoker() {
+ return Context.getGlobal().getDynamicInvoker(OBJECT_SETTER_INVOKER_KEY, new Callable<MethodHandle>() {
+ @Override
+ public MethodHandle call() throws Exception {
+ return getINVOKE_UA_SETTER(Object.class);
+ }
+ });
+ }
+
static MethodHandle getINVOKE_UA_SETTER(final Class<?> valueType) {
return Bootstrap.createDynamicInvoker("dyn:call", void.class, Object.class, Object.class, valueType);
}
@@ -181,7 +200,7 @@
@Override
public Object getObjectValue(final ScriptObject self, final ScriptObject owner) {
try {
- return invokeObjectGetter(getAccessors((owner != null) ? owner : self), getINVOKE_UA_GETTER(Object.class, INVALID_PROGRAM_POINT), self);
+ return invokeObjectGetter(getAccessors((owner != null) ? owner : self), getObjectGetterInvoker(), self);
} catch (final Error | RuntimeException t) {
throw t;
} catch (final Throwable t) {
@@ -207,7 +226,7 @@
@Override
public void setValue(final ScriptObject self, final ScriptObject owner, final Object value, final boolean strict) {
try {
- invokeObjectSetter(getAccessors((owner != null) ? owner : self), getINVOKE_UA_SETTER(Object.class), strict ? getKey() : null, self, value);
+ invokeObjectSetter(getAccessors((owner != null) ? owner : self), getObjectSetterInvoker(), strict ? getKey() : null, self, value);
} catch (final Error | RuntimeException t) {
throw t;
} catch (final Throwable t) {
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/options/OptionTemplate.java Thu Aug 27 12:59:56 2015 -0700
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/options/OptionTemplate.java Thu Aug 27 13:22:30 2015 -0700
@@ -304,8 +304,8 @@
}
}
- boolean matches(final String key0) {
- return key0.equals(this.shortName) || key0.equals(this.name);
+ boolean nameMatches(final String aName) {
+ return aName.equals(this.shortName) || aName.equals(this.name);
}
private static final int LINE_BREAK = 64;
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/options/Options.java Thu Aug 27 12:59:56 2015 -0700
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/options/Options.java Thu Aug 27 13:22:30 2015 -0700
@@ -520,9 +520,25 @@
}
}
- private static OptionTemplate getOptionTemplate(final String key) {
+ /**
+ * Retrieves an option template identified by key.
+ * @param shortKey the short (that is without the e.g. "nashorn.option." part) key
+ * @return the option template identified by the key
+ * @throws IllegalArgumentException if the key doesn't specify an existing template
+ */
+ public OptionTemplate getOptionTemplateByKey(final String shortKey) {
+ final String fullKey = key(shortKey);
+ for(final OptionTemplate t: validOptions) {
+ if(t.getKey().equals(fullKey)) {
+ return t;
+ }
+ }
+ throw new IllegalArgumentException(shortKey);
+ }
+
+ private static OptionTemplate getOptionTemplateByName(final String name) {
for (final OptionTemplate t : Options.validOptions) {
- if (t.matches(key)) {
+ if (t.nameMatches(name)) {
return t;
}
}
@@ -682,7 +698,7 @@
}
final String token = st.nextToken();
- this.template = Options.getOptionTemplate(token);
+ this.template = getOptionTemplateByName(token);
if (this.template == null) {
throw new IllegalArgumentException(argument);
}
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/resources/Messages.properties Thu Aug 27 12:59:56 2015 -0700
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/resources/Messages.properties Thu Aug 27 13:22:30 2015 -0700
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2010, 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
@@ -31,6 +31,7 @@
lexer.error.json.invalid.number=Invalid JSON number format
lexer.error.invalid.escape.char=Invalid escape character
lexer.error.illegal.identifier.character=Illegal character in identifier
+lexer.error.here.non.matching.delimiter=Quoted here string end marker must have matching delimiters
parser.error.illegal.continue.stmt=Illegal continue statement
parser.error.illegal.break.stmt=Illegal break statement
@@ -172,7 +173,9 @@
syntax.error.unprotected.switch.declaration=Unsupported {0} declaration in unprotected switch statement
io.error.cant.write=cannot write "{0}"
+
config.error.no.dest=no destination directory supplied
+config.error.eagerCompilationConflictsWithOptimisticTypes={0}=false (eager compilation) is not compatible with {1}=true.
uri.error.bad.uri=Bad URI "{0}" near offset {1}
list.adapter.null.global=Attempted to create the adapter from outside a JavaScript execution context.
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/tools/resources/Shell.properties Thu Aug 27 12:59:56 2015 -0700
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/tools/resources/Shell.properties Thu Aug 27 13:22:30 2015 -0700
@@ -29,4 +29,6 @@
shell.prompt=jjs>
+shell.prompt2=...>
+no.editor=AWT Headless mode set and no external editor is configured!
--- a/nashorn/test/script/basic/JDK-8053905.js Thu Aug 27 12:59:56 2015 -0700
+++ b/nashorn/test/script/basic/JDK-8053905.js Thu Aug 27 13:22:30 2015 -0700
@@ -28,6 +28,7 @@
* @runif external.octane
* @fork
* @option -Dnashorn.compiler.splitter.threshold=1000
+ * @option -Dnashorn.options.allowEagerCompilationSilentOverride
* @option -scripting
* @option --lazy-compilation=false
*/
--- a/nashorn/test/script/basic/JDK-8058561.js Thu Aug 27 12:59:56 2015 -0700
+++ b/nashorn/test/script/basic/JDK-8058561.js Thu Aug 27 13:22:30 2015 -0700
@@ -26,7 +26,9 @@
*
* @test
* @run
+ * @fork
* @option --lazy-compilation=false
+ * @option -Dnashorn.options.allowEagerCompilationSilentOverride
*/
// Just attempting to compile this caused the NPE
--- a/nashorn/test/script/basic/JDK-8078612_eager_1a.js Thu Aug 27 12:59:56 2015 -0700
+++ b/nashorn/test/script/basic/JDK-8078612_eager_1a.js Thu Aug 27 13:22:30 2015 -0700
@@ -29,6 +29,7 @@
* @option -pcc
* @option --lazy-compilation=false
* @option -Dnashorn.persistent.code.cache=build/nashorn_code_cache
+ * @option -Dnashorn.options.allowEagerCompilationSilentOverride
* @fork
*/
--- a/nashorn/test/script/basic/JDK-8078612_eager_1b.js Thu Aug 27 12:59:56 2015 -0700
+++ b/nashorn/test/script/basic/JDK-8078612_eager_1b.js Thu Aug 27 13:22:30 2015 -0700
@@ -29,6 +29,7 @@
* @option -pcc
* @option --lazy-compilation=false
* @option -Dnashorn.persistent.code.cache=build/nashorn_code_cache
+ * @option -Dnashorn.options.allowEagerCompilationSilentOverride
* @fork
*/
--- a/nashorn/test/script/basic/JDK-8078612_eager_2a.js Thu Aug 27 12:59:56 2015 -0700
+++ b/nashorn/test/script/basic/JDK-8078612_eager_2a.js Thu Aug 27 13:22:30 2015 -0700
@@ -29,6 +29,7 @@
* @option -pcc
* @option --lazy-compilation=false
* @option -Dnashorn.persistent.code.cache=build/nashorn_code_cache
+ * @option -Dnashorn.options.allowEagerCompilationSilentOverride
* @fork
*/
--- a/nashorn/test/script/basic/JDK-8078612_eager_2b.js Thu Aug 27 12:59:56 2015 -0700
+++ b/nashorn/test/script/basic/JDK-8078612_eager_2b.js Thu Aug 27 13:22:30 2015 -0700
@@ -29,6 +29,7 @@
* @option -pcc
* @option --lazy-compilation=false
* @option -Dnashorn.persistent.code.cache=build/nashorn_code_cache
+ * @option -Dnashorn.options.allowEagerCompilationSilentOverride
* @fork
*/
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/nosecurity/JDK-8073613.js Thu Aug 27 13:22:30 2015 -0700
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ *
+ * 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-8073613: Here documents: how to avoid string interpolation?
+ *
+ * @test
+ * @option -scripting
+ * @run
+ */
+
+var a = 2,
+ b = 3
+
+print(<<EOD)
+${a}${b}
+EOD
+
+print(<<"EOD")
+${a}${b}
+EOD
+
+print(<<'EOM')
+${a}${b}
+EOM
+
+print(<<"EOM")
+$\{a}
+EOM
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/nosecurity/JDK-8073613.js.EXPECTED Thu Aug 27 13:22:30 2015 -0700
@@ -0,0 +1,4 @@
+23
+${a}${b}
+${a}${b}
+$\{a}