changeset 3844:e5a42ddaf633 jdk-9+150

8170162: jshell tool: no mechanism to programmatically launch 8170044: jshell tool: jshell missing from javax.tools.ToolProvider Reviewed-by: jjg
author rfield
date Tue, 20 Dec 2016 13:42:13 -0800
parents 53c1667131d5
children fb05da552def 51b3b07c0b4f
files src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellToolBuilder.java src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellToolProvider.java src/jdk.jshell/share/classes/jdk/internal/jshell/tool/PersistentStorage.java src/jdk.jshell/share/classes/jdk/jshell/overview.html src/jdk.jshell/share/classes/jdk/jshell/tool/JavaShellToolBuilder.java src/jdk.jshell/share/classes/jdk/jshell/tool/package-info.java src/jdk.jshell/share/classes/module-info.java test/jdk/jshell/CommandCompletionTest.java test/jdk/jshell/HistoryTest.java test/jdk/jshell/ReplToolTesting.java test/jdk/jshell/StartOptionTest.java test/jdk/jshell/ToolBasicTest.java test/jdk/jshell/ToolProviderTest.java
diffstat 15 files changed, 1237 insertions(+), 270 deletions(-) [+]
line wrap: on
line diff
--- a/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java	Tue Dec 20 06:06:01 2016 -0800
+++ b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java	Tue Dec 20 13:42:13 2016 -0800
@@ -46,7 +46,6 @@
 import java.util.Objects;
 import java.util.Optional;
 import java.util.function.Function;
-import java.util.prefs.BackingStoreException;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -97,7 +96,7 @@
         List<String> persistenHistory = Stream.of(repl.prefs.keys())
                                               .filter(key -> key.startsWith(HISTORY_LINE_PREFIX))
                                               .sorted()
-                                              .map(key -> repl.prefs.get(key, null))
+                                              .map(key -> repl.prefs.get(key))
                                               .collect(Collectors.toList());
         in.setHistory(history = new EditingHistory(in, persistenHistory) {
             @Override protected boolean isComplete(CharSequence input) {
@@ -215,23 +214,21 @@
     @Override
     public void close() throws IOException {
         //save history:
-        try {
-            for (String key : repl.prefs.keys()) {
-                if (key.startsWith(HISTORY_LINE_PREFIX))
-                    repl.prefs.remove(key);
+        for (String key : repl.prefs.keys()) {
+            if (key.startsWith(HISTORY_LINE_PREFIX)) {
+                repl.prefs.remove(key);
             }
-            Collection<? extends String> savedHistory = history.save();
-            if (!savedHistory.isEmpty()) {
-                int len = (int) Math.ceil(Math.log10(savedHistory.size()+1));
-                String format = HISTORY_LINE_PREFIX + "%0" + len + "d";
-                int index = 0;
-                for (String historyLine : savedHistory) {
-                    repl.prefs.put(String.format(format, index++), historyLine);
-                }
+        }
+        Collection<? extends String> savedHistory = history.save();
+        if (!savedHistory.isEmpty()) {
+            int len = (int) Math.ceil(Math.log10(savedHistory.size()+1));
+            String format = HISTORY_LINE_PREFIX + "%0" + len + "d";
+            int index = 0;
+            for (String historyLine : savedHistory) {
+                repl.prefs.put(String.format(format, index++), historyLine);
             }
-        } catch (BackingStoreException ex) {
-            throw new IllegalStateException(ex);
         }
+        repl.prefs.flush();
         in.shutdown();
         try {
             in.getTerminal().restore();
@@ -417,6 +414,7 @@
         }
     }
 
+    @Override
     public void beforeUserCode() {
         synchronized (this) {
             inputBytes = null;
@@ -424,6 +422,7 @@
         input.setState(State.BUFFER);
     }
 
+    @Override
     public void afterUserCode() {
         input.setState(State.WAIT);
     }
--- a/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java	Tue Dec 20 06:06:01 2016 -0800
+++ b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java	Tue Dec 20 13:42:13 2016 -0800
@@ -136,26 +136,13 @@
     final InputStream userin;
     final PrintStream userout;
     final PrintStream usererr;
-    final Preferences prefs;
+    final PersistentStorage prefs;
     final Map<String, String> envvars;
     final Locale locale;
 
     final Feedback feedback = new Feedback();
 
     /**
-     * Simple constructor for the tool used by main.
-     * @param in command line input
-     * @param out command line output, feedback including errors, user System.out
-     * @param err start-up errors and debugging info, user System.err
-     */
-    public JShellTool(InputStream in, PrintStream out, PrintStream err) {
-        this(in, out, err, out, null, out, err,
-                Preferences.userRoot().node("tool/JShell"),
-                System.getenv(),
-                Locale.getDefault());
-    }
-
-    /**
      * The complete constructor for the tool (used by test harnesses).
      * @param cmdin command line input -- snippets and commands
      * @param cmdout command line output, feedback including errors
@@ -164,14 +151,14 @@
      * @param userin code execution input, or null to use IOContext
      * @param userout code execution output  -- System.out.printf("hi")
      * @param usererr code execution error stream  -- System.err.printf("Oops")
-     * @param prefs preferences to use
+     * @param prefs persistence implementation to use
      * @param envvars environment variable mapping to use
      * @param locale locale to use
      */
-    public JShellTool(InputStream cmdin, PrintStream cmdout, PrintStream cmderr,
+    JShellTool(InputStream cmdin, PrintStream cmdout, PrintStream cmderr,
             PrintStream console,
             InputStream userin, PrintStream userout, PrintStream usererr,
-            Preferences prefs, Map<String, String> envvars, Locale locale) {
+            PersistentStorage prefs, Map<String, String> envvars, Locale locale) {
         this.cmdin = cmdin;
         this.cmdout = cmdout;
         this.cmderr = cmderr;
@@ -478,16 +465,6 @@
         }
     }
 
-    /**
-     * Normal start entry point
-     * @param args
-     * @throws Exception
-     */
-    public static void main(String[] args) throws Exception {
-        new JShellTool(System.in, System.out, System.err)
-                .start(args);
-    }
-
     public void start(String[] args) throws Exception {
         List<String> loadList = processCommandArgs(args);
         if (loadList == null) {
@@ -502,7 +479,7 @@
     private void start(IOContext in, List<String> loadList) {
         // If startup hasn't been set by command line, set from retained/default
         if (startup == null) {
-            startup = prefs.get(STARTUP_KEY, null);
+            startup = prefs.get(STARTUP_KEY);
             if (startup == null) {
                 startup = DEFAULT_STARTUP;
             }
@@ -513,7 +490,7 @@
         resetState(); // Initialize
 
         // Read replay history from last jshell session into previous history
-        String prevReplay = prefs.get(REPLAY_RESTORE_KEY, null);
+        String prevReplay = prefs.get(REPLAY_RESTORE_KEY);
         if (prevReplay != null) {
             replayableHistoryPrevious = Arrays.asList(prevReplay.split(RECORD_SEPARATOR));
         }
@@ -788,7 +765,7 @@
         // These predefined modes are read-only
         feedback.markModesReadOnly();
         // Restore user defined modes retained on previous run with /set mode -retain
-        String encoded = prefs.get(MODE_KEY, null);
+        String encoded = prefs.get(MODE_KEY);
         if (encoded != null && !encoded.isEmpty()) {
             if (!feedback.restoreEncodedModes(initmh, encoded)) {
                 // Catastrophic corruption -- remove the retained modes
@@ -802,7 +779,7 @@
             }
             commandLineFeedbackMode = null;
         } else {
-            String fb = prefs.get(FEEDBACK_KEY, null);
+            String fb = prefs.get(FEEDBACK_KEY);
             if (fb != null) {
                 // Restore the feedback mode to use that was retained
                 // on a previous run with /set feedback -retain
@@ -1485,9 +1462,9 @@
         }
 
         // returns null if not stored in preferences
-        static EditorSetting fromPrefs(Preferences prefs) {
+        static EditorSetting fromPrefs(PersistentStorage prefs) {
             // Read retained editor setting (if any)
-            String editorString = prefs.get(EDITOR_KEY, "");
+            String editorString = prefs.get(EDITOR_KEY);
             if (editorString == null || editorString.isEmpty()) {
                 return null;
             } else if (editorString.equals(BUILT_IN_REP)) {
@@ -1504,11 +1481,11 @@
             }
         }
 
-        static void removePrefs(Preferences prefs) {
+        static void removePrefs(PersistentStorage prefs) {
             prefs.remove(EDITOR_KEY);
         }
 
-        void toPrefs(Preferences prefs) {
+        void toPrefs(PersistentStorage prefs) {
             prefs.put(EDITOR_KEY, (this == BUILT_IN_EDITOR)
                     ? BUILT_IN_REP
                     : (wait ? WAIT_PREFIX : NORMAL_PREFIX) + String.join(RECORD_SEPARATOR, cmd));
@@ -1676,7 +1653,7 @@
     }
 
     void showSetStart() {
-        String retained = prefs.get(STARTUP_KEY, null);
+        String retained = prefs.get(STARTUP_KEY);
         if (retained != null) {
             showSetStart(true, retained);
         }
@@ -1774,6 +1751,7 @@
                     replayableHistory.subList(first + 1, replayableHistory.size()));
             prefs.put(REPLAY_RESTORE_KEY, hist);
         }
+        prefs.flush();
         fluffmsg("jshell.msg.goodbye");
         return true;
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellToolBuilder.java	Tue Dec 20 13:42:13 2016 -0800
@@ -0,0 +1,348 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.internal.jshell.tool;
+
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.prefs.BackingStoreException;
+import java.util.prefs.Preferences;
+import jdk.jshell.tool.JavaShellToolBuilder;
+
+/**
+ * Builder for programmatically building the jshell tool.
+ */
+public class JShellToolBuilder implements JavaShellToolBuilder {
+
+    private static final String PREFERENCES_NODE = "tool/JShell";
+    private InputStream cmdIn = System.in;
+    private InputStream userIn = null;
+    private PrintStream cmdOut = System.out;
+    private PrintStream console = System.out;
+    private PrintStream userOut = System.out;
+    private PrintStream cmdErr = System.err;
+    private PrintStream userErr = System.err;
+    private PersistentStorage prefs = null;
+    private Map<String, String> vars = null;
+    private Locale locale = Locale.getDefault();
+    private boolean capturePrompt = false;
+
+    /**
+     * Set the input channels.
+     * Default, if not set, {@code in(System.in, null)}.
+     *
+     * @param cmdIn source of command input
+     * @param userIn source of input for running user code, or {@code null} to
+     * be extracted from cmdIn
+     * @return the {@code JavaShellToolBuilder} instance
+     */
+    @Override
+    public JavaShellToolBuilder in(InputStream cmdIn, InputStream userIn) {
+        this.cmdIn = cmdIn;
+        this.userIn = userIn;
+        return this;
+    }
+
+    /**
+     * Set the output channels. Same as {@code out(output, output, output)}.
+     * Default, if not set, {@code out(System.out)}.
+     *
+     * @param output destination of command feedback, console interaction, and
+     * user code output
+     * @return the {@code JavaShellToolBuilder} instance
+     */
+    @Override
+    public JavaShellToolBuilder out(PrintStream output) {
+        this.cmdOut = output;
+        this.console = output;
+        this.userOut = output;
+        return this;
+    }
+
+    /**
+     * Set the output channels.
+     * Default, if not set, {@code out(System.out, System.out, System.out)}.
+     *
+     * @param cmdOut destination of command feedback including error messages
+     * for users
+     * @param console destination of console interaction
+     * @param userOut destination of user code output.  For example, user snippet
+     * {@code System.out.println("Hello")} when executed {@code Hello} goes to
+     * userOut.
+     * @return the {@code JavaShellToolBuilder} instance
+     */
+    @Override
+    public JavaShellToolBuilder out(PrintStream cmdOut, PrintStream console, PrintStream userOut) {
+        this.cmdOut = cmdOut;
+        this.console = console;
+        this.userOut = userOut;
+        return this;
+    }
+
+    /**
+     * Set the error channels. Same as {@code err(error, error)}.
+     * Default, if not set, {@code err(System.err)}.
+     *
+     * @param error destination of tool errors, and
+     * user code errors
+     * @return the {@code JavaShellToolBuilder} instance
+     */
+    @Override
+    public JavaShellToolBuilder err(PrintStream error) {
+        this.cmdErr = error;
+        this.userErr = error;
+        return this;
+    }
+
+    /**
+     * Set the error channels.
+     * Default, if not set, {@code err(System.err, System.err, System.err)}.
+     *
+     * @param cmdErr destination of tool start-up and fatal errors
+     * @param userErr destination of user code error output.
+     * For example, user snippet  {@code System.err.println("Oops")}
+     * when executed {@code Oops} goes to userErr.
+     * @return the {@code JavaShellToolBuilder} instance
+     */
+    @Override
+    public JavaShellToolBuilder err(PrintStream cmdErr, PrintStream userErr) {
+        this.cmdErr = cmdErr;
+        this.userErr = userErr;
+        return this;
+    }
+
+    /**
+     * Set the storage mechanism for persistent information which includes
+     * input history and retained settings. Default if not set is the
+     * tool's standard persistence mechanism.
+     *
+     * @param prefs an instance of {@link java.util.prefs.Preferences} that
+     * is used to retrieve and store persistent information
+     * @return the {@code JavaShellToolBuilder} instance
+     */
+    @Override
+    public JavaShellToolBuilder persistence(Preferences prefs) {
+        this.prefs = new PreferencesStorage(prefs);
+        return this;
+    }
+
+    /**
+     * Set the storage mechanism for persistent information which includes
+     * input history and retained settings.   Default if not set is the
+     * tool's standard persistence mechanism.
+     *
+     * @param prefsMap  an instance of {@link java.util.Map} that
+     * is used to retrieve and store persistent information
+     * @return the {@code JavaShellToolBuilder} instance
+     */
+    @Override
+    public JavaShellToolBuilder persistence(Map<String, String> prefsMap) {
+        this.prefs = new MapStorage(prefsMap);
+        return this;
+    }
+
+    /**
+     * Set the source for environment variables.
+     * Default, if not set, {@code env(System.getenv())}.
+     *
+     * @param vars the Map of environment variable names to values
+     * @return the {@code JavaShellToolBuilder} instance
+     */
+    @Override
+    public JavaShellToolBuilder env(Map<String, String> vars) {
+        this.vars = vars;
+        return this;
+    }
+
+    /**
+     * Set the locale.
+     * Default, if not set, {@code locale(Locale.getDefault())}.
+     *
+     * @param locale the locale
+     * @return the {@code JavaShellToolBuilder} instance
+     */
+    @Override
+    public JavaShellToolBuilder locale(Locale locale) {
+        this.locale = locale;
+        return this;
+    }
+
+    /**
+     * Set if the special command capturing prompt override should be used.
+     * Default, if not set, {@code promptCapture(false)}.
+     *
+     * @param capture if {@code true}, basic prompt is the {@code ENQ}
+     * character and continuation prompt is the {@code ACK} character.
+     * If false, prompts are as set with set-up or user {@code /set} commands.
+     * @return the {@code JavaShellToolBuilder} instance
+     */
+    @Override
+    public JavaShellToolBuilder promptCapture(boolean capture) {
+        this.capturePrompt = capture;
+        return this;
+    }
+
+    /**
+     * Create a tool instance for testing. Not in JavaShellToolBuilder.
+     *
+     * @return the tool instance
+     */
+    public JShellTool rawTool() {
+        if (prefs == null) {
+            prefs = new PreferencesStorage(Preferences.userRoot().node(PREFERENCES_NODE));
+        }
+        if (vars == null) {
+            vars = System.getenv();
+        }
+        JShellTool sh = new JShellTool(cmdIn, cmdOut, cmdErr, console, userIn,
+                userOut, userErr, prefs, vars, locale);
+        sh.testPrompt = capturePrompt;
+        return sh;
+    }
+
+    /**
+     * Run an instance of the Java shell tool as configured by the other methods
+     * in this interface.  This call is not destructive, more than one call of
+     * this method may be made from a configured builder.
+     *
+     * @param arguments the command-line arguments (including options), if any
+     * @throws Exception an unexpected fatal exception
+     */
+    @Override
+    public void run(String... arguments) throws Exception {
+        rawTool().start(arguments);
+    }
+
+    /**
+     * Persistence stored in Preferences.
+     */
+    private static class PreferencesStorage implements PersistentStorage {
+
+        final Preferences p;
+
+        PreferencesStorage(Preferences p) {
+            this.p = p;
+        }
+
+        @Override
+        public void clear() {
+            try {
+                p.clear();
+            } catch (BackingStoreException ex) {
+                throw new IllegalStateException(ex);
+            }
+        }
+
+        @Override
+        public String[] keys() {
+            try {
+                return p.keys();
+            } catch (BackingStoreException ex) {
+                throw new IllegalStateException(ex);
+            }
+        }
+
+        @Override
+        public String get(String key) {
+            return p.get(key, null);
+        }
+
+        @Override
+        public void put(String key, String value) {
+            p.put(key, value);
+        }
+
+        @Override
+        public void remove(String key) {
+            p.remove(key);
+        }
+
+        @Override
+        public void flush() {
+            try {
+                p.flush();
+            } catch (BackingStoreException ex) {
+                throw new IllegalStateException(ex);
+            }
+        }
+    }
+
+    /**
+     * Persistence stored in a Map.
+     */
+    private static class MapStorage implements PersistentStorage {
+
+        final Map<String, String> map;
+
+        MapStorage(Map<String, String> map) {
+            this.map = map;
+        }
+
+        @Override
+        public void clear() {
+
+            try {
+                map.clear();
+            } catch (UnsupportedOperationException ex) {
+                throw new IllegalStateException(ex);
+            }
+        }
+
+        @Override
+        public String[] keys() {
+            Set<String> ks = map.keySet();
+            return ks.toArray(new String[ks.size()]);
+        }
+
+        @Override
+        public String get(String key) {
+            Objects.requireNonNull(key);
+            return map.get(key);
+        }
+
+        @Override
+        public void put(String key, String value) {
+            Objects.requireNonNull(key);
+            Objects.requireNonNull(value);
+            map.put(key, value);
+        }
+
+        @Override
+        public void remove(String key) {
+            Objects.requireNonNull(key);
+            map.remove(key);
+        }
+
+        @Override
+        public void flush() {
+            // no-op always up-to-date
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellToolProvider.java	Tue Dec 20 13:42:13 2016 -0800
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.internal.jshell.tool;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Set;
+import javax.lang.model.SourceVersion;
+import javax.tools.Tool;
+import jdk.jshell.tool.JavaShellToolBuilder;
+
+/**
+ * Provider for launching the jshell tool.
+ */
+public class JShellToolProvider implements Tool {
+
+    /**
+     * Returns the name of this Java shell tool provider.
+     *
+     * @return the name of this tool provider
+     */
+    @Override
+    public String name() {
+        return "jshell";
+    }
+
+    /**
+     * Run the jshell tool.  The streams {@code out} and {@code err} are
+     * converted to {@code PrintStream} if they are not already.
+     * Any {@code Exception} is caught, printed and results in a non-zero return.
+     *
+     * @param in command line input (snippets and commands), and execution
+     * "standard" input; use System.in if null
+     * @param out command line output, feedback including errors, and execution
+     * "standard" output; use System.out if null
+     * @param err start-up errors and execution "standard" error; use System.err
+     * if null
+     * @param arguments arguments to pass to the tool
+     * @return 0 for success; nonzero otherwise
+     * @throws NullPointerException if the array of arguments contains
+     * any {@code null} elements.
+     */
+    @Override
+    public int run(InputStream in, OutputStream out, OutputStream err, String... arguments) {
+        InputStream xin =
+                (in == null)
+                        ? System.in
+                        : in;
+        PrintStream xout =
+                (out == null)
+                        ? System.out
+                        : (out instanceof PrintStream)
+                                ? (PrintStream) out
+                                : new PrintStream(out);
+        PrintStream xerr =
+                (err == null)
+                        ? System.err
+                        : (err instanceof PrintStream)
+                                ? (PrintStream) err
+                                : new PrintStream(err);
+        try {
+            JavaShellToolBuilder
+                    .builder()
+                    .in(xin, null)
+                    .out(xout)
+                    .err(xerr)
+                    .run(arguments);
+            return 0;
+        } catch (Throwable ex) {
+            xerr.println(ex.getMessage());
+            return 1;
+        }
+    }
+
+    /**
+     * Returns the source versions of the jshell tool.
+     * @return a set of supported source versions
+     */
+    @Override
+    public Set<SourceVersion> getSourceVersions() {
+        return Collections.unmodifiableSet(
+                EnumSet.range(SourceVersion.RELEASE_9, SourceVersion.latest()));
+    }
+
+    /**
+     * Launch the tool.
+     * @param arguments the command-line arguments (including options), if any
+     * @throws Exception an unexpected fatal exception
+     */
+    public static void main(String[] arguments) throws Exception {
+        JavaShellToolBuilder
+                .builder()
+                .run(arguments);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/PersistentStorage.java	Tue Dec 20 13:42:13 2016 -0800
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.internal.jshell.tool;
+
+/**
+ * The required functionality jshell uses for persistent storage.  Implementable
+ * by both Preferences API and Map.
+ */
+interface PersistentStorage {
+
+    /**
+     * Removes all of the preferences (key-value associations) in
+     * preferences.
+     *
+     * @throws IllegalStateException if this operation cannot be completed
+     * because of the state of the system.
+     */
+    void clear();
+
+    /**
+     * Returns all of the keys that have an associated value in
+     * preferences.
+     *
+     * @return an array of the keys that have an associated value in this
+     * preference node.
+     * @throws IllegalStateException if this operation cannot be completed
+     * because of the state of the system.
+     */
+    String[] keys();
+
+    /**
+     * Returns the value associated with the specified key in preferences.
+     *
+     * @param key key whose associated value is to be returned.
+     * @return the value associated with {@code key}, or {@code null} if no
+     * value is associated with {@code key}.
+     * @throws IllegalStateException if this operation cannot be completed
+     * because of the state of the system.
+     * @throws NullPointerException if {@code key} is {@code null}.
+     */
+    String get(String key);
+
+    /**
+     * Associates the specified value with the specified key in this
+     * preference node.
+     *
+     * @param key key with which the specified value is to be associated.
+     * @param value value to be associated with the specified key.
+     * @throws NullPointerException if key or value is {@code null}.
+     * @throws IllegalArgumentException if key or value are too long.
+     * @throws IllegalStateException if this operation cannot be completed
+     * because of the state of the system.
+     */
+    void put(String key, String value);
+
+    /**
+     * Removes the value associated with the specified key in preferences,
+     * if any.
+     *
+     * @param key key whose mapping is to be removed from the preference
+     * node.
+     * @throws NullPointerException if {@code key} is {@code null}.
+     * @throws IllegalStateException if this operation cannot be completed
+     * because of the state of the system.
+     */
+    void remove(String key);
+
+    /**
+     * Forces any changes in the contents of this preferences to be stored.
+     * Once this method returns successfully, it is safe to assume that all
+     * changes have become as permanent as they are going to be.
+     * <p>
+     * Implementations are free to flush changes into the persistent store
+     * at any time. They do not need to wait for this method to be called.
+     *
+     * @throws IllegalStateException if this operation cannot be completed
+     * because of the state of the system.
+     */
+    void flush();
+
+}
--- a/src/jdk.jshell/share/classes/jdk/jshell/overview.html	Tue Dec 20 06:06:01 2016 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,38 +0,0 @@
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
-<html>
-<head>
-<!--
-
-Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
-DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
-
-This code is free software; you can redistribute it and/or modify it
-under the terms of the GNU General Public License version 2 only, as
-published by the Free Software Foundation.  Oracle designates this
-particular file as subject to the "Classpath" exception as provided
-by Oracle in the LICENSE file that accompanied this code.
-
-This code is distributed in the hope that it will be useful, but WITHOUT
-ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
-version 2 for more details (a copy is included in the LICENSE file that
-accompanied this code).
-
-You should have received a copy of the GNU General Public License version
-2 along with this work; if not, write to the Free Software Foundation,
-Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
-
-Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
-or visit www.oracle.com if you need additional information or have any
-questions.
--->
-
-</head>
-<body bgcolor="white">
-
-This document is the API specification for JShell -- support for
-Java&#x2122; Programming Language 'snippet' evaluating tools, such as
-Read-Eval-Print Loops (REPLs).
-
-</body>
-</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/jshell/tool/JavaShellToolBuilder.java	Tue Dec 20 13:42:13 2016 -0800
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.jshell.tool;
+
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.util.Locale;
+import java.util.Map;
+import java.util.prefs.Preferences;
+import jdk.internal.jshell.tool.JShellToolBuilder;
+
+/**
+ * Interface to configure and run a Java shell tool instance. An instance of the
+ * builder is created with the static {@link #builder} method. This builder can,
+ * optionally, be configured with the configuration methods. All configuration
+ * methods return the builder instance for use in chained initialization. All
+ * configuration methods have sensible defaults which will be used if they are
+ * not called.. After zero or more calls to configuration methods, the tool is
+ * launched with a call to {@link #run(java.lang.String...) }.
+ */
+public interface JavaShellToolBuilder {
+
+    /**
+     * Create a builder for launching the JDK jshell tool.
+     *
+     * @return a builder which can be used to configure and launch the jshell
+     * tool
+     */
+    static JavaShellToolBuilder builder() {
+        return new JShellToolBuilder();
+    }
+
+    /**
+     * Set the input channels.
+     *
+     * @implSpec If this method is not called, the behavior should be
+     * equivalent to calling {@code in(System.in, null)}.
+     *
+     * @param cmdIn source of command input
+     * @param userIn source of input for running user code, or {@code null} to
+     * extract user input from cmdIn
+     * @return the {@code JavaShellToolBuilder} instance
+     */
+    JavaShellToolBuilder in(InputStream cmdIn, InputStream userIn);
+
+    /**
+     * Set the output channels. Same as {@code out(output, output, output)}.
+     *
+     * @implSpec If neither {@code out} method is called, the behavior should be
+     * equivalent to calling {@code out(System.out)}.
+     *
+     * @param output destination of command feedback, console interaction, and
+     * user code output
+     * @return the {@code JavaShellToolBuilder} instance
+     */
+    JavaShellToolBuilder out(PrintStream output);
+
+    /**
+     * Set the output channels.
+     *
+     * @implSpec If neither {@code out} method is called, the behavior should be
+     * equivalent to calling {@code out(System.out, System.out, System.out)}.
+     *
+     * @param cmdOut destination of command feedback including error messages
+     * for users
+     * @param console destination of console interaction
+     * @param userOut destination of user code output.  For example, user snippet
+     * {@code System.out.println("Hello")} when executed {@code Hello} goes to
+     * userOut.
+     * @return the {@code JavaShellToolBuilder} instance
+     */
+    JavaShellToolBuilder out(PrintStream cmdOut, PrintStream console, PrintStream userOut);
+
+    /**
+     * Set the error channels. Same as {@code err(error, error)}.
+     *
+     * @implSpec If neither {@code err} method is called, the behavior should be
+     * equivalent to calling {@code err(System.err)}.
+     *
+     * @param error destination of tool errors, and
+     * user code errors
+     * @return the {@code JavaShellToolBuilder} instance
+     */
+    JavaShellToolBuilder err(PrintStream error);
+
+    /**
+     * Set the error channels.
+     *
+     * @implSpec If neither {@code err} method is called, the behavior should be
+     * equivalent to calling {@code err(System.err, System.err, System.err)}.
+     *
+     * @param cmdErr destination of tool start-up and fatal errors
+     * @param userErr destination of user code error output.
+     * For example, user snippet  {@code System.err.println("Oops")}
+     * when executed {@code Oops} goes to userErr.
+     * @return the {@code JavaShellToolBuilder} instance
+     */
+    JavaShellToolBuilder err(PrintStream cmdErr, PrintStream userErr);
+
+    /**
+     * Set the storage mechanism for persistent information which includes
+     * input history and retained settings.
+     *
+     * @implSpec If neither {@code persistence} method is called, the behavior
+     * should be to use the tool's standard persistence mechanism.
+     *
+     * @param prefs an instance of {@link java.util.prefs.Preferences} that
+     * is used to retrieve and store persistent information
+     * @return the {@code JavaShellToolBuilder} instance
+     */
+    JavaShellToolBuilder persistence(Preferences prefs);
+
+    /**
+     * Set the storage mechanism for persistent information which includes
+     * input history and retained settings.
+     *
+     * @implSpec If neither {@code persistence} method is called, the behavior
+     * should be to use the tool's standard persistence mechanism.
+     *
+     * @param prefsMap  an instance of {@link java.util.Map} that
+     * is used to retrieve and store persistent information
+     * @return the {@code JavaShellToolBuilder} instance
+     */
+    JavaShellToolBuilder persistence(Map<String,String> prefsMap);
+
+    /**
+     * Set the source for environment variables.
+     *
+     * @implSpec If this method is not called, the behavior should be
+     * equivalent to calling {@code env(System.getenv())}.
+     *
+     * @param vars the Map of environment variable names to values
+     * @return the {@code JavaShellToolBuilder} instance
+     */
+    JavaShellToolBuilder env(Map<String,String> vars);
+
+    /**
+     * Set the locale.
+     *
+     * @implSpec If this method is not called, the behavior should be
+     * equivalent to calling {@code locale(Locale.getDefault())}.
+     *
+     * @param locale the locale
+     * @return the {@code JavaShellToolBuilder} instance
+     */
+    JavaShellToolBuilder locale(Locale locale);
+
+    /**
+     * Set to enable a command capturing prompt override.
+     *
+     * @implSpec If this method is not called, the behavior should be
+     * equivalent to calling {@code promptCapture(false)}.
+     *
+     * @param capture if {@code true}, basic prompt is the {@code ENQ}
+     * character and continuation prompt is the {@code ACK} character.
+     * If false, prompts are as set with set-up or user {@code /set} commands.
+     * @return the {@code JavaShellToolBuilder} instance
+     */
+    JavaShellToolBuilder promptCapture(boolean capture);
+
+    /**
+     * Run an instance of the Java shell tool as configured by the other methods
+     * in this interface.  This call is not destructive, more than one call of
+     * this method may be made from a configured builder.
+     *
+     * @param arguments the command-line arguments (including options), if any
+     * @throws Exception an unexpected fatal exception
+     */
+    void run(String... arguments) throws Exception;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/jshell/tool/package-info.java	Tue Dec 20 13:42:13 2016 -0800
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * Provides a mechanism to launch an instance of a Java&trade; shell tool.
+ * Allows configuration of the tool before launching. A builder is used
+ * to configure and launch the tool.
+ * <p>
+ * At the simplest, a builder is retrieved, and the builder is used to run the
+ * tool:
+ * <pre>
+ * {@code
+ *       JavaShellToolBuilder
+ *             .builder()
+ *             .run();
+ * }
+ * </pre>
+ * The builder can be configured and the run can have arguments:
+ * <pre>
+ * {@code
+ *       JavaShellToolBuilder
+ *             .builder()
+ *             .out(myCommandPrintStream, myOutputPrintStream)
+ *             .locale(Locale.CANADA)
+ *             .run("--feedback", "silent", "MyStart");
+ * }
+ * </pre>
+ */
+
+
+package jdk.jshell.tool;
+
--- a/src/jdk.jshell/share/classes/module-info.java	Tue Dec 20 06:06:01 2016 -0800
+++ b/src/jdk.jshell/share/classes/module-info.java	Tue Dec 20 13:42:13 2016 -0800
@@ -24,14 +24,38 @@
  */
 
 /**
- * This document is the API specification for JShell -- support for
+ * This module provides support for
  * Java&#x2122; Programming Language 'snippet' evaluating tools, such as
  * Read-Eval-Print Loops (REPLs).
+ * Separate packages support building tools, configuring the execution of tools,
+ * and programmatically launching the existing Java&#x2122; shell tool.
+ * <p>
+ *     The {@link jdk.jshell} is the package for creating 'snippet' evaluating tools.
+ *     Generally, this is only package that would be needed for creating tools.
+ * </p>
+ * <p>
+ *     The {@link jdk.jshell.spi} package specifies a Service Provider Interface (SPI)
+ *     for defining execution engine implementations for tools based on the
+ *     {@link jdk.jshell} API. The {@link jdk.jshell.execution} package provides
+ *     standard implementations of {@link jdk.jshell.spi} interfaces and supporting code.  It
+ *     also serves as a library of functionality for defining new execution engine
+ *     implementations.
+ * </p>
+ * <p>
+ *     The {@link jdk.jshell.tool} supports programmatically launching the
+ *     "jshell tool".
+ * </p>
+ * <p>
+ *     The {@link jdk.jshell.execution} package contains implementations of the
+ *     interfaces in {@link jdk.jshell.spi}.  Otherwise, the four packages are
+ *     independent, operate at different levels, and do not share functionality or
+ *     definitions.
+ * </p>
  */
 module jdk.jshell {
     requires transitive java.compiler;
     requires transitive jdk.jdi;
-    requires java.prefs;
+    requires transitive java.prefs;
     requires jdk.compiler;
     requires jdk.internal.le;
     requires jdk.internal.ed;
@@ -40,6 +64,9 @@
     exports jdk.jshell;
     exports jdk.jshell.spi;
     exports jdk.jshell.execution;
+    exports jdk.jshell.tool;
 
     uses jdk.internal.editor.spi.BuildInEditorProvider;
+
+    provides javax.tools.Tool with jdk.internal.jshell.tool.JShellToolProvider;
 }
--- a/test/jdk/jshell/CommandCompletionTest.java	Tue Dec 20 06:06:01 2016 -0800
+++ b/test/jdk/jshell/CommandCompletionTest.java	Tue Dec 20 13:42:13 2016 -0800
@@ -43,22 +43,73 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Locale;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import java.util.stream.StreamSupport;
 
 import org.testng.annotations.Test;
+import jdk.internal.jshell.tool.JShellTool;
+import jdk.internal.jshell.tool.JShellToolBuilder;
+import jdk.jshell.SourceCodeAnalysis.Suggestion;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
 
-@Test
 public class CommandCompletionTest extends ReplToolTesting {
 
-    public void testCommand() {
-        assertCompletion("/deb|", false);
-        assertCompletion("/re|", false, "/reload ", "/reset ");
-        assertCompletion("/h|", false, "/help ", "/history ");
+
+    private JShellTool repl;
+
+    @Override
+    protected void testRawRun(Locale locale, String[] args) {
+        repl = ((JShellToolBuilder) builder(locale))
+                .rawTool();
+        try {
+            repl.start(args);
+        } catch (Exception ex) {
+            fail("Repl tool died with exception", ex);
+        }
     }
 
+    public void assertCompletion(boolean after, String code, boolean isSmart, String... expected) {
+        if (!after) {
+            setCommandInput("\n");
+        } else {
+            assertCompletion(code, isSmart, expected);
+        }
+    }
+
+    public void assertCompletion(String code, boolean isSmart, String... expected) {
+        List<String> completions = computeCompletions(code, isSmart);
+        assertEquals(completions, Arrays.asList(expected), "Command: " + code + ", output: " +
+                completions.toString());
+    }
+
+    private List<String> computeCompletions(String code, boolean isSmart) {
+        int cursor =  code.indexOf('|');
+        code = code.replace("|", "");
+        assertTrue(cursor > -1, "'|' not found: " + code);
+        List<Suggestion> completions =
+                repl.commandCompletionSuggestions(code, cursor, new int[] {-1}); //XXX: ignoring anchor for now
+        return completions.stream()
+                          .filter(s -> isSmart == s.matchesType())
+                          .map(s -> s.continuation())
+                          .distinct()
+                          .collect(Collectors.toList());
+    }
+
+    @Test
+    public void testCommand() {
+        testNoStartUp(
+                a -> assertCompletion(a, "/deb|", false),
+                a -> assertCompletion(a, "/re|", false, "/reload ", "/reset "),
+                a -> assertCompletion(a, "/h|", false, "/help ", "/history ")
+        );
+    }
+
+    @Test
     public void testList() {
         test(false, new String[] {"--no-startup"},
                 a -> assertCompletion(a, "/l|", false, "/list "),
@@ -72,6 +123,7 @@
         );
     }
 
+    @Test
     public void testDrop() {
         test(false, new String[] {"--no-startup"},
                 a -> assertCompletion(a, "/d|", false, "/drop "),
@@ -83,6 +135,7 @@
         );
     }
 
+    @Test
     public void testEdit() {
         test(false, new String[]{"--no-startup"},
                 a -> assertCompletion(a, "/e|", false, "/edit ", "/exit "),
@@ -101,31 +154,38 @@
         );
     }
 
+    @Test
     public void testHelp() {
-        assertCompletion("/help |", false,
+        testNoStartUp(
+                a -> assertCompletion(a, "/help |", false,
                 "/! ", "/-<n> ", "/<id> ", "/? ", "/classpath ", "/drop ",
                 "/edit ", "/exit ", "/help ", "/history ", "/imports ",
                 "/list ", "/methods ", "/open ", "/reload ", "/reset ",
-                "/save ", "/set ", "/types ", "/vars ", "intro ", "shortcuts ");
-        assertCompletion("/? |", false,
+                "/save ", "/set ", "/types ", "/vars ", "intro ", "shortcuts "),
+                a -> assertCompletion(a, "/? |", false,
                 "/! ", "/-<n> ", "/<id> ", "/? ", "/classpath ", "/drop ",
                 "/edit ", "/exit ", "/help ", "/history ", "/imports ",
                 "/list ", "/methods ", "/open ", "/reload ", "/reset ",
-                "/save ", "/set ", "/types ", "/vars ", "intro ", "shortcuts ");
-        assertCompletion("/help /s|", false,
-                "/save ", "/set ");
-        assertCompletion("/help /set |", false,
-                "editor", "feedback", "format", "mode", "prompt", "start", "truncation");
-        assertCompletion("/help /edit |", false);
+                "/save ", "/set ", "/types ", "/vars ", "intro ", "shortcuts "),
+                a -> assertCompletion(a, "/help /s|", false,
+                "/save ", "/set "),
+                a -> assertCompletion(a, "/help /set |", false,
+                "editor", "feedback", "format", "mode", "prompt", "start", "truncation"),
+                a -> assertCompletion(a, "/help /edit |", false)
+        );
     }
 
+    @Test
     public void testReload() {
-        assertCompletion("/reload |", false, "-quiet ", "-restore ");
-        assertCompletion("/reload -restore |", false, "-quiet");
-        assertCompletion("/reload -quiet |", false, "-restore");
-        assertCompletion("/reload -restore -quiet |", false);
+        testNoStartUp(
+                a -> assertCompletion(a, "/reload |", false, "-quiet ", "-restore "),
+                a -> assertCompletion(a, "/reload -restore |", false, "-quiet"),
+                a -> assertCompletion(a, "/reload -quiet |", false, "-restore"),
+                a -> assertCompletion(a, "/reload -restore -quiet |", false)
+        );
     }
 
+    @Test
     public void testVarsMethodsTypes() {
         test(false, new String[]{"--no-startup"},
                 a -> assertCompletion(a, "/v|", false, "/vars "),
@@ -141,36 +201,53 @@
         );
     }
 
+    @Test
     public void testOpen() throws IOException {
         Compiler compiler = new Compiler();
-        assertCompletion("/o|", false, "/open ");
+        testNoStartUp(
+                a -> assertCompletion(a, "/o|", false, "/open ")
+        );
         List<String> p1 = listFiles(Paths.get(""));
         getRootDirectories().forEach(s -> p1.add(s.toString()));
         Collections.sort(p1);
-        assertCompletion("/open |", false, p1.toArray(new String[p1.size()]));
+        testNoStartUp(
+                a -> assertCompletion(a, "/open |", false, p1.toArray(new String[p1.size()]))
+        );
         Path classDir = compiler.getClassDir();
         List<String> p2 = listFiles(classDir);
-        assertCompletion("/open " + classDir + "/|", false, p2.toArray(new String[p2.size()]));
+        testNoStartUp(
+                a -> assertCompletion(a, "/open " + classDir + "/|", false, p2.toArray(new String[p2.size()]))
+        );
     }
 
+    @Test
     public void testSave() throws IOException {
         Compiler compiler = new Compiler();
-        assertCompletion("/s|", false, "/save ", "/set ");
+        testNoStartUp(
+                a -> assertCompletion(a, "/s|", false, "/save ", "/set ")
+        );
         List<String> p1 = listFiles(Paths.get(""));
         Collections.addAll(p1, "-all ", "-history ", "-start ");
         getRootDirectories().forEach(s -> p1.add(s.toString()));
         Collections.sort(p1);
-        assertCompletion("/save |", false, p1.toArray(new String[p1.size()]));
+        testNoStartUp(
+                a -> assertCompletion(a, "/save |", false, p1.toArray(new String[p1.size()]))
+        );
         Path classDir = compiler.getClassDir();
         List<String> p2 = listFiles(classDir);
-        assertCompletion("/save " + classDir + "/|",
-                false, p2.toArray(new String[p2.size()]));
-        assertCompletion("/save -all " + classDir + "/|",
-                false, p2.toArray(new String[p2.size()]));
+        testNoStartUp(
+                a -> assertCompletion(a, "/save " + classDir + "/|",
+                false, p2.toArray(new String[p2.size()])),
+                a -> assertCompletion(a, "/save -all " + classDir + "/|",
+                false, p2.toArray(new String[p2.size()]))
+        );
     }
 
+    @Test
     public void testClassPath() throws IOException {
-        assertCompletion("/classp|", false, "/classpath ");
+        testNoStartUp(
+                a -> assertCompletion(a, "/classp|", false, "/classpath ")
+        );
         Compiler compiler = new Compiler();
         Path outDir = compiler.getPath("testClasspathCompletion");
         Files.createDirectories(outDir);
@@ -182,9 +259,12 @@
         compiler.jar(outDir, jarName, "pkg/A.class");
         compiler.getPath(outDir).resolve(jarName);
         List<String> paths = listFiles(outDir, CLASSPATH_FILTER);
-        assertCompletion("/classpath " + outDir + "/|", false, paths.toArray(new String[paths.size()]));
+        testNoStartUp(
+                a -> assertCompletion(a, "/classpath " + outDir + "/|", false, paths.toArray(new String[paths.size()]))
+        );
     }
 
+    @Test
     public void testUserHome() throws IOException {
         List<String> completions;
         Path home = Paths.get(System.getProperty("user.home"));
@@ -194,9 +274,12 @@
                                  .sorted()
                                  .collect(Collectors.toList());
         }
-        assertCompletion("/classpath ~/|", false, completions.toArray(new String[completions.size()]));
+        testNoStartUp(
+                a -> assertCompletion(a, "/classpath ~/|", false, completions.toArray(new String[completions.size()]))
+        );
     }
 
+    @Test
     public void testSet() throws IOException {
         List<String> p1 = listFiles(Paths.get(""));
         getRootDirectories().forEach(s -> p1.add(s.toString()));
--- a/test/jdk/jshell/HistoryTest.java	Tue Dec 20 06:06:01 2016 -0800
+++ b/test/jdk/jshell/HistoryTest.java	Tue Dec 20 13:42:13 2016 -0800
@@ -32,13 +32,29 @@
  */
 
 import java.lang.reflect.Field;
+import java.util.Locale;
 import jdk.internal.jline.extra.EditingHistory;
 import org.testng.annotations.Test;
+import jdk.internal.jshell.tool.JShellTool;
+import jdk.internal.jshell.tool.JShellToolBuilder;
 import static org.testng.Assert.*;
 
-@Test
 public class HistoryTest extends ReplToolTesting {
 
+    private JShellTool repl;
+
+    @Override
+    protected void testRawRun(Locale locale, String[] args) {
+        repl = ((JShellToolBuilder) builder(locale))
+                .rawTool();
+        try {
+            repl.start(args);
+        } catch (Exception ex) {
+            fail("Repl tool died with exception", ex);
+        }
+    }
+
+    @Test
     public void testHistory() {
         test(
              a -> {if (!a) setCommandInput("void test() {\n");},
@@ -76,6 +92,7 @@
              });
     }
 
+    @Test
     public void test8166744() {
         test(
              a -> {if (!a) setCommandInput("class C {\n");},
--- a/test/jdk/jshell/ReplToolTesting.java	Tue Dec 20 06:06:01 2016 -0800
+++ b/test/jdk/jshell/ReplToolTesting.java	Tue Dec 20 13:42:13 2016 -0800
@@ -25,7 +25,6 @@
 import java.io.OutputStream;
 import java.io.PrintStream;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -36,17 +35,15 @@
 import java.util.function.Predicate;
 import java.util.prefs.AbstractPreferences;
 import java.util.prefs.BackingStoreException;
-import java.util.prefs.Preferences;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-import jdk.internal.jshell.tool.JShellTool;
-import jdk.jshell.SourceCodeAnalysis.Suggestion;
 
 import org.testng.annotations.BeforeMethod;
 
+import jdk.jshell.tool.JavaShellToolBuilder;
 import static java.util.stream.Collectors.toList;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
@@ -92,11 +89,9 @@
     private Map<String, ClassInfo> classes;
     private Map<String, ImportInfo> imports;
     private boolean isDefaultStartUp = true;
-    private Preferences prefs;
+    private Map<String, String> prefsMap;
     private Map<String, String> envvars;
 
-    public JShellTool repl = null;
-
     public interface ReplTest {
         void run(boolean after);
     }
@@ -202,6 +197,10 @@
         test(Locale.ROOT, isDefaultStartUp, args, DEFAULT_STARTUP_MESSAGE, tests);
     }
 
+    public void testNoStartUp(ReplTest... tests) {
+        test(Locale.ROOT, false, new String[] {"--no-startup"}, DEFAULT_STARTUP_MESSAGE, tests);
+    }
+
     public void test(Locale locale, boolean isDefaultStartUp, String[] args, String startUpMessage, ReplTest... tests) {
         this.isDefaultStartUp = isDefaultStartUp;
         initSnippets();
@@ -232,7 +231,7 @@
 
     @BeforeMethod
     public void setUp() {
-        prefs = new MemoryPreferences();
+        prefsMap = new HashMap<>();
         envvars = new HashMap<>();
     }
 
@@ -240,7 +239,25 @@
         envvars.put(name, value);
     }
 
-    public void testRaw(Locale locale, String[] args, ReplTest... tests) {
+    protected JavaShellToolBuilder builder(Locale locale) {
+        return JavaShellToolBuilder
+                    .builder()
+                    .in(cmdin, userin)
+                    .out(new PrintStream(cmdout), new PrintStream(console), new PrintStream(userout))
+                    .err(new PrintStream(cmderr), new PrintStream(usererr))
+                    .persistence(prefsMap)
+                    .env(envvars)
+                    .locale(locale)
+                    .promptCapture(true);
+    }
+
+    private void testRaw(Locale locale, String[] args, ReplTest... tests) {
+        testRawInit(tests);
+        testRawRun(locale, args);
+        testRawCheck(locale);
+    }
+
+    private void testRawInit(ReplTest... tests) {
         cmdin = new WaitingTestingInputStream();
         cmdout = new ByteArrayOutputStream();
         cmderr = new ByteArrayOutputStream();
@@ -248,23 +265,18 @@
         userin = new TestingInputStream();
         userout = new ByteArrayOutputStream();
         usererr = new ByteArrayOutputStream();
-        repl = new JShellTool(
-                cmdin,
-                new PrintStream(cmdout),
-                new PrintStream(cmderr),
-                new PrintStream(console),
-                userin,
-                new PrintStream(userout),
-                new PrintStream(usererr),
-                prefs,
-                envvars,
-                locale);
-        repl.testPrompt = true;
+    }
+
+    protected void testRawRun(Locale locale, String[] args) {
         try {
-            repl.start(args);
+            builder(locale)
+                    .run(args);
         } catch (Exception ex) {
             fail("Repl tool died with exception", ex);
         }
+    }
+
+    private void testRawCheck(Locale locale) {
         // perform internal consistency checks on state, if desired
         String cos = getCommandOutput();
         String ceos = getCommandErrorOutput();
@@ -272,9 +284,9 @@
         String ueos = getUserErrorOutput();
         assertTrue((cos.isEmpty() || cos.startsWith("|  Goodbye") || !locale.equals(Locale.ROOT)),
                 "Expected a goodbye, but got: " + cos);
-        assertTrue(ceos.isEmpty(), "Expected empty error output, got: " + ceos);
-        assertTrue(uos.isEmpty(), "Expected empty output, got: " + uos);
-        assertTrue(ueos.isEmpty(), "Expected empty error output, got: " + ueos);
+        assertTrue(ceos.isEmpty(), "Expected empty command error output, got: " + ceos);
+        assertTrue(uos.isEmpty(), "Expected empty user output, got: " + uos);
+        assertTrue(ueos.isEmpty(), "Expected empty user error output, got: " + ueos);
     }
 
     public void assertReset(boolean after, String cmd) {
@@ -454,36 +466,6 @@
         }
     }
 
-    public void assertCompletion(boolean after, String code, boolean isSmart, String... expected) {
-        if (!after) {
-            setCommandInput("\n");
-        } else {
-            assertCompletion(code, isSmart, expected);
-        }
-    }
-
-    public void assertCompletion(String code, boolean isSmart, String... expected) {
-        List<String> completions = computeCompletions(code, isSmart);
-        assertEquals(completions, Arrays.asList(expected), "Command: " + code + ", output: " +
-                completions.toString());
-    }
-
-    private List<String> computeCompletions(String code, boolean isSmart) {
-        JShellTool js = this.repl != null ? this.repl
-                                      : new JShellTool(null, null, null, null, null, null, null,
-                                              prefs, envvars, Locale.ROOT);
-        int cursor =  code.indexOf('|');
-        code = code.replace("|", "");
-        assertTrue(cursor > -1, "'|' not found: " + code);
-        List<Suggestion> completions =
-                js.commandCompletionSuggestions(code, cursor, new int[] {-1}); //XXX: ignoring anchor for now
-        return completions.stream()
-                          .filter(s -> isSmart == s.matchesType())
-                          .map(s -> s.continuation())
-                          .distinct()
-                          .collect(Collectors.toList());
-    }
-
     public Consumer<String> assertStartsWith(String prefix) {
         return (output) -> assertTrue(output.startsWith(prefix), "Output: \'" + output + "' does not start with: " + prefix);
     }
--- a/test/jdk/jshell/StartOptionTest.java	Tue Dec 20 06:06:01 2016 -0800
+++ b/test/jdk/jshell/StartOptionTest.java	Tue Dec 20 13:42:13 2016 -0800
@@ -22,7 +22,7 @@
  */
 
 /*
- * @test 8151754 8080883 8160089 8166581
+ * @test 8151754 8080883 8160089 8170162 8166581
  * @summary Testing start-up options.
  * @modules jdk.compiler/com.sun.tools.javac.api
  *          jdk.compiler/com.sun.tools.javac.main
@@ -33,19 +33,21 @@
  * @run testng StartOptionTest
  */
 
+import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.PrintStream;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
 import java.util.HashMap;
 import java.util.Locale;
+import java.util.ServiceLoader;
 import java.util.function.Consumer;
 
-import jdk.internal.jshell.tool.JShellTool;
+import javax.tools.Tool;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
-
+import jdk.jshell.tool.JavaShellToolBuilder;
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
 import static org.testng.Assert.fail;
@@ -59,21 +61,26 @@
     private ByteArrayOutputStream userout;
     private ByteArrayOutputStream usererr;
 
-    private JShellTool getShellTool() {
-        return new JShellTool(
-                new TestingInputStream(),
-                new PrintStream(cmdout),
-                new PrintStream(cmderr),
-                new PrintStream(console),
-                null,
-                new PrintStream(userout),
-                new PrintStream(usererr),
-                new ReplToolTesting.MemoryPreferences(),
-                new HashMap<>(),
-                Locale.ROOT);
+    private JavaShellToolBuilder builder() {
+        return JavaShellToolBuilder
+                    .builder()
+                    .out(new PrintStream(cmdout), new PrintStream(console), new PrintStream(userout))
+                    .err(new PrintStream(cmderr), new PrintStream(usererr))
+                    .persistence(new HashMap<>())
+                    .env(new HashMap<>())
+                    .locale(Locale.ROOT);
     }
 
-    private void check(ByteArrayOutputStream str, Consumer<String> checkOut, String label) {
+    private void runShell(String... args) {
+        try {
+            builder()
+                    .run(args);
+        } catch (Exception ex) {
+            fail("Repl tool died with exception", ex);
+        }
+    }
+
+    protected void check(ByteArrayOutputStream str, Consumer<String> checkOut, String label) {
         byte[] bytes = str.toByteArray();
         str.reset();
         String out =  new String(bytes, StandardCharsets.UTF_8);
@@ -84,18 +91,28 @@
         }
     }
 
-    private void start(Consumer<String> checkOutput, Consumer<String> checkError, String... args) throws Exception {
-        JShellTool tool = getShellTool();
-        tool.start(args);
-        check(cmdout, checkOutput, "cmdout");
+    protected void start(Consumer<String> checkCmdOutput,
+            Consumer<String> checkUserOutput, Consumer<String> checkError,
+            String... args) throws Exception {
+        runShell(args);
+        check(cmdout, checkCmdOutput, "cmdout");
         check(cmderr, checkError, "cmderr");
         check(console, null, "console");
-        check(userout, null, "userout");
+        check(userout, checkUserOutput, "userout");
         check(usererr, null, "usererr");
     }
 
-    private void start(String expectedOutput, String expectedError, String... args) throws Exception {
-        start(s -> assertEquals(s.trim(), expectedOutput, "cmdout: "), s -> assertEquals(s.trim(), expectedError, "cmderr: "), args);
+    protected void start(String expectedCmdOutput, String expectedError, String... args) throws Exception {
+        startWithUserOutput(expectedCmdOutput, "",  expectedError, args);
+    }
+
+    private void startWithUserOutput(String expectedCmdOutput, String expectedUserOutput,
+            String expectedError, String... args) throws Exception {
+        start(
+                s -> assertEquals(s.trim(), expectedCmdOutput, "cmdout: "),
+                s -> assertEquals(s.trim(), expectedUserOutput, "userout: "),
+                s -> assertEquals(s.trim(), expectedError, "cmderr: "),
+                args);
     }
 
     @BeforeMethod
@@ -107,21 +124,31 @@
         usererr = new ByteArrayOutputStream();
     }
 
-    @Test
+    protected String writeToFile(String stuff) throws Exception {
+        Compiler compiler = new Compiler();
+        Path p = compiler.getPath("doit.repl");
+        compiler.writeToFile(p, stuff);
+        return p.toString();
+    }
+
+    public void testCommandFile() throws Exception {
+        String fn = writeToFile("String str = \"Hello \"\n/list\nSystem.out.println(str + str)\n/exit\n");
+        startWithUserOutput("1 : String str = \"Hello \";", "Hello Hello", "", "--no-startup", fn, "-s");
+    }
+
     public void testUsage() throws Exception {
         for (String opt : new String[]{"-h", "--help"}) {
             start(s -> {
                 assertTrue(s.split("\n").length >= 7, "Not enough usage lines: " + s);
                 assertTrue(s.startsWith("Usage:   jshell <options>"), "Unexpect usage start: " + s);
-            }, null, opt);
+            }, null, null, opt);
         }
     }
 
-    @Test
     public void testUnknown() throws Exception {
-        start(s -> { },
+        start(null, null,
               s -> assertEquals(s.trim(), "Unknown option: u"), "-unknown");
-        start(s -> { },
+        start(null, null,
               s -> assertEquals(s.trim(), "Unknown option: unknown"), "--unknown");
     }
 
@@ -138,7 +165,7 @@
 
     public void testStartupFailedOption() throws Exception {
         try {
-            start("", "", "-R-hoge-foo-bar");
+            builder().run("-R-hoge-foo-bar");
         } catch (IllegalStateException ex) {
             String s = ex.getMessage();
             assertTrue(s.startsWith("Launching JShell execution engine threw: Failed remote"), s);
@@ -151,7 +178,6 @@
         start("", "File 'UNKNOWN' for '--startup' is not found.", "--startup", "UNKNOWN");
     }
 
-    @Test
     public void testClasspath() throws Exception {
         for (String cp : new String[] {"--class-path"}) {
             start("", "Only one --class-path option may be used.", cp, ".", "--class-path", ".");
@@ -159,7 +185,6 @@
         }
     }
 
-    @Test
     public void testFeedbackOptionConflict() throws Exception {
         start("", "Only one feedback option (--feedback, -q, -s, or -v) may be used.",
                 "--feedback", "concise", "--feedback", "verbose");
@@ -173,15 +198,13 @@
         start("", "Only one feedback option (--feedback, -q, -s, or -v) may be used.", "-q", "-s");
     }
 
-    @Test
     public void testNegFeedbackOption() throws Exception {
         start("", "Argument to feedback missing.", "--feedback");
         start("", "Does not match any current feedback mode: blorp -- --feedback blorp", "--feedback", "blorp");
     }
 
-    @Test
     public void testVersion() throws Exception {
-        start(s -> assertTrue(s.startsWith("jshell"), "unexpected version: " + s), null, "--version");
+        start(s -> assertTrue(s.startsWith("jshell"), "unexpected version: " + s), null, null, "--version");
     }
 
     @AfterMethod
--- a/test/jdk/jshell/ToolBasicTest.java	Tue Dec 20 06:06:01 2016 -0800
+++ b/test/jdk/jshell/ToolBasicTest.java	Tue Dec 20 13:42:13 2016 -0800
@@ -23,7 +23,7 @@
 
 /*
  * @test
- * @bug 8143037 8142447 8144095 8140265 8144906 8146138 8147887 8147886 8148316 8148317 8143955 8157953 8080347 8154714 8166649 8167643
+ * @bug 8143037 8142447 8144095 8140265 8144906 8146138 8147887 8147886 8148316 8148317 8143955 8157953 8080347 8154714 8166649 8167643 8170162
  * @summary Tests for Basic tests for REPL tool
  * @modules jdk.compiler/com.sun.tools.javac.api
  *          jdk.compiler/com.sun.tools.javac.main
@@ -49,8 +49,6 @@
 import java.util.function.BiFunction;
 import java.util.function.Consumer;
 import java.util.function.Function;
-import java.util.prefs.BackingStoreException;
-import java.util.prefs.Preferences;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -305,22 +303,18 @@
     }
 
     public void testStartupFileOption() {
-        try {
-            Compiler compiler = new Compiler();
-            Path startup = compiler.getPath("StartupFileOption/startup.txt");
-            compiler.writeToFile(startup, "class A { public String toString() { return \"A\"; } }");
-            test(new String[]{"--startup", startup.toString()},
-                    (a) -> evaluateExpression(a, "A", "new A()", "A")
-            );
-            test(new String[]{"--no-startup"},
-                    (a) -> assertCommandCheckOutput(a, "printf(\"\")", assertStartsWith("|  Error:\n|  cannot find symbol"))
-            );
-            test(
-                    (a) -> assertCommand(a, "printf(\"A\")", "", "", null, "A", "")
-            );
-        } finally {
-            removeStartup();
-        }
+        Compiler compiler = new Compiler();
+        Path startup = compiler.getPath("StartupFileOption/startup.txt");
+        compiler.writeToFile(startup, "class A { public String toString() { return \"A\"; } }");
+        test(new String[]{"--startup", startup.toString()},
+                (a) -> evaluateExpression(a, "A", "new A()", "A")
+        );
+        test(new String[]{"--no-startup"},
+                (a) -> assertCommandCheckOutput(a, "printf(\"\")", assertStartsWith("|  Error:\n|  cannot find symbol"))
+        );
+        test(
+                (a) -> assertCommand(a, "printf(\"A\")", "", "", null, "A", "")
+        );
     }
 
     public void testLoadingFromArgs() {
@@ -436,45 +430,34 @@
         assertEquals(Files.readAllLines(path), output);
     }
 
-    public void testStartRetain() throws BackingStoreException {
-        try {
-            Compiler compiler = new Compiler();
-            Path startUpFile = compiler.getPath("startUp.txt");
-            test(
-                    (a) -> assertVariable(a, "int", "a"),
-                    (a) -> assertVariable(a, "double", "b", "10", "10.0"),
-                    (a) -> assertMethod(a, "void f() {}", "()V", "f"),
-                    (a) -> assertImport(a, "import java.util.stream.*;", "", "java.util.stream.*"),
-                    (a) -> assertCommand(a, "/save " + startUpFile.toString(), null),
-                    (a) -> assertCommand(a, "/set start -retain " + startUpFile.toString(), null)
-            );
-            Path unknown = compiler.getPath("UNKNOWN");
-            test(
-                    (a) -> assertCommandOutputStartsWith(a, "/set start -retain " + unknown.toString(),
-                            "|  File '" + unknown + "' for '/set start' is not found.")
-            );
-            test(false, new String[0],
-                    (a) -> {
-                        loadVariable(a, "int", "a");
-                        loadVariable(a, "double", "b", "10.0", "10.0");
-                        loadMethod(a, "void f() {}", "()void", "f");
-                        loadImport(a, "import java.util.stream.*;", "", "java.util.stream.*");
-                        assertCommandCheckOutput(a, "/types", assertClasses());
-                    },
-                    (a) -> assertCommandCheckOutput(a, "/vars", assertVariables()),
-                    (a) -> assertCommandCheckOutput(a, "/methods", assertMethods()),
-                    (a) -> assertCommandCheckOutput(a, "/imports", assertImports())
-            );
-        } finally {
-            removeStartup();
-        }
-    }
-
-    private void removeStartup() {
-        Preferences preferences = Preferences.userRoot().node("tool/JShell");
-        if (preferences != null) {
-            preferences.remove("STARTUP");
-        }
+    public void testStartRetain() {
+        Compiler compiler = new Compiler();
+        Path startUpFile = compiler.getPath("startUp.txt");
+        test(
+                (a) -> assertVariable(a, "int", "a"),
+                (a) -> assertVariable(a, "double", "b", "10", "10.0"),
+                (a) -> assertMethod(a, "void f() {}", "()V", "f"),
+                (a) -> assertImport(a, "import java.util.stream.*;", "", "java.util.stream.*"),
+                (a) -> assertCommand(a, "/save " + startUpFile.toString(), null),
+                (a) -> assertCommand(a, "/set start -retain " + startUpFile.toString(), null)
+        );
+        Path unknown = compiler.getPath("UNKNOWN");
+        test(
+                (a) -> assertCommandOutputStartsWith(a, "/set start -retain " + unknown.toString(),
+                        "|  File '" + unknown + "' for '/set start' is not found.")
+        );
+        test(false, new String[0],
+                (a) -> {
+                    loadVariable(a, "int", "a");
+                    loadVariable(a, "double", "b", "10.0", "10.0");
+                    loadMethod(a, "void f() {}", "()void", "f");
+                    loadImport(a, "import java.util.stream.*;", "", "java.util.stream.*");
+                    assertCommandCheckOutput(a, "/types", assertClasses());
+                },
+                (a) -> assertCommandCheckOutput(a, "/vars", assertVariables()),
+                (a) -> assertCommandCheckOutput(a, "/methods", assertMethods()),
+                (a) -> assertCommandCheckOutput(a, "/imports", assertImports())
+        );
     }
 
     public void testStartSave() throws IOException {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jshell/ToolProviderTest.java	Tue Dec 20 13:42:13 2016 -0800
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.ServiceLoader;
+import java.util.function.Consumer;
+import javax.tools.Tool;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import static org.testng.Assert.fail;
+
+/*
+ * @test
+ * @bug 8170044
+ * @summary Test ServiceLoader launching of jshell tool
+ * @modules jdk.compiler/com.sun.tools.javac.api
+ *          jdk.compiler/com.sun.tools.javac.main
+ *          jdk.jdeps/com.sun.tools.javap
+ *          jdk.jshell/jdk.internal.jshell.tool
+ * @library /tools/lib
+ * @build Compiler toolbox.ToolBox
+ * @run testng ToolProviderTest
+ */
+@Test
+public class ToolProviderTest extends StartOptionTest {
+
+    private ByteArrayOutputStream cmdout;
+    private ByteArrayOutputStream cmderr;
+
+    @BeforeMethod
+    @Override
+    public void setUp() {
+        cmdout = new ByteArrayOutputStream();
+        cmderr = new ByteArrayOutputStream();
+    }
+
+    @Override
+    protected void start(Consumer<String> checkCmdOutput,
+            Consumer<String> checkUserOutput, Consumer<String> checkError,
+            String... args) throws Exception {
+        if (runShellServiceLoader(args) != 0) {
+            fail("Repl tool failed");
+        }
+        check(cmdout, checkCmdOutput, "cmdout");
+        check(cmderr, checkError, "cmderr");
+    }
+
+    private int runShellServiceLoader(String... args) {
+        ServiceLoader<Tool> sl = ServiceLoader.load(Tool.class);
+        for (Tool provider : sl) {
+            if (provider.name().equals("jshell")) {
+                return provider.run(new ByteArrayInputStream(new byte[0]), cmdout, cmderr, args);
+            }
+        }
+        throw new AssertionError("Repl tool not found by ServiceLoader: " + sl);
+    }
+
+    @Override
+    public void testCommandFile() throws Exception {
+        String fn = writeToFile("String str = \"Hello \"\n/list\nSystem.out.println(str + str)\n/exit\n");
+        start("1 : String str = \"Hello \";" + "\n" + "Hello Hello", "", "--no-startup", fn, "-s");
+    }
+
+    @Override
+    public void testStartupFailedOption() throws Exception {
+        if (runShellServiceLoader("-R-hoge-foo-bar") == 0) {
+            fail("Expected tool failure");
+        } else {
+            check(cmderr, s -> s.startsWith("Launching JShell execution engine threw: Failed remote"), "cmderr");
+        }
+    }
+}