mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-28 07:14:30 +02:00
8187443: Forest Consolidation: Move files to unified layout
Reviewed-by: darcy, ihse
This commit is contained in:
parent
270fe13182
commit
3789983e89
56923 changed files with 3 additions and 15727 deletions
|
@ -0,0 +1,202 @@
|
|||
/*
|
||||
* 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.event.ActionListener;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.io.Writer;
|
||||
import java.nio.file.Files;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
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.KeyMap;
|
||||
import jdk.internal.jline.extra.EditingHistory;
|
||||
import jdk.internal.misc.Signal;
|
||||
import jdk.internal.misc.Signal.Handler;
|
||||
|
||||
class Console implements AutoCloseable {
|
||||
private static final String DOCUMENTATION_SHORTCUT = "\033\133\132"; //Shift-TAB
|
||||
private final ConsoleReader in;
|
||||
private final File historyFile;
|
||||
|
||||
Console(final InputStream cmdin, final PrintStream cmdout, final File historyFile,
|
||||
final NashornCompleter completer, final Function<String, String> docHelper) throws IOException {
|
||||
this.historyFile = historyFile;
|
||||
|
||||
TerminalFactory.registerFlavor(Flavor.WINDOWS, isCygwin()? JJSUnixTerminal::new : JJSWindowsTerminal::new);
|
||||
TerminalFactory.registerFlavor(Flavor.UNIX, JJSUnixTerminal::new);
|
||||
in = new ConsoleReader(cmdin, cmdout);
|
||||
in.setExpandEvents(false);
|
||||
in.setHandleUserInterrupt(true);
|
||||
in.setBellEnabled(true);
|
||||
in.setCopyPasteDetection(true);
|
||||
final Iterable<String> existingHistory = historyFile.exists() ? Files.readAllLines(historyFile.toPath()) : null;
|
||||
in.setHistory(new EditingHistory(in, existingHistory) {
|
||||
@Override protected boolean isComplete(CharSequence input) {
|
||||
return completer.isComplete(input.toString());
|
||||
}
|
||||
});
|
||||
in.addCompleter(completer);
|
||||
Runtime.getRuntime().addShutdownHook(new Thread((Runnable)this::saveHistory));
|
||||
bind(DOCUMENTATION_SHORTCUT, (ActionListener)evt -> showDocumentation(docHelper));
|
||||
try {
|
||||
Signal.handle(new Signal("CONT"), new Handler() {
|
||||
@Override public void handle(Signal sig) {
|
||||
try {
|
||||
in.getTerminal().reset();
|
||||
in.redrawLine();
|
||||
in.flush();
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
//the CONT signal does not exist on this platform
|
||||
}
|
||||
}
|
||||
|
||||
String readLine(final String prompt) throws IOException {
|
||||
return in.readLine(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
saveHistory();
|
||||
}
|
||||
|
||||
private void saveHistory() {
|
||||
try (Writer out = Files.newBufferedWriter(historyFile.toPath())) {
|
||||
String lineSeparator = System.getProperty("line.separator");
|
||||
|
||||
out.write(getHistory().save()
|
||||
.stream()
|
||||
.collect(Collectors.joining(lineSeparator)));
|
||||
} catch (final IOException exp) {}
|
||||
}
|
||||
|
||||
EditingHistory getHistory() {
|
||||
return (EditingHistory) 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);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isCygwin() {
|
||||
return System.getenv("SHELL") != null;
|
||||
}
|
||||
|
||||
private void bind(String shortcut, Object action) {
|
||||
KeyMap km = in.getKeys();
|
||||
for (int i = 0; i < shortcut.length(); i++) {
|
||||
final Object value = km.getBound(Character.toString(shortcut.charAt(i)));
|
||||
if (value instanceof KeyMap) {
|
||||
km = (KeyMap) value;
|
||||
} else {
|
||||
km.bind(shortcut.substring(i), action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void showDocumentation(final Function<String, String> docHelper) {
|
||||
final String buffer = in.getCursorBuffer().buffer.toString();
|
||||
final int cursor = in.getCursorBuffer().cursor;
|
||||
final String doc = docHelper.apply(buffer.substring(0, cursor));
|
||||
try {
|
||||
if (doc != null) {
|
||||
in.println();
|
||||
in.println(doc);
|
||||
in.redrawLine();
|
||||
in.flush();
|
||||
} else {
|
||||
in.beep();
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
* Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.nashorn.tools.jjs;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.ServiceLoader;
|
||||
import jdk.nashorn.api.scripting.AbstractJSObject;
|
||||
import jdk.internal.editor.spi.BuildInEditorProvider;
|
||||
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) {
|
||||
try {
|
||||
ServiceLoader<BuildInEditorProvider> sl
|
||||
= ServiceLoader.load(BuildInEditorProvider.class);
|
||||
//find the highest ranking provider
|
||||
BuildInEditorProvider provider = null;
|
||||
for (BuildInEditorProvider p : sl){
|
||||
if (provider == null || p.rank() > provider.rank()) {
|
||||
provider = p;
|
||||
}
|
||||
}
|
||||
if (provider != null) {
|
||||
provider.edit(null, initText, saveHandler, errorHandler);
|
||||
} else {
|
||||
errorHandler.accept(Main.getMessage("jjs.err.no.builtin.editor"));
|
||||
}
|
||||
} catch (RuntimeException ex) {
|
||||
errorHandler.accept(Main.getMessage("jjs.err.cant.launch.editor"));
|
||||
}
|
||||
} else {
|
||||
errorHandler.accept(Main.getMessage("no.editor"));
|
||||
}
|
||||
return UNDEFINED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFunction() {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -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(final Consumer<String> errorHandler, final Consumer<String> saveHandler, final Console input) {
|
||||
this.errorHandler = errorHandler;
|
||||
this.saveHandler = saveHandler;
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
private void edit(final String cmd, final 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(final 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 (final ClosedWatchServiceException ex) {
|
||||
break;
|
||||
} catch (final 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(final String cmd) throws IOException {
|
||||
ProcessBuilder pb = new ProcessBuilder(cmd, tmpfile.toString());
|
||||
pb = pb.inheritIO();
|
||||
|
||||
try {
|
||||
input.suspend();
|
||||
Process process = pb.start();
|
||||
process.waitFor();
|
||||
} catch (final IOException ex) {
|
||||
errorHandler.accept("process IO failure: " + ex.getMessage());
|
||||
} catch (final 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 (final 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(final String cmd, final Consumer<String> errorHandler, final String initialText,
|
||||
final Consumer<String> saveHandler, final Console input) {
|
||||
ExternalEditor ed = new ExternalEditor(errorHandler, saveHandler, input);
|
||||
ed.edit(cmd, initialText);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
* 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.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.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;
|
||||
|
||||
/*
|
||||
* A script friendly object that exposes history of commands to scripts.
|
||||
*/
|
||||
final class HistoryObject extends AbstractJSObject {
|
||||
private static final Set<String> props;
|
||||
static {
|
||||
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 History hist;
|
||||
private final PrintWriter err;
|
||||
private final Consumer<String> evaluator;
|
||||
|
||||
HistoryObject(final History 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
|
||||
public Object getMember(final String name) {
|
||||
switch (name) {
|
||||
case "clear":
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getDefaultValue(final Class<?> hint) {
|
||||
if (hint == String.class) {
|
||||
return toString();
|
||||
}
|
||||
return UNDEFINED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder buf = new StringBuilder();
|
||||
for (History.Entry e : hist) {
|
||||
buf.append(e.value()).append('\n');
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> keySet() {
|
||||
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.printf("%3d %s\n", e.index() + 1, e.value());
|
||||
}
|
||||
}
|
||||
|
||||
private Object iterate(final JSObject func) {
|
||||
for (History.Entry e : hist) {
|
||||
if (JSType.toBoolean(func.call(this, e.value().toString()))) {
|
||||
break; // return true from callback to skip iteration
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,293 @@
|
|||
/*
|
||||
* 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 static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
|
||||
|
||||
import java.awt.Desktop;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
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.objects.NativeJava;
|
||||
import jdk.nashorn.internal.runtime.Context;
|
||||
import jdk.nashorn.internal.runtime.NativeJavaPackage;
|
||||
import jdk.nashorn.internal.runtime.Property;
|
||||
import jdk.nashorn.internal.runtime.ScriptEnvironment;
|
||||
import jdk.nashorn.internal.runtime.ScriptFunction;
|
||||
import jdk.nashorn.internal.runtime.ScriptingFunctions;
|
||||
import jdk.nashorn.internal.runtime.ScriptObject;
|
||||
import jdk.nashorn.internal.runtime.ScriptRuntime;
|
||||
import jdk.nashorn.tools.Shell;
|
||||
|
||||
/**
|
||||
* Interactive command line Shell for Nashorn.
|
||||
*/
|
||||
public final class Main extends Shell {
|
||||
private Main() {}
|
||||
|
||||
private static final String DOC_PROPERTY_NAME = "__doc__";
|
||||
|
||||
static final boolean DEBUG = Boolean.getBoolean("nashorn.jjs.debug");
|
||||
static final boolean HEADLESS = GraphicsEnvironment.isHeadless();
|
||||
|
||||
// file where history is persisted.
|
||||
private static final File HIST_FILE = new File(new File(System.getProperty("user.home")), ".jjs.history");
|
||||
|
||||
/**
|
||||
* Main entry point with the default input, output and error streams.
|
||||
*
|
||||
* @param args The command line arguments
|
||||
*/
|
||||
public static void main(final String[] args) {
|
||||
try {
|
||||
final int exitCode = main(System.in, System.out, System.err, args);
|
||||
if (exitCode != SUCCESS) {
|
||||
System.exit(exitCode);
|
||||
}
|
||||
} catch (final IOException e) {
|
||||
System.err.println(e); //bootstrapping, Context.err may not exist
|
||||
System.exit(IO_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starting point for executing a {@code Shell}. Starts a shell with the
|
||||
* given arguments and streams and lets it run until exit.
|
||||
*
|
||||
* @param in input stream for Shell
|
||||
* @param out output stream for Shell
|
||||
* @param err error stream for Shell
|
||||
* @param args arguments to Shell
|
||||
*
|
||||
* @return exit code
|
||||
*
|
||||
* @throws IOException if there's a problem setting up the streams
|
||||
*/
|
||||
public static int main(final InputStream in, final OutputStream out, final OutputStream err, final String[] args) throws IOException {
|
||||
return new Main().run(in, out, err, args);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* read-eval-print loop for Nashorn shell.
|
||||
*
|
||||
* @param context the nashorn context
|
||||
* @param global global scope object to use
|
||||
* @return return code
|
||||
*/
|
||||
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 PropertiesHelper propsHelper = new PropertiesHelper(context);
|
||||
final NashornCompleter completer = new NashornCompleter(context, global, this, propsHelper);
|
||||
|
||||
try (final Console in = new Console(System.in, System.out, HIST_FILE, completer,
|
||||
str -> {
|
||||
try {
|
||||
final Object res = context.eval(global, str, global, "<shell>");
|
||||
if (res != null && res != UNDEFINED) {
|
||||
// Special case Java types: show the javadoc for the class.
|
||||
if (NativeJava.isType(UNDEFINED, res)) {
|
||||
final String typeName = NativeJava.typeName(UNDEFINED, res).toString();
|
||||
final String url = typeName.replace('.', '/').replace('$', '.') + ".html";
|
||||
openBrowserForJavadoc(url);
|
||||
} else if (res instanceof NativeJavaPackage) {
|
||||
final String pkgName = ((NativeJavaPackage)res).getName();
|
||||
final String url = pkgName.replace('.', '/') + "/package-summary.html";
|
||||
openBrowserForJavadoc(url);
|
||||
} else if (res instanceof ScriptObject) {
|
||||
final ScriptObject sobj = (ScriptObject)res;
|
||||
if (sobj.has(DOC_PROPERTY_NAME)) {
|
||||
return toString(sobj.get(DOC_PROPERTY_NAME), global);
|
||||
} else if (sobj instanceof ScriptFunction) {
|
||||
return ((ScriptFunction)sobj).getDocumentation();
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: better than toString for other cases?
|
||||
return toString(res, global);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return null;
|
||||
})) {
|
||||
|
||||
if (globalChanged) {
|
||||
Context.setGlobal(global);
|
||||
}
|
||||
|
||||
global.addShellBuiltins();
|
||||
|
||||
// redefine readLine to use jline Console's readLine!
|
||||
ScriptingFunctions.setReadLineHelper(str-> {
|
||||
try {
|
||||
return in.readLine(str);
|
||||
} catch (final IOException ioExp) {
|
||||
throw new UncheckedIOException(ioExp);
|
||||
}
|
||||
});
|
||||
|
||||
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 = "";
|
||||
try {
|
||||
source = in.readLine(prompt);
|
||||
} catch (final IOException ioe) {
|
||||
err.println(ioe.toString());
|
||||
if (env._dump_on_error) {
|
||||
ioe.printStackTrace(err);
|
||||
}
|
||||
return IO_ERROR;
|
||||
} catch (final UserInterruptException ex) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (source == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (source.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
final Object res = context.eval(global, source, global, "<shell>");
|
||||
if (res != UNDEFINED) {
|
||||
err.println(toString(res, global));
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
err.println(e);
|
||||
if (env._dump_on_error) {
|
||||
e.printStackTrace(err);
|
||||
}
|
||||
} finally {
|
||||
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 != UNDEFINED) {
|
||||
err.println(toString(res, global));
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
err.println(e);
|
||||
if (doe) {
|
||||
e.printStackTrace(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String JAVADOC_BASE = "https://docs.oracle.com/javase/9/docs/api/";
|
||||
|
||||
private static void openBrowserForJavadoc(String relativeUrl) {
|
||||
try {
|
||||
final URI uri = new URI(JAVADOC_BASE + relativeUrl);
|
||||
Desktop.getDesktop().browse(uri);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,448 @@
|
|||
/*
|
||||
* 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.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;
|
||||
import jdk.nashorn.api.tree.CompoundAssignmentTree;
|
||||
import jdk.nashorn.api.tree.ConditionalExpressionTree;
|
||||
import jdk.nashorn.api.tree.ExpressionTree;
|
||||
import jdk.nashorn.api.tree.ExpressionStatementTree;
|
||||
import jdk.nashorn.api.tree.FunctionCallTree;
|
||||
import jdk.nashorn.api.tree.IdentifierTree;
|
||||
import jdk.nashorn.api.tree.InstanceOfTree;
|
||||
import jdk.nashorn.api.tree.MemberSelectTree;
|
||||
import jdk.nashorn.api.tree.NewTree;
|
||||
import jdk.nashorn.api.tree.SimpleTreeVisitorES5_1;
|
||||
import jdk.nashorn.api.tree.Tree;
|
||||
import jdk.nashorn.api.tree.UnaryTree;
|
||||
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. 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, final PropertiesHelper propsHelper) {
|
||||
this.context = context;
|
||||
this.global = global;
|
||||
this.env = context.getEnv();
|
||||
this.partialParser = partialParser;
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isComplete(String input) {
|
||||
try {
|
||||
parser.parse("<shell>", input, 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.
|
||||
int line = input.split("\n").length;
|
||||
int lastLineLen = input.length() - (input.lastIndexOf("\n") + 1);
|
||||
|
||||
if (isParseErrorAt(pexp, line, lastLineLen)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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!
|
||||
if (cursor != test.length()) {
|
||||
return cursor;
|
||||
}
|
||||
|
||||
// get the start of the last expression embedded in the given code
|
||||
// using the partial parsing support - so that we can complete expressions
|
||||
// inside statements, function call argument lists, array index etc.
|
||||
final int exprStart = partialParser.getLastExpressionStart(context, test);
|
||||
if (exprStart == -1) {
|
||||
return cursor;
|
||||
}
|
||||
|
||||
|
||||
// extract the last expression string
|
||||
final String exprStr = test.substring(exprStart);
|
||||
|
||||
// do we have an incomplete member selection expression that misses property name?
|
||||
final boolean endsWithDot = SELECT_PROP_MISSING.matcher(exprStr).matches();
|
||||
|
||||
// If this is an incomplete member selection, then it is not legal code.
|
||||
// Make it legal by adding a random property name "x" to it.
|
||||
final String completeExpr = endsWithDot? exprStr + "x" : exprStr;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
// Find 'right most' expression of the top level expression
|
||||
final Tree rightMostExpr = getRightMostExpression(topExpr);
|
||||
if (rightMostExpr instanceof MemberSelectTree) {
|
||||
return completeMemberSelect(exprStr, cursor, result, (MemberSelectTree)rightMostExpr, endsWithDot);
|
||||
} else if (rightMostExpr instanceof IdentifierTree) {
|
||||
return completeIdentifier(exprStr, cursor, result, (IdentifierTree)rightMostExpr);
|
||||
} else {
|
||||
// expression that we cannot handle for completion
|
||||
return cursor;
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
final String objExprCode = exprStr.substring((int)objExpr.getStartPosition(), (int)objExpr.getEndPosition());
|
||||
|
||||
// try to evaluate the object expression part as a script
|
||||
Object obj = null;
|
||||
try {
|
||||
obj = context.eval(global, objExprCode, global, "<suggestions>");
|
||||
} 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(propsHelper.getProperties(obj));
|
||||
return cursor;
|
||||
} else {
|
||||
// list of properties matching the user specified prefix
|
||||
final String prefix = select.getIdentifier();
|
||||
result.addAll(propsHelper.getProperties(obj, prefix));
|
||||
return cursor - prefix.length();
|
||||
}
|
||||
}
|
||||
|
||||
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(propsHelper.getProperties(global, name));
|
||||
return cursor - name.length();
|
||||
}
|
||||
|
||||
// returns ExpressionTree if the given code parses to a top level expression.
|
||||
// Or else returns null.
|
||||
private ExpressionTree getTopLevelExpression(final Parser parser, final String code) {
|
||||
try {
|
||||
final CompilationUnitTree cut = parser.parse("<code>", code, null);
|
||||
final List<? extends Tree> stats = cut.getSourceElements();
|
||||
if (stats.size() == 1) {
|
||||
final Tree stat = stats.get(0);
|
||||
if (stat instanceof ExpressionStatementTree) {
|
||||
return ((ExpressionStatementTree)stat).getExpression();
|
||||
}
|
||||
}
|
||||
} catch (final NashornException ignored) {
|
||||
// ignore any parser error. This is for completion anyway!
|
||||
// And user will get that error later when the expression is evaluated.
|
||||
}
|
||||
|
||||
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
|
||||
public Tree visitAssignment(final AssignmentTree at, final Void v) {
|
||||
return getRightMostExpression(at.getExpression());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tree visitCompoundAssignment(final CompoundAssignmentTree cat, final Void v) {
|
||||
return getRightMostExpression(cat.getExpression());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tree visitConditionalExpression(final ConditionalExpressionTree cet, final Void v) {
|
||||
return getRightMostExpression(cet.getFalseExpression());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tree visitBinary(final BinaryTree bt, final Void v) {
|
||||
return getRightMostExpression(bt.getRightOperand());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tree visitIdentifier(final IdentifierTree ident, final Void v) {
|
||||
return ident;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Tree visitInstanceOf(final InstanceOfTree it, final Void v) {
|
||||
return it.getType();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Tree visitMemberSelect(final MemberSelectTree select, final Void v) {
|
||||
return select;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tree visitNew(final NewTree nt, final Void v) {
|
||||
final ExpressionTree call = nt.getConstructorExpression();
|
||||
if (call instanceof FunctionCallTree) {
|
||||
final ExpressionTree func = ((FunctionCallTree)call).getFunctionSelect();
|
||||
// Is this "new Foo" or "new obj.Foo" with no user arguments?
|
||||
// If so, we may be able to do completion of constructor name.
|
||||
if (func.getEndPosition() == nt.getEndPosition()) {
|
||||
return func;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tree visitUnary(final UnaryTree ut, final Void v) {
|
||||
return getRightMostExpression(ut.getExpression());
|
||||
}
|
||||
}, 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");
|
||||
}
|
||||
|
||||
if (env._es6) {
|
||||
args.add("--language=es6");
|
||||
}
|
||||
|
||||
return Parser.create(args.toArray(new String[0]));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,260 @@
|
|||
/*
|
||||
* Copyright (c) 2015, 2016, 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.lang.reflect.Modifier;
|
||||
import java.io.IOException;
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Path;
|
||||
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;
|
||||
import jdk.nashorn.internal.runtime.Context;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
static {
|
||||
// Use javac only if security manager is not around!
|
||||
compiler = System.getSecurityManager() == null? ToolProvider.getSystemJavaCompiler() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is javac available?
|
||||
*
|
||||
* @return true if javac is available
|
||||
*/
|
||||
private static boolean isJavacAvailable() {
|
||||
return compiler != null;
|
||||
}
|
||||
|
||||
private final Context context;
|
||||
private final boolean modulePathSet;
|
||||
private final StandardJavaFileManager fm;
|
||||
private final Set<JavaFileObject.Kind> fileKinds;
|
||||
private final FileSystem jrtfs;
|
||||
|
||||
/**
|
||||
* Construct a new PackagesHelper.
|
||||
*
|
||||
* @param context the current Nashorn Context
|
||||
*/
|
||||
PackagesHelper(final Context context) throws IOException {
|
||||
this.context = context;
|
||||
final String modulePath = context.getEnv()._module_path;
|
||||
this.modulePathSet = modulePath != null && !modulePath.isEmpty();
|
||||
if (isJavacAvailable()) {
|
||||
final String classPath = context.getEnv()._classpath;
|
||||
fm = compiler.getStandardFileManager(null, null, null);
|
||||
fileKinds = EnumSet.of(JavaFileObject.Kind.CLASS);
|
||||
|
||||
if (this.modulePathSet) {
|
||||
fm.setLocation(StandardLocation.MODULE_PATH, getFiles(modulePath));
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
jrtfs = null;
|
||||
} else {
|
||||
// javac is not available - directly use jrt fs
|
||||
// to support at least platform classes.
|
||||
fm = null;
|
||||
fileKinds = null;
|
||||
jrtfs = FileSystems.getFileSystem(URI.create("jrt:/"));
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if (fm != null) {
|
||||
fm.close();
|
||||
}
|
||||
}
|
||||
|
||||
private Set<String> listPackage(final String pkg) throws IOException {
|
||||
final Set<String> props = new HashSet<>();
|
||||
if (fm != null) {
|
||||
listPackage(StandardLocation.PLATFORM_CLASS_PATH, pkg, props);
|
||||
if (this.modulePathSet) {
|
||||
for (Set<Location> locs : fm.listLocationsForModules(StandardLocation.MODULE_PATH)) {
|
||||
for (Location loc : locs) {
|
||||
listPackage(loc, pkg, props);
|
||||
}
|
||||
}
|
||||
}
|
||||
listPackage(StandardLocation.CLASS_PATH, pkg, props);
|
||||
} else if (jrtfs != null) {
|
||||
// look for the /packages/<package_name> directory
|
||||
Path pkgDir = jrtfs.getPath("/packages/" + pkg);
|
||||
if (Files.exists(pkgDir)) {
|
||||
String pkgSlashName = pkg.replace('.', '/');
|
||||
try (DirectoryStream<Path> ds = Files.newDirectoryStream(pkgDir)) {
|
||||
// it has module links under which this package occurs
|
||||
for (Path mod : ds) {
|
||||
// get the package directory under /modules
|
||||
Path pkgUnderMod = jrtfs.getPath(mod.toString() + "/" + pkgSlashName);
|
||||
try (DirectoryStream<Path> ds2 = Files.newDirectoryStream(pkgUnderMod)) {
|
||||
for (Path p : ds2) {
|
||||
String str = p.getFileName().toString();
|
||||
// get rid of ".class", if any
|
||||
if (str.endsWith(".class")) {
|
||||
final String clsName = str.substring(0, str.length() - ".class".length());
|
||||
if (clsName.indexOf('$') == -1 && isClassAccessible(pkg + "." + clsName)) {
|
||||
props.add(str);
|
||||
}
|
||||
} else if (isPackageAccessible(pkg + "." + str)) {
|
||||
props.add(str);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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"
|
||||
final String pkgName = binaryName.substring(start, nextDot);
|
||||
if (isPackageAccessible(binaryName.substring(0, nextDot))) {
|
||||
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 && isClassAccessible(binaryName)) {
|
||||
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());
|
||||
}
|
||||
|
||||
private boolean isClassAccessible(final String className) {
|
||||
try {
|
||||
final Class<?> clz = context.findClass(className);
|
||||
return Modifier.isPublic(clz.getModifiers());
|
||||
} catch (final ClassNotFoundException cnfe) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isPackageAccessible(final String pkgName) {
|
||||
try {
|
||||
Context.checkPackageAccess(pkgName);
|
||||
return true;
|
||||
} catch (final SecurityException se) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,203 @@
|
|||
/*
|
||||
* 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.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import jdk.nashorn.internal.runtime.Context;
|
||||
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;
|
||||
import jdk.nashorn.internal.objects.NativeJava;
|
||||
|
||||
/*
|
||||
* A helper class to get properties of a given object for source code completion.
|
||||
*/
|
||||
final class PropertiesHelper {
|
||||
// Java package properties helper, may be null
|
||||
private PackagesHelper pkgsHelper;
|
||||
// cached properties list
|
||||
private final WeakHashMap<Object, List<String>> propsCache = new WeakHashMap<>();
|
||||
|
||||
/**
|
||||
* Construct a new PropertiesHelper.
|
||||
*
|
||||
* @param context the current nashorn Context
|
||||
*/
|
||||
PropertiesHelper(final Context context) {
|
||||
try {
|
||||
this.pkgsHelper = new PackagesHelper(context);
|
||||
} 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();
|
||||
if (propsCache.containsKey(pmap)) {
|
||||
return propsCache.get(pmap);
|
||||
}
|
||||
final String[] keys = sobj.getAllKeys();
|
||||
List<String> props = Arrays.asList(keys);
|
||||
props = props.stream()
|
||||
.filter(s -> Character.isJavaIdentifierStart(s.charAt(0)))
|
||||
.collect(Collectors.toList());
|
||||
Collections.sort(props);
|
||||
// cache properties against the PropertyMap
|
||||
propsCache.put(pmap, props);
|
||||
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);
|
||||
}
|
||||
final List<String> props = NativeJava.getProperties(obj);
|
||||
Collections.sort(props);
|
||||
// cache properties against the StaticClass representing the class
|
||||
propsCache.put(obj, props);
|
||||
return props;
|
||||
}
|
||||
|
||||
// any other Java object
|
||||
final Class<?> clazz = obj.getClass();
|
||||
if (propsCache.containsKey(clazz)) {
|
||||
return propsCache.get(clazz);
|
||||
}
|
||||
|
||||
final List<String> props = NativeJava.getProperties(obj);
|
||||
Collections.sort(props);
|
||||
// cache properties against the Class object
|
||||
propsCache.put(clazz, props);
|
||||
return props;
|
||||
}
|
||||
|
||||
// This method creates a regex Pattern to use to do CamelCase
|
||||
// matching. The pattern is derived from user supplied string
|
||||
// containing one or more upper case characters in it.
|
||||
private static Pattern makeCamelCasePattern(final String str) {
|
||||
assert !str.isEmpty();
|
||||
|
||||
final char[] chars = str.toCharArray();
|
||||
final StringBuilder buf = new StringBuilder();
|
||||
boolean seenUpperCase = false;
|
||||
|
||||
// Skip first char for case check. Even if it is upper case,
|
||||
// we do not want to put lower case matching pattern before
|
||||
// the first letter!
|
||||
buf.append(chars[0]);
|
||||
|
||||
for (int idx = 1; idx < chars.length; idx++) {
|
||||
final char ch = chars[idx];
|
||||
if (ch >= 'A' && ch <= 'Z') {
|
||||
seenUpperCase = true;
|
||||
buf.append("[^A-Z]*");
|
||||
}
|
||||
buf.append(ch);
|
||||
}
|
||||
|
||||
if (seenUpperCase) {
|
||||
// match anything at the end!
|
||||
buf.append(".*");
|
||||
try {
|
||||
return Pattern.compile(buf.toString());
|
||||
} catch (Exception exp) {
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
List<String> allProps = getProperties(obj);
|
||||
List<String> props = allProps.stream()
|
||||
.filter(s -> s.startsWith(prefix))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// If no match, try CamelCase completion..
|
||||
if (props.isEmpty()) {
|
||||
final Pattern pat = makeCamelCasePattern(prefix);
|
||||
if (pat != null) {
|
||||
return allProps.stream()
|
||||
.filter(s -> pat.matcher(s).matches())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
return props;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Defines Nashorn shell module.
|
||||
*
|
||||
* <p>This module includes the command line tool <em>{@index jjs jjs tool}</em>
|
||||
* to invoke the Nashorn engine.
|
||||
*
|
||||
* <dl style="font-family:'DejaVu Sans', Arial, Helvetica, sans serif">
|
||||
* <dt class="simpleTagLabel">Tool Guides:
|
||||
* <dd>{@extLink jjs_tool_reference jjs}
|
||||
* </dl>
|
||||
*
|
||||
* @moduleGraph
|
||||
* @since 9
|
||||
*/
|
||||
module jdk.scripting.nashorn.shell {
|
||||
requires java.compiler;
|
||||
requires java.desktop;
|
||||
requires jdk.internal.le;
|
||||
requires jdk.scripting.nashorn;
|
||||
requires jdk.internal.ed;
|
||||
uses jdk.internal.editor.spi.BuildInEditorProvider;
|
||||
}
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue