# HG changeset patch # User rfield # Date 1485910915 28800 # Node ID 4287765963d55a9384e19025797420d7296ac89c # Parent f4aff695ffe05cfdb69d8af25a4ddc6a029754ea 8173652: jshell tool: store history on fatal exit Reviewed-by: jlahoda diff -r f4aff695ffe0 -r 4287765963d5 langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java --- a/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java Wed Jul 05 22:46:23 2017 +0200 +++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java Tue Jan 31 17:01:55 2017 -0800 @@ -197,7 +197,6 @@ private boolean debug = false; public boolean testPrompt = false; - private String defaultStartup = null; private Startup startup = null; private String executionControlSpec = null; private EditorSetting editor = BUILT_IN_EDITOR; @@ -205,9 +204,9 @@ private static final String[] EDITOR_ENV_VARS = new String[] { "JSHELLEDITOR", "VISUAL", "EDITOR"}; - // Commands and snippets which should be replayed - private List replayableHistory; - private List replayableHistoryPrevious; + // 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"; @@ -507,6 +506,76 @@ } /** + * Encapsulate a history of snippets and commands which can be replayed. + */ + private static class ReplayableHistory { + + // the history + private List hist; + + // the length of the history as of last save + private int lastSaved; + + private ReplayableHistory(List 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 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 @@ -756,10 +825,7 @@ // initialize JShell instance resetState(); // Read replay history from last jshell session into previous history - String prevReplay = prefs.get(REPLAY_RESTORE_KEY); - if (prevReplay != null) { - replayableHistoryPrevious = Arrays.asList(prevReplay.split(RECORD_SEPARATOR)); - } + replayableHistoryPrevious = ReplayableHistory.fromPrevious(prefs); // load snippet/command files given on command-line for (String loadFile : commandLineArgs.nonOptions()) { runFile(loadFile, "jshell"); @@ -775,6 +841,13 @@ if (feedback.shouldDisplayCommandFluff()) { hardmsg("jshell.msg.welcome", version()); } + // Be sure history is always saved so that user code isn't lost + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + replayableHistory.storeHistory(prefs); + } + }); // execute from user input try (IOContext in = new ConsoleIOContext(this, cmdin, console)) { start(in); @@ -868,7 +941,7 @@ // Reset the replayable history, saving the old for restore replayableHistoryPrevious = replayableHistory; - replayableHistory = new ArrayList<>(); + replayableHistory = ReplayableHistory.emptyHistory(); JShell.Builder builder = JShell.builder() .in(userin) @@ -1933,20 +2006,7 @@ private boolean cmdExit() { regenerateOnDeath = false; live = false; - if (!replayableHistory.isEmpty()) { - // Prevent history overflow by calculating what will fit, starting - // with most recent - int sepLen = RECORD_SEPARATOR.length(); - int length = 0; - int first = replayableHistory.size(); - while(length < Preferences.MAX_VALUE_LENGTH && --first >= 0) { - length += replayableHistory.get(first).length() + sepLen; - } - String hist = String.join(RECORD_SEPARATOR, - replayableHistory.subList(first + 1, replayableHistory.size())); - prefs.put(REPLAY_RESTORE_KEY, hist); - } - prefs.flush(); + replayableHistory.storeHistory(prefs); fluffmsg("jshell.msg.goodbye"); return true; } @@ -2420,7 +2480,7 @@ if (!parseCommandLineLikeFlags(rawargs, ap)) { return false; } - Iterable history; + ReplayableHistory history; if (ap.restore()) { if (replayableHistoryPrevious == null) { errormsg("jshell.err.reload.no.previous"); @@ -2432,7 +2492,13 @@ history = replayableHistory; fluffmsg("jshell.err.reload.restarting.state"); } - return doReload(history, !ap.quiet()); + boolean success = doReload(history, !ap.quiet()); + 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) { @@ -2460,9 +2526,9 @@ return doReload(replayableHistory, false); } - private boolean doReload(Iterable history, boolean echo) { + private boolean doReload(ReplayableHistory history, boolean echo) { resetState(); - run(new ReloadIOContext(history, + run(new ReloadIOContext(history.iterable(), echo ? cmdout : null)); return true; }