8170162: jshell tool: no mechanism to programmatically launch

8170044: jshell tool: jshell missing from javax.tools.ToolProvider

Reviewed-by: jjg
This commit is contained in:
Robert Field 2016-12-20 13:42:13 -08:00
parent 23c1a004ec
commit ea579f7bec
15 changed files with 1238 additions and 271 deletions

View File

@ -46,7 +46,6 @@ import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.prefs.BackingStoreException;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -97,7 +96,7 @@ class ConsoleIOContext extends IOContext {
List<String> persistenHistory = Stream.of(repl.prefs.keys())
.filter(key -> key.startsWith(HISTORY_LINE_PREFIX))
.sorted()
.map(key -> repl.prefs.get(key, null))
.map(key -> repl.prefs.get(key))
.collect(Collectors.toList());
in.setHistory(history = new EditingHistory(in, persistenHistory) {
@Override protected boolean isComplete(CharSequence input) {
@ -215,23 +214,21 @@ class ConsoleIOContext extends IOContext {
@Override
public void close() throws IOException {
//save history:
try {
for (String key : repl.prefs.keys()) {
if (key.startsWith(HISTORY_LINE_PREFIX))
repl.prefs.remove(key);
for (String key : repl.prefs.keys()) {
if (key.startsWith(HISTORY_LINE_PREFIX)) {
repl.prefs.remove(key);
}
Collection<? extends String> savedHistory = history.save();
if (!savedHistory.isEmpty()) {
int len = (int) Math.ceil(Math.log10(savedHistory.size()+1));
String format = HISTORY_LINE_PREFIX + "%0" + len + "d";
int index = 0;
for (String historyLine : savedHistory) {
repl.prefs.put(String.format(format, index++), historyLine);
}
}
} catch (BackingStoreException ex) {
throw new IllegalStateException(ex);
}
Collection<? extends String> savedHistory = history.save();
if (!savedHistory.isEmpty()) {
int len = (int) Math.ceil(Math.log10(savedHistory.size()+1));
String format = HISTORY_LINE_PREFIX + "%0" + len + "d";
int index = 0;
for (String historyLine : savedHistory) {
repl.prefs.put(String.format(format, index++), historyLine);
}
}
repl.prefs.flush();
in.shutdown();
try {
in.getTerminal().restore();
@ -417,6 +414,7 @@ class ConsoleIOContext extends IOContext {
}
}
@Override
public void beforeUserCode() {
synchronized (this) {
inputBytes = null;
@ -424,6 +422,7 @@ class ConsoleIOContext extends IOContext {
input.setState(State.BUFFER);
}
@Override
public void afterUserCode() {
input.setState(State.WAIT);
}

View File

@ -136,25 +136,12 @@ public class JShellTool implements MessageHandler {
final InputStream userin;
final PrintStream userout;
final PrintStream usererr;
final Preferences prefs;
final PersistentStorage prefs;
final Map<String, String> envvars;
final Locale locale;
final Feedback feedback = new Feedback();
/**
* Simple constructor for the tool used by main.
* @param in command line input
* @param out command line output, feedback including errors, user System.out
* @param err start-up errors and debugging info, user System.err
*/
public JShellTool(InputStream in, PrintStream out, PrintStream err) {
this(in, out, err, out, null, out, err,
Preferences.userRoot().node("tool/JShell"),
System.getenv(),
Locale.getDefault());
}
/**
* The complete constructor for the tool (used by test harnesses).
* @param cmdin command line input -- snippets and commands
@ -164,14 +151,14 @@ public class JShellTool implements MessageHandler {
* @param userin code execution input, or null to use IOContext
* @param userout code execution output -- System.out.printf("hi")
* @param usererr code execution error stream -- System.err.printf("Oops")
* @param prefs preferences to use
* @param prefs persistence implementation to use
* @param envvars environment variable mapping to use
* @param locale locale to use
*/
public JShellTool(InputStream cmdin, PrintStream cmdout, PrintStream cmderr,
JShellTool(InputStream cmdin, PrintStream cmdout, PrintStream cmderr,
PrintStream console,
InputStream userin, PrintStream userout, PrintStream usererr,
Preferences prefs, Map<String, String> envvars, Locale locale) {
PersistentStorage prefs, Map<String, String> envvars, Locale locale) {
this.cmdin = cmdin;
this.cmdout = cmdout;
this.cmderr = cmderr;
@ -478,16 +465,6 @@ public class JShellTool implements MessageHandler {
}
}
/**
* Normal start entry point
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
new JShellTool(System.in, System.out, System.err)
.start(args);
}
public void start(String[] args) throws Exception {
List<String> loadList = processCommandArgs(args);
if (loadList == null) {
@ -502,7 +479,7 @@ public class JShellTool implements MessageHandler {
private void start(IOContext in, List<String> loadList) {
// If startup hasn't been set by command line, set from retained/default
if (startup == null) {
startup = prefs.get(STARTUP_KEY, null);
startup = prefs.get(STARTUP_KEY);
if (startup == null) {
startup = DEFAULT_STARTUP;
}
@ -513,7 +490,7 @@ public class JShellTool implements MessageHandler {
resetState(); // Initialize
// Read replay history from last jshell session into previous history
String prevReplay = prefs.get(REPLAY_RESTORE_KEY, null);
String prevReplay = prefs.get(REPLAY_RESTORE_KEY);
if (prevReplay != null) {
replayableHistoryPrevious = Arrays.asList(prevReplay.split(RECORD_SEPARATOR));
}
@ -788,7 +765,7 @@ public class JShellTool implements MessageHandler {
// These predefined modes are read-only
feedback.markModesReadOnly();
// Restore user defined modes retained on previous run with /set mode -retain
String encoded = prefs.get(MODE_KEY, null);
String encoded = prefs.get(MODE_KEY);
if (encoded != null && !encoded.isEmpty()) {
if (!feedback.restoreEncodedModes(initmh, encoded)) {
// Catastrophic corruption -- remove the retained modes
@ -802,7 +779,7 @@ public class JShellTool implements MessageHandler {
}
commandLineFeedbackMode = null;
} else {
String fb = prefs.get(FEEDBACK_KEY, null);
String fb = prefs.get(FEEDBACK_KEY);
if (fb != null) {
// Restore the feedback mode to use that was retained
// on a previous run with /set feedback -retain
@ -1485,9 +1462,9 @@ public class JShellTool implements MessageHandler {
}
// returns null if not stored in preferences
static EditorSetting fromPrefs(Preferences prefs) {
static EditorSetting fromPrefs(PersistentStorage prefs) {
// Read retained editor setting (if any)
String editorString = prefs.get(EDITOR_KEY, "");
String editorString = prefs.get(EDITOR_KEY);
if (editorString == null || editorString.isEmpty()) {
return null;
} else if (editorString.equals(BUILT_IN_REP)) {
@ -1504,11 +1481,11 @@ public class JShellTool implements MessageHandler {
}
}
static void removePrefs(Preferences prefs) {
static void removePrefs(PersistentStorage prefs) {
prefs.remove(EDITOR_KEY);
}
void toPrefs(Preferences prefs) {
void toPrefs(PersistentStorage prefs) {
prefs.put(EDITOR_KEY, (this == BUILT_IN_EDITOR)
? BUILT_IN_REP
: (wait ? WAIT_PREFIX : NORMAL_PREFIX) + String.join(RECORD_SEPARATOR, cmd));
@ -1676,7 +1653,7 @@ public class JShellTool implements MessageHandler {
}
void showSetStart() {
String retained = prefs.get(STARTUP_KEY, null);
String retained = prefs.get(STARTUP_KEY);
if (retained != null) {
showSetStart(true, retained);
}
@ -1774,6 +1751,7 @@ public class JShellTool implements MessageHandler {
replayableHistory.subList(first + 1, replayableHistory.size()));
prefs.put(REPLAY_RESTORE_KEY, hist);
}
prefs.flush();
fluffmsg("jshell.msg.goodbye");
return true;
}

View File

@ -0,0 +1,348 @@
/*
* Copyright (c) 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.internal.jshell.tool;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import jdk.jshell.tool.JavaShellToolBuilder;
/**
* Builder for programmatically building the jshell tool.
*/
public class JShellToolBuilder implements JavaShellToolBuilder {
private static final String PREFERENCES_NODE = "tool/JShell";
private InputStream cmdIn = System.in;
private InputStream userIn = null;
private PrintStream cmdOut = System.out;
private PrintStream console = System.out;
private PrintStream userOut = System.out;
private PrintStream cmdErr = System.err;
private PrintStream userErr = System.err;
private PersistentStorage prefs = null;
private Map<String, String> vars = null;
private Locale locale = Locale.getDefault();
private boolean capturePrompt = false;
/**
* Set the input channels.
* Default, if not set, {@code in(System.in, null)}.
*
* @param cmdIn source of command input
* @param userIn source of input for running user code, or {@code null} to
* be extracted from cmdIn
* @return the {@code JavaShellToolBuilder} instance
*/
@Override
public JavaShellToolBuilder in(InputStream cmdIn, InputStream userIn) {
this.cmdIn = cmdIn;
this.userIn = userIn;
return this;
}
/**
* Set the output channels. Same as {@code out(output, output, output)}.
* Default, if not set, {@code out(System.out)}.
*
* @param output destination of command feedback, console interaction, and
* user code output
* @return the {@code JavaShellToolBuilder} instance
*/
@Override
public JavaShellToolBuilder out(PrintStream output) {
this.cmdOut = output;
this.console = output;
this.userOut = output;
return this;
}
/**
* Set the output channels.
* Default, if not set, {@code out(System.out, System.out, System.out)}.
*
* @param cmdOut destination of command feedback including error messages
* for users
* @param console destination of console interaction
* @param userOut destination of user code output. For example, user snippet
* {@code System.out.println("Hello")} when executed {@code Hello} goes to
* userOut.
* @return the {@code JavaShellToolBuilder} instance
*/
@Override
public JavaShellToolBuilder out(PrintStream cmdOut, PrintStream console, PrintStream userOut) {
this.cmdOut = cmdOut;
this.console = console;
this.userOut = userOut;
return this;
}
/**
* Set the error channels. Same as {@code err(error, error)}.
* Default, if not set, {@code err(System.err)}.
*
* @param error destination of tool errors, and
* user code errors
* @return the {@code JavaShellToolBuilder} instance
*/
@Override
public JavaShellToolBuilder err(PrintStream error) {
this.cmdErr = error;
this.userErr = error;
return this;
}
/**
* Set the error channels.
* Default, if not set, {@code err(System.err, System.err, System.err)}.
*
* @param cmdErr destination of tool start-up and fatal errors
* @param userErr destination of user code error output.
* For example, user snippet {@code System.err.println("Oops")}
* when executed {@code Oops} goes to userErr.
* @return the {@code JavaShellToolBuilder} instance
*/
@Override
public JavaShellToolBuilder err(PrintStream cmdErr, PrintStream userErr) {
this.cmdErr = cmdErr;
this.userErr = userErr;
return this;
}
/**
* Set the storage mechanism for persistent information which includes
* input history and retained settings. Default if not set is the
* tool's standard persistence mechanism.
*
* @param prefs an instance of {@link java.util.prefs.Preferences} that
* is used to retrieve and store persistent information
* @return the {@code JavaShellToolBuilder} instance
*/
@Override
public JavaShellToolBuilder persistence(Preferences prefs) {
this.prefs = new PreferencesStorage(prefs);
return this;
}
/**
* Set the storage mechanism for persistent information which includes
* input history and retained settings. Default if not set is the
* tool's standard persistence mechanism.
*
* @param prefsMap an instance of {@link java.util.Map} that
* is used to retrieve and store persistent information
* @return the {@code JavaShellToolBuilder} instance
*/
@Override
public JavaShellToolBuilder persistence(Map<String, String> prefsMap) {
this.prefs = new MapStorage(prefsMap);
return this;
}
/**
* Set the source for environment variables.
* Default, if not set, {@code env(System.getenv())}.
*
* @param vars the Map of environment variable names to values
* @return the {@code JavaShellToolBuilder} instance
*/
@Override
public JavaShellToolBuilder env(Map<String, String> vars) {
this.vars = vars;
return this;
}
/**
* Set the locale.
* Default, if not set, {@code locale(Locale.getDefault())}.
*
* @param locale the locale
* @return the {@code JavaShellToolBuilder} instance
*/
@Override
public JavaShellToolBuilder locale(Locale locale) {
this.locale = locale;
return this;
}
/**
* Set if the special command capturing prompt override should be used.
* Default, if not set, {@code promptCapture(false)}.
*
* @param capture if {@code true}, basic prompt is the {@code ENQ}
* character and continuation prompt is the {@code ACK} character.
* If false, prompts are as set with set-up or user {@code /set} commands.
* @return the {@code JavaShellToolBuilder} instance
*/
@Override
public JavaShellToolBuilder promptCapture(boolean capture) {
this.capturePrompt = capture;
return this;
}
/**
* Create a tool instance for testing. Not in JavaShellToolBuilder.
*
* @return the tool instance
*/
public JShellTool rawTool() {
if (prefs == null) {
prefs = new PreferencesStorage(Preferences.userRoot().node(PREFERENCES_NODE));
}
if (vars == null) {
vars = System.getenv();
}
JShellTool sh = new JShellTool(cmdIn, cmdOut, cmdErr, console, userIn,
userOut, userErr, prefs, vars, locale);
sh.testPrompt = capturePrompt;
return sh;
}
/**
* Run an instance of the Java shell tool as configured by the other methods
* in this interface. This call is not destructive, more than one call of
* this method may be made from a configured builder.
*
* @param arguments the command-line arguments (including options), if any
* @throws Exception an unexpected fatal exception
*/
@Override
public void run(String... arguments) throws Exception {
rawTool().start(arguments);
}
/**
* Persistence stored in Preferences.
*/
private static class PreferencesStorage implements PersistentStorage {
final Preferences p;
PreferencesStorage(Preferences p) {
this.p = p;
}
@Override
public void clear() {
try {
p.clear();
} catch (BackingStoreException ex) {
throw new IllegalStateException(ex);
}
}
@Override
public String[] keys() {
try {
return p.keys();
} catch (BackingStoreException ex) {
throw new IllegalStateException(ex);
}
}
@Override
public String get(String key) {
return p.get(key, null);
}
@Override
public void put(String key, String value) {
p.put(key, value);
}
@Override
public void remove(String key) {
p.remove(key);
}
@Override
public void flush() {
try {
p.flush();
} catch (BackingStoreException ex) {
throw new IllegalStateException(ex);
}
}
}
/**
* Persistence stored in a Map.
*/
private static class MapStorage implements PersistentStorage {
final Map<String, String> map;
MapStorage(Map<String, String> map) {
this.map = map;
}
@Override
public void clear() {
try {
map.clear();
} catch (UnsupportedOperationException ex) {
throw new IllegalStateException(ex);
}
}
@Override
public String[] keys() {
Set<String> ks = map.keySet();
return ks.toArray(new String[ks.size()]);
}
@Override
public String get(String key) {
Objects.requireNonNull(key);
return map.get(key);
}
@Override
public void put(String key, String value) {
Objects.requireNonNull(key);
Objects.requireNonNull(value);
map.put(key, value);
}
@Override
public void remove(String key) {
Objects.requireNonNull(key);
map.remove(key);
}
@Override
public void flush() {
// no-op always up-to-date
}
}
}

View File

@ -0,0 +1,121 @@
/*
* Copyright (c) 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.internal.jshell.tool;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Set;
import javax.lang.model.SourceVersion;
import javax.tools.Tool;
import jdk.jshell.tool.JavaShellToolBuilder;
/**
* Provider for launching the jshell tool.
*/
public class JShellToolProvider implements Tool {
/**
* Returns the name of this Java shell tool provider.
*
* @return the name of this tool provider
*/
@Override
public String name() {
return "jshell";
}
/**
* Run the jshell tool. The streams {@code out} and {@code err} are
* converted to {@code PrintStream} if they are not already.
* Any {@code Exception} is caught, printed and results in a non-zero return.
*
* @param in command line input (snippets and commands), and execution
* "standard" input; use System.in if null
* @param out command line output, feedback including errors, and execution
* "standard" output; use System.out if null
* @param err start-up errors and execution "standard" error; use System.err
* if null
* @param arguments arguments to pass to the tool
* @return 0 for success; nonzero otherwise
* @throws NullPointerException if the array of arguments contains
* any {@code null} elements.
*/
@Override
public int run(InputStream in, OutputStream out, OutputStream err, String... arguments) {
InputStream xin =
(in == null)
? System.in
: in;
PrintStream xout =
(out == null)
? System.out
: (out instanceof PrintStream)
? (PrintStream) out
: new PrintStream(out);
PrintStream xerr =
(err == null)
? System.err
: (err instanceof PrintStream)
? (PrintStream) err
: new PrintStream(err);
try {
JavaShellToolBuilder
.builder()
.in(xin, null)
.out(xout)
.err(xerr)
.run(arguments);
return 0;
} catch (Throwable ex) {
xerr.println(ex.getMessage());
return 1;
}
}
/**
* Returns the source versions of the jshell tool.
* @return a set of supported source versions
*/
@Override
public Set<SourceVersion> getSourceVersions() {
return Collections.unmodifiableSet(
EnumSet.range(SourceVersion.RELEASE_9, SourceVersion.latest()));
}
/**
* Launch the tool.
* @param arguments the command-line arguments (including options), if any
* @throws Exception an unexpected fatal exception
*/
public static void main(String[] arguments) throws Exception {
JavaShellToolBuilder
.builder()
.run(arguments);
}
}

View File

@ -0,0 +1,104 @@
/*
* Copyright (c) 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.internal.jshell.tool;
/**
* The required functionality jshell uses for persistent storage. Implementable
* by both Preferences API and Map.
*/
interface PersistentStorage {
/**
* Removes all of the preferences (key-value associations) in
* preferences.
*
* @throws IllegalStateException if this operation cannot be completed
* because of the state of the system.
*/
void clear();
/**
* Returns all of the keys that have an associated value in
* preferences.
*
* @return an array of the keys that have an associated value in this
* preference node.
* @throws IllegalStateException if this operation cannot be completed
* because of the state of the system.
*/
String[] keys();
/**
* Returns the value associated with the specified key in preferences.
*
* @param key key whose associated value is to be returned.
* @return the value associated with {@code key}, or {@code null} if no
* value is associated with {@code key}.
* @throws IllegalStateException if this operation cannot be completed
* because of the state of the system.
* @throws NullPointerException if {@code key} is {@code null}.
*/
String get(String key);
/**
* Associates the specified value with the specified key in this
* preference node.
*
* @param key key with which the specified value is to be associated.
* @param value value to be associated with the specified key.
* @throws NullPointerException if key or value is {@code null}.
* @throws IllegalArgumentException if key or value are too long.
* @throws IllegalStateException if this operation cannot be completed
* because of the state of the system.
*/
void put(String key, String value);
/**
* Removes the value associated with the specified key in preferences,
* if any.
*
* @param key key whose mapping is to be removed from the preference
* node.
* @throws NullPointerException if {@code key} is {@code null}.
* @throws IllegalStateException if this operation cannot be completed
* because of the state of the system.
*/
void remove(String key);
/**
* Forces any changes in the contents of this preferences to be stored.
* Once this method returns successfully, it is safe to assume that all
* changes have become as permanent as they are going to be.
* <p>
* Implementations are free to flush changes into the persistent store
* at any time. They do not need to wait for this method to be called.
*
* @throws IllegalStateException if this operation cannot be completed
* because of the state of the system.
*/
void flush();
}

View File

@ -1,38 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html>
<head>
<!--
Copyright (c) 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.
-->
</head>
<body bgcolor="white">
This document is the API specification for JShell -- support for
Java&#x2122; Programming Language 'snippet' evaluating tools, such as
Read-Eval-Print Loops (REPLs).
</body>
</html>

View File

@ -0,0 +1,193 @@
/*
* Copyright (c) 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.jshell.tool;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.Locale;
import java.util.Map;
import java.util.prefs.Preferences;
import jdk.internal.jshell.tool.JShellToolBuilder;
/**
* Interface to configure and run a Java shell tool instance. An instance of the
* builder is created with the static {@link #builder} method. This builder can,
* optionally, be configured with the configuration methods. All configuration
* methods return the builder instance for use in chained initialization. All
* configuration methods have sensible defaults which will be used if they are
* not called.. After zero or more calls to configuration methods, the tool is
* launched with a call to {@link #run(java.lang.String...) }.
*/
public interface JavaShellToolBuilder {
/**
* Create a builder for launching the JDK jshell tool.
*
* @return a builder which can be used to configure and launch the jshell
* tool
*/
static JavaShellToolBuilder builder() {
return new JShellToolBuilder();
}
/**
* Set the input channels.
*
* @implSpec If this method is not called, the behavior should be
* equivalent to calling {@code in(System.in, null)}.
*
* @param cmdIn source of command input
* @param userIn source of input for running user code, or {@code null} to
* extract user input from cmdIn
* @return the {@code JavaShellToolBuilder} instance
*/
JavaShellToolBuilder in(InputStream cmdIn, InputStream userIn);
/**
* Set the output channels. Same as {@code out(output, output, output)}.
*
* @implSpec If neither {@code out} method is called, the behavior should be
* equivalent to calling {@code out(System.out)}.
*
* @param output destination of command feedback, console interaction, and
* user code output
* @return the {@code JavaShellToolBuilder} instance
*/
JavaShellToolBuilder out(PrintStream output);
/**
* Set the output channels.
*
* @implSpec If neither {@code out} method is called, the behavior should be
* equivalent to calling {@code out(System.out, System.out, System.out)}.
*
* @param cmdOut destination of command feedback including error messages
* for users
* @param console destination of console interaction
* @param userOut destination of user code output. For example, user snippet
* {@code System.out.println("Hello")} when executed {@code Hello} goes to
* userOut.
* @return the {@code JavaShellToolBuilder} instance
*/
JavaShellToolBuilder out(PrintStream cmdOut, PrintStream console, PrintStream userOut);
/**
* Set the error channels. Same as {@code err(error, error)}.
*
* @implSpec If neither {@code err} method is called, the behavior should be
* equivalent to calling {@code err(System.err)}.
*
* @param error destination of tool errors, and
* user code errors
* @return the {@code JavaShellToolBuilder} instance
*/
JavaShellToolBuilder err(PrintStream error);
/**
* Set the error channels.
*
* @implSpec If neither {@code err} method is called, the behavior should be
* equivalent to calling {@code err(System.err, System.err, System.err)}.
*
* @param cmdErr destination of tool start-up and fatal errors
* @param userErr destination of user code error output.
* For example, user snippet {@code System.err.println("Oops")}
* when executed {@code Oops} goes to userErr.
* @return the {@code JavaShellToolBuilder} instance
*/
JavaShellToolBuilder err(PrintStream cmdErr, PrintStream userErr);
/**
* Set the storage mechanism for persistent information which includes
* input history and retained settings.
*
* @implSpec If neither {@code persistence} method is called, the behavior
* should be to use the tool's standard persistence mechanism.
*
* @param prefs an instance of {@link java.util.prefs.Preferences} that
* is used to retrieve and store persistent information
* @return the {@code JavaShellToolBuilder} instance
*/
JavaShellToolBuilder persistence(Preferences prefs);
/**
* Set the storage mechanism for persistent information which includes
* input history and retained settings.
*
* @implSpec If neither {@code persistence} method is called, the behavior
* should be to use the tool's standard persistence mechanism.
*
* @param prefsMap an instance of {@link java.util.Map} that
* is used to retrieve and store persistent information
* @return the {@code JavaShellToolBuilder} instance
*/
JavaShellToolBuilder persistence(Map<String,String> prefsMap);
/**
* Set the source for environment variables.
*
* @implSpec If this method is not called, the behavior should be
* equivalent to calling {@code env(System.getenv())}.
*
* @param vars the Map of environment variable names to values
* @return the {@code JavaShellToolBuilder} instance
*/
JavaShellToolBuilder env(Map<String,String> vars);
/**
* Set the locale.
*
* @implSpec If this method is not called, the behavior should be
* equivalent to calling {@code locale(Locale.getDefault())}.
*
* @param locale the locale
* @return the {@code JavaShellToolBuilder} instance
*/
JavaShellToolBuilder locale(Locale locale);
/**
* Set to enable a command capturing prompt override.
*
* @implSpec If this method is not called, the behavior should be
* equivalent to calling {@code promptCapture(false)}.
*
* @param capture if {@code true}, basic prompt is the {@code ENQ}
* character and continuation prompt is the {@code ACK} character.
* If false, prompts are as set with set-up or user {@code /set} commands.
* @return the {@code JavaShellToolBuilder} instance
*/
JavaShellToolBuilder promptCapture(boolean capture);
/**
* Run an instance of the Java shell tool as configured by the other methods
* in this interface. This call is not destructive, more than one call of
* this method may be made from a configured builder.
*
* @param arguments the command-line arguments (including options), if any
* @throws Exception an unexpected fatal exception
*/
void run(String... arguments) throws Exception;
}

View File

@ -0,0 +1,54 @@
/*
* Copyright (c) 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.
*/
/**
* Provides a mechanism to launch an instance of a Java&trade; shell tool.
* Allows configuration of the tool before launching. A builder is used
* to configure and launch the tool.
* <p>
* At the simplest, a builder is retrieved, and the builder is used to run the
* tool:
* <pre>
* {@code
* JavaShellToolBuilder
* .builder()
* .run();
* }
* </pre>
* The builder can be configured and the run can have arguments:
* <pre>
* {@code
* JavaShellToolBuilder
* .builder()
* .out(myCommandPrintStream, myOutputPrintStream)
* .locale(Locale.CANADA)
* .run("--feedback", "silent", "MyStart");
* }
* </pre>
*/
package jdk.jshell.tool;

View File

@ -24,14 +24,38 @@
*/
/**
* This document is the API specification for JShell -- support for
* This module provides support for
* Java&#x2122; Programming Language 'snippet' evaluating tools, such as
* Read-Eval-Print Loops (REPLs).
* Separate packages support building tools, configuring the execution of tools,
* and programmatically launching the existing Java&#x2122; shell tool.
* <p>
* The {@link jdk.jshell} is the package for creating 'snippet' evaluating tools.
* Generally, this is only package that would be needed for creating tools.
* </p>
* <p>
* The {@link jdk.jshell.spi} package specifies a Service Provider Interface (SPI)
* for defining execution engine implementations for tools based on the
* {@link jdk.jshell} API. The {@link jdk.jshell.execution} package provides
* standard implementations of {@link jdk.jshell.spi} interfaces and supporting code. It
* also serves as a library of functionality for defining new execution engine
* implementations.
* </p>
* <p>
* The {@link jdk.jshell.tool} supports programmatically launching the
* "jshell tool".
* </p>
* <p>
* The {@link jdk.jshell.execution} package contains implementations of the
* interfaces in {@link jdk.jshell.spi}. Otherwise, the four packages are
* independent, operate at different levels, and do not share functionality or
* definitions.
* </p>
*/
module jdk.jshell {
requires transitive java.compiler;
requires transitive jdk.jdi;
requires java.prefs;
requires transitive java.prefs;
requires jdk.compiler;
requires jdk.internal.le;
requires jdk.internal.ed;
@ -40,6 +64,9 @@ module jdk.jshell {
exports jdk.jshell;
exports jdk.jshell.spi;
exports jdk.jshell.execution;
exports jdk.jshell.tool;
uses jdk.internal.editor.spi.BuildInEditorProvider;
provides javax.tools.Tool with jdk.internal.jshell.tool.JShellToolProvider;
}

View File

@ -43,22 +43,73 @@ import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.testng.annotations.Test;
import jdk.internal.jshell.tool.JShellTool;
import jdk.internal.jshell.tool.JShellToolBuilder;
import jdk.jshell.SourceCodeAnalysis.Suggestion;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
@Test
public class CommandCompletionTest extends ReplToolTesting {
public void testCommand() {
assertCompletion("/deb|", false);
assertCompletion("/re|", false, "/reload ", "/reset ");
assertCompletion("/h|", false, "/help ", "/history ");
private JShellTool repl;
@Override
protected void testRawRun(Locale locale, String[] args) {
repl = ((JShellToolBuilder) builder(locale))
.rawTool();
try {
repl.start(args);
} catch (Exception ex) {
fail("Repl tool died with exception", ex);
}
}
public void assertCompletion(boolean after, String code, boolean isSmart, String... expected) {
if (!after) {
setCommandInput("\n");
} else {
assertCompletion(code, isSmart, expected);
}
}
public void assertCompletion(String code, boolean isSmart, String... expected) {
List<String> completions = computeCompletions(code, isSmart);
assertEquals(completions, Arrays.asList(expected), "Command: " + code + ", output: " +
completions.toString());
}
private List<String> computeCompletions(String code, boolean isSmart) {
int cursor = code.indexOf('|');
code = code.replace("|", "");
assertTrue(cursor > -1, "'|' not found: " + code);
List<Suggestion> completions =
repl.commandCompletionSuggestions(code, cursor, new int[] {-1}); //XXX: ignoring anchor for now
return completions.stream()
.filter(s -> isSmart == s.matchesType())
.map(s -> s.continuation())
.distinct()
.collect(Collectors.toList());
}
@Test
public void testCommand() {
testNoStartUp(
a -> assertCompletion(a, "/deb|", false),
a -> assertCompletion(a, "/re|", false, "/reload ", "/reset "),
a -> assertCompletion(a, "/h|", false, "/help ", "/history ")
);
}
@Test
public void testList() {
test(false, new String[] {"--no-startup"},
a -> assertCompletion(a, "/l|", false, "/list "),
@ -72,6 +123,7 @@ public class CommandCompletionTest extends ReplToolTesting {
);
}
@Test
public void testDrop() {
test(false, new String[] {"--no-startup"},
a -> assertCompletion(a, "/d|", false, "/drop "),
@ -83,6 +135,7 @@ public class CommandCompletionTest extends ReplToolTesting {
);
}
@Test
public void testEdit() {
test(false, new String[]{"--no-startup"},
a -> assertCompletion(a, "/e|", false, "/edit ", "/exit "),
@ -101,31 +154,38 @@ public class CommandCompletionTest extends ReplToolTesting {
);
}
@Test
public void testHelp() {
assertCompletion("/help |", false,
testNoStartUp(
a -> assertCompletion(a, "/help |", false,
"/! ", "/-<n> ", "/<id> ", "/? ", "/classpath ", "/drop ",
"/edit ", "/exit ", "/help ", "/history ", "/imports ",
"/list ", "/methods ", "/open ", "/reload ", "/reset ",
"/save ", "/set ", "/types ", "/vars ", "intro ", "shortcuts ");
assertCompletion("/? |", false,
"/save ", "/set ", "/types ", "/vars ", "intro ", "shortcuts "),
a -> assertCompletion(a, "/? |", false,
"/! ", "/-<n> ", "/<id> ", "/? ", "/classpath ", "/drop ",
"/edit ", "/exit ", "/help ", "/history ", "/imports ",
"/list ", "/methods ", "/open ", "/reload ", "/reset ",
"/save ", "/set ", "/types ", "/vars ", "intro ", "shortcuts ");
assertCompletion("/help /s|", false,
"/save ", "/set ");
assertCompletion("/help /set |", false,
"editor", "feedback", "format", "mode", "prompt", "start", "truncation");
assertCompletion("/help /edit |", false);
"/save ", "/set ", "/types ", "/vars ", "intro ", "shortcuts "),
a -> assertCompletion(a, "/help /s|", false,
"/save ", "/set "),
a -> assertCompletion(a, "/help /set |", false,
"editor", "feedback", "format", "mode", "prompt", "start", "truncation"),
a -> assertCompletion(a, "/help /edit |", false)
);
}
@Test
public void testReload() {
assertCompletion("/reload |", false, "-quiet ", "-restore ");
assertCompletion("/reload -restore |", false, "-quiet");
assertCompletion("/reload -quiet |", false, "-restore");
assertCompletion("/reload -restore -quiet |", false);
testNoStartUp(
a -> assertCompletion(a, "/reload |", false, "-quiet ", "-restore "),
a -> assertCompletion(a, "/reload -restore |", false, "-quiet"),
a -> assertCompletion(a, "/reload -quiet |", false, "-restore"),
a -> assertCompletion(a, "/reload -restore -quiet |", false)
);
}
@Test
public void testVarsMethodsTypes() {
test(false, new String[]{"--no-startup"},
a -> assertCompletion(a, "/v|", false, "/vars "),
@ -141,36 +201,53 @@ public class CommandCompletionTest extends ReplToolTesting {
);
}
@Test
public void testOpen() throws IOException {
Compiler compiler = new Compiler();
assertCompletion("/o|", false, "/open ");
testNoStartUp(
a -> assertCompletion(a, "/o|", false, "/open ")
);
List<String> p1 = listFiles(Paths.get(""));
getRootDirectories().forEach(s -> p1.add(s.toString()));
Collections.sort(p1);
assertCompletion("/open |", false, p1.toArray(new String[p1.size()]));
testNoStartUp(
a -> assertCompletion(a, "/open |", false, p1.toArray(new String[p1.size()]))
);
Path classDir = compiler.getClassDir();
List<String> p2 = listFiles(classDir);
assertCompletion("/open " + classDir + "/|", false, p2.toArray(new String[p2.size()]));
testNoStartUp(
a -> assertCompletion(a, "/open " + classDir + "/|", false, p2.toArray(new String[p2.size()]))
);
}
@Test
public void testSave() throws IOException {
Compiler compiler = new Compiler();
assertCompletion("/s|", false, "/save ", "/set ");
testNoStartUp(
a -> assertCompletion(a, "/s|", false, "/save ", "/set ")
);
List<String> p1 = listFiles(Paths.get(""));
Collections.addAll(p1, "-all ", "-history ", "-start ");
getRootDirectories().forEach(s -> p1.add(s.toString()));
Collections.sort(p1);
assertCompletion("/save |", false, p1.toArray(new String[p1.size()]));
testNoStartUp(
a -> assertCompletion(a, "/save |", false, p1.toArray(new String[p1.size()]))
);
Path classDir = compiler.getClassDir();
List<String> p2 = listFiles(classDir);
assertCompletion("/save " + classDir + "/|",
false, p2.toArray(new String[p2.size()]));
assertCompletion("/save -all " + classDir + "/|",
false, p2.toArray(new String[p2.size()]));
testNoStartUp(
a -> assertCompletion(a, "/save " + classDir + "/|",
false, p2.toArray(new String[p2.size()])),
a -> assertCompletion(a, "/save -all " + classDir + "/|",
false, p2.toArray(new String[p2.size()]))
);
}
@Test
public void testClassPath() throws IOException {
assertCompletion("/classp|", false, "/classpath ");
testNoStartUp(
a -> assertCompletion(a, "/classp|", false, "/classpath ")
);
Compiler compiler = new Compiler();
Path outDir = compiler.getPath("testClasspathCompletion");
Files.createDirectories(outDir);
@ -182,9 +259,12 @@ public class CommandCompletionTest extends ReplToolTesting {
compiler.jar(outDir, jarName, "pkg/A.class");
compiler.getPath(outDir).resolve(jarName);
List<String> paths = listFiles(outDir, CLASSPATH_FILTER);
assertCompletion("/classpath " + outDir + "/|", false, paths.toArray(new String[paths.size()]));
testNoStartUp(
a -> assertCompletion(a, "/classpath " + outDir + "/|", false, paths.toArray(new String[paths.size()]))
);
}
@Test
public void testUserHome() throws IOException {
List<String> completions;
Path home = Paths.get(System.getProperty("user.home"));
@ -194,9 +274,12 @@ public class CommandCompletionTest extends ReplToolTesting {
.sorted()
.collect(Collectors.toList());
}
assertCompletion("/classpath ~/|", false, completions.toArray(new String[completions.size()]));
testNoStartUp(
a -> assertCompletion(a, "/classpath ~/|", false, completions.toArray(new String[completions.size()]))
);
}
@Test
public void testSet() throws IOException {
List<String> p1 = listFiles(Paths.get(""));
getRootDirectories().forEach(s -> p1.add(s.toString()));

View File

@ -32,13 +32,29 @@
*/
import java.lang.reflect.Field;
import java.util.Locale;
import jdk.internal.jline.extra.EditingHistory;
import org.testng.annotations.Test;
import jdk.internal.jshell.tool.JShellTool;
import jdk.internal.jshell.tool.JShellToolBuilder;
import static org.testng.Assert.*;
@Test
public class HistoryTest extends ReplToolTesting {
private JShellTool repl;
@Override
protected void testRawRun(Locale locale, String[] args) {
repl = ((JShellToolBuilder) builder(locale))
.rawTool();
try {
repl.start(args);
} catch (Exception ex) {
fail("Repl tool died with exception", ex);
}
}
@Test
public void testHistory() {
test(
a -> {if (!a) setCommandInput("void test() {\n");},
@ -76,6 +92,7 @@ public class HistoryTest extends ReplToolTesting {
});
}
@Test
public void test8166744() {
test(
a -> {if (!a) setCommandInput("class C {\n");},

View File

@ -25,7 +25,6 @@ import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@ -36,17 +35,15 @@ import java.util.function.Function;
import java.util.function.Predicate;
import java.util.prefs.AbstractPreferences;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import jdk.internal.jshell.tool.JShellTool;
import jdk.jshell.SourceCodeAnalysis.Suggestion;
import org.testng.annotations.BeforeMethod;
import jdk.jshell.tool.JavaShellToolBuilder;
import static java.util.stream.Collectors.toList;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
@ -92,11 +89,9 @@ public class ReplToolTesting {
private Map<String, ClassInfo> classes;
private Map<String, ImportInfo> imports;
private boolean isDefaultStartUp = true;
private Preferences prefs;
private Map<String, String> prefsMap;
private Map<String, String> envvars;
public JShellTool repl = null;
public interface ReplTest {
void run(boolean after);
}
@ -202,6 +197,10 @@ public class ReplToolTesting {
test(Locale.ROOT, isDefaultStartUp, args, DEFAULT_STARTUP_MESSAGE, tests);
}
public void testNoStartUp(ReplTest... tests) {
test(Locale.ROOT, false, new String[] {"--no-startup"}, DEFAULT_STARTUP_MESSAGE, tests);
}
public void test(Locale locale, boolean isDefaultStartUp, String[] args, String startUpMessage, ReplTest... tests) {
this.isDefaultStartUp = isDefaultStartUp;
initSnippets();
@ -232,7 +231,7 @@ public class ReplToolTesting {
@BeforeMethod
public void setUp() {
prefs = new MemoryPreferences();
prefsMap = new HashMap<>();
envvars = new HashMap<>();
}
@ -240,7 +239,25 @@ public class ReplToolTesting {
envvars.put(name, value);
}
public void testRaw(Locale locale, String[] args, ReplTest... tests) {
protected JavaShellToolBuilder builder(Locale locale) {
return JavaShellToolBuilder
.builder()
.in(cmdin, userin)
.out(new PrintStream(cmdout), new PrintStream(console), new PrintStream(userout))
.err(new PrintStream(cmderr), new PrintStream(usererr))
.persistence(prefsMap)
.env(envvars)
.locale(locale)
.promptCapture(true);
}
private void testRaw(Locale locale, String[] args, ReplTest... tests) {
testRawInit(tests);
testRawRun(locale, args);
testRawCheck(locale);
}
private void testRawInit(ReplTest... tests) {
cmdin = new WaitingTestingInputStream();
cmdout = new ByteArrayOutputStream();
cmderr = new ByteArrayOutputStream();
@ -248,23 +265,18 @@ public class ReplToolTesting {
userin = new TestingInputStream();
userout = new ByteArrayOutputStream();
usererr = new ByteArrayOutputStream();
repl = new JShellTool(
cmdin,
new PrintStream(cmdout),
new PrintStream(cmderr),
new PrintStream(console),
userin,
new PrintStream(userout),
new PrintStream(usererr),
prefs,
envvars,
locale);
repl.testPrompt = true;
}
protected void testRawRun(Locale locale, String[] args) {
try {
repl.start(args);
builder(locale)
.run(args);
} catch (Exception ex) {
fail("Repl tool died with exception", ex);
}
}
private void testRawCheck(Locale locale) {
// perform internal consistency checks on state, if desired
String cos = getCommandOutput();
String ceos = getCommandErrorOutput();
@ -272,9 +284,9 @@ public class ReplToolTesting {
String ueos = getUserErrorOutput();
assertTrue((cos.isEmpty() || cos.startsWith("| Goodbye") || !locale.equals(Locale.ROOT)),
"Expected a goodbye, but got: " + cos);
assertTrue(ceos.isEmpty(), "Expected empty error output, got: " + ceos);
assertTrue(uos.isEmpty(), "Expected empty output, got: " + uos);
assertTrue(ueos.isEmpty(), "Expected empty error output, got: " + ueos);
assertTrue(ceos.isEmpty(), "Expected empty command error output, got: " + ceos);
assertTrue(uos.isEmpty(), "Expected empty user output, got: " + uos);
assertTrue(ueos.isEmpty(), "Expected empty user error output, got: " + ueos);
}
public void assertReset(boolean after, String cmd) {
@ -454,36 +466,6 @@ public class ReplToolTesting {
}
}
public void assertCompletion(boolean after, String code, boolean isSmart, String... expected) {
if (!after) {
setCommandInput("\n");
} else {
assertCompletion(code, isSmart, expected);
}
}
public void assertCompletion(String code, boolean isSmart, String... expected) {
List<String> completions = computeCompletions(code, isSmart);
assertEquals(completions, Arrays.asList(expected), "Command: " + code + ", output: " +
completions.toString());
}
private List<String> computeCompletions(String code, boolean isSmart) {
JShellTool js = this.repl != null ? this.repl
: new JShellTool(null, null, null, null, null, null, null,
prefs, envvars, Locale.ROOT);
int cursor = code.indexOf('|');
code = code.replace("|", "");
assertTrue(cursor > -1, "'|' not found: " + code);
List<Suggestion> completions =
js.commandCompletionSuggestions(code, cursor, new int[] {-1}); //XXX: ignoring anchor for now
return completions.stream()
.filter(s -> isSmart == s.matchesType())
.map(s -> s.continuation())
.distinct()
.collect(Collectors.toList());
}
public Consumer<String> assertStartsWith(String prefix) {
return (output) -> assertTrue(output.startsWith(prefix), "Output: \'" + output + "' does not start with: " + prefix);
}

View File

@ -22,7 +22,7 @@
*/
/*
* @test 8151754 8080883 8160089 8166581
* @test 8151754 8080883 8160089 8170162 8166581
* @summary Testing start-up options.
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
@ -33,19 +33,21 @@
* @run testng StartOptionTest
*/
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Locale;
import java.util.ServiceLoader;
import java.util.function.Consumer;
import jdk.internal.jshell.tool.JShellTool;
import javax.tools.Tool;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import jdk.jshell.tool.JavaShellToolBuilder;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
@ -59,21 +61,26 @@ public class StartOptionTest {
private ByteArrayOutputStream userout;
private ByteArrayOutputStream usererr;
private JShellTool getShellTool() {
return new JShellTool(
new TestingInputStream(),
new PrintStream(cmdout),
new PrintStream(cmderr),
new PrintStream(console),
null,
new PrintStream(userout),
new PrintStream(usererr),
new ReplToolTesting.MemoryPreferences(),
new HashMap<>(),
Locale.ROOT);
private JavaShellToolBuilder builder() {
return JavaShellToolBuilder
.builder()
.out(new PrintStream(cmdout), new PrintStream(console), new PrintStream(userout))
.err(new PrintStream(cmderr), new PrintStream(usererr))
.persistence(new HashMap<>())
.env(new HashMap<>())
.locale(Locale.ROOT);
}
private void check(ByteArrayOutputStream str, Consumer<String> checkOut, String label) {
private void runShell(String... args) {
try {
builder()
.run(args);
} catch (Exception ex) {
fail("Repl tool died with exception", ex);
}
}
protected void check(ByteArrayOutputStream str, Consumer<String> checkOut, String label) {
byte[] bytes = str.toByteArray();
str.reset();
String out = new String(bytes, StandardCharsets.UTF_8);
@ -84,18 +91,28 @@ public class StartOptionTest {
}
}
private void start(Consumer<String> checkOutput, Consumer<String> checkError, String... args) throws Exception {
JShellTool tool = getShellTool();
tool.start(args);
check(cmdout, checkOutput, "cmdout");
protected void start(Consumer<String> checkCmdOutput,
Consumer<String> checkUserOutput, Consumer<String> checkError,
String... args) throws Exception {
runShell(args);
check(cmdout, checkCmdOutput, "cmdout");
check(cmderr, checkError, "cmderr");
check(console, null, "console");
check(userout, null, "userout");
check(userout, checkUserOutput, "userout");
check(usererr, null, "usererr");
}
private void start(String expectedOutput, String expectedError, String... args) throws Exception {
start(s -> assertEquals(s.trim(), expectedOutput, "cmdout: "), s -> assertEquals(s.trim(), expectedError, "cmderr: "), args);
protected void start(String expectedCmdOutput, String expectedError, String... args) throws Exception {
startWithUserOutput(expectedCmdOutput, "", expectedError, args);
}
private void startWithUserOutput(String expectedCmdOutput, String expectedUserOutput,
String expectedError, String... args) throws Exception {
start(
s -> assertEquals(s.trim(), expectedCmdOutput, "cmdout: "),
s -> assertEquals(s.trim(), expectedUserOutput, "userout: "),
s -> assertEquals(s.trim(), expectedError, "cmderr: "),
args);
}
@BeforeMethod
@ -107,21 +124,31 @@ public class StartOptionTest {
usererr = new ByteArrayOutputStream();
}
@Test
protected String writeToFile(String stuff) throws Exception {
Compiler compiler = new Compiler();
Path p = compiler.getPath("doit.repl");
compiler.writeToFile(p, stuff);
return p.toString();
}
public void testCommandFile() throws Exception {
String fn = writeToFile("String str = \"Hello \"\n/list\nSystem.out.println(str + str)\n/exit\n");
startWithUserOutput("1 : String str = \"Hello \";", "Hello Hello", "", "--no-startup", fn, "-s");
}
public void testUsage() throws Exception {
for (String opt : new String[]{"-h", "--help"}) {
start(s -> {
assertTrue(s.split("\n").length >= 7, "Not enough usage lines: " + s);
assertTrue(s.startsWith("Usage: jshell <options>"), "Unexpect usage start: " + s);
}, null, opt);
}, null, null, opt);
}
}
@Test
public void testUnknown() throws Exception {
start(s -> { },
start(null, null,
s -> assertEquals(s.trim(), "Unknown option: u"), "-unknown");
start(s -> { },
start(null, null,
s -> assertEquals(s.trim(), "Unknown option: unknown"), "--unknown");
}
@ -138,7 +165,7 @@ public class StartOptionTest {
public void testStartupFailedOption() throws Exception {
try {
start("", "", "-R-hoge-foo-bar");
builder().run("-R-hoge-foo-bar");
} catch (IllegalStateException ex) {
String s = ex.getMessage();
assertTrue(s.startsWith("Launching JShell execution engine threw: Failed remote"), s);
@ -151,7 +178,6 @@ public class StartOptionTest {
start("", "File 'UNKNOWN' for '--startup' is not found.", "--startup", "UNKNOWN");
}
@Test
public void testClasspath() throws Exception {
for (String cp : new String[] {"--class-path"}) {
start("", "Only one --class-path option may be used.", cp, ".", "--class-path", ".");
@ -159,7 +185,6 @@ public class StartOptionTest {
}
}
@Test
public void testFeedbackOptionConflict() throws Exception {
start("", "Only one feedback option (--feedback, -q, -s, or -v) may be used.",
"--feedback", "concise", "--feedback", "verbose");
@ -173,15 +198,13 @@ public class StartOptionTest {
start("", "Only one feedback option (--feedback, -q, -s, or -v) may be used.", "-q", "-s");
}
@Test
public void testNegFeedbackOption() throws Exception {
start("", "Argument to feedback missing.", "--feedback");
start("", "Does not match any current feedback mode: blorp -- --feedback blorp", "--feedback", "blorp");
}
@Test
public void testVersion() throws Exception {
start(s -> assertTrue(s.startsWith("jshell"), "unexpected version: " + s), null, "--version");
start(s -> assertTrue(s.startsWith("jshell"), "unexpected version: " + s), null, null, "--version");
}
@AfterMethod

View File

@ -23,7 +23,7 @@
/*
* @test
* @bug 8143037 8142447 8144095 8140265 8144906 8146138 8147887 8147886 8148316 8148317 8143955 8157953 8080347 8154714 8166649 8167643
* @bug 8143037 8142447 8144095 8140265 8144906 8146138 8147887 8147886 8148316 8148317 8143955 8157953 8080347 8154714 8166649 8167643 8170162
* @summary Tests for Basic tests for REPL tool
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
@ -49,8 +49,6 @@ import java.util.Scanner;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -305,22 +303,18 @@ public class ToolBasicTest extends ReplToolTesting {
}
public void testStartupFileOption() {
try {
Compiler compiler = new Compiler();
Path startup = compiler.getPath("StartupFileOption/startup.txt");
compiler.writeToFile(startup, "class A { public String toString() { return \"A\"; } }");
test(new String[]{"--startup", startup.toString()},
(a) -> evaluateExpression(a, "A", "new A()", "A")
);
test(new String[]{"--no-startup"},
(a) -> assertCommandCheckOutput(a, "printf(\"\")", assertStartsWith("| Error:\n| cannot find symbol"))
);
test(
(a) -> assertCommand(a, "printf(\"A\")", "", "", null, "A", "")
);
} finally {
removeStartup();
}
Compiler compiler = new Compiler();
Path startup = compiler.getPath("StartupFileOption/startup.txt");
compiler.writeToFile(startup, "class A { public String toString() { return \"A\"; } }");
test(new String[]{"--startup", startup.toString()},
(a) -> evaluateExpression(a, "A", "new A()", "A")
);
test(new String[]{"--no-startup"},
(a) -> assertCommandCheckOutput(a, "printf(\"\")", assertStartsWith("| Error:\n| cannot find symbol"))
);
test(
(a) -> assertCommand(a, "printf(\"A\")", "", "", null, "A", "")
);
}
public void testLoadingFromArgs() {
@ -436,45 +430,34 @@ public class ToolBasicTest extends ReplToolTesting {
assertEquals(Files.readAllLines(path), output);
}
public void testStartRetain() throws BackingStoreException {
try {
Compiler compiler = new Compiler();
Path startUpFile = compiler.getPath("startUp.txt");
test(
(a) -> assertVariable(a, "int", "a"),
(a) -> assertVariable(a, "double", "b", "10", "10.0"),
(a) -> assertMethod(a, "void f() {}", "()V", "f"),
(a) -> assertImport(a, "import java.util.stream.*;", "", "java.util.stream.*"),
(a) -> assertCommand(a, "/save " + startUpFile.toString(), null),
(a) -> assertCommand(a, "/set start -retain " + startUpFile.toString(), null)
);
Path unknown = compiler.getPath("UNKNOWN");
test(
(a) -> assertCommandOutputStartsWith(a, "/set start -retain " + unknown.toString(),
"| File '" + unknown + "' for '/set start' is not found.")
);
test(false, new String[0],
(a) -> {
loadVariable(a, "int", "a");
loadVariable(a, "double", "b", "10.0", "10.0");
loadMethod(a, "void f() {}", "()void", "f");
loadImport(a, "import java.util.stream.*;", "", "java.util.stream.*");
assertCommandCheckOutput(a, "/types", assertClasses());
},
(a) -> assertCommandCheckOutput(a, "/vars", assertVariables()),
(a) -> assertCommandCheckOutput(a, "/methods", assertMethods()),
(a) -> assertCommandCheckOutput(a, "/imports", assertImports())
);
} finally {
removeStartup();
}
}
private void removeStartup() {
Preferences preferences = Preferences.userRoot().node("tool/JShell");
if (preferences != null) {
preferences.remove("STARTUP");
}
public void testStartRetain() {
Compiler compiler = new Compiler();
Path startUpFile = compiler.getPath("startUp.txt");
test(
(a) -> assertVariable(a, "int", "a"),
(a) -> assertVariable(a, "double", "b", "10", "10.0"),
(a) -> assertMethod(a, "void f() {}", "()V", "f"),
(a) -> assertImport(a, "import java.util.stream.*;", "", "java.util.stream.*"),
(a) -> assertCommand(a, "/save " + startUpFile.toString(), null),
(a) -> assertCommand(a, "/set start -retain " + startUpFile.toString(), null)
);
Path unknown = compiler.getPath("UNKNOWN");
test(
(a) -> assertCommandOutputStartsWith(a, "/set start -retain " + unknown.toString(),
"| File '" + unknown + "' for '/set start' is not found.")
);
test(false, new String[0],
(a) -> {
loadVariable(a, "int", "a");
loadVariable(a, "double", "b", "10.0", "10.0");
loadMethod(a, "void f() {}", "()void", "f");
loadImport(a, "import java.util.stream.*;", "", "java.util.stream.*");
assertCommandCheckOutput(a, "/types", assertClasses());
},
(a) -> assertCommandCheckOutput(a, "/vars", assertVariables()),
(a) -> assertCommandCheckOutput(a, "/methods", assertMethods()),
(a) -> assertCommandCheckOutput(a, "/imports", assertImports())
);
}
public void testStartSave() throws IOException {

View File

@ -0,0 +1,93 @@
/*
* Copyright (c) 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.
*
* 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.
*/
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.ServiceLoader;
import java.util.function.Consumer;
import javax.tools.Tool;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import static org.testng.Assert.fail;
/*
* @test
* @bug 8170044
* @summary Test ServiceLoader launching of jshell tool
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
* jdk.jdeps/com.sun.tools.javap
* jdk.jshell/jdk.internal.jshell.tool
* @library /tools/lib
* @build Compiler toolbox.ToolBox
* @run testng ToolProviderTest
*/
@Test
public class ToolProviderTest extends StartOptionTest {
private ByteArrayOutputStream cmdout;
private ByteArrayOutputStream cmderr;
@BeforeMethod
@Override
public void setUp() {
cmdout = new ByteArrayOutputStream();
cmderr = new ByteArrayOutputStream();
}
@Override
protected void start(Consumer<String> checkCmdOutput,
Consumer<String> checkUserOutput, Consumer<String> checkError,
String... args) throws Exception {
if (runShellServiceLoader(args) != 0) {
fail("Repl tool failed");
}
check(cmdout, checkCmdOutput, "cmdout");
check(cmderr, checkError, "cmderr");
}
private int runShellServiceLoader(String... args) {
ServiceLoader<Tool> sl = ServiceLoader.load(Tool.class);
for (Tool provider : sl) {
if (provider.name().equals("jshell")) {
return provider.run(new ByteArrayInputStream(new byte[0]), cmdout, cmderr, args);
}
}
throw new AssertionError("Repl tool not found by ServiceLoader: " + sl);
}
@Override
public void testCommandFile() throws Exception {
String fn = writeToFile("String str = \"Hello \"\n/list\nSystem.out.println(str + str)\n/exit\n");
start("1 : String str = \"Hello \";" + "\n" + "Hello Hello", "", "--no-startup", fn, "-s");
}
@Override
public void testStartupFailedOption() throws Exception {
if (runShellServiceLoader("-R-hoge-foo-bar") == 0) {
fail("Expected tool failure");
} else {
check(cmderr, s -> s.startsWith("Launching JShell execution engine threw: Failed remote"), "cmderr");
}
}
}