Categories:
Audio (13)
Biotech (29)
Bytecode (36)
Database (77)
Framework (7)
Game (7)
General (507)
Graphics (53)
I/O (35)
IDE (2)
JAR Tools (101)
JavaBeans (21)
JDBC (121)
JDK (426)
JSP (20)
Logging (108)
Mail (58)
Messaging (8)
Network (84)
PDF (97)
Report (7)
Scripting (84)
Security (32)
Server (121)
Servlet (26)
SOAP (24)
Testing (54)
Web (15)
XML (309)
Collections:
Other Resources:
JDK 11 jdk.jshell.jmod - JShell Tool
JDK 11 jdk.jshell.jmod is the JMOD file for JDK 11 JShell tool, which can be invoked by the "jshell" command.
JDK 11 JShell tool compiled class files are stored in \fyicenter\jdk-11.0.1\jmods\jdk.jshell.jmod.
JDK 11 JShell tool compiled class files are also linked and stored in the \fyicenter\jdk-11.0.1\lib\modules JImage file.
JDK 11 JShell tool source code files are stored in \fyicenter\jdk-11.0.1\lib\src.zip\jdk.jshell.
You can click and view the content of each source code file in the list below.
✍: FYIcenter
⏎ jdk/internal/jshell/tool/JShellTool.java
/* * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. * * * * * * * * * * * * * * * * * * * * */ package jdk.internal.jshell.tool; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.EOFException; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintStream; import java.io.Reader; import java.io.StringReader; import java.lang.module.ModuleDescriptor; import java.lang.module.ModuleFinder; import java.lang.module.ModuleReference; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.nio.charset.Charset; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.Paths; 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; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.Scanner; import java.util.Set; import java.util.function.Consumer; import java.util.function.Predicate; 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 java.util.stream.StreamSupport; import jdk.internal.jshell.debug.InternalDebugControl; import jdk.internal.jshell.tool.IOContext.InputInterruptedException; import jdk.jshell.DeclarationSnippet; import jdk.jshell.Diag; import jdk.jshell.EvalException; import jdk.jshell.ExpressionSnippet; import jdk.jshell.ImportSnippet; import jdk.jshell.JShell; import jdk.jshell.JShell.Subscription; import jdk.jshell.JShellException; import jdk.jshell.MethodSnippet; import jdk.jshell.Snippet; import jdk.jshell.Snippet.Kind; import jdk.jshell.Snippet.Status; import jdk.jshell.SnippetEvent; import jdk.jshell.SourceCodeAnalysis; import jdk.jshell.SourceCodeAnalysis.CompletionInfo; import jdk.jshell.SourceCodeAnalysis.Completeness; import jdk.jshell.SourceCodeAnalysis.Suggestion; import jdk.jshell.TypeDeclSnippet; import jdk.jshell.UnresolvedReferenceException; import jdk.jshell.VarSnippet; import static java.nio.file.StandardOpenOption.CREATE; import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; import static java.nio.file.StandardOpenOption.WRITE; import java.util.AbstractMap.SimpleEntry; import java.util.MissingResourceException; import java.util.ResourceBundle; import java.util.ServiceLoader; import java.util.Spliterators; import java.util.function.Function; import java.util.function.Supplier; import jdk.internal.joptsimple.*; import jdk.internal.jshell.tool.Feedback.FormatAction; import jdk.internal.jshell.tool.Feedback.FormatCase; import jdk.internal.jshell.tool.Feedback.FormatErrors; import jdk.internal.jshell.tool.Feedback.FormatResolve; import jdk.internal.jshell.tool.Feedback.FormatUnresolved; import jdk.internal.jshell.tool.Feedback.FormatWhen; import jdk.internal.editor.spi.BuildInEditorProvider; import jdk.internal.editor.external.ExternalEditor; import static java.util.Arrays.asList; import static java.util.Arrays.stream; import static java.util.Collections.singletonList; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; import static jdk.jshell.Snippet.SubKind.TEMP_VAR_EXPRESSION_SUBKIND; import static jdk.jshell.Snippet.SubKind.VAR_VALUE_SUBKIND; import static java.util.stream.Collectors.toMap; import static jdk.internal.jshell.debug.InternalDebugControl.DBG_COMPA; import static jdk.internal.jshell.debug.InternalDebugControl.DBG_DEP; import static jdk.internal.jshell.debug.InternalDebugControl.DBG_EVNT; import static jdk.internal.jshell.debug.InternalDebugControl.DBG_FMGR; import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN; import static jdk.internal.jshell.debug.InternalDebugControl.DBG_WRAP; import static jdk.internal.jshell.tool.ContinuousCompletionProvider.STARTSWITH_MATCHER; /** * Command line REPL tool for Java using the JShell API. * @author Robert Field */ public class JShellTool implements MessageHandler { private static final Pattern LINEBREAK = Pattern.compile("\\R"); private static final Pattern ID = Pattern.compile("[se]?\\d+([-\\s].*)?"); private static final Pattern RERUN_ID = Pattern.compile("/" + ID.pattern()); private static final Pattern RERUN_PREVIOUS = Pattern.compile("/\\-\\d+( .*)?"); private static final Pattern SET_SUB = Pattern.compile("/?set .*"); static final String RECORD_SEPARATOR = "\u241E"; private static final String RB_NAME_PREFIX = "jdk.internal.jshell.tool.resources"; private static final String VERSION_RB_NAME = RB_NAME_PREFIX + ".version"; private static final String L10N_RB_NAME = RB_NAME_PREFIX + ".l10n"; final InputStream cmdin; final PrintStream cmdout; final PrintStream cmderr; final PrintStream console; final InputStream userin; final PrintStream userout; final PrintStream usererr; final PersistentStorage prefs; final Map<String, String> envvars; final Locale locale; final Feedback feedback = new Feedback(); /** * The complete constructor for the tool (used by test harnesses). * @param cmdin command line input -- snippets and commands * @param cmdout command line output, feedback including errors * @param cmderr start-up errors and debugging info * @param console console control interaction * @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 persistence implementation to use * @param envvars environment variable mapping to use * @param locale locale to use */ JShellTool(InputStream cmdin, PrintStream cmdout, PrintStream cmderr, PrintStream console, InputStream userin, PrintStream userout, PrintStream usererr, PersistentStorage prefs, Map<String, String> envvars, Locale locale) { this.cmdin = cmdin; this.cmdout = cmdout; this.cmderr = cmderr; this.console = console; this.userin = userin != null ? userin : new InputStream() { @Override public int read() throws IOException { return input.readUserInput(); } }; this.userout = userout; this.usererr = usererr; this.prefs = prefs; this.envvars = envvars; this.locale = locale; } private ResourceBundle versionRB = null; private ResourceBundle outputRB = null; private IOContext input = null; private boolean regenerateOnDeath = true; private boolean live = false; private boolean interactiveModeBegun = false; private Options options; SourceCodeAnalysis analysis; private JShell state = null; Subscription shutdownSubscription = null; static final EditorSetting BUILT_IN_EDITOR = new EditorSetting(null, false); private boolean debug = false; private int debugFlags = 0; public boolean testPrompt = false; private Startup startup = null; private boolean isCurrentlyRunningStartup = false; private String executionControlSpec = null; private EditorSetting editor = BUILT_IN_EDITOR; private int exitCode = 0; private static final String[] EDITOR_ENV_VARS = new String[] { "JSHELLEDITOR", "VISUAL", "EDITOR"}; // Commands and snippets which can be replayed private ReplayableHistory replayableHistory; private ReplayableHistory replayableHistoryPrevious; static final String STARTUP_KEY = "STARTUP"; static final String EDITOR_KEY = "EDITOR"; static final String FEEDBACK_KEY = "FEEDBACK"; static final String MODE_KEY = "MODE"; static final String REPLAY_RESTORE_KEY = "REPLAY_RESTORE"; static final Pattern BUILTIN_FILE_PATTERN = Pattern.compile("\\w+"); static final String BUILTIN_FILE_PATH_FORMAT = "/jdk/jshell/tool/resources/%s.jsh"; static final String INT_PREFIX = "int $$exit$$ = "; static final int OUTPUT_WIDTH = 72; // 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() + "(?<dd>-??)(?<flag>-([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+(?<val>\\S*)"); // Tool id (tid) mapping: the three name spaces NameSpace mainNamespace; NameSpace startNamespace; NameSpace errorNamespace; // Tool id (tid) mapping: the current name spaces NameSpace currentNameSpace; Map<Snippet, SnippetInfo> 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), ENABLE_PREVIEW("--enable-preview", true), SOURCE_RELEASE("-source", true, true, true, false, false), // virtual option, generated by --enable-preview TO_COMPILER("-C", false, false, true, false, false), TO_REMOTE_VM("-R", false, false, false, true, false),; final String optionFlag; final boolean onlyOne; final boolean passFlag; final boolean toCompiler; final boolean toRemoteVm; final boolean showOption; private OptionKind(String optionFlag, boolean onlyOne) { this(optionFlag, onlyOne, true, true, true, true); } private OptionKind(String optionFlag, boolean onlyOne, boolean passFlag, boolean toCompiler, boolean toRemoteVm, boolean showOption) { this.optionFlag = optionFlag; this.onlyOne = onlyOne; this.passFlag = passFlag; this.toCompiler = toCompiler; this.toRemoteVm = toRemoteVm; this.showOption= showOption; } } // compiler/runtime init option values private static class Options { private final Map<OptionKind, List<String>> optMap; // New blank Options Options() { optMap = new HashMap<>(); } // Options as a copy private Options(Options opts) { optMap = new HashMap<>(opts.optMap); } private String[] selectOptions(Predicate<Entry<OptionKind, List<String>>> 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[] shownOptions() { return selectOptions(e -> e.getKey().showOption); } void addAll(OptionKind kind, Collection<String> vals) { optMap.computeIfAbsent(kind, k -> new ArrayList<>()) .addAll(vals); } // return a new Options, with parameter options overriding receiver options Options override(Options newer) { Options result = new Options(this); newer.optMap.entrySet().stream() .forEach(e -> { if (e.getKey().onlyOne) { // Only one allowed, override last result.optMap.put(e.getKey(), e.getValue()); } else { // Additive result.addAll(e.getKey(), e.getValue()); } }); return result; } } // base option parsing of /env, /reload, and /reset and command-line options private class OptionParserBase { final OptionParser parser = new OptionParser(); private final OptionSpec<String> argClassPath = parser.accepts("class-path").withRequiredArg(); private final OptionSpec<String> argModulePath = parser.accepts("module-path").withRequiredArg(); private final OptionSpec<String> argAddModules = parser.accepts("add-modules").withRequiredArg(); private final OptionSpec<String> argAddExports = parser.accepts("add-exports").withRequiredArg(); private final OptionSpecBuilder argEnablePreview = parser.accepts("enable-preview"); private final NonOptionArgumentSpec<String> argNonOptions = parser.nonOptions(); private Options opts = new Options(); private List<String> nonOptions; private boolean failed = false; List<String> 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(", "))); } exitCode = 1; return null; } } // check that the supplied string represent valid class/module paths // converting any ~/ to user home private Collection<String> validPaths(Collection<String> vals, String context, boolean isModulePath) { Stream<String> result = vals.stream() .map(s -> Arrays.stream(s.split(File.pathSeparator)) .flatMap(sp -> toPathImpl(sp, context)) .filter(p -> checkValidPathEntry(p, context, isModulePath)) .map(p -> p.toString()) .collect(Collectors.joining(File.pathSeparator))); if (failed) { return Collections.emptyList(); } else { return result.collect(toList()); } } // Adapted from compiler method Locations.checkValidModulePathEntry private boolean checkValidPathEntry(Path p, String context, boolean isModulePath) { if (!Files.exists(p)) { msg("jshell.err.file.not.found", context, p); failed = true; return false; } if (Files.isDirectory(p)) { // if module-path, either an exploded module or a directory of modules return true; } String name = p.getFileName().toString(); int lastDot = name.lastIndexOf("."); if (lastDot > 0) { switch (name.substring(lastDot)) { case ".jar": return true; case ".jmod": if (isModulePath) { return true; } } } msg("jshell.err.arg", context, p); failed = true; return false; } private Stream<Path> toPathImpl(String path, String context) { try { return Stream.of(toPathResolvingUserHome(path)); } catch (InvalidPathException ex) { msg("jshell.err.file.not.found", context, path); failed = true; return Stream.empty(); } } Options parse(OptionSet options) { addOptions(OptionKind.CLASS_PATH, validPaths(options.valuesOf(argClassPath), "--class-path", false)); addOptions(OptionKind.MODULE_PATH, validPaths(options.valuesOf(argModulePath), "--module-path", true)); 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()) ); if (options.has(argEnablePreview)) { opts.addAll(OptionKind.ENABLE_PREVIEW, List.of( OptionKind.ENABLE_PREVIEW.optionFlag)); opts.addAll(OptionKind.SOURCE_RELEASE, List.of( OptionKind.SOURCE_RELEASE.optionFlag, System.getProperty("java.specification.version"))); } if (failed) { exitCode = 1; return null; } else { return opts; } } void addOptions(OptionKind kind, Collection<String> 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<String> argStart = parser.accepts("startup").withRequiredArg(); private final OptionSpecBuilder argNoStart = parser.acceptsAll(asList("n", "no-startup")); private final OptionSpec<String> argFeedback = parser.accepts("feedback").withRequiredArg(); private final OptionSpec<String> 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<String> argR = parser.accepts("R").withRequiredArg(); private final OptionSpec<String> 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 argShowVersion = parser.accepts("show-version"); private final OptionSpecBuilder argHelpExtra = parser.acceptsAll(asList("X", "help-extra")); private String feedbackMode = null; private Startup initialStartup = null; String feedbackMode() { return feedbackMode; } Startup startup() { return initialStartup; } @Override void msg(String key, Object... args) { errormsg(key, args); } /** * Parse the command line options. * @return the options as an Options object, or null if error */ @Override Options parse(OptionSet options) { if (options.has(argHelp)) { printUsage(); return null; } if (options.has(argHelpExtra)) { 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.has(argShowVersion)) { cmdout.printf("jshell %s\n", version()); } 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"); exitCode = 1; 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<String> sts = options.valuesOf(argStart); if (options.has("no-startup")) { msg("jshell.err.opt.startup.conflict"); exitCode = 1; return null; } initialStartup = Startup.fromFileList(sts, "--startup", new InitMessageHandler()); if (initialStartup == null) { exitCode = 1; return null; } } else if (options.has(argNoStart)) { initialStartup = Startup.noStartup(); } else { String packedStartup = prefs.get(STARTUP_KEY); initialStartup = Startup.unpack(packedStartup, new InitMessageHandler()); } 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); } } /** * Encapsulate a history of snippets and commands which can be replayed. */ private static class ReplayableHistory { // the history private List<String> hist; // the length of the history as of last save private int lastSaved; private ReplayableHistory(List<String> hist) { this.hist = hist; this.lastSaved = 0; } // factory for empty histories static ReplayableHistory emptyHistory() { return new ReplayableHistory(new ArrayList<>()); } // factory for history stored in persistent storage static ReplayableHistory fromPrevious(PersistentStorage prefs) { // Read replay history from last jshell session String prevReplay = prefs.get(REPLAY_RESTORE_KEY); if (prevReplay == null) { return null; } else { return new ReplayableHistory(Arrays.asList(prevReplay.split(RECORD_SEPARATOR))); } } // store the history in persistent storage void storeHistory(PersistentStorage prefs) { if (hist.size() > lastSaved) { // Prevent history overflow by calculating what will fit, starting // with most recent int sepLen = RECORD_SEPARATOR.length(); int length = 0; int first = hist.size(); while (length < Preferences.MAX_VALUE_LENGTH && --first >= 0) { length += hist.get(first).length() + sepLen; } if (first >= 0) { hist = hist.subList(first + 1, hist.size()); } String shist = String.join(RECORD_SEPARATOR, hist); prefs.put(REPLAY_RESTORE_KEY, shist); markSaved(); } prefs.flush(); } // add a snippet or command to the history void add(String s) { hist.add(s); } // return history to reloaded Iterable<String> iterable() { return hist; } // mark that persistent storage and current history are in sync void markSaved() { lastSaved = hist.size(); } } /** * Is the input/output currently interactive * * @return true if console */ boolean interactive() { return input != null && input.interactiveOutput(); } void debug(String format, Object... args) { if (debug) { cmderr.printf(format + "\n", args); } } /** * Must show command output * * @param format printf format * @param args printf args */ @Override public void hard(String format, Object... args) { cmdout.printf(prefix(format), args); } /** * Error command output * * @param format printf format * @param args printf args */ void error(String format, Object... args) { (interactiveModeBegun? cmdout : cmderr).printf(prefixError(format), args); } /** * Should optional informative be displayed? * @return true if they should be displayed */ @Override public boolean showFluff() { return feedback.shouldDisplayCommandFluff() && interactive(); } /** * Optional output * * @param format printf format * @param args printf args */ @Override public void fluff(String format, Object... args) { if (showFluff()) { hard(format, args); } } /** * Resource bundle look-up * * @param key the resource key */ String getResourceString(String key) { if (outputRB == null) { try { outputRB = ResourceBundle.getBundle(L10N_RB_NAME, locale); } catch (MissingResourceException mre) { error("Cannot find ResourceBundle: %s for locale: %s", L10N_RB_NAME, locale); return ""; } } String s; try { s = outputRB.getString(key); } catch (MissingResourceException mre) { error("Missing resource: %s in %s", key, L10N_RB_NAME); return ""; } return s; } /** * Add normal prefixing/postfixing to embedded newlines in a string, * bracketing with normal prefix/postfix * * @param s the string to prefix * @return the pre/post-fixed and bracketed string */ String prefix(String s) { return prefix(s, feedback.getPre(), feedback.getPost()); } /** * Add error prefixing/postfixing to embedded newlines in a string, * bracketing with error prefix/postfix * * @param s the string to prefix * @return the pre/post-fixed and bracketed string */ String prefixError(String s) { return prefix(s, feedback.getErrorPre(), feedback.getErrorPost()); } /** * Add prefixing/postfixing to embedded newlines in a string, * bracketing with prefix/postfix. No prefixing when non-interactive. * Result is expected to be the format for a printf. * * @param s the string to prefix * @param pre the string to prepend to each line * @param post the string to append to each line (replacing newline) * @return the pre/post-fixed and bracketed string */ String prefix(String s, String pre, String post) { if (s == null) { return ""; } if (!interactiveModeBegun) { // messages expect to be new-line terminated (even when not prefixed) return s + "%n"; } String pp = s.replaceAll("\\R", post + pre); if (pp.endsWith(post + pre)) { // prevent an extra prefix char and blank line when the string // already terminates with newline pp = pp.substring(0, pp.length() - (post + pre).length()); } return pre + pp + post; } /** * Print using resource bundle look-up and adding prefix and postfix * * @param key the resource key */ void hardrb(String key) { hard(getResourceString(key)); } /** * Format using resource bundle look-up using MessageFormat * * @param key the resource key * @param args */ String messageFormat(String key, Object... args) { String rs = getResourceString(key); return MessageFormat.format(rs, args); } /** * Print using resource bundle look-up, MessageFormat, and add prefix and * postfix * * @param key the resource key * @param args */ @Override public void hardmsg(String key, Object... args) { hard(messageFormat(key, args)); } /** * Print error using resource bundle look-up, MessageFormat, and add prefix * and postfix * * @param key the resource key * @param args */ @Override public void errormsg(String key, Object... args) { error(messageFormat(key, args)); } /** * Print (fluff) using resource bundle look-up, MessageFormat, and add * prefix and postfix * * @param key the resource key * @param args */ @Override public void fluffmsg(String key, Object... args) { if (showFluff()) { hardmsg(key, args); } } <T> void hardPairs(Stream<T> stream, Function<T, String> a, Function<T, String> b) { Map<String, String> a2b = stream.collect(toMap(a, b, (m1, m2) -> m1, LinkedHashMap::new)); for (Entry<String, String> e : a2b.entrySet()) { hard("%s", e.getKey()); cmdout.printf(prefix(e.getValue(), feedback.getPre() + "\t", feedback.getPost())); } } /** * Trim whitespace off end of string * * @param s * @return */ static String trimEnd(String s) { int last = s.length() - 1; int i = last; while (i >= 0 && Character.isWhitespace(s.charAt(i))) { --i; } if (i != last) { return s.substring(0, i + 1); } else { return s; } } /** * The entry point into the JShell tool. * * @param args the command-line arguments * @throws Exception catastrophic fatal exception * @return the exit code */ public int start(String[] args) throws Exception { OptionParserCommandLine commandLineArgs = new OptionParserCommandLine(); options = commandLineArgs.parse(args); if (options == null) { // A null means end immediately, this may be an error or because // of options like --version. Exit code has been set. return exitCode; } startup = commandLineArgs.startup(); // initialize editor settings configEditor(); // initialize JShell instance try { resetState(); } catch (IllegalStateException ex) { // Display just the cause (not a exception backtrace) cmderr.println(ex.getMessage()); //abort return 1; } // Read replay history from last jshell session into previous history replayableHistoryPrevious = ReplayableHistory.fromPrevious(prefs); // load snippet/command files given on command-line for (String loadFile : commandLineArgs.nonOptions()) { if (!runFile(loadFile, "jshell")) { // Load file failed -- abort return 1; } } // 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 interactiveModeBegun = true; if (feedback.shouldDisplayCommandFluff()) { hardmsg("jshell.msg.welcome", version()); } // Be sure history is always saved so that user code isn't lost Thread shutdownHook = new Thread() { @Override public void run() { replayableHistory.storeHistory(prefs); } }; Runtime.getRuntime().addShutdownHook(shutdownHook); // execute from user input try (IOContext in = new ConsoleIOContext(this, cmdin, console)) { while (regenerateOnDeath) { if (!live) { resetState(); } run(in); } } finally { replayableHistory.storeHistory(prefs); closeState(); try { Runtime.getRuntime().removeShutdownHook(shutdownHook); } catch (Exception ex) { // ignore, this probably caused by VM aready being shutdown // and this is the last act anyhow } } } closeState(); return exitCode; } private EditorSetting configEditor() { // Read retained editor setting (if any) editor = EditorSetting.fromPrefs(prefs); if (editor != null) { return editor; } // Try getting editor setting from OS environment variables for (String envvar : EDITOR_ENV_VARS) { String v = envvars.get(envvar); if (v != null) { return editor = new EditorSetting(v.split("\\s+"), false); } } // Default to the built-in editor return editor = BUILT_IN_EDITOR; } private void printUsage() { cmdout.print(getResourceString("help.usage")); } private void printUsageX() { cmdout.print(getResourceString("help.usage.x")); } /** * Message handler to use during initial start-up. */ private class InitMessageHandler implements MessageHandler { @Override public void fluff(String format, Object... args) { //ignore } @Override public void fluffmsg(String messageKey, Object... args) { //ignore } @Override public void hard(String format, Object... args) { //ignore } @Override public void hardmsg(String messageKey, Object... args) { //ignore } @Override public void errormsg(String messageKey, Object... args) { JShellTool.this.errormsg(messageKey, args); } @Override public boolean showFluff() { return false; } } private void resetState() { closeState(); // Initialize tool id mapping mainNamespace = new NameSpace("main", ""); startNamespace = new NameSpace("start", "s"); errorNamespace = new NameSpace("error", "e"); mapSnippet = new LinkedHashMap<>(); currentNameSpace = startNamespace; // Reset the replayable history, saving the old for restore replayableHistoryPrevious = replayableHistory; replayableHistory = ReplayableHistory.emptyHistory(); JShell.Builder builder = JShell.builder() .in(userin) .out(userout) .err(usererr) .tempVariableNameGenerator(() -> "$" + currentNameSpace.tidNext()) .idGenerator((sn, i) -> (currentNameSpace == startNamespace || state.status(sn).isActive()) ? currentNameSpace.tid(sn) : errorNamespace.tid(sn)) .remoteVMOptions(options.remoteVmOptions()) .compilerOptions(options.compilerOptions()); if (executionControlSpec != null) { builder.executionEngine(executionControlSpec); } state = builder.build(); InternalDebugControl.setDebugFlags(state, debugFlags); shutdownSubscription = state.onShutdown((JShell deadState) -> { if (deadState == state) { hardmsg("jshell.msg.terminated"); fluffmsg("jshell.msg.terminated.restore"); live = false; } }); analysis = state.sourceCodeAnalysis(); live = true; // Run the start-up script. // Avoid an infinite loop running start-up while running start-up. // This could, otherwise, occur when /env /reset or /reload commands are // in the start-up script. if (!isCurrentlyRunningStartup) { try { isCurrentlyRunningStartup = true; startUpRun(startup.toString()); } finally { isCurrentlyRunningStartup = false; } } // Record subsequent snippets in the main namespace. currentNameSpace = mainNamespace; } //where -- one-time per run initialization of feedback modes 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 startUpRun(getResourceString("startup.feedback")); // 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); if (encoded != null && !encoded.isEmpty()) { if (!feedback.restoreEncodedModes(initmh, encoded)) { // Catastrophic corruption -- remove the retained modes prefs.remove(MODE_KEY); } } if (initMode != null) { // The feedback mode to use was specified on the command line, use it if (!setFeedback(initmh, new ArgTokenizer("--feedback", initMode))) { regenerateOnDeath = false; exitCode = 1; } } else { 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 setFeedback(initmh, new ArgTokenizer("previous retain feedback", "-retain " + fb)); } } } //where private void startUpRun(String start) { try (IOContext suin = new ScannerIOContext(new StringReader(start))) { run(suin); } catch (Exception ex) { errormsg("jshell.err.startup.unexpected.exception", ex); ex.printStackTrace(cmderr); } } private void closeState() { live = false; JShell oldState = state; if (oldState != null) { state = null; analysis = null; oldState.unsubscribe(shutdownSubscription); // No notification oldState.close(); } } /** * Main loop * * @param in the line input/editing context */ private void run(IOContext in) { IOContext oldInput = input; input = in; try { // remaining is the source left after one snippet is evaluated String remaining = ""; while (live) { // Get a line(s) of input String src = getInput(remaining); // Process the snippet or command, returning the remaining source remaining = processInput(src); } } catch (EOFException ex) { // Just exit loop } catch (IOException ex) { errormsg("jshell.err.unexpected.exception", ex); } finally { input = oldInput; } } /** * Process an input command or snippet. * * @param src the source to process * @return any remaining input to processed */ private String processInput(String src) { if (isCommand(src)) { // It is a command processCommand(src.trim()); // No remaining input after a command return ""; } else { // It is a snipet. Separate the source from the remaining. Evaluate // the source CompletionInfo an = analysis.analyzeCompletion(src); if (processSourceCatchingReset(trimEnd(an.source()))) { // Snippet was successful use any leftover source return an.remaining(); } else { // Snippet failed, throw away any remaining source return ""; } } } /** * Get the input line (or, if incomplete, lines). * * @param initial leading input (left over after last snippet) * @return the complete input snippet or command * @throws IOException on unexpected I/O error */ private String getInput(String initial) throws IOException{ String src = initial; while (live) { // loop while incomplete (and live) if (!src.isEmpty()) { // We have some source, see if it is complete, if so, use it String check; if (isCommand(src)) { // A command can only be incomplete if it is a /exit with // an argument int sp = src.indexOf(" "); if (sp < 0) return src; check = src.substring(sp).trim(); if (check.isEmpty()) return src; String cmd = src.substring(0, sp); Command[] match = findCommand(cmd, c -> c.kind.isRealCommand); if (match.length != 1 || !match[0].command.equals("/exit")) { // A command with no snippet arg, so no multi-line input return src; } } else { // For a snippet check the whole source check = src; } Completeness comp = analysis.analyzeCompletion(check).completeness(); if (comp.isComplete() || comp == Completeness.EMPTY) { return src; } } String prompt = interactive() ? testPrompt ? src.isEmpty() ? "\u0005" //ENQ -- test prompt : "\u0006" //ACK -- test continuation prompt : src.isEmpty() ? feedback.getPrompt(currentNameSpace.tidNext()) : feedback.getContinuationPrompt(currentNameSpace.tidNext()) : "" // Non-interactive -- no prompt ; String line; try { line = input.readLine(prompt, src); } catch (InputInterruptedException ex) { //input interrupted - clearing current state src = ""; continue; } if (line == null) { //EOF if (input.interactiveOutput()) { // End after user ctrl-D regenerateOnDeath = false; } throw new EOFException(); // no more input } src = src.isEmpty() ? line : src + "\n" + line; } throw new EOFException(); // not longer live } private boolean isCommand(String line) { return line.startsWith("/") && !line.startsWith("//") && !line.startsWith("/*"); } private void addToReplayHistory(String s) { if (!isCurrentlyRunningStartup) { replayableHistory.add(s); } } /** * Process a source snippet. * * @param src the snippet source to process * @return true on success, false on failure */ private boolean processSourceCatchingReset(String src) { try { input.beforeUserCode(); return processSource(src); } catch (IllegalStateException ex) { hard("Resetting..."); live = false; // Make double sure return false; } finally { input.afterUserCode(); } } /** * Process a command (as opposed to a snippet) -- things that start with * slash. * * @param input */ private void processCommand(String input) { if (input.startsWith("/-")) { try { //handle "/-[number]" cmdUseHistoryEntry(Integer.parseInt(input.substring(1))); return ; } catch (NumberFormatException ex) { //ignore } } String cmd; String arg; int idx = input.indexOf(' '); if (idx > 0) { arg = input.substring(idx + 1).trim(); cmd = input.substring(0, idx); } else { cmd = input; arg = ""; } // find the command as a "real command", not a pseudo-command or doc subject Command[] candidates = findCommand(cmd, c -> c.kind.isRealCommand); switch (candidates.length) { case 0: // not found, it is either a rerun-ID command or an error if (RERUN_ID.matcher(cmd).matches()) { // it is in the form of a snipppet id, see if it is a valid history reference rerunHistoryEntriesById(input); } else { errormsg("jshell.err.invalid.command", cmd); fluffmsg("jshell.msg.help.for.help"); } break; case 1: Command command = candidates[0]; // If comand was successful and is of a replayable kind, add it the replayable history if (command.run.apply(arg) && command.kind == CommandKind.REPLAY) { addToReplayHistory((command.command + " " + arg).trim()); } break; default: // command if too short (ambigous), show the possibly matches errormsg("jshell.err.command.ambiguous", cmd, Arrays.stream(candidates).map(c -> c.command).collect(Collectors.joining(", "))); fluffmsg("jshell.msg.help.for.help"); break; } } private Command[] findCommand(String cmd, Predicate<Command> filter) { Command exact = commands.get(cmd); if (exact != null) return new Command[] {exact}; return commands.values() .stream() .filter(filter) .filter(command -> command.command.startsWith(cmd)) .toArray(Command[]::new); } static Path toPathResolvingUserHome(String pathString) { if (pathString.replace(File.separatorChar, '/').startsWith("~/")) return Paths.get(System.getProperty("user.home"), pathString.substring(2)); else return Paths.get(pathString); } static final class Command { public final String command; public final String helpKey; public final Function<String,Boolean> run; public final CompletionProvider completions; public final CommandKind kind; // NORMAL Commands public Command(String command, Function<String,Boolean> run, CompletionProvider completions) { this(command, run, completions, CommandKind.NORMAL); } // Special kinds of Commands public Command(String command, Function<String,Boolean> run, CompletionProvider completions, CommandKind kind) { this(command, "help." + command.substring(1), run, completions, kind); } // Documentation pseudo-commands public Command(String command, String helpKey, CommandKind kind) { this(command, helpKey, arg -> { throw new IllegalStateException(); }, EMPTY_COMPLETION_PROVIDER, kind); } public Command(String command, String helpKey, Function<String,Boolean> run, CompletionProvider completions, CommandKind kind) { this.command = command; this.helpKey = helpKey; this.run = run; this.completions = completions; this.kind = kind; } } interface CompletionProvider { List<Suggestion> completionSuggestions(String input, int cursor, int[] anchor); } enum CommandKind { NORMAL(true, true, true), REPLAY(true, true, true), HIDDEN(true, false, false), HELP_ONLY(false, true, false), HELP_SUBJECT(false, false, false); final boolean isRealCommand; final boolean showInHelp; final boolean shouldSuggestCompletions; private CommandKind(boolean isRealCommand, boolean showInHelp, boolean shouldSuggestCompletions) { this.isRealCommand = isRealCommand; this.showInHelp = showInHelp; this.shouldSuggestCompletions = shouldSuggestCompletions; } } static final class FixedCompletionProvider implements CompletionProvider { private final String[] alternatives; public FixedCompletionProvider(String... alternatives) { this.alternatives = alternatives; } // Add more options to an existing provider public FixedCompletionProvider(FixedCompletionProvider base, String... alternatives) { List<String> l = new ArrayList<>(Arrays.asList(base.alternatives)); l.addAll(Arrays.asList(alternatives)); this.alternatives = l.toArray(new String[l.size()]); } @Override public List<Suggestion> completionSuggestions(String input, int cursor, int[] anchor) { List<Suggestion> result = new ArrayList<>(); for (String alternative : alternatives) { if (alternative.startsWith(input)) { result.add(new ArgSuggestion(alternative)); } } anchor[0] = 0; return result; } } static final CompletionProvider EMPTY_COMPLETION_PROVIDER = new FixedCompletionProvider(); 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 HISTORY_OPTION_COMPLETION_PROVIDER = new FixedCompletionProvider("-all"); private static final CompletionProvider SNIPPET_OPTION_COMPLETION_PROVIDER = new FixedCompletionProvider("-all", "-start " ); 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<String, CompletionProvider> 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<String, Command> commands = new LinkedHashMap<>(); private void registerCommand(Command cmd) { commands.put(cmd.command, cmd); } private static CompletionProvider skipWordThenCompletion(CompletionProvider completionProvider) { return (input, cursor, anchor) -> { List<Suggestion> result = Collections.emptyList(); int space = input.indexOf(' '); if (space != -1) { String rest = input.substring(space + 1); result = completionProvider.completionSuggestions(rest, cursor - space - 1, anchor); anchor[0] += space + 1; } return result; }; } private static CompletionProvider fileCompletions(Predicate<Path> accept) { return (code, cursor, anchor) -> { int lastSlash = code.lastIndexOf('/'); String path = code.substring(0, lastSlash + 1); String prefix = lastSlash != (-1) ? code.substring(lastSlash + 1) : code; Path current = toPathResolvingUserHome(path); List<Suggestion> result = new ArrayList<>(); try (Stream<Path> dir = Files.list(current)) { dir.filter(f -> accept.test(f) && f.getFileName().toString().startsWith(prefix)) .map(f -> new ArgSuggestion(f.getFileName() + (Files.isDirectory(f) ? "/" : ""))) .forEach(result::add); } catch (IOException ex) { //ignore... } if (path.isEmpty()) { StreamSupport.stream(FileSystems.getDefault().getRootDirectories().spliterator(), false) .filter(root -> Files.exists(root)) .filter(root -> accept.test(root) && root.toString().startsWith(prefix)) .map(root -> new ArgSuggestion(root.toString())) .forEach(result::add); } anchor[0] = path.length(); return result; }; } private static CompletionProvider classPathCompletion() { return fileCompletions(p -> Files.isDirectory(p) || p.getFileName().toString().endsWith(".zip") || p.getFileName().toString().endsWith(".jar")); } // Completion based on snippet supplier private CompletionProvider snippetCompletion(Supplier<Stream<? extends Snippet>> snippetsSupplier) { return (prefix, cursor, anchor) -> { anchor[0] = 0; int space = prefix.lastIndexOf(' '); Set<String> prior = new HashSet<>(Arrays.asList(prefix.split(" "))); if (prior.contains("-all") || prior.contains("-history")) { return Collections.emptyList(); } String argPrefix = prefix.substring(space + 1); return snippetsSupplier.get() .filter(k -> !prior.contains(String.valueOf(k.id())) && (!(k instanceof DeclarationSnippet) || !prior.contains(((DeclarationSnippet) k).name()))) .flatMap(k -> (k instanceof DeclarationSnippet) ? Stream.of(String.valueOf(k.id()) + " ", ((DeclarationSnippet) k).name() + " ") : Stream.of(String.valueOf(k.id()) + " ")) .filter(k -> k.startsWith(argPrefix)) .map(ArgSuggestion::new) .collect(Collectors.toList()); }; } // Completion based on snippet supplier with -all -start (and sometimes -history) options private CompletionProvider snippetWithOptionCompletion(CompletionProvider optionProvider, Supplier<Stream<? extends Snippet>> snippetsSupplier) { return (code, cursor, anchor) -> { List<Suggestion> result = new ArrayList<>(); int pastSpace = code.lastIndexOf(' ') + 1; // zero if no space if (pastSpace == 0) { result.addAll(optionProvider.completionSuggestions(code, cursor, anchor)); } result.addAll(snippetCompletion(snippetsSupplier).completionSuggestions(code, cursor, anchor)); anchor[0] += pastSpace; return result; }; } // Completion of help, commands and subjects private CompletionProvider helpCompletion() { return (code, cursor, anchor) -> { List<Suggestion> result; int pastSpace = code.indexOf(' ') + 1; // zero if no space if (pastSpace == 0) { // initially suggest commands (with slash) and subjects, // however, if their subject starts without slash, include // commands without slash boolean noslash = code.length() > 0 && !code.startsWith("/"); result = new FixedCompletionProvider(commands.values().stream() .filter(cmd -> cmd.kind.showInHelp || cmd.kind == CommandKind.HELP_SUBJECT) .map(c -> ((noslash && c.command.startsWith("/")) ? c.command.substring(1) : c.command) + " ") .toArray(String[]::new)) .completionSuggestions(code, cursor, anchor); } else if (code.startsWith("/se") || code.startsWith("se")) { result = new FixedCompletionProvider(SET_SUBCOMMANDS) .completionSuggestions(code.substring(pastSpace), cursor - pastSpace, anchor); } else { result = Collections.emptyList(); } anchor[0] += pastSpace; return result; }; } private static CompletionProvider saveCompletion() { return (code, cursor, anchor) -> { List<Suggestion> result = new ArrayList<>(); int space = code.indexOf(' '); if (space == (-1)) { result.addAll(SAVE_OPTION_COMPLETION_PROVIDER.completionSuggestions(code, cursor, anchor)); } result.addAll(FILE_COMPLETION_PROVIDER.completionSuggestions(code.substring(space + 1), cursor - space - 1, anchor)); anchor[0] += space + 1; return result; }; } // command-line-like option completion -- options with values private static CompletionProvider optionCompletion(CompletionProvider provider) { return (code, cursor, anchor) -> { Matcher ovm = OPTION_VALUE_PATTERN.matcher(code); if (ovm.matches()) { String flag = ovm.group("flag"); List<CompletionProvider> 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<Suggestion> result = ps.get(0).completionSuggestions( ovm.group("val"), cursor - pastSpace, anchor); anchor[0] += pastSpace; return result; } } Matcher om = OPTION_PATTERN.matcher(code); if (om.matches()) { int pastSpace = om.start("flag"); List<Suggestion> 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<Suggestion> result = provider.completionSuggestions( "", cursor - pastSpace, anchor); anchor[0] += pastSpace; return result; } return Collections.emptyList(); }; } // /history command completion private static CompletionProvider historyCompletion() { return optionCompletion(HISTORY_OPTION_COMPLETION_PROVIDER); } // /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) -> { int[] leftAnchor = {-1}; int[] rightAnchor = {-1}; List<Suggestion> leftSuggestions = left.completionSuggestions(code, cursor, leftAnchor); List<Suggestion> rightSuggestions = right.completionSuggestions(code, cursor, rightAnchor); List<Suggestion> suggestions = new ArrayList<>(); if (leftAnchor[0] >= rightAnchor[0]) { anchor[0] = leftAnchor[0]; suggestions.addAll(leftSuggestions); } if (leftAnchor[0] <= rightAnchor[0]) { anchor[0] = rightAnchor[0]; suggestions.addAll(rightSuggestions); } return suggestions; }; } // Snippet lists Stream<Snippet> allSnippets() { return state.snippets(); } Stream<Snippet> dropableSnippets() { return state.snippets() .filter(sn -> state.status(sn).isActive()); } Stream<VarSnippet> allVarSnippets() { return state.snippets() .filter(sn -> sn.kind() == Snippet.Kind.VAR) .map(sn -> (VarSnippet) sn); } Stream<MethodSnippet> allMethodSnippets() { return state.snippets() .filter(sn -> sn.kind() == Snippet.Kind.METHOD) .map(sn -> (MethodSnippet) sn); } Stream<TypeDeclSnippet> allTypeSnippets() { return state.snippets() .filter(sn -> sn.kind() == Snippet.Kind.TYPE_DECL) .map(sn -> (TypeDeclSnippet) sn); } // Table of commands -- with command forms, argument kinds, helpKey message, implementation, ... { registerCommand(new Command("/list", this::cmdList, snippetWithOptionCompletion(SNIPPET_HISTORY_OPTION_COMPLETION_PROVIDER, this::allSnippets))); registerCommand(new Command("/edit", this::cmdEdit, snippetWithOptionCompletion(SNIPPET_OPTION_COMPLETION_PROVIDER, this::allSnippets))); registerCommand(new Command("/drop", this::cmdDrop, snippetCompletion(this::dropableSnippets), CommandKind.REPLAY)); registerCommand(new Command("/save", this::cmdSave, saveCompletion())); registerCommand(new Command("/open", this::cmdOpen, FILE_COMPLETION_PROVIDER)); registerCommand(new Command("/vars", this::cmdVars, snippetWithOptionCompletion(SNIPPET_OPTION_COMPLETION_PROVIDER, this::allVarSnippets))); registerCommand(new Command("/methods", this::cmdMethods, snippetWithOptionCompletion(SNIPPET_OPTION_COMPLETION_PROVIDER, this::allMethodSnippets))); registerCommand(new Command("/types", this::cmdTypes, snippetWithOptionCompletion(SNIPPET_OPTION_COMPLETION_PROVIDER, this::allTypeSnippets))); registerCommand(new Command("/imports", arg -> cmdImports(), EMPTY_COMPLETION_PROVIDER)); registerCommand(new Command("/exit", arg -> cmdExit(arg), (sn, c, a) -> { if (analysis == null || sn.isEmpty()) { // No completions if uninitialized or snippet not started return Collections.emptyList(); } else { // Give exit code an int context by prefixing the arg List<Suggestion> suggestions = analysis.completionSuggestions(INT_PREFIX + sn, INT_PREFIX.length() + c, a); a[0] -= INT_PREFIX.length(); return suggestions; } })); registerCommand(new Command("/env", arg -> cmdEnv(arg), envCompletion())); registerCommand(new Command("/reset", arg -> cmdReset(arg), envCompletion())); registerCommand(new Command("/reload", this::cmdReload, reloadCompletion())); registerCommand(new Command("/history", this::cmdHistory, historyCompletion())); registerCommand(new Command("/debug", this::cmdDebug, EMPTY_COMPLETION_PROVIDER, CommandKind.HIDDEN)); registerCommand(new Command("/help", this::cmdHelp, helpCompletion())); registerCommand(new Command("/set", this::cmdSet, new ContinuousCompletionProvider(Map.of( // need more completion for format for usability "format", feedback.modeCompletions(), "truncation", feedback.modeCompletions(), "feedback", feedback.modeCompletions(), "mode", skipWordThenCompletion(orMostSpecificCompletion( feedback.modeCompletions(SET_MODE_OPTIONS_COMPLETION_PROVIDER), SET_MODE_OPTIONS_COMPLETION_PROVIDER)), "prompt", feedback.modeCompletions(), "editor", fileCompletions(Files::isExecutable), "start", FILE_COMPLETION_PROVIDER), STARTSWITH_MATCHER))); registerCommand(new Command("/?", "help.quest", this::cmdHelp, helpCompletion(), CommandKind.NORMAL)); registerCommand(new Command("/!", "help.bang", arg -> cmdUseHistoryEntry(-1), EMPTY_COMPLETION_PROVIDER, CommandKind.NORMAL)); // Documentation pseudo-commands registerCommand(new Command("/<id>", "help.slashID", arg -> cmdHelp("rerun"), EMPTY_COMPLETION_PROVIDER, CommandKind.HELP_ONLY)); registerCommand(new Command("/-<n>", "help.previous", arg -> cmdHelp("rerun"), EMPTY_COMPLETION_PROVIDER, CommandKind.HELP_ONLY)); registerCommand(new Command("intro", "help.intro", CommandKind.HELP_SUBJECT)); registerCommand(new Command("id", "help.id", CommandKind.HELP_SUBJECT)); registerCommand(new Command("shortcuts", "help.shortcuts", CommandKind.HELP_SUBJECT)); registerCommand(new Command("context", "help.context", CommandKind.HELP_SUBJECT)); registerCommand(new Command("rerun", "help.rerun", CommandKind.HELP_SUBJECT)); commandCompletions = new ContinuousCompletionProvider( commands.values().stream() .filter(c -> c.kind.shouldSuggestCompletions) .collect(toMap(c -> c.command, c -> c.completions)), STARTSWITH_MATCHER); } private ContinuousCompletionProvider commandCompletions; public List<Suggestion> commandCompletionSuggestions(String code, int cursor, int[] anchor) { return commandCompletions.completionSuggestions(code, cursor, anchor); } public List<String> commandDocumentation(String code, int cursor, boolean shortDescription) { code = code.substring(0, cursor).replaceAll("\\h+", " "); String stripped = code.replaceFirst("/(he(lp?)?|\\?) ", ""); boolean inHelp = !code.equals(stripped); int space = stripped.indexOf(' '); String prefix = space != (-1) ? stripped.substring(0, space) : stripped; List<String> result = new ArrayList<>(); List<Entry<String, String>> toShow; if (SET_SUB.matcher(stripped).matches()) { String setSubcommand = stripped.replaceFirst("/?set ([^ ]*)($| .*)", "$1"); toShow = Arrays.stream(SET_SUBCOMMANDS) .filter(s -> s.startsWith(setSubcommand)) .map(s -> new SimpleEntry<>("/set " + s, "help.set." + s)) .collect(toList()); } else if (RERUN_ID.matcher(stripped).matches()) { toShow = singletonList(new SimpleEntry<>("/<id>", "help.rerun")); } else if (RERUN_PREVIOUS.matcher(stripped).matches()) { toShow = singletonList(new SimpleEntry<>("/-<n>", "help.rerun")); } else { toShow = commands.values() .stream() .filter(c -> c.command.startsWith(prefix) || c.command.substring(1).startsWith(prefix)) .filter(c -> c.kind.showInHelp || (inHelp && c.kind == CommandKind.HELP_SUBJECT)) .sorted((c1, c2) -> c1.command.compareTo(c2.command)) .map(c -> new SimpleEntry<>(c.command, c.helpKey)) .collect(toList()); } if (toShow.size() == 1 && !inHelp) { result.add(getResourceString(toShow.get(0).getValue() + (shortDescription ? ".summary" : ""))); } else { for (Entry<String, String> e : toShow) { result.add(e.getKey() + "\n" + getResourceString(e.getValue() + (shortDescription ? ".summary" : ""))); } } return result; } // Attempt to stop currently running evaluation void stop() { state.stop(); } // --- Command implementations --- private static final String[] SET_SUBCOMMANDS = new String[]{ "format", "truncation", "feedback", "mode", "prompt", "editor", "start"}; final boolean cmdSet(String arg) { String cmd = "/set"; ArgTokenizer at = new ArgTokenizer(cmd, arg.trim()); String which = subCommand(cmd, at, SET_SUBCOMMANDS); if (which == null) { return false; } switch (which) { case "_retain": { errormsg("jshell.err.setting.to.retain.must.be.specified", at.whole()); return false; } case "_blank": { // show top-level settings new SetEditor().set(); showSetStart(); setFeedback(this, at); // no args so shows feedback setting hardmsg("jshell.msg.set.show.mode.settings"); return true; } case "format": return feedback.setFormat(this, at); case "truncation": return feedback.setTruncation(this, at); case "feedback": return setFeedback(this, at); case "mode": return feedback.setMode(this, at, retained -> prefs.put(MODE_KEY, retained)); case "prompt": return feedback.setPrompt(this, at); case "editor": return new SetEditor(at).set(); case "start": return setStart(at); default: errormsg("jshell.err.arg", cmd, at.val()); return false; } } boolean setFeedback(MessageHandler messageHandler, ArgTokenizer at) { return feedback.setFeedback(messageHandler, at, fb -> prefs.put(FEEDBACK_KEY, fb)); } // Find which, if any, sub-command matches. // Return null on error String subCommand(String cmd, ArgTokenizer at, String[] subs) { at.allowedOptions("-retain"); String sub = at.next(); if (sub == null) { // No sub-command was given return at.hasOption("-retain") ? "_retain" : "_blank"; } String[] matches = Arrays.stream(subs) .filter(s -> s.startsWith(sub)) .toArray(String[]::new); if (matches.length == 0) { // There are no matching sub-commands errormsg("jshell.err.arg", cmd, sub); fluffmsg("jshell.msg.use.one.of", Arrays.stream(subs) .collect(Collectors.joining(", ")) ); return null; } if (matches.length > 1) { // More than one sub-command matches the initial characters provided errormsg("jshell.err.sub.ambiguous", cmd, sub); fluffmsg("jshell.msg.use.one.of", Arrays.stream(matches) .collect(Collectors.joining(", ")) ); return null; } return matches[0]; } static class EditorSetting { static String BUILT_IN_REP = "-default"; static char WAIT_PREFIX = '-'; static char NORMAL_PREFIX = '*'; final String[] cmd; final boolean wait; EditorSetting(String[] cmd, boolean wait) { this.wait = wait; this.cmd = cmd; } // returns null if not stored in preferences static EditorSetting fromPrefs(PersistentStorage prefs) { // Read retained editor setting (if any) String editorString = prefs.get(EDITOR_KEY); if (editorString == null || editorString.isEmpty()) { return null; } else if (editorString.equals(BUILT_IN_REP)) { return BUILT_IN_EDITOR; } else { boolean wait = false; char waitMarker = editorString.charAt(0); if (waitMarker == WAIT_PREFIX || waitMarker == NORMAL_PREFIX) { wait = waitMarker == WAIT_PREFIX; editorString = editorString.substring(1); } String[] cmd = editorString.split(RECORD_SEPARATOR); return new EditorSetting(cmd, wait); } } static void removePrefs(PersistentStorage prefs) { prefs.remove(EDITOR_KEY); } 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)); } @Override public boolean equals(Object o) { if (o instanceof EditorSetting) { EditorSetting ed = (EditorSetting) o; return Arrays.equals(cmd, ed.cmd) && wait == ed.wait; } else { return false; } } @Override public int hashCode() { int hash = 7; hash = 71 * hash + Arrays.deepHashCode(this.cmd); hash = 71 * hash + (this.wait ? 1 : 0); return hash; } } class SetEditor { private final ArgTokenizer at; private final String[] command; private final boolean hasCommand; private final boolean defaultOption; private final boolean deleteOption; private final boolean waitOption; private final boolean retainOption; private final int primaryOptionCount; SetEditor(ArgTokenizer at) { at.allowedOptions("-default", "-wait", "-retain", "-delete"); String prog = at.next(); List<String> ed = new ArrayList<>(); while (at.val() != null) { ed.add(at.val()); at.nextToken(); // so that options are not interpreted as jshell options } this.at = at; this.command = ed.toArray(new String[ed.size()]); this.hasCommand = command.length > 0; this.defaultOption = at.hasOption("-default"); this.deleteOption = at.hasOption("-delete"); this.waitOption = at.hasOption("-wait"); this.retainOption = at.hasOption("-retain"); this.primaryOptionCount = (hasCommand? 1 : 0) + (defaultOption? 1 : 0) + (deleteOption? 1 : 0); } SetEditor() { this(new ArgTokenizer("", "")); } boolean set() { if (!check()) { return false; } if (primaryOptionCount == 0 && !retainOption) { // No settings or -retain, so this is a query EditorSetting retained = EditorSetting.fromPrefs(prefs); if (retained != null) { // retained editor is set hard("/set editor -retain %s", format(retained)); } if (retained == null || !retained.equals(editor)) { // editor is not retained or retained is different from set hard("/set editor %s", format(editor)); } return true; } if (retainOption && deleteOption) { EditorSetting.removePrefs(prefs); } install(); if (retainOption && !deleteOption) { editor.toPrefs(prefs); fluffmsg("jshell.msg.set.editor.retain", format(editor)); } return true; } private boolean check() { if (!checkOptionsAndRemainingInput(at)) { return false; } if (primaryOptionCount > 1) { errormsg("jshell.err.default.option.or.program", at.whole()); return false; } if (waitOption && !hasCommand) { errormsg("jshell.err.wait.applies.to.external.editor", at.whole()); return false; } return true; } private void install() { if (hasCommand) { editor = new EditorSetting(command, waitOption); } else if (defaultOption) { editor = BUILT_IN_EDITOR; } else if (deleteOption) { configEditor(); } else { return; } fluffmsg("jshell.msg.set.editor.set", format(editor)); } private String format(EditorSetting ed) { if (ed == BUILT_IN_EDITOR) { return "-default"; } else { Stream<String> elems = Arrays.stream(ed.cmd); if (ed.wait) { elems = Stream.concat(Stream.of("-wait"), elems); } return elems.collect(joining(" ")); } } } // The sub-command: /set start <start-file> boolean setStart(ArgTokenizer at) { at.allowedOptions("-default", "-none", "-retain"); List<String> fns = new ArrayList<>(); while (at.next() != null) { fns.add(at.val()); } if (!checkOptionsAndRemainingInput(at)) { return false; } boolean defaultOption = at.hasOption("-default"); boolean noneOption = at.hasOption("-none"); boolean retainOption = at.hasOption("-retain"); boolean hasFile = !fns.isEmpty(); int argCount = (defaultOption ? 1 : 0) + (noneOption ? 1 : 0) + (hasFile ? 1 : 0); if (argCount > 1) { errormsg("jshell.err.option.or.filename", at.whole()); return false; } if (argCount == 0 && !retainOption) { // no options or filename, show current setting showSetStart(); return true; } if (hasFile) { startup = Startup.fromFileList(fns, "/set start", this); if (startup == null) { return false; } } else if (defaultOption) { startup = Startup.defaultStartup(this); } else if (noneOption) { startup = Startup.noStartup(); } if (retainOption) { // retain startup setting prefs.put(STARTUP_KEY, startup.storedForm()); } return true; } // show the "/set start" settings (retained and, if different, current) // as commands (and file contents). All commands first, then contents. void showSetStart() { StringBuilder sb = new StringBuilder(); String retained = prefs.get(STARTUP_KEY); if (retained != null) { Startup retainedStart = Startup.unpack(retained, this); boolean currentDifferent = !startup.equals(retainedStart); sb.append(retainedStart.show(true)); if (currentDifferent) { sb.append(startup.show(false)); } sb.append(retainedStart.showDetail()); if (currentDifferent) { sb.append(startup.showDetail()); } } else { sb.append(startup.show(false)); sb.append(startup.showDetail()); } hard(sb.toString()); } boolean cmdDebug(String arg) { if (arg.isEmpty()) { debug = !debug; InternalDebugControl.setDebugFlags(state, debug ? DBG_GEN : 0); fluff("Debugging %s", debug ? "on" : "off"); } else { for (char ch : arg.toCharArray()) { switch (ch) { case '0': debugFlags = 0; debug = false; fluff("Debugging off"); break; case 'r': debug = true; fluff("REPL tool debugging on"); break; case 'g': debugFlags |= DBG_GEN; fluff("General debugging on"); break; case 'f': debugFlags |= DBG_FMGR; fluff("File manager debugging on"); break; case 'c': debugFlags |= DBG_COMPA; fluff("Completion analysis debugging on"); break; case 'd': debugFlags |= DBG_DEP; fluff("Dependency debugging on"); break; case 'e': debugFlags |= DBG_EVNT; fluff("Event debugging on"); break; case 'w': debugFlags |= DBG_WRAP; fluff("Wrap debugging on"); break; case 'b': cmdout.printf("RemoteVM Options: %s\nCompiler options: %s\n", Arrays.toString(options.remoteVmOptions()), Arrays.toString(options.compilerOptions())); break; default: error("Unknown debugging option: %c", ch); fluff("Use: 0 r g f c d e w b"); return false; } } InternalDebugControl.setDebugFlags(state, debugFlags); } return true; } private boolean cmdExit(String arg) { if (!arg.trim().isEmpty()) { debug("Compiling exit: %s", arg); List<SnippetEvent> events = state.eval(arg); for (SnippetEvent e : events) { // Only care about main snippet if (e.causeSnippet() == null) { Snippet sn = e.snippet(); // Show any diagnostics List<Diag> diagnostics = state.diagnostics(sn).collect(toList()); String source = sn.source(); displayDiagnostics(source, diagnostics); // Show any exceptions if (e.exception() != null && e.status() != Status.REJECTED) { if (displayException(e.exception())) { // Abort: an exception occurred (reported) return false; } } if (e.status() != Status.VALID) { // Abort: can only use valid snippets, diagnostics have been reported (above) return false; } String typeName; if (sn.kind() == Kind.EXPRESSION) { typeName = ((ExpressionSnippet) sn).typeName(); } else if (sn.subKind() == TEMP_VAR_EXPRESSION_SUBKIND) { typeName = ((VarSnippet) sn).typeName(); } else { // Abort: not an expression errormsg("jshell.err.exit.not.expression", arg); return false; } switch (typeName) { case "int": case "Integer": case "byte": case "Byte": case "short": case "Short": try { int i = Integer.parseInt(e.value()); /** addToReplayHistory("/exit " + arg); replayableHistory.storeHistory(prefs); closeState(); try { input.close(); } catch (Exception exc) { // ignore } * **/ exitCode = i; break; } catch (NumberFormatException exc) { // Abort: bad value errormsg("jshell.err.exit.bad.value", arg, e.value()); return false; } default: // Abort: bad type errormsg("jshell.err.exit.bad.type", arg, typeName); return false; } } } } regenerateOnDeath = false; live = false; if (exitCode == 0) { fluffmsg("jshell.msg.goodbye"); } else { fluffmsg("jshell.msg.goodbye.value", exitCode); } return true; } boolean cmdHelp(String arg) { ArgTokenizer at = new ArgTokenizer("/help", arg); String subject = at.next(); if (subject != null) { // check if the requested subject is a help subject or // a command, with or without slash Command[] matches = commands.values().stream() .filter(c -> c.command.startsWith(subject) || c.command.substring(1).startsWith(subject)) .toArray(Command[]::new); if (matches.length == 1) { String cmd = matches[0].command; if (cmd.equals("/set")) { // Print the help doc for the specified sub-command String which = subCommand(cmd, at, SET_SUBCOMMANDS); if (which == null) { return false; } if (!which.equals("_blank")) { printHelp("/set " + which, "help.set." + which); return true; } } } if (matches.length > 0) { for (Command c : matches) { printHelp(c.command, c.helpKey); } return true; } else { // failing everything else, check if this is the start of // a /set sub-command name String[] subs = Arrays.stream(SET_SUBCOMMANDS) .filter(s -> s.startsWith(subject)) .toArray(String[]::new); if (subs.length > 0) { for (String sub : subs) { printHelp("/set " + sub, "help.set." + sub); } return true; } errormsg("jshell.err.help.arg", arg); } } hardmsg("jshell.msg.help.begin"); hardPairs(commands.values().stream() .filter(cmd -> cmd.kind.showInHelp), cmd -> cmd.command + " " + getResourceString(cmd.helpKey + ".args"), cmd -> getResourceString(cmd.helpKey + ".summary") ); hardmsg("jshell.msg.help.subject"); hardPairs(commands.values().stream() .filter(cmd -> cmd.kind == CommandKind.HELP_SUBJECT), cmd -> cmd.command, cmd -> getResourceString(cmd.helpKey + ".summary") ); return true; } private void printHelp(String name, String key) { int len = name.length(); String centered = "%" + ((OUTPUT_WIDTH + len) / 2) + "s"; hard(""); hard(centered, name); hard(centered, Stream.generate(() -> "=").limit(len).collect(Collectors.joining())); hard(""); hardrb(key); } private boolean cmdHistory(String rawArgs) { ArgTokenizer at = new ArgTokenizer("/history", rawArgs.trim()); at.allowedOptions("-all"); if (!checkOptionsAndRemainingInput(at)) { return false; } cmdout.println(); for (String s : input.history(!at.hasOption("-all"))) { // No number prefix, confusing with snippet ids cmdout.printf("%s\n", s); } return true; } /** * Avoid parameterized varargs possible heap pollution warning. */ private interface SnippetPredicate<T extends Snippet> extends Predicate<T> { } /** * Apply filters to a stream until one that is non-empty is found. * Adapted from Stuart Marks * * @param supplier Supply the Snippet stream to filter * @param filters Filters to attempt * @return The non-empty filtered Stream, or null */ @SafeVarargs private static <T extends Snippet> Stream<T> nonEmptyStream(Supplier<Stream<T>> supplier, SnippetPredicate<T>... filters) { for (SnippetPredicate<T> filt : filters) { Iterator<T> iterator = supplier.get().filter(filt).iterator(); if (iterator.hasNext()) { return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false); } } return null; } private boolean inStartUp(Snippet sn) { return mapSnippet.get(sn).space == startNamespace; } private boolean isActive(Snippet sn) { return state.status(sn).isActive(); } private boolean mainActive(Snippet sn) { return !inStartUp(sn) && isActive(sn); } private boolean matchingDeclaration(Snippet sn, String name) { return sn instanceof DeclarationSnippet && ((DeclarationSnippet) sn).name().equals(name); } /** * Convert user arguments to a Stream of snippets referenced by those * arguments (or lack of arguments). * * @param snippets the base list of possible snippets * @param defFilter the filter to apply to the arguments if no argument * @param rawargs the user's argument to the command, maybe be the empty * string * @return a Stream of referenced snippets or null if no matches are found */ private <T extends Snippet> Stream<T> argsOptionsToSnippets(Supplier<Stream<T>> snippetSupplier, Predicate<Snippet> defFilter, String rawargs, String cmd) { ArgTokenizer at = new ArgTokenizer(cmd, rawargs.trim()); at.allowedOptions("-all", "-start"); return argsOptionsToSnippets(snippetSupplier, defFilter, at); } /** * Convert user arguments to a Stream of snippets referenced by those * arguments (or lack of arguments). * * @param snippets the base list of possible snippets * @param defFilter the filter to apply to the arguments if no argument * @param at the ArgTokenizer, with allowed options set * @return */ private <T extends Snippet> Stream<T> argsOptionsToSnippets(Supplier<Stream<T>> snippetSupplier, Predicate<Snippet> defFilter, ArgTokenizer at) { List<String> args = new ArrayList<>(); String s; while ((s = at.next()) != null) { args.add(s); } if (!checkOptionsAndRemainingInput(at)) { return null; } if (at.optionCount() > 0 && args.size() > 0) { errormsg("jshell.err.may.not.specify.options.and.snippets", at.whole()); return null; } if (at.optionCount() > 1) { errormsg("jshell.err.conflicting.options", at.whole()); return null; } if (at.isAllowedOption("-all") && at.hasOption("-all")) { // all snippets including start-up, failed, and overwritten return snippetSupplier.get(); } if (at.isAllowedOption("-start") && at.hasOption("-start")) { // start-up snippets return snippetSupplier.get() .filter(this::inStartUp); } if (args.isEmpty()) { // Default is all active user snippets return snippetSupplier.get() .filter(defFilter); } return new ArgToSnippets<>(snippetSupplier).argsToSnippets(args); } /** * Support for converting arguments that are definition names, snippet ids, * or snippet id ranges into a stream of snippets, * * @param <T> the snipper subtype */ private class ArgToSnippets<T extends Snippet> { // the supplier of snippet streams final Supplier<Stream<T>> snippetSupplier; // these two are parallel, and lazily filled if a range is encountered List<T> allSnippets; String[] allIds = null; /** * * @param snippetSupplier the base list of possible snippets */ ArgToSnippets(Supplier<Stream<T>> snippetSupplier) { this.snippetSupplier = snippetSupplier; } /** * Convert user arguments to a Stream of snippets referenced by those * arguments. * * @param args the user's argument to the command, maybe be the empty * list * @return a Stream of referenced snippets or null if no matches to * specific arg */ Stream<T> argsToSnippets(List<String> args) { Stream<T> result = null; for (String arg : args) { // Find the best match Stream<T> st = argToSnippets(arg); if (st == null) { return null; } else { result = (result == null) ? st : Stream.concat(result, st); } } return result; } /** * Convert a user argument to a Stream of snippets referenced by the * argument. * * @param snippetSupplier the base list of possible snippets * @param arg the user's argument to the command * @return a Stream of referenced snippets or null if no matches to * specific arg */ Stream<T> argToSnippets(String arg) { if (arg.contains("-")) { return range(arg); } // Find the best match Stream<T> st = layeredSnippetSearch(snippetSupplier, arg); if (st == null) { badSnippetErrormsg(arg); return null; } else { return st; } } /** * Look for inappropriate snippets to give best error message * * @param arg the bad snippet arg * @param errKey the not found error key */ void badSnippetErrormsg(String arg) { Stream<Snippet> est = layeredSnippetSearch(state::snippets, arg); if (est == null) { if (ID.matcher(arg).matches()) { errormsg("jshell.err.no.snippet.with.id", arg); } else { errormsg("jshell.err.no.such.snippets", arg); } } else { errormsg("jshell.err.the.snippet.cannot.be.used.with.this.command", arg, est.findFirst().get().source()); } } /** * Search through the snippets for the best match to the id/name. * * @param <R> the snippet type * @param aSnippetSupplier the supplier of snippet streams * @param arg the arg to match * @return a Stream of referenced snippets or null if no matches to * specific arg */ <R extends Snippet> Stream<R> layeredSnippetSearch(Supplier<Stream<R>> aSnippetSupplier, String arg) { return nonEmptyStream( // the stream supplier aSnippetSupplier, // look for active user declarations matching the name sn -> isActive(sn) && matchingDeclaration(sn, arg), // else, look for any declarations matching the name sn -> matchingDeclaration(sn, arg), // else, look for an id of this name sn -> sn.id().equals(arg) ); } /** * Given an id1-id2 range specifier, return a stream of snippets within * our context * * @param arg the range arg * @return a Stream of referenced snippets or null if no matches to * specific arg */ Stream<T> range(String arg) { int dash = arg.indexOf('-'); String iid = arg.substring(0, dash); String tid = arg.substring(dash + 1); int iidx = snippetIndex(iid); if (iidx < 0) { return null; } int tidx = snippetIndex(tid); if (tidx < 0) { return null; } if (tidx < iidx) { errormsg("jshell.err.end.snippet.range.less.than.start", iid, tid); return null; } return allSnippets.subList(iidx, tidx+1).stream(); } /** * Lazily initialize the id mapping -- needed only for id ranges. */ void initIdMapping() { if (allIds == null) { allSnippets = snippetSupplier.get() .sorted((a, b) -> order(a) - order(b)) .collect(toList()); allIds = allSnippets.stream() .map(sn -> sn.id()) .toArray(n -> new String[n]); } } /** * Return all the snippet ids -- within the context, and in order. * * @return the snippet ids */ String[] allIds() { initIdMapping(); return allIds; } /** * Establish an order on snippet ids. All startup snippets are first, * all error snippets are last -- within that is by snippet number. * * @param id the id string * @return an ordering int */ int order(String id) { try { switch (id.charAt(0)) { case 's': return Integer.parseInt(id.substring(1)); case 'e': return 0x40000000 + Integer.parseInt(id.substring(1)); default: return 0x20000000 + Integer.parseInt(id); } } catch (Exception ex) { return 0x60000000; } } /** * Establish an order on snippets, based on its snippet id. All startup * snippets are first, all error snippets are last -- within that is by * snippet number. * * @param sn the id string * @return an ordering int */ int order(Snippet sn) { return order(sn.id()); } /** * Find the index into the parallel allSnippets and allIds structures. * * @param s the snippet id name * @return the index, or, if not found, report the error and return a * negative number */ int snippetIndex(String s) { int idx = Arrays.binarySearch(allIds(), 0, allIds().length, s, (a, b) -> order(a) - order(b)); if (idx < 0) { // the id is not in the snippet domain, find the right error to report if (!ID.matcher(s).matches()) { errormsg("jshell.err.range.requires.id", s); } else { badSnippetErrormsg(s); } } return idx; } } private boolean cmdDrop(String rawargs) { ArgTokenizer at = new ArgTokenizer("/drop", rawargs.trim()); at.allowedOptions(); List<String> args = new ArrayList<>(); String s; while ((s = at.next()) != null) { args.add(s); } if (!checkOptionsAndRemainingInput(at)) { return false; } if (args.isEmpty()) { errormsg("jshell.err.drop.arg"); return false; } Stream<Snippet> stream = new ArgToSnippets<>(this::dropableSnippets).argsToSnippets(args); if (stream == null) { // Snippet not found. Error already printed fluffmsg("jshell.msg.see.classes.etc"); return false; } stream.forEach(sn -> state.drop(sn).forEach(this::handleEvent)); return true; } private boolean cmdEdit(String arg) { Stream<Snippet> stream = argsOptionsToSnippets(state::snippets, this::mainActive, arg, "/edit"); if (stream == null) { return false; } Set<String> srcSet = new LinkedHashSet<>(); stream.forEachOrdered(sn -> { String src = sn.source(); switch (sn.subKind()) { case VAR_VALUE_SUBKIND: break; case ASSIGNMENT_SUBKIND: case OTHER_EXPRESSION_SUBKIND: case TEMP_VAR_EXPRESSION_SUBKIND: case UNKNOWN_SUBKIND: if (!src.endsWith(";")) { src = src + ";"; } srcSet.add(src); break; case STATEMENT_SUBKIND: if (src.endsWith("}")) { // Could end with block or, for example, new Foo() {...} // so, we need deeper analysis to know if it needs a semicolon src = analysis.analyzeCompletion(src).source(); } else if (!src.endsWith(";")) { src = src + ";"; } srcSet.add(src); break; default: srcSet.add(src); break; } }); StringBuilder sb = new StringBuilder(); for (String s : srcSet) { sb.append(s); sb.append('\n'); } String src = sb.toString(); Consumer<String> saveHandler = new SaveHandler(src, srcSet); Consumer<String> errorHandler = s -> hard("Edit Error: %s", s); if (editor == BUILT_IN_EDITOR) { return builtInEdit(src, saveHandler, errorHandler); } else { // Changes have occurred in temp edit directory, // transfer the new sources to JShell (unless the editor is // running directly in JShell's window -- don't make a mess) String[] buffer = new String[1]; Consumer<String> extSaveHandler = s -> { if (input.terminalEditorRunning()) { buffer[0] = s; } else { saveHandler.accept(s); } }; ExternalEditor.edit(editor.cmd, src, errorHandler, extSaveHandler, () -> input.suspend(), () -> input.resume(), editor.wait, () -> hardrb("jshell.msg.press.return.to.leave.edit.mode")); if (buffer[0] != null) { saveHandler.accept(buffer[0]); } } return true; } //where // start the built-in editor private boolean builtInEdit(String initialText, Consumer<String> saveHandler, Consumer<String> errorHandler) { try { ServiceLoader<BuildInEditorProvider> sl = ServiceLoader.load(BuildInEditorProvider.class); // Find the highest ranking provider BuildInEditorProvider provider = null; for (BuildInEditorProvider p : sl) { if (provider == null || p.rank() > provider.rank()) { provider = p; } } if (provider != null) { provider.edit(getResourceString("jshell.label.editpad"), initialText, saveHandler, errorHandler); return true; } else { errormsg("jshell.err.no.builtin.editor"); } } catch (RuntimeException ex) { errormsg("jshell.err.cant.launch.editor", ex); } fluffmsg("jshell.msg.try.set.editor"); return false; } //where // receives editor requests to save private class SaveHandler implements Consumer<String> { String src; Set<String> currSrcs; SaveHandler(String src, Set<String> ss) { this.src = src; this.currSrcs = ss; } @Override public void accept(String s) { if (!s.equals(src)) { // quick check first src = s; try { Set<String> nextSrcs = new LinkedHashSet<>(); boolean failed = false; while (true) { CompletionInfo an = analysis.analyzeCompletion(s); if (!an.completeness().isComplete()) { break; } String tsrc = trimNewlines(an.source()); if (!failed && !currSrcs.contains(tsrc)) { failed = processSource(tsrc); } nextSrcs.add(tsrc); if (an.remaining().isEmpty()) { break; } s = an.remaining(); } currSrcs = nextSrcs; } catch (IllegalStateException ex) { errormsg("jshell.msg.resetting"); resetState(); currSrcs = new LinkedHashSet<>(); // re-process everything } } } private String trimNewlines(String s) { int b = 0; while (b < s.length() && s.charAt(b) == '\n') { ++b; } int e = s.length() -1; while (e >= 0 && s.charAt(e) == '\n') { --e; } return s.substring(b, e + 1); } } private boolean cmdList(String arg) { if (arg.length() >= 2 && "-history".startsWith(arg)) { return cmdHistory(""); } Stream<Snippet> stream = argsOptionsToSnippets(state::snippets, this::mainActive, arg, "/list"); if (stream == null) { return false; } // prevent double newline on empty list boolean[] hasOutput = new boolean[1]; stream.forEachOrdered(sn -> { if (!hasOutput[0]) { cmdout.println(); hasOutput[0] = true; } cmdout.printf("%4s : %s\n", sn.id(), sn.source().replace("\n", "\n ")); }); return true; } private boolean cmdOpen(String filename) { return runFile(filename, "/open"); } private boolean runFile(String filename, String context) { if (!filename.isEmpty()) { try { Scanner scanner; if (!interactiveModeBegun && filename.equals("-")) { // - on command line: no interactive later, read from input regenerateOnDeath = false; scanner = new Scanner(cmdin); } else { Path path = null; URL url = null; String resource; try { path = toPathResolvingUserHome(filename); } catch (InvalidPathException ipe) { try { url = new URL(filename); if (url.getProtocol().equalsIgnoreCase("file")) { path = Paths.get(url.toURI()); } } catch (MalformedURLException | URISyntaxException e) { throw new FileNotFoundException(filename); } } if (path != null && Files.exists(path)) { scanner = new Scanner(new FileReader(path.toString())); } else if ((resource = getResource(filename)) != null) { scanner = new Scanner(new StringReader(resource)); } else { if (url == null) { try { url = new URL(filename); } catch (MalformedURLException mue) { throw new FileNotFoundException(filename); } } scanner = new Scanner(url.openStream()); } } try (var scannerIOContext = new ScannerIOContext(scanner)) { run(scannerIOContext); } return true; } catch (FileNotFoundException e) { errormsg("jshell.err.file.not.found", context, filename, e.getMessage()); } catch (Exception e) { errormsg("jshell.err.file.exception", context, filename, e); } } else { errormsg("jshell.err.file.filename", context); } return false; } static String getResource(String name) { if (BUILTIN_FILE_PATTERN.matcher(name).matches()) { try { return readResource(name); } catch (Throwable t) { // Fall-through to null } } return null; } // Read a built-in file from resources or compute it static String readResource(String name) throws Exception { // Class to compute imports by following requires for a module class ComputeImports { final String base; ModuleFinder finder = ModuleFinder.ofSystem(); ComputeImports(String base) { this.base = base; } Set<ModuleDescriptor> modules() { Set<ModuleDescriptor> closure = new HashSet<>(); moduleClosure(finder.find(base), closure); return closure; } void moduleClosure(Optional<ModuleReference> omr, Set<ModuleDescriptor> closure) { if (omr.isPresent()) { ModuleDescriptor mdesc = omr.get().descriptor(); if (closure.add(mdesc)) { for (ModuleDescriptor.Requires req : mdesc.requires()) { if (!req.modifiers().contains(ModuleDescriptor.Requires.Modifier.STATIC)) { moduleClosure(finder.find(req.name()), closure); } } } } } Set<String> packages() { return modules().stream().flatMap(md -> md.exports().stream()) .filter(e -> !e.isQualified()).map(Object::toString).collect(Collectors.toSet()); } String imports() { Set<String> si = packages(); String[] ai = si.toArray(new String[si.size()]); Arrays.sort(ai); return Arrays.stream(ai) .map(p -> String.format("import %s.*;\n", p)) .collect(Collectors.joining()); } } if (name.equals("JAVASE")) { // The built-in JAVASE is computed as the imports of all the packages in Java SE return new ComputeImports("java.se").imports(); } // Attempt to find the file as a resource String spec = String.format(BUILTIN_FILE_PATH_FORMAT, name); try (InputStream in = JShellTool.class.getResourceAsStream(spec); BufferedReader reader = new BufferedReader(new InputStreamReader(in))) { return reader.lines().collect(Collectors.joining("\n", "", "\n")); } } private boolean cmdReset(String rawargs) { Options oldOptions = rawargs.trim().isEmpty()? null : options; if (!parseCommandLineLikeFlags(rawargs, new OptionParserBase())) { return false; } live = false; fluffmsg("jshell.msg.resetting.state"); return doReload(null, false, oldOptions); } private boolean cmdReload(String rawargs) { Options oldOptions = rawargs.trim().isEmpty()? null : options; OptionParserReload ap = new OptionParserReload(); if (!parseCommandLineLikeFlags(rawargs, ap)) { return false; } ReplayableHistory history; if (ap.restore()) { if (replayableHistoryPrevious == null) { errormsg("jshell.err.reload.no.previous"); return false; } history = replayableHistoryPrevious; fluffmsg("jshell.err.reload.restarting.previous.state"); } else { history = replayableHistory; fluffmsg("jshell.err.reload.restarting.state"); } boolean success = doReload(history, !ap.quiet(), oldOptions); if (success && ap.restore()) { // if we are restoring from previous, then if nothing was added // before time of exit, there is nothing to save replayableHistory.markSaved(); } return success; } 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.shownOptions()) { sb.append( a.startsWith("-") ? sb.length() > 0 ? "\n " : " " : " "); sb.append(a); } if (sb.length() > 0) { hard(sb.toString()); } return false; } Options oldOptions = options; if (!parseCommandLineLikeFlags(rawargs, new OptionParserBase())) { return false; } fluffmsg("jshell.msg.set.restore"); return doReload(replayableHistory, false, oldOptions); } private boolean doReload(ReplayableHistory history, boolean echo, Options oldOptions) { if (oldOptions != null) { try { resetState(); } catch (IllegalStateException ex) { currentNameSpace = mainNamespace; // back out of start-up (messages) errormsg("jshell.err.restart.failed", ex.getMessage()); // attempt recovery to previous option settings options = oldOptions; resetState(); } } else { resetState(); } if (history != null) { run(new ReloadIOContext(history.iterable(), 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 = options.override(opts); return true; } private boolean cmdSave(String rawargs) { // The filename to save to is the last argument, extract it String[] args = rawargs.split("\\s"); String filename = args[args.length - 1]; if (filename.isEmpty()) { errormsg("jshell.err.file.filename", "/save"); return false; } // All the non-filename arguments are the specifier of what to save String srcSpec = Arrays.stream(args, 0, args.length - 1) .collect(Collectors.joining("\n")); // From the what to save specifier, compute the snippets (as a stream) ArgTokenizer at = new ArgTokenizer("/save", srcSpec); at.allowedOptions("-all", "-start", "-history"); Stream<Snippet> snippetStream = argsOptionsToSnippets(state::snippets, this::mainActive, at); if (snippetStream == null) { // error occurred, already reported return false; } try (BufferedWriter writer = Files.newBufferedWriter(toPathResolvingUserHome(filename), Charset.defaultCharset(), CREATE, TRUNCATE_EXISTING, WRITE)) { if (at.hasOption("-history")) { // they want history (commands and snippets), ignore the snippet stream for (String s : input.history(true)) { writer.write(s); writer.write("\n"); } } else { // write the snippet stream to the file writer.write(snippetStream .map(Snippet::source) .collect(Collectors.joining("\n"))); } } catch (FileNotFoundException e) { errormsg("jshell.err.file.not.found", "/save", filename, e.getMessage()); return false; } catch (Exception e) { errormsg("jshell.err.file.exception", "/save", filename, e); return false; } return true; } private boolean cmdVars(String arg) { Stream<VarSnippet> stream = argsOptionsToSnippets(this::allVarSnippets, this::isActive, arg, "/vars"); if (stream == null) { return false; } stream.forEachOrdered(vk -> { String val = state.status(vk) == Status.VALID ? feedback.truncateVarValue(state.varValue(vk)) : getResourceString("jshell.msg.vars.not.active"); hard(" %s %s = %s", vk.typeName(), vk.name(), val); }); return true; } private boolean cmdMethods(String arg) { Stream<MethodSnippet> stream = argsOptionsToSnippets(this::allMethodSnippets, this::isActive, arg, "/methods"); if (stream == null) { return false; } stream.forEachOrdered(meth -> { String sig = meth.signature(); int i = sig.lastIndexOf(")") + 1; if (i <= 0) { hard(" %s", meth.name()); } else { hard(" %s %s%s", sig.substring(i), meth.name(), sig.substring(0, i)); } printSnippetStatus(meth, true); }); return true; } private boolean cmdTypes(String arg) { Stream<TypeDeclSnippet> stream = argsOptionsToSnippets(this::allTypeSnippets, this::isActive, arg, "/types"); if (stream == null) { return false; } stream.forEachOrdered(ck -> { String kind; switch (ck.subKind()) { case INTERFACE_SUBKIND: kind = "interface"; break; case CLASS_SUBKIND: kind = "class"; break; case ENUM_SUBKIND: kind = "enum"; break; case ANNOTATION_TYPE_SUBKIND: kind = "@interface"; break; default: assert false : "Wrong kind" + ck.subKind(); kind = "class"; break; } hard(" %s %s", kind, ck.name()); printSnippetStatus(ck, true); }); return true; } private boolean cmdImports() { state.imports().forEach(ik -> { hard(" import %s%s", ik.isStatic() ? "static " : "", ik.fullname()); }); return true; } private boolean cmdUseHistoryEntry(int index) { List<Snippet> keys = state.snippets().collect(toList()); if (index < 0) index += keys.size(); else index--; if (index >= 0 && index < keys.size()) { rerunSnippet(keys.get(index)); } else { errormsg("jshell.err.out.of.range"); return false; } return true; } boolean checkOptionsAndRemainingInput(ArgTokenizer at) { String junk = at.remainder(); if (!junk.isEmpty()) { errormsg("jshell.err.unexpected.at.end", junk, at.whole()); return false; } else { String bad = at.badOptions(); if (!bad.isEmpty()) { errormsg("jshell.err.unknown.option", bad, at.whole()); return false; } } return true; } /** * Handle snippet reevaluation commands: {@code /<id>}. These commands are a * sequence of ids and id ranges (names are permitted, though not in the * first position. Support for names is purposely not documented). * * @param rawargs the whole command including arguments */ private void rerunHistoryEntriesById(String rawargs) { ArgTokenizer at = new ArgTokenizer("/<id>", rawargs.trim().substring(1)); at.allowedOptions(); Stream<Snippet> stream = argsOptionsToSnippets(state::snippets, sn -> true, at); if (stream != null) { // successfully parsed, rerun snippets stream.forEach(sn -> rerunSnippet(sn)); } } private void rerunSnippet(Snippet snippet) { String source = snippet.source(); cmdout.printf("%s\n", source); input.replaceLastHistoryEntry(source); processSourceCatchingReset(source); } /** * Filter diagnostics for only errors (no warnings, ...) * @param diagnostics input list * @return filtered list */ List<Diag> errorsOnly(List<Diag> diagnostics) { return diagnostics.stream() .filter(Diag::isError) .collect(toList()); } /** * Print out a snippet exception. * * @param exception the throwable to print * @return true on fatal exception */ private boolean displayException(Throwable exception) { Throwable rootCause = exception; while (rootCause instanceof EvalException) { rootCause = rootCause.getCause(); } if (rootCause != exception && rootCause instanceof UnresolvedReferenceException) { // An unresolved reference caused a chained exception, just show the unresolved return displayException(rootCause, null); } else { return displayException(exception, null); } } //where private boolean displayException(Throwable exception, StackTraceElement[] caused) { if (exception instanceof EvalException) { // User exception return displayEvalException((EvalException) exception, caused); } else if (exception instanceof UnresolvedReferenceException) { // Reference to an undefined snippet return displayUnresolvedException((UnresolvedReferenceException) exception); } else { // Should never occur error("Unexpected execution exception: %s", exception); return true; } } //where private boolean displayUnresolvedException(UnresolvedReferenceException ex) { // Display the resolution issue printSnippetStatus(ex.getSnippet(), false); return false; } //where private boolean displayEvalException(EvalException ex, StackTraceElement[] caused) { // The message for the user exception is configured based on the // existance of an exception message and if this is a recursive // invocation for a chained exception. String msg = ex.getMessage(); String key = "jshell.err.exception" + (caused == null? ".thrown" : ".cause") + (msg == null? "" : ".message"); errormsg(key, ex.getExceptionClassName(), msg); // The caused trace is sent to truncate duplicate elements in the cause trace printStackTrace(ex.getStackTrace(), caused); JShellException cause = ex.getCause(); if (cause != null) { // Display the cause (recursively) displayException(cause, ex.getStackTrace()); } return true; } /** * Display a list of diagnostics. * * @param source the source line with the error/warning * @param diagnostics the diagnostics to display */ private void displayDiagnostics(String source, List<Diag> diagnostics) { for (Diag d : diagnostics) { errormsg(d.isError() ? "jshell.msg.error" : "jshell.msg.warning"); List<String> disp = new ArrayList<>(); displayableDiagnostic(source, d, disp); disp.stream() .forEach(l -> error("%s", l)); } } /** * Convert a diagnostic into a list of pretty displayable strings with * source context. * * @param source the source line for the error/warning * @param diag the diagnostic to convert * @param toDisplay a list that the displayable strings are added to */ private void displayableDiagnostic(String source, Diag diag, List<String> toDisplay) { for (String line : diag.getMessage(null).split("\\r?\\n")) { // TODO: Internationalize if (!line.trim().startsWith("location:")) { toDisplay.add(line); } } int pstart = (int) diag.getStartPosition(); int pend = (int) diag.getEndPosition(); Matcher m = LINEBREAK.matcher(source); int pstartl = 0; int pendl = -2; while (m.find(pstartl)) { pendl = m.start(); if (pendl >= pstart) { break; } else { pstartl = m.end(); } } if (pendl < pstart) { pendl = source.length(); } toDisplay.add(source.substring(pstartl, pendl)); StringBuilder sb = new StringBuilder(); int start = pstart - pstartl; for (int i = 0; i < start; ++i) { sb.append(' '); } sb.append('^'); boolean multiline = pend > pendl; int end = (multiline ? pendl : pend) - pstartl - 1; if (end > start) { for (int i = start + 1; i < end; ++i) { sb.append('-'); } if (multiline) { sb.append("-..."); } else { sb.append('^'); } } toDisplay.add(sb.toString()); debug("printDiagnostics start-pos = %d ==> %d -- wrap = %s", diag.getStartPosition(), start, this); debug("Code: %s", diag.getCode()); debug("Pos: %d (%d - %d)", diag.getPosition(), diag.getStartPosition(), diag.getEndPosition()); } /** * Process a source snippet. * * @param source the input source * @return true if the snippet succeeded */ boolean processSource(String source) { debug("Compiling: %s", source); boolean failed = false; boolean isActive = false; List<SnippetEvent> events = state.eval(source); for (SnippetEvent e : events) { // Report the event, recording failure failed |= handleEvent(e); // If any main snippet is active, this should be replayable // also ignore var value queries isActive |= e.causeSnippet() == null && e.status().isActive() && e.snippet().subKind() != VAR_VALUE_SUBKIND; } // If this is an active snippet and it didn't cause the backend to die, // add it to the replayable history if (isActive && live) { addToReplayHistory(source); } return !failed; } // Handle incoming snippet events -- return true on failure private boolean handleEvent(SnippetEvent ste) { Snippet sn = ste.snippet(); if (sn == null) { debug("Event with null key: %s", ste); return false; } List<Diag> diagnostics = state.diagnostics(sn).collect(toList()); String source = sn.source(); if (ste.causeSnippet() == null) { // main event displayDiagnostics(source, diagnostics); if (ste.status() != Status.REJECTED) { if (ste.exception() != null) { if (displayException(ste.exception())) { return true; } } else { new DisplayEvent(ste, FormatWhen.PRIMARY, ste.value(), diagnostics) .displayDeclarationAndValue(); } } else { if (diagnostics.isEmpty()) { errormsg("jshell.err.failed"); } return true; } } else { // Update if (sn instanceof DeclarationSnippet) { List<Diag> other = errorsOnly(diagnostics); // display update information new DisplayEvent(ste, FormatWhen.UPDATE, ste.value(), other) .displayDeclarationAndValue(); } } return false; } // Print a stack trace, elide frames displayed for the caused exception void printStackTrace(StackTraceElement[] stes, StackTraceElement[] caused) { int overlap = 0; if (caused != null) { int maxOverlap = Math.min(stes.length, caused.length); while (overlap < maxOverlap && stes[stes.length - (overlap + 1)].equals(caused[caused.length - (overlap + 1)])) { ++overlap; } } for (int i = 0; i < stes.length - overlap; ++i) { StackTraceElement ste = stes[i]; StringBuilder sb = new StringBuilder(); String cn = ste.getClassName(); if (!cn.isEmpty()) { int dot = cn.lastIndexOf('.'); if (dot > 0) { sb.append(cn.substring(dot + 1)); } else { sb.append(cn); } sb.append("."); } if (!ste.getMethodName().isEmpty()) { sb.append(ste.getMethodName()); sb.append(" "); } String fileName = ste.getFileName(); int lineNumber = ste.getLineNumber(); String loc = ste.isNativeMethod() ? getResourceString("jshell.msg.native.method") : fileName == null ? getResourceString("jshell.msg.unknown.source") : lineNumber >= 0 ? fileName + ":" + lineNumber : fileName; error(" at %s(%s)", sb, loc); } if (overlap != 0) { error(" ..."); } } private FormatAction toAction(Status status, Status previousStatus, boolean isSignatureChange) { FormatAction act; switch (status) { case VALID: case RECOVERABLE_DEFINED: case RECOVERABLE_NOT_DEFINED: if (previousStatus.isActive()) { act = isSignatureChange ? FormatAction.REPLACED : FormatAction.MODIFIED; } else { act = FormatAction.ADDED; } break; case OVERWRITTEN: act = FormatAction.OVERWROTE; break; case DROPPED: act = FormatAction.DROPPED; break; case REJECTED: case NONEXISTENT: default: // Should not occur error("Unexpected status: " + previousStatus.toString() + "=>" + status.toString()); act = FormatAction.DROPPED; } return act; } void printSnippetStatus(DeclarationSnippet sn, boolean resolve) { List<Diag> otherErrors = errorsOnly(state.diagnostics(sn).collect(toList())); new DisplayEvent(sn, state.status(sn), resolve, otherErrors) .displayDeclarationAndValue(); } class DisplayEvent { private final Snippet sn; private final FormatAction action; private final FormatWhen update; private final String value; private final List<String> errorLines; private final FormatResolve resolution; private final String unresolved; private final FormatUnresolved unrcnt; private final FormatErrors errcnt; private final boolean resolve; DisplayEvent(SnippetEvent ste, FormatWhen update, String value, List<Diag> errors) { this(ste.snippet(), ste.status(), false, toAction(ste.status(), ste.previousStatus(), ste.isSignatureChange()), update, value, errors); } DisplayEvent(Snippet sn, Status status, boolean resolve, List<Diag> errors) { this(sn, status, resolve, FormatAction.USED, FormatWhen.UPDATE, null, errors); } private DisplayEvent(Snippet sn, Status status, boolean resolve, FormatAction action, FormatWhen update, String value, List<Diag> errors) { this.sn = sn; this.resolve =resolve; this.action = action; this.update = update; this.value = value; this.errorLines = new ArrayList<>(); for (Diag d : errors) { displayableDiagnostic(sn.source(), d, errorLines); } if (resolve) { // resolve needs error lines indented for (int i = 0; i < errorLines.size(); ++i) { errorLines.set(i, " " + errorLines.get(i)); } } long unresolvedCount; if (sn instanceof DeclarationSnippet && (status == Status.RECOVERABLE_DEFINED || status == Status.RECOVERABLE_NOT_DEFINED)) { resolution = (status == Status.RECOVERABLE_NOT_DEFINED) ? FormatResolve.NOTDEFINED : FormatResolve.DEFINED; unresolved = unresolved((DeclarationSnippet) sn); unresolvedCount = state.unresolvedDependencies((DeclarationSnippet) sn).count(); } else { resolution = FormatResolve.OK; unresolved = ""; unresolvedCount = 0; } unrcnt = unresolvedCount == 0 ? FormatUnresolved.UNRESOLVED0 : unresolvedCount == 1 ? FormatUnresolved.UNRESOLVED1 : FormatUnresolved.UNRESOLVED2; errcnt = errors.isEmpty() ? FormatErrors.ERROR0 : errors.size() == 1 ? FormatErrors.ERROR1 : FormatErrors.ERROR2; } private String unresolved(DeclarationSnippet key) { List<String> unr = state.unresolvedDependencies(key).collect(toList()); StringBuilder sb = new StringBuilder(); int fromLast = unr.size(); if (fromLast > 0) { sb.append(" "); } for (String u : unr) { --fromLast; sb.append(u); switch (fromLast) { // No suffix case 0: break; case 1: sb.append(", and "); break; default: sb.append(", "); break; } } return sb.toString(); } private void custom(FormatCase fcase, String name) { custom(fcase, name, null); } private void custom(FormatCase fcase, String name, String type) { if (resolve) { String resolutionErrors = feedback.format("resolve", fcase, action, update, resolution, unrcnt, errcnt, name, type, value, unresolved, errorLines); if (!resolutionErrors.trim().isEmpty()) { error(" %s", resolutionErrors); } } else if (interactive()) { String display = feedback.format(fcase, action, update, resolution, unrcnt, errcnt, name, type, value, unresolved, errorLines); cmdout.print(display); } } @SuppressWarnings("fallthrough") private void displayDeclarationAndValue() { switch (sn.subKind()) { case CLASS_SUBKIND: custom(FormatCase.CLASS, ((TypeDeclSnippet) sn).name()); break; case INTERFACE_SUBKIND: custom(FormatCase.INTERFACE, ((TypeDeclSnippet) sn).name()); break; case ENUM_SUBKIND: custom(FormatCase.ENUM, ((TypeDeclSnippet) sn).name()); break; case ANNOTATION_TYPE_SUBKIND: custom(FormatCase.ANNOTATION, ((TypeDeclSnippet) sn).name()); break; case METHOD_SUBKIND: custom(FormatCase.METHOD, ((MethodSnippet) sn).name(), ((MethodSnippet) sn).parameterTypes()); break; case VAR_DECLARATION_SUBKIND: { VarSnippet vk = (VarSnippet) sn; custom(FormatCase.VARDECL, vk.name(), vk.typeName()); break; } case VAR_DECLARATION_WITH_INITIALIZER_SUBKIND: { VarSnippet vk = (VarSnippet) sn; custom(FormatCase.VARINIT, vk.name(), vk.typeName()); break; } case TEMP_VAR_EXPRESSION_SUBKIND: { VarSnippet vk = (VarSnippet) sn; custom(FormatCase.EXPRESSION, vk.name(), vk.typeName()); break; } case OTHER_EXPRESSION_SUBKIND: error("Unexpected expression form -- value is: %s", (value)); break; case VAR_VALUE_SUBKIND: { ExpressionSnippet ek = (ExpressionSnippet) sn; custom(FormatCase.VARVALUE, ek.name(), ek.typeName()); break; } case ASSIGNMENT_SUBKIND: { ExpressionSnippet ek = (ExpressionSnippet) sn; custom(FormatCase.ASSIGNMENT, ek.name(), ek.typeName()); break; } case SINGLE_TYPE_IMPORT_SUBKIND: case TYPE_IMPORT_ON_DEMAND_SUBKIND: case SINGLE_STATIC_IMPORT_SUBKIND: case STATIC_IMPORT_ON_DEMAND_SUBKIND: custom(FormatCase.IMPORT, ((ImportSnippet) sn).name()); break; case STATEMENT_SUBKIND: custom(FormatCase.STATEMENT, null); break; } } } /** The current version number as a string. */ String version() { return version("release"); // mm.nn.oo[-milestone] } /** The current full version number as a string. */ String fullVersion() { return version("full"); // mm.mm.oo[-milestone]-build } private String version(String key) { if (versionRB == null) { try { versionRB = ResourceBundle.getBundle(VERSION_RB_NAME, locale); } catch (MissingResourceException e) { return "(version info not available)"; } } try { return versionRB.getString(key); } catch (MissingResourceException e) { return "(version info not available)"; } } class NameSpace { final String spaceName; final String prefix; private int nextNum; NameSpace(String spaceName, String prefix) { this.spaceName = spaceName; this.prefix = prefix; this.nextNum = 1; } String tid(Snippet sn) { String tid = prefix + nextNum++; mapSnippet.put(sn, new SnippetInfo(sn, this, tid)); return tid; } String tidNext() { return prefix + nextNum; } } static class SnippetInfo { final Snippet snippet; final NameSpace space; final String tid; SnippetInfo(Snippet snippet, NameSpace space, String tid) { this.snippet = snippet; this.space = space; this.tid = tid; } } static class ArgSuggestion implements Suggestion { private final String continuation; /** * Create a {@code Suggestion} instance. * * @param continuation a candidate continuation of the user's input */ public ArgSuggestion(String continuation) { this.continuation = continuation; } /** * The candidate continuation of the given user's input. * * @return the continuation string */ @Override public String continuation() { return continuation; } /** * Indicates whether input continuation matches the target type and is thus * more likely to be the desired continuation. A matching continuation is * preferred. * * @return {@code false}, non-types analysis */ @Override public boolean matchesType() { return false; } } } abstract class NonInteractiveIOContext extends IOContext { @Override public boolean interactiveOutput() { return false; } @Override public Iterable<String> history(boolean currentSession) { return Collections.emptyList(); } @Override public boolean terminalEditorRunning() { return false; } @Override public void suspend() { } @Override public void resume() { } @Override public void beforeUserCode() { } @Override public void afterUserCode() { } @Override public void replaceLastHistoryEntry(String source) { } } class ScannerIOContext extends NonInteractiveIOContext { private final Scanner scannerIn; ScannerIOContext(Scanner scannerIn) { this.scannerIn = scannerIn; } ScannerIOContext(Reader rdr) throws FileNotFoundException { this(new Scanner(rdr)); } @Override public String readLine(String prompt, String prefix) { if (scannerIn.hasNextLine()) { return scannerIn.nextLine(); } else { return null; } } @Override public void close() { scannerIn.close(); } @Override public int readUserInput() { return -1; } } class ReloadIOContext extends NonInteractiveIOContext { private final Iterator<String> it; private final PrintStream echoStream; ReloadIOContext(Iterable<String> history, PrintStream echoStream) { this.it = history.iterator(); this.echoStream = echoStream; } @Override public String readLine(String prompt, String prefix) { String s = it.hasNext() ? it.next() : null; if (echoStream != null && s != null) { String p = "-: "; String p2 = "\n "; echoStream.printf("%s%s\n", p, s.replace("\n", p2)); } return s; } @Override public void close() { } @Override public int readUserInput() { return -1; } }
⏎ jdk/internal/jshell/tool/JShellTool.java
Or download all of them as a single archive file:
File name: jdk.jshell-11.0.1-src.zip File size: 283093 bytes Release date: 2018-11-04 Download
⇒ JDK 11 jdk.jsobject.jmod - JS Object Module
2020-06-30, 29292👍, 0💬
Popular Posts:
commons-io-2.6-sources.j aris the source JAR file for Apache Commons IO 2.6, which is a library of u...
JSP(tm) Standard Tag Library 1.1 implementation - Jakarta Taglibs hosts the Standard Taglib 1.1, an ...
What Is mail.jar of JavaMail 1.4.2? I got the JAR file from javamail-1.4.2.zip. mail.jar in javamail...
How to display XML element type information with the jaxp\TypeInfoWriter.java provided in the Apache...
Java Servlet 3.0 Specification API. JAR File Size and Download Location: File name: servlet-api.jar,...