# HG changeset patch # User rfield # Date 1483727485 28800 # Node ID 7b8b8750a78eb0475bef3285f9d603d37a155cfc # Parent 3e1520a857fad70bd53946798204d4ab23e9ec78 8165405: jshell tool: /classpath is inconsistent 8172103: JShell: crash in TaskFactory$WrapSourceHandler.diag Reviewed-by: jlahoda diff -r 3e1520a857fa -r 7b8b8750a78e langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java --- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java Fri Jan 06 14:16:45 2017 +0100 +++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java Fri Jan 06 10:31:25 2017 -0800 @@ -47,7 +47,9 @@ import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; @@ -185,10 +187,7 @@ private IOContext input = null; private boolean regenerateOnDeath = true; private boolean live = false; - private boolean feedbackInitialized = false; - private String commandLineFeedbackMode = null; - private List remoteVMOptions = new ArrayList<>(); - private List compilerOptions = new ArrayList<>(); + private Options options; SourceCodeAnalysis analysis; JShell state = null; @@ -198,7 +197,6 @@ private boolean debug = false; public boolean testPrompt = false; - private String cmdlineClasspath = null; private String defaultStartup = null; private String startup = null; private String executionControlSpec = null; @@ -221,6 +219,16 @@ static final Pattern BUILTIN_FILE_PATTERN = Pattern.compile("\\w+"); static final String BUILTIN_FILE_PATH_FORMAT = "jrt:/jdk.jshell/jdk/jshell/tool/resources/%s.jsh"; + // match anything followed by whitespace + private static final Pattern OPTION_PRE_PATTERN = + Pattern.compile("\\s*(\\S+\\s+)*?"); + // match a (possibly incomplete) option flag with optional double-dash and/or internal dashes + private static final Pattern OPTION_PATTERN = + Pattern.compile(OPTION_PRE_PATTERN.pattern() + "(?
-??)(?-([a-z][a-z\\-]*)?)"); + // match an option flag and a (possibly missing or incomplete) value + private static final Pattern OPTION_VALUE_PATTERN = + Pattern.compile(OPTION_PATTERN.pattern() + "\\s+(?\\S*)"); + // Tool id (tid) mapping: the three name spaces NameSpace mainNamespace; NameSpace startNamespace; @@ -229,7 +237,278 @@ // Tool id (tid) mapping: the current name spaces NameSpace currentNameSpace; - Map mapSnippet; + Map mapSnippet; + + // Kinds of compiler/runtime init options + private enum OptionKind { + CLASS_PATH("--class-path", true), + MODULE_PATH("--module-path", true), + ADD_MODULES("--add-modules", false), + ADD_EXPORTS("--add-exports", false), + TO_COMPILER("-C", false, false, true, false), + TO_REMOTE_VM("-R", false, false, false, true),; + final String optionFlag; + final boolean onlyOne; + final boolean passFlag; + final boolean toCompiler; + final boolean toRemoteVm; + + private OptionKind(String optionFlag, boolean onlyOne) { + this(optionFlag, onlyOne, true, true, true); + } + + private OptionKind(String optionFlag, boolean onlyOne, boolean passFlag, + boolean toCompiler, boolean toRemoteVm) { + this.optionFlag = optionFlag; + this.onlyOne = onlyOne; + this.passFlag = passFlag; + this.toCompiler = toCompiler; + this.toRemoteVm = toRemoteVm; + } + + } + + // compiler/runtime init option values + private static class Options { + + private Map> optMap = new HashMap<>(); + + private String[] selectOptions(Predicate>> pred) { + return optMap.entrySet().stream() + .filter(pred) + .flatMap(e -> e.getValue().stream()) + .toArray(String[]::new); + } + + String[] remoteVmOptions() { + return selectOptions(e -> e.getKey().toRemoteVm); + } + + String[] compilerOptions() { + return selectOptions(e -> e.getKey().toCompiler); + } + + String[] commonOptions() { + return selectOptions(e -> e.getKey().passFlag); + } + + void addAll(OptionKind kind, Collection vals) { + optMap.computeIfAbsent(kind, k -> new ArrayList<>()) + .addAll(vals); + } + + void override(Options newer) { + newer.optMap.entrySet().stream() + .forEach(e -> { + if (e.getKey().onlyOne) { + // Only one allowed, override last + optMap.put(e.getKey(), e.getValue()); + } else { + // Additive + addAll(e.getKey(), e.getValue()); + } + }); + } + } + + // base option parsing of /env, /reload, and /reset and command-line options + private class OptionParserBase { + + final OptionParser parser = new OptionParser(); + private final OptionSpec argClassPath = parser.accepts("class-path").withRequiredArg(); + private final OptionSpec argModulePath = parser.accepts("module-path").withRequiredArg(); + private final OptionSpec argAddModules = parser.accepts("add-modules").withRequiredArg(); + private final OptionSpec argAddExports = parser.accepts("add-exports").withRequiredArg(); + private final NonOptionArgumentSpec argNonOptions = parser.nonOptions(); + + private Options opts = new Options(); + private List nonOptions; + private boolean failed = false; + + List nonOptions() { + return nonOptions; + } + + void msg(String key, Object... args) { + errormsg(key, args); + } + + Options parse(String[] args) throws OptionException { + try { + OptionSet oset = parser.parse(args); + nonOptions = oset.valuesOf(argNonOptions); + return parse(oset); + } catch (OptionException ex) { + if (ex.options().isEmpty()) { + msg("jshell.err.opt.invalid", stream(args).collect(joining(", "))); + } else { + boolean isKnown = parser.recognizedOptions().containsKey(ex.options().iterator().next()); + msg(isKnown + ? "jshell.err.opt.arg" + : "jshell.err.opt.unknown", + ex.options() + .stream() + .collect(joining(", "))); + } + return null; + } + } + + Options parse(OptionSet options) { + addOptions(OptionKind.CLASS_PATH, options.valuesOf(argClassPath)); + addOptions(OptionKind.MODULE_PATH, options.valuesOf(argModulePath)); + addOptions(OptionKind.ADD_MODULES, options.valuesOf(argAddModules)); + addOptions(OptionKind.ADD_EXPORTS, options.valuesOf(argAddExports).stream() + .map(mp -> mp.contains("=") ? mp : mp + "=ALL-UNNAMED") + .collect(toList()) + ); + + return failed ? null : opts; + } + + void addOptions(OptionKind kind, Collection vals) { + if (!vals.isEmpty()) { + if (kind.onlyOne && vals.size() > 1) { + msg("jshell.err.opt.one", kind.optionFlag); + failed = true; + return; + } + if (kind.passFlag) { + vals = vals.stream() + .flatMap(mp -> Stream.of(kind.optionFlag, mp)) + .collect(toList()); + } + opts.addAll(kind, vals); + } + } + } + + // option parsing for /reload (adds -restore -quiet) + private class OptionParserReload extends OptionParserBase { + + private final OptionSpecBuilder argRestore = parser.accepts("restore"); + private final OptionSpecBuilder argQuiet = parser.accepts("quiet"); + + private boolean restore = false; + private boolean quiet = false; + + boolean restore() { + return restore; + } + + boolean quiet() { + return quiet; + } + + @Override + Options parse(OptionSet options) { + if (options.has(argRestore)) { + restore = true; + } + if (options.has(argQuiet)) { + quiet = true; + } + return super.parse(options); + } + } + + // option parsing for command-line + private class OptionParserCommandLine extends OptionParserBase { + + private final OptionSpec argStart = parser.accepts("startup").withRequiredArg(); + private final OptionSpecBuilder argNoStart = parser.acceptsAll(asList("n", "no-startup")); + private final OptionSpec argFeedback = parser.accepts("feedback").withRequiredArg(); + private final OptionSpec argExecution = parser.accepts("execution").withRequiredArg(); + private final OptionSpecBuilder argQ = parser.accepts("q"); + private final OptionSpecBuilder argS = parser.accepts("s"); + private final OptionSpecBuilder argV = parser.accepts("v"); + private final OptionSpec argR = parser.accepts("R").withRequiredArg(); + private final OptionSpec argC = parser.accepts("C").withRequiredArg(); + private final OptionSpecBuilder argHelp = parser.acceptsAll(asList("h", "help")); + private final OptionSpecBuilder argVersion = parser.accepts("version"); + private final OptionSpecBuilder argFullVersion = parser.accepts("full-version"); + private final OptionSpecBuilder argX = parser.accepts("X"); + + private String feedbackMode = null; + private String initialStartup = null; + + String feedbackMode() { + return feedbackMode; + } + + String startup() { + return initialStartup; + } + + @Override + void msg(String key, Object... args) { + startmsg(key, args); + } + + @Override + Options parse(OptionSet options) { + if (options.has(argHelp)) { + printUsage(); + return null; + } + if (options.has(argX)) { + printUsageX(); + return null; + } + if (options.has(argVersion)) { + cmdout.printf("jshell %s\n", version()); + return null; + } + if (options.has(argFullVersion)) { + cmdout.printf("jshell %s\n", fullVersion()); + return null; + } + if ((options.valuesOf(argFeedback).size() + + (options.has(argQ) ? 1 : 0) + + (options.has(argS) ? 1 : 0) + + (options.has(argV) ? 1 : 0)) > 1) { + msg("jshell.err.opt.feedback.one"); + return null; + } else if (options.has(argFeedback)) { + feedbackMode = options.valueOf(argFeedback); + } else if (options.has("q")) { + feedbackMode = "concise"; + } else if (options.has("s")) { + feedbackMode = "silent"; + } else if (options.has("v")) { + feedbackMode = "verbose"; + } + if (options.has(argStart)) { + List sts = options.valuesOf(argStart); + if (options.has("no-startup")) { + startmsg("jshell.err.opt.startup.conflict"); + return null; + } + StringBuilder sb = new StringBuilder(); + for (String fn : sts) { + String s = readFile(fn, "--startup"); + if (s == null) { + return null; + } + sb.append(s); + } + initialStartup = sb.toString(); + } else if (options.has(argNoStart)) { + initialStartup = ""; + } else { + initialStartup = prefs.get(STARTUP_KEY); + if (initialStartup == null) { + initialStartup = defaultStartup(); + } + } + if (options.has(argExecution)) { + executionControlSpec = options.valueOf(argExecution); + } + addOptions(OptionKind.TO_REMOTE_VM, options.valuesOf(argR)); + addOptions(OptionKind.TO_COMPILER, options.valuesOf(argC)); + return super.parse(options); + } + } /** * Is the input/output currently interactive @@ -464,43 +743,45 @@ } public void start(String[] args) throws Exception { - List loadList = processCommandArgs(args); - if (loadList == null) { + OptionParserCommandLine commandLineArgs = new OptionParserCommandLine(); + options = commandLineArgs.parse(args); + if (options == null) { // Abort return; } - try (IOContext in = new ConsoleIOContext(this, cmdin, console)) { - start(in, loadList); - } - } - - private void start(IOContext in, List loadList) { - // If startup hasn't been set by command line, set from retained/default - if (startup == null) { - startup = prefs.get(STARTUP_KEY); - if (startup == null) { - startup = defaultStartup(); - } - } - + startup = commandLineArgs.startup(); + // initialize editor settings configEditor(); - - resetState(); // Initialize - + // initialize JShell instance + resetState(); // Read replay history from last jshell session into previous history String prevReplay = prefs.get(REPLAY_RESTORE_KEY); if (prevReplay != null) { replayableHistoryPrevious = Arrays.asList(prevReplay.split(RECORD_SEPARATOR)); } - - for (String loadFile : loadList) { + // load snippet/command files given on command-line + for (String loadFile : commandLineArgs.nonOptions()) { runFile(loadFile, "jshell"); } + // if we survived that... + if (regenerateOnDeath) { + // initialize the predefined feedback modes + initFeedback(commandLineArgs.feedbackMode()); + } + // check again, as feedback setting could have failed + if (regenerateOnDeath) { + // if we haven't died, and the feedback mode wants fluff, print welcome + if (feedback.shouldDisplayCommandFluff()) { + hardmsg("jshell.msg.welcome", version()); + } + // execute from user input + try (IOContext in = new ConsoleIOContext(this, cmdin, console)) { + start(in); + } + } + } - if (regenerateOnDeath && feedback.shouldDisplayCommandFluff()) { - hardmsg("jshell.msg.welcome", version()); - } - + private void start(IOContext in) { try { while (regenerateOnDeath) { if (!live) { @@ -530,144 +811,6 @@ return editor = BUILT_IN_EDITOR; } - /** - * Process the command line arguments. - * Set options. - * @param args the command line arguments - * @return the list of files to be loaded - */ - private List processCommandArgs(String[] args) { - OptionParser parser = new OptionParser(); - OptionSpec cp = parser.accepts("class-path").withRequiredArg(); - OptionSpec mpath = parser.accepts("module-path").withRequiredArg(); - OptionSpec amods = parser.accepts("add-modules").withRequiredArg(); - OptionSpec st = parser.accepts("startup").withRequiredArg(); - parser.acceptsAll(asList("n", "no-startup")); - OptionSpec fb = parser.accepts("feedback").withRequiredArg(); - OptionSpec ec = parser.accepts("execution").withRequiredArg(); - parser.accepts("q"); - parser.accepts("s"); - parser.accepts("v"); - OptionSpec r = parser.accepts("R").withRequiredArg(); - OptionSpec c = parser.accepts("C").withRequiredArg(); - parser.acceptsAll(asList("h", "help")); - parser.accepts("version"); - parser.accepts("full-version"); - - parser.accepts("X"); - OptionSpec addExports = parser.accepts("add-exports").withRequiredArg(); - - NonOptionArgumentSpec loadFileSpec = parser.nonOptions(); - - OptionSet options; - try { - options = parser.parse(args); - } catch (OptionException ex) { - if (ex.options().isEmpty()) { - startmsg("jshell.err.opt.invalid", stream(args).collect(joining(", "))); - } else { - boolean isKnown = parser.recognizedOptions().containsKey(ex.options().iterator().next()); - startmsg(isKnown - ? "jshell.err.opt.arg" - : "jshell.err.opt.unknown", - ex.options() - .stream() - .collect(joining(", "))); - } - return null; - } - - if (options.has("help")) { - printUsage(); - return null; - } - if (options.has("X")) { - printUsageX(); - return null; - } - if (options.has("version")) { - cmdout.printf("jshell %s\n", version()); - return null; - } - if (options.has("full-version")) { - cmdout.printf("jshell %s\n", fullVersion()); - return null; - } - if (options.has(cp)) { - List cps = options.valuesOf(cp); - if (cps.size() > 1) { - startmsg("jshell.err.opt.one", "--class-path"); - return null; - } - cmdlineClasspath = cps.get(0); - } - if (options.has(st)) { - List sts = options.valuesOf(st); - if (options.has("no-startup")) { - startmsg("jshell.err.opt.startup.conflict"); - return null; - } - StringBuilder sb = new StringBuilder(); - for (String fn : sts) { - String s = readFile(fn, "--startup"); - if (s == null) { - return null; - } - sb.append(s); - } - startup = sb.toString(); - } else if (options.has("no-startup")) { - startup = ""; - } - if ((options.valuesOf(fb).size() + - (options.has("q") ? 1 : 0) + - (options.has("s") ? 1 : 0) + - (options.has("v") ? 1 : 0)) > 1) { - startmsg("jshell.err.opt.feedback.one"); - return null; - } else if (options.has(fb)) { - commandLineFeedbackMode = options.valueOf(fb); - } else if (options.has("q")) { - commandLineFeedbackMode = "concise"; - } else if (options.has("s")) { - commandLineFeedbackMode = "silent"; - } else if (options.has("v")) { - commandLineFeedbackMode = "verbose"; - } - if (options.has(r)) { - remoteVMOptions.addAll(options.valuesOf(r)); - } - if (options.has(c)) { - compilerOptions.addAll(options.valuesOf(c)); - } - if (options.has(mpath)) { - compilerOptions.add("--module-path"); - compilerOptions.addAll(options.valuesOf(mpath)); - remoteVMOptions.add("--module-path"); - remoteVMOptions.addAll(options.valuesOf(mpath)); - } - if (options.has(amods)) { - compilerOptions.add("--add-modules"); - compilerOptions.addAll(options.valuesOf(amods)); - remoteVMOptions.add("--add-modules"); - remoteVMOptions.addAll(options.valuesOf(amods)); - } - if (options.has(ec)) { - executionControlSpec = options.valueOf(ec); - } - - if (options.has(addExports)) { - List exports = options.valuesOf(addExports).stream() - .map(mp -> mp + "=ALL-UNNAMED") - .flatMap(mp -> Stream.of("--add-exports", mp)) - .collect(toList()); - remoteVMOptions.addAll(exports); - compilerOptions.addAll(exports); - } - - return options.valuesOf(loadFileSpec); - } - private void printUsage() { cmdout.print(getResourceString("help.usage")); } @@ -734,8 +877,8 @@ .idGenerator((sn, i) -> (currentNameSpace == startNamespace || state.status(sn).isActive()) ? currentNameSpace.tid(sn) : errorNamespace.tid(sn)) - .remoteVMOptions(remoteVMOptions.stream().toArray(String[]::new)) - .compilerOptions(compilerOptions.stream().toArray(String[]::new)); + .remoteVMOptions(options.remoteVmOptions()) + .compilerOptions(options.compilerOptions()); if (executionControlSpec != null) { builder.executionEngine(executionControlSpec); } @@ -748,15 +891,6 @@ }); analysis = state.sourceCodeAnalysis(); live = true; - if (!feedbackInitialized) { - // One time per run feedback initialization - feedbackInitialized = true; - initFeedback(); - } - - if (cmdlineClasspath != null) { - state.addToClasspath(cmdlineClasspath); - } startUpRun(startup); currentNameSpace = mainNamespace; @@ -767,7 +901,7 @@ } //where -- one-time per run initialization of feedback modes - private void initFeedback() { + private void initFeedback(String initMode) { // No fluff, no prefix, for init failures MessageHandler initmh = new InitMessageHandler(); // Execute the feedback initialization code in the resource file @@ -782,12 +916,11 @@ prefs.remove(MODE_KEY); } } - if (commandLineFeedbackMode != null) { + if (initMode != null) { // The feedback mode to use was specified on the command line, use it - if (!setFeedback(initmh, new ArgTokenizer("--feedback", commandLineFeedbackMode))) { + if (!setFeedback(initmh, new ArgTokenizer("--feedback", initMode))) { regenerateOnDeath = false; } - commandLineFeedbackMode = null; } else { String fb = prefs.get(FEEDBACK_KEY); if (fb != null) { @@ -1016,6 +1149,13 @@ this.alternatives = alternatives; } + // Add more options to an existing provider + public FixedCompletionProvider(FixedCompletionProvider base, String... alternatives) { + List l = new ArrayList<>(Arrays.asList(base.alternatives)); + l.addAll(Arrays.asList(alternatives)); + this.alternatives = l.toArray(new String[l.size()]); + } + @Override public List completionSuggestions(String input, int cursor, int[] anchor) { List result = new ArrayList<>(); @@ -1037,11 +1177,20 @@ private static final CompletionProvider SNIPPET_HISTORY_OPTION_COMPLETION_PROVIDER = new FixedCompletionProvider("-all", "-start ", "-history"); private static final CompletionProvider SAVE_OPTION_COMPLETION_PROVIDER = new FixedCompletionProvider("-all ", "-start ", "-history "); private static final CompletionProvider SNIPPET_OPTION_COMPLETION_PROVIDER = new FixedCompletionProvider("-all", "-start " ); - private static final CompletionProvider RELOAD_OPTIONS_COMPLETION_PROVIDER = new FixedCompletionProvider("-restore ", "-quiet "); - private static final CompletionProvider RESTORE_COMPLETION_PROVIDER = new FixedCompletionProvider("-restore"); - private static final CompletionProvider QUIET_COMPLETION_PROVIDER = new FixedCompletionProvider("-quiet"); + private static final FixedCompletionProvider COMMAND_LINE_LIKE_OPTIONS_COMPLETION_PROVIDER = new FixedCompletionProvider( + "-class-path ", "-module-path ", "-add-modules ", "-add-exports "); + private static final CompletionProvider RELOAD_OPTIONS_COMPLETION_PROVIDER = new FixedCompletionProvider( + COMMAND_LINE_LIKE_OPTIONS_COMPLETION_PROVIDER, + "-restore ", "-quiet "); private static final CompletionProvider SET_MODE_OPTIONS_COMPLETION_PROVIDER = new FixedCompletionProvider("-command", "-quiet", "-delete"); private static final CompletionProvider FILE_COMPLETION_PROVIDER = fileCompletions(p -> true); + private static final Map ARG_OPTIONS = new HashMap<>(); + static { + ARG_OPTIONS.put("-class-path", classPathCompletion()); + ARG_OPTIONS.put("-module-path", fileCompletions(Files::isDirectory)); + ARG_OPTIONS.put("-add-modules", EMPTY_COMPLETION_PROVIDER); + ARG_OPTIONS.put("-add-exports", EMPTY_COMPLETION_PROVIDER); + } private final Map commands = new LinkedHashMap<>(); private void registerCommand(Command cmd) { commands.put(cmd.command, cmd); @@ -1167,32 +1316,70 @@ }; } - private static CompletionProvider reloadCompletion() { + // command-line-like option completion -- options with values + private static CompletionProvider optionCompletion(CompletionProvider provider) { return (code, cursor, anchor) -> { - CompletionProvider provider; - int pastSpace = code.indexOf(' ') + 1; // zero if no space - if (pastSpace == 0) { - provider = RELOAD_OPTIONS_COMPLETION_PROVIDER; - } else { - switch (code.substring(0, pastSpace - 1)) { - case "-quiet": - provider = RESTORE_COMPLETION_PROVIDER; - break; - case "-restore": - provider = QUIET_COMPLETION_PROVIDER; - break; - default: - provider = EMPTY_COMPLETION_PROVIDER; - break; + Matcher ovm = OPTION_VALUE_PATTERN.matcher(code); + if (ovm.matches()) { + String flag = ovm.group("flag"); + List ps = ARG_OPTIONS.entrySet().stream() + .filter(es -> es.getKey().startsWith(flag)) + .map(es -> es.getValue()) + .collect(toList()); + if (ps.size() == 1) { + int pastSpace = ovm.start("val"); + List result = ps.get(0).completionSuggestions( + ovm.group("val"), cursor - pastSpace, anchor); + anchor[0] += pastSpace; + return result; } } - List result = provider.completionSuggestions( - code.substring(pastSpace), cursor - pastSpace, anchor); - anchor[0] += pastSpace; - return result; + Matcher om = OPTION_PATTERN.matcher(code); + if (om.matches()) { + int pastSpace = om.start("flag"); + List result = provider.completionSuggestions( + om.group("flag"), cursor - pastSpace, anchor); + if (!om.group("dd").isEmpty()) { + result = result.stream() + .map(sug -> new Suggestion() { + @Override + public String continuation() { + return "-" + sug.continuation(); + } + + @Override + public boolean matchesType() { + return false; + } + }) + .collect(toList()); + --pastSpace; + } + anchor[0] += pastSpace; + return result; + } + Matcher opp = OPTION_PRE_PATTERN.matcher(code); + if (opp.matches()) { + int pastSpace = opp.end(); + List result = provider.completionSuggestions( + "", cursor - pastSpace, anchor); + anchor[0] += pastSpace; + return result; + } + return Collections.emptyList(); }; } + // /reload command completion + private static CompletionProvider reloadCompletion() { + return optionCompletion(RELOAD_OPTIONS_COMPLETION_PROVIDER); + } + + // /env command completion + private static CompletionProvider envCompletion() { + return optionCompletion(COMMAND_LINE_LIKE_OPTIONS_COMPLETION_PROVIDER); + } + private static CompletionProvider orMostSpecificCompletion( CompletionProvider left, CompletionProvider right) { return (code, cursor, anchor) -> { @@ -1286,16 +1473,15 @@ registerCommand(new Command("/exit", arg -> cmdExit(), EMPTY_COMPLETION_PROVIDER)); + registerCommand(new Command("/env", + arg -> cmdEnv(arg), + envCompletion())); registerCommand(new Command("/reset", - arg -> cmdReset(), - EMPTY_COMPLETION_PROVIDER)); + arg -> cmdReset(arg), + envCompletion())); registerCommand(new Command("/reload", this::cmdReload, reloadCompletion())); - registerCommand(new Command("/classpath", - this::cmdClasspath, - classPathCompletion(), - CommandKind.REPLAY)); registerCommand(new Command("/history", arg -> cmdHistory(), EMPTY_COMPLETION_PROVIDER)); @@ -1344,6 +1530,9 @@ registerCommand(new Command("shortcuts", "help.shortcuts", CommandKind.HELP_SUBJECT)); + registerCommand(new Command("context", + "help.context", + CommandKind.HELP_SUBJECT)); commandCompletions = new ContinuousCompletionProvider( commands.values().stream() @@ -1692,17 +1881,6 @@ hard(stset); } - boolean cmdClasspath(String arg) { - if (arg.isEmpty()) { - errormsg("jshell.err.classpath.arg"); - return false; - } else { - state.addToClasspath(toPathResolvingUserHome(arg).toString()); - fluffmsg("jshell.msg.classpath", arg); - return true; - } - } - boolean cmdDebug(String arg) { if (arg.isEmpty()) { debug = !debug; @@ -2228,7 +2406,6 @@ } - // Read a built-in file from resources or null String getResource(String name) { if (BUILTIN_FILE_PATTERN.matcher(name).matches()) { try { @@ -2266,20 +2443,22 @@ return defaultStartup; } - private boolean cmdReset() { + private boolean cmdReset(String rawargs) { + if (!parseCommandLineLikeFlags(rawargs, new OptionParserBase())) { + return false; + } live = false; fluffmsg("jshell.msg.resetting.state"); return true; } private boolean cmdReload(String rawargs) { - ArgTokenizer at = new ArgTokenizer("/reload", rawargs.trim()); - at.allowedOptions("-restore", "-quiet"); - if (!checkOptionsAndRemainingInput(at)) { + OptionParserReload ap = new OptionParserReload(); + if (!parseCommandLineLikeFlags(rawargs, ap)) { return false; } Iterable history; - if (at.hasOption("-restore")) { + if (ap.restore()) { if (replayableHistoryPrevious == null) { errormsg("jshell.err.reload.no.previous"); return false; @@ -2290,13 +2469,57 @@ history = replayableHistory; fluffmsg("jshell.err.reload.restarting.state"); } - boolean echo = !at.hasOption("-quiet"); + return doReload(history, !ap.quiet()); + } + + private boolean cmdEnv(String rawargs) { + if (rawargs.trim().isEmpty()) { + // No arguments, display current settings (as option flags) + StringBuilder sb = new StringBuilder(); + for (String a : options.commonOptions()) { + sb.append( + a.startsWith("-") + ? sb.length() > 0 + ? "\n " + : " " + : " "); + sb.append(a); + } + if (sb.length() > 0) { + rawout(prefix(sb.toString())); + } + return false; + } + if (!parseCommandLineLikeFlags(rawargs, new OptionParserBase())) { + return false; + } + fluffmsg("jshell.msg.set.restore"); + return doReload(replayableHistory, false); + } + + private boolean doReload(Iterable history, boolean echo) { resetState(); run(new ReloadIOContext(history, echo ? cmdout : null)); return true; } + private boolean parseCommandLineLikeFlags(String rawargs, OptionParserBase ap) { + String[] args = Arrays.stream(rawargs.split("\\s+")) + .filter(s -> !s.isEmpty()) + .toArray(String[]::new); + Options opts = ap.parse(args); + if (opts == null) { + return false; + } + if (!ap.nonOptions().isEmpty()) { + errormsg("jshell.err.unexpected.at.end", ap.nonOptions(), rawargs); + return false; + } + options.override(opts); + return true; + } + private boolean cmdSave(String rawargs) { ArgTokenizer at = new ArgTokenizer("/save", rawargs.trim()); at.allowedOptions("-all", "-start", "-history"); diff -r 3e1520a857fa -r 7b8b8750a78e langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n.properties --- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n.properties Fri Jan 06 14:16:45 2017 +0100 +++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/l10n.properties Fri Jan 06 10:31:25 2017 -0800 @@ -52,6 +52,7 @@ jshell.err.no.such.command.or.snippet.id = No such command or snippet id: {0} jshell.err.command.ambiguous = Command: ''{0}'' is ambiguous: {1} +jshell.msg.set.restore = Setting new options and restoring state. jshell.msg.set.editor.set = Editor set to: {0} jshell.msg.set.editor.retain = Editor setting retained: {0} jshell.err.no.builtin.editor = Built-in editor not available. @@ -319,21 +320,25 @@ Save any work before using this command help.reset.summary = reset jshell -help.reset.args = +help.reset.args = \ +[-class-path ] [-module-path ] [-add-modules ]... help.reset =\ Reset the jshell tool code and execution state:\n\t\ * All entered code is lost.\n\t\ * Start-up code is re-executed.\n\t\ * The execution state is restarted.\n\t\ - * The classpath is cleared.\n\ Tool settings are maintained, as set with: /set ...\n\ -Save any work before using this command +Save any work before using this command.\n\ +The /reset command accepts context options, see:\n\n\t\ + /help context\n\ + help.reload.summary = reset and replay relevant history -- current or previous (-restore) -help.reload.args = [-restore] [-quiet] +help.reload.args = \ +[-restore] [-quiet] [-class-path ] [-module-path ]... help.reload =\ Reset the jshell tool code and execution state then replay each valid snippet\n\ -and any /drop or /classpath commands in the order they were entered.\n\ +and any /drop commands in the order they were entered.\n\ \n\ /reload\n\t\ Reset and replay the valid history since jshell was entered, or\n\t\ @@ -345,12 +350,31 @@ command was executed. This can thus be used to restore a previous\n\t\ jshell tool session.\n\n\ /reload [-restore] -quiet\n\t\ - With the '-quiet' argument the replay is not shown. Errors will display. + With the '-quiet' argument the replay is not shown. Errors will display.\n\ +\n\ +Each of the above accepts context options, see:\n\n\t\ + /help context\n\ +\n\ +For example:\n\n\t\ + /reload -add-modules com.greetings -restore -help.classpath.summary = add a path to the classpath -help.classpath.args = -help.classpath =\ -Append a additional path to the classpath. +help.env.summary = view or change the evaluation context +help.env.args = \ +[-class-path ] [-module-path ] [-add-modules ] ... +help.env =\ +View or change the evaluation context. The evaluation context is the class path,\n\ +module path, etc.\n\ +/env\n\t\ + Show the evaluation context displayed as context options.\n\n\ +/env [-class-path ] [-module-path ] [-add-modules ] ...\n\t\ + With at least one option set, sets the evaluation context. If snippets\n\t\ + have been defined, the execution state is reset with the new\n\t\ + evaluation context and the snippets will be replayed -- the replay is not\n\t\ + shown, however, errors will display. This is equivalent to: /reload -quiet\n\t\ + For details of context options, see:\n\n\t\t\ + /help context\n\n\t\ + For example:\n\n\t\t\ + /env -add-modules com.greetings help.history.summary = history of what you have typed help.history.args = @@ -473,6 +497,37 @@ possible fully qualified names based on the content of the specified classpath.\n\t\t\ The "" is either Alt-F1 or Alt-Enter, depending on the platform. +help.context.summary = the evaluation context options for /env /reload and /reset +help.context =\ +These options configure the evaluation context, they can be specified when\n\ +jshell is started: on the command-line, or restarted with the commands /env,\n\ +/reload, or /reset.\n\ +\n\ +They are:\n\t\ + --class-path \n\t\t\ + A list of directories, JAR archives,\n\t\t\ + and ZIP archives to search for class files.\n\t\t\ + The list is separated with the path separator\n\t\t\ + (a : on unix/linux/mac, and ; on windows).\n\t\ + --module-path ...\n\t\t\ + A list of directories, each directory\n\t\t\ + is a directory of modules.\n\t\t\ + The list is separated with the path separator\n\t\t\ + (a : on unix/linux/mac, and ; on windows).\n\t\ + --add-modules [,...]\n\t\t\ + root modules to resolve in addition to the initial module.\n\t\t\ + can also be ALL-DEFAULT, ALL-SYSTEM,\n\t\t\ + ALL-MODULE-PATH.\n\t\ + --add-exports /=(,)*\n\t\t\ + updates to export to ,\n\t\t\ + regardless of module declaration.\n\t\t\ + can be ALL-UNNAMED to export to all\n\t\t\ + unnamed modules. In jshell, if the is not\n\t\t\ + specified (no =) then ALL-UNNAMED is used.\n\ +\n\ +On the command-line these options must have two dashes, e.g.: --module-path\n\ +On jshell commands they can have one or two dashes, e.g.: -module-path\n\ + help.set._retain = \ The '-retain' option saves a setting so that it is used in future sessions.\n\ The -retain option can be used on the following forms of /set:\n\n\t\ diff -r 3e1520a857fa -r 7b8b8750a78e langtools/src/jdk.jshell/share/classes/jdk/jshell/TaskFactory.java --- a/langtools/src/jdk.jshell/share/classes/jdk/jshell/TaskFactory.java Fri Jan 06 14:16:45 2017 +0100 +++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/TaskFactory.java Fri Jan 06 10:31:25 2017 -0800 @@ -161,6 +161,10 @@ @Override public Diag diag(Diagnostic d) { SourceMemoryJavaFileObject smjfo = (SourceMemoryJavaFileObject) d.getSource(); + if (smjfo == null) { + // Handle failure that doesn't preserve mapping + return new StringSourceHandler().diag(d); + } OuterWrap w = (OuterWrap) smjfo.getOrigin(); return w.wrapDiag(d); } diff -r 3e1520a857fa -r 7b8b8750a78e langtools/test/jdk/jshell/CommandCompletionTest.java --- a/langtools/test/jdk/jshell/CommandCompletionTest.java Fri Jan 06 14:16:45 2017 +0100 +++ b/langtools/test/jdk/jshell/CommandCompletionTest.java Fri Jan 06 10:31:25 2017 -0800 @@ -23,7 +23,7 @@ /* * @test - * @bug 8144095 8164825 8169818 8153402 + * @bug 8144095 8164825 8169818 8153402 8165405 * @summary Test Command Completion * @modules jdk.compiler/com.sun.tools.javac.api * jdk.compiler/com.sun.tools.javac.main @@ -138,7 +138,7 @@ @Test public void testEdit() { test(false, new String[]{"--no-startup"}, - a -> assertCompletion(a, "/e|", false, "/edit ", "/exit "), + a -> assertCompletion(a, "/e|", false, "/edit ", "/env ", "/exit "), a -> assertCompletion(a, "/ed|", false, "/edit "), a -> assertClass(a, "class cTest {}", "class", "cTest"), a -> assertMethod(a, "int mTest() { return 0; }", "()I", "mTest"), @@ -158,15 +158,17 @@ public void testHelp() { testNoStartUp( a -> assertCompletion(a, "/help |", false, - "/! ", "/- ", "/ ", "/? ", "/classpath ", "/drop ", - "/edit ", "/exit ", "/help ", "/history ", "/imports ", + "/! ", "/- ", "/ ", "/? ", "/drop ", + "/edit ", "/env ", "/exit ", + "/help ", "/history ", "/imports ", "/list ", "/methods ", "/open ", "/reload ", "/reset ", - "/save ", "/set ", "/types ", "/vars ", "intro ", "shortcuts "), + "/save ", "/set ", "/types ", "/vars ", "context ", "intro ", "shortcuts "), a -> assertCompletion(a, "/? |", false, - "/! ", "/- ", "/ ", "/? ", "/classpath ", "/drop ", - "/edit ", "/exit ", "/help ", "/history ", "/imports ", + "/! ", "/- ", "/ ", "/? ", "/drop ", + "/edit ", "/env ", "/exit ", + "/help ", "/history ", "/imports ", "/list ", "/methods ", "/open ", "/reload ", "/reset ", - "/save ", "/set ", "/types ", "/vars ", "intro ", "shortcuts "), + "/save ", "/set ", "/types ", "/vars ", "context ", "intro ", "shortcuts "), a -> assertCompletion(a, "/help /s|", false, "/save ", "/set "), a -> assertCompletion(a, "/help /set |", false, @@ -177,17 +179,63 @@ @Test public void testReload() { + String[] ropts = new String[] { "-add-exports ", "-add-modules ", + "-class-path ", "-module-path ", "-quiet ", "-restore " }; + String[] dropts = new String[] { "--add-exports ", "--add-modules ", + "--class-path ", "--module-path ", "--quiet ", "--restore " }; 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) + a -> assertCompletion(a, "/reloa |", false, ropts), + a -> assertCompletion(a, "/relo |", false, ropts), + a -> assertCompletion(a, "/reload -|", false, ropts), + a -> assertCompletion(a, "/reload --|", false, dropts), + a -> assertCompletion(a, "/reload -restore |", false, ropts), + a -> assertCompletion(a, "/reload -restore --|", false, dropts), + a -> assertCompletion(a, "/reload -rest|", false, "-restore "), + a -> assertCompletion(a, "/reload --r|", false, "--restore "), + a -> assertCompletion(a, "/reload -q|", false, "-quiet "), + a -> assertCompletion(a, "/reload -add|", false, "-add-exports ", "-add-modules "), + a -> assertCompletion(a, "/reload -class-path . -quiet |", false, ropts) + ); + } + + @Test + public void testEnv() { + String[] ropts = new String[] { "-add-exports ", "-add-modules ", + "-class-path ", "-module-path " }; + String[] dropts = new String[] { "--add-exports ", "--add-modules ", + "--class-path ", "--module-path " }; + testNoStartUp( + a -> assertCompletion(a, "/env |", false, ropts), + a -> assertCompletion(a, "/env -|", false, ropts), + a -> assertCompletion(a, "/env --|", false, dropts), + a -> assertCompletion(a, "/env --a|", false, "--add-exports ", "--add-modules "), + a -> assertCompletion(a, "/env -add-|", false, "-add-exports ", "-add-modules "), + a -> assertCompletion(a, "/env -class-path . |", false, ropts), + a -> assertCompletion(a, "/env -class-path . --|", false, dropts) + ); + } + + @Test + public void testReset() { + String[] ropts = new String[] { "-add-exports ", "-add-modules ", + "-class-path ", "-module-path " }; + String[] dropts = new String[] { "--add-exports ", "--add-modules ", + "--class-path ", "--module-path " }; + testNoStartUp( + a -> assertCompletion(a, "/reset |", false, ropts), + a -> assertCompletion(a, "/res -m|", false, "-module-path "), + a -> assertCompletion(a, "/res -module-|", false, "-module-path "), + a -> assertCompletion(a, "/res --m|", false, "--module-path "), + a -> assertCompletion(a, "/res --module-|", false, "--module-path "), + a -> assertCompletion(a, "/reset -add|", false, "-add-exports ", "-add-modules "), + a -> assertCompletion(a, "/rese -class-path . |", false, ropts), + a -> assertCompletion(a, "/rese -class-path . --|", false, dropts) ); } @Test public void testVarsMethodsTypes() { - test(false, new String[]{"--no-startup"}, + testNoStartUp( a -> assertCompletion(a, "/v|", false, "/vars "), a -> assertCompletion(a, "/m|", false, "/methods "), a -> assertCompletion(a, "/t|", false, "/types "), @@ -245,9 +293,6 @@ @Test public void testClassPath() throws IOException { - testNoStartUp( - a -> assertCompletion(a, "/classp|", false, "/classpath ") - ); Compiler compiler = new Compiler(); Path outDir = compiler.getPath("testClasspathCompletion"); Files.createDirectories(outDir); @@ -259,8 +304,13 @@ compiler.jar(outDir, jarName, "pkg/A.class"); compiler.getPath(outDir).resolve(jarName); List paths = listFiles(outDir, CLASSPATH_FILTER); + String[] pathArray = paths.toArray(new String[paths.size()]); testNoStartUp( - a -> assertCompletion(a, "/classpath " + outDir + "/|", false, paths.toArray(new String[paths.size()])) + a -> assertCompletion(a, "/env -class-path " + outDir + "/|", false, pathArray), + a -> assertCompletion(a, "/env --class-path " + outDir + "/|", false, pathArray), + a -> assertCompletion(a, "/env -clas " + outDir + "/|", false, pathArray), + a -> assertCompletion(a, "/env --class-p " + outDir + "/|", false, pathArray), + a -> assertCompletion(a, "/env --module-path . --class-p " + outDir + "/|", false, pathArray) ); } @@ -275,7 +325,7 @@ .collect(Collectors.toList()); } testNoStartUp( - a -> assertCompletion(a, "/classpath ~/|", false, completions.toArray(new String[completions.size()])) + a -> assertCompletion(a, "/env --class-path ~/|", false, completions.toArray(new String[completions.size()])) ); } diff -r 3e1520a857fa -r 7b8b8750a78e langtools/test/jdk/jshell/ToolBasicTest.java --- a/langtools/test/jdk/jshell/ToolBasicTest.java Fri Jan 06 14:16:45 2017 +0100 +++ b/langtools/test/jdk/jshell/ToolBasicTest.java Fri Jan 06 10:31:25 2017 -0800 @@ -23,7 +23,7 @@ /* * @test - * @bug 8143037 8142447 8144095 8140265 8144906 8146138 8147887 8147886 8148316 8148317 8143955 8157953 8080347 8154714 8166649 8167643 8170162 8172102 + * @bug 8143037 8142447 8144095 8140265 8144906 8146138 8147887 8147886 8148316 8148317 8143955 8157953 8080347 8154714 8166649 8167643 8170162 8172102 8165405 * @summary Tests for Basic tests for REPL tool * @modules jdk.compiler/com.sun.tools.javac.api * jdk.compiler/com.sun.tools.javac.main @@ -263,7 +263,8 @@ compiler.compile(outDir, "package pkg; public class A { public String toString() { return \"A\"; } }"); Path classpath = compiler.getPath(outDir); test( - (a) -> assertCommand(a, "/classpath " + classpath, String.format("| Path '%s' added to classpath", classpath)), + (a) -> assertCommand(a, "/env --class-path " + classpath, + "| Setting new options and restoring state."), (a) -> evaluateExpression(a, "pkg.A", "new pkg.A();", "A") ); test(new String[] { "--class-path", classpath.toString() }, @@ -279,7 +280,8 @@ compiler.jar(outDir, jarName, "pkg/A.class"); Path jarPath = compiler.getPath(outDir).resolve(jarName); test( - (a) -> assertCommand(a, "/classpath " + jarPath, String.format("| Path '%s' added to classpath", jarPath)), + (a) -> assertCommand(a, "/env --class-path " + jarPath, + "| Setting new options and restoring state."), (a) -> evaluateExpression(a, "pkg.A", "new pkg.A();", "A") ); test(new String[] { "--class-path", jarPath.toString() }, diff -r 3e1520a857fa -r 7b8b8750a78e langtools/test/jdk/jshell/ToolReloadTest.java --- a/langtools/test/jdk/jshell/ToolReloadTest.java Fri Jan 06 14:16:45 2017 +0100 +++ b/langtools/test/jdk/jshell/ToolReloadTest.java Fri Jan 06 10:31:25 2017 -0800 @@ -24,7 +24,7 @@ /* * @test * @key intermittent - * @bug 8081845 8147898 8143955 + * @bug 8081845 8147898 8143955 8165405 * @summary Tests for /reload in JShell tool * @modules jdk.compiler/com.sun.tools.javac.api * jdk.compiler/com.sun.tools.javac.main @@ -70,8 +70,8 @@ compiler.compile(outDir, prog.apply("A")); Path classpath = compiler.getPath(outDir); test( - (a) -> assertCommand(a, "/classpath " + classpath, - String.format("| Path '%s' added to classpath", classpath)), + (a) -> assertCommand(a, "/env --class-path " + classpath, + "| Setting new options and restoring state."), (a) -> assertMethod(a, "String foo() { return (new pkg.A()).toString(); }", "()String", "foo"), (a) -> assertVariable(a, "String", "v", "foo()", "\"A\""), @@ -79,7 +79,6 @@ if (!a) compiler.compile(outDir, prog.apply("Aprime")); assertCommand(a, "/reload", "| Restarting and restoring state.\n" + - "-: /classpath " + classpath + "\n" + "-: String foo() { return (new pkg.A()).toString(); }\n" + "-: String v = foo();\n"); }, diff -r 3e1520a857fa -r 7b8b8750a78e langtools/test/jdk/jshell/ToolSimpleTest.java --- a/langtools/test/jdk/jshell/ToolSimpleTest.java Fri Jan 06 14:16:45 2017 +0100 +++ b/langtools/test/jdk/jshell/ToolSimpleTest.java Fri Jan 06 10:31:25 2017 -0800 @@ -23,7 +23,7 @@ /* * @test - * @bug 8153716 8143955 8151754 8150382 8153920 8156910 8131024 8160089 8153897 8167128 8154513 8170015 8170368 8172102 + * @bug 8153716 8143955 8151754 8150382 8153920 8156910 8131024 8160089 8153897 8167128 8154513 8170015 8170368 8172102 8172103 8165405 * @summary Simple jshell tool tests * @modules jdk.compiler/com.sun.tools.javac.api * jdk.compiler/com.sun.tools.javac.main @@ -196,7 +196,7 @@ @Test public void testEmptyClassPath() { - test(after -> assertCommand(after, "/classpath", "| The /classpath command requires a path argument.")); + test(after -> assertCommand(after, "/env --class-path", "| Argument to class-path missing.")); } @Test @@ -606,6 +606,13 @@ } @Test + public void testWrapSourceHandlerDiagCrash() { + test(new String[]{"--add-exports", "jdk.javadoc/ALL-UNNAMED"}, + (a) -> assertCommand(a, "1+1", "$1 ==> 2") + ); + } + + @Test public void test8156910() { test( (a) -> assertCommandOutputContains(a, "System.out.println(\"%5d\", 10);", "%5d"),