changeset 3065:96a99cfb21be

Merge
author lana
date Wed, 21 Oct 2015 18:40:01 -0700
parents ac57d80b205d 0cce85265987
children 820841f0e8bd
files
diffstat 136 files changed, 25452 insertions(+), 233 deletions(-) [+]
line wrap: on
line diff
--- a/make/build.properties	Wed Oct 21 15:15:36 2015 -0700
+++ b/make/build.properties	Wed Oct 21 18:40:01 2015 -0700
@@ -1,5 +1,5 @@
 #
-# Copyright (c) 2007, 2014, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2007, 2015, 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
@@ -47,11 +47,21 @@
 boot.javac.target = 8
 
 #configuration of submodules (share by both the bootstrap and normal compilation):
-langtools.modules=java.compiler:jdk.compiler:jdk.jdeps:jdk.javadoc
+langtools.modules=java.compiler:jdk.compiler:jdk.jdeps:jdk.javadoc:jdk.jshell:jdk.internal.le:jdk.jdi
 java.compiler.dependencies=
 jdk.compiler.dependencies=java.compiler
 jdk.javadoc.dependencies=java.compiler:jdk.compiler
 jdk.jdeps.dependencies=java.compiler:jdk.compiler
+jdk.internal.le.dependencies=
+jdk.jdi.dependencies=
+jdk.jshell.dependencies=java.compiler:jdk.internal.le:jdk.compiler:jdk.jdi
+
+tool.javac.main.class=com.sun.tools.javac.Main
+tool.javadoc.main.class=com.sun.tools.javadoc.Main
+tool.javap.main.class=com.sun.tools.javap.Main
+tool.javah.main.class=com.sun.tools.javah.Main
+tool.sjavac.main.class=com.sun.tools.sjavac.Main
+tool.jshell.main.class=jdk.internal.jshell.tool.JShellTool
 
 javac.resource.includes = \
         com/sun/tools/javac/resources/compiler.properties
--- a/make/build.xml	Wed Oct 21 15:15:36 2015 -0700
+++ b/make/build.xml	Wed Oct 21 18:40:01 2015 -0700
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!--
- Copyright (c) 2007, 2014, Oracle and/or its affiliates. All rights reserved.
+ Copyright (c) 2007, 2015, 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
@@ -172,6 +172,7 @@
         <build-tool name="javap"/>
         <build-tool name="javah"/>
         <build-tool name="sjavac"/>
+        <build-tool name="jshell"/>
     </target>
 
     <target name="build-all-classes" depends="-def-build-all-module-classes,build-bootstrap-javac-classes">
@@ -464,6 +465,9 @@
                 <build-module-jar module.name="jdk.compiler" compilation.kind="@{compilation.kind}" />
                 <build-module-jar module.name="jdk.javadoc" compilation.kind="@{compilation.kind}" />
                 <build-module-jar module.name="jdk.jdeps" compilation.kind="@{compilation.kind}" />
+                <build-module-jar module.name="jdk.internal.le" compilation.kind="@{compilation.kind}" />
+                <build-module-jar module.name="jdk.jdi" compilation.kind="@{compilation.kind}" />
+                <build-module-jar module.name="jdk.jshell" compilation.kind="@{compilation.kind}" />
             </sequential>
         </macrodef>
     </target>
@@ -502,11 +506,12 @@
             <attribute name="compilation.kind" default=""/>
             <attribute name="bin.dir" default="${@{compilation.kind}dist.bin.dir}"/>
             <attribute name="java" default="${launcher.java}"/>
+            <attribute name="main.class" default="${tool.@{name}.main.class}"/>
             <sequential>
                 <mkdir dir="@{bin.dir}"/>
                 <copy file="${make.dir}/launcher.sh-template" tofile="@{bin.dir}/@{name}">
                     <filterset begintoken="#" endtoken="#">
-                        <filter token="PROGRAM" value="@{name}"/>
+                        <filter token="PROGRAM" value="@{main.class}"/>
                         <filter token="TARGET_JAVA" value="@{java}"/>
                         <filter token="PS" value="${path.separator}"/>
                     </filterset>
@@ -529,11 +534,17 @@
                                       compilation.kind="@{compilation.kind}" />
                 <build-module-classes module.name="jdk.jdeps"
                                       compilation.kind="@{compilation.kind}" />
+                <copy-module-classes  module.name="jdk.internal.le"
+                                      compilation.kind="@{compilation.kind}" />
+                <copy-module-classes  module.name="jdk.jdi"
+                                      compilation.kind="@{compilation.kind}" />
+                <build-module-classes module.name="jdk.jshell"
+                                      compilation.kind="@{compilation.kind}" />
             </sequential>
         </macrodef>
     </target>
 
-    <target name="-def-build-module-classes" depends="-def-pcompile,-def-pparse">
+    <target name="-def-build-module-classes" depends="-def-pcompile,-def-pparse,-def-cdumper">
         <macrodef name="build-module-classes">
             <attribute name="module.name"/>
             <attribute name="compilation.kind" default=""/>
@@ -646,6 +657,18 @@
                 </copy>
             </sequential>
         </macrodef>
+        <macrodef name="copy-module-classes">
+            <attribute name="module.name"/>
+            <attribute name="compilation.kind" default=""/>
+            <attribute name="build.dir" default="${@{compilation.kind}build.dir}"/>
+            <attribute name="classes.dir" default="@{build.dir}/@{module.name}/classes"/>
+            <attribute name="java.home" default="${boot.java.home}"/>
+            <sequential>
+                <property name="classes.origin.dir" location="${target.java.home}/../../jdk/modules/@{module.name}"/>
+                <mkdir dir="@{classes.dir}"/>
+                <dumpclasses moduleName="@{module.name}" destDir="@{classes.dir}" />
+            </sequential>
+        </macrodef>
     </target>
 
     <target name="-def-pparse">
@@ -670,6 +693,25 @@
                  classpath="${build.toolclasses.dir}/"/>
     </target>
 
+    <target name="-def-cdumper">
+        <mkdir dir="${build.toolclasses.dir}"/>
+        <javac fork="true"
+               source="${boot.javac.source}"
+               target="${boot.javac.target}"
+               executable="${boot.java.home}/bin/javac"
+               srcdir="${make.tools.dir}"
+               includes="anttasks/DumpClass*"
+               destdir="${build.toolclasses.dir}/"
+               classpath="${ant.core.lib}"
+               bootclasspath="${boot.java.home}/jre/lib/rt.jar"
+               includeantruntime="false">
+            <compilerarg line="${javac.lint.opts}"/>
+        </javac>
+        <taskdef name="dumpclasses"
+                 classname="anttasks.DumpClassesTask"
+                 classpath="${build.toolclasses.dir}/:${target.java.home}/jrt-fs.jar"/>
+    </target>
+
     <target name="-do-depend" if="do.depend">
         <depend srcdir="${src.dir}:${gensrc.dir}" destdir="${classes.dir}" classpath="${classpath}"
                 cache="${depcache.dir}"/>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/gensrc/Gensrc-jdk.jshell.gmk	Wed Oct 21 18:40:01 2015 -0700
@@ -0,0 +1,34 @@
+#
+# Copyright (c) 2015, 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.
+#
+
+include GensrcCommon.gmk
+
+$(eval $(call SetupVersionProperties,JSHELL_VERSION, \
+    jdk/internal/jshell/tool/resources/version.properties))
+
+$(eval $(call SetupCompileProperties,COMPILE_PROPERTIES, \
+    $(JSHELL_VERSION) $(JAVAH_VERSION)))
+
+all: $(COMPILE_PROPERTIES)
--- a/make/intellij/langtools.iml	Wed Oct 21 15:15:36 2015 -0700
+++ b/make/intellij/langtools.iml	Wed Oct 21 18:40:01 2015 -0700
@@ -13,6 +13,7 @@
       <sourceFolder url="file://$MODULE_DIR$/build/bootstrap/jdk.compiler/gensrc" isTestSource="false" />
       <sourceFolder url="file://$MODULE_DIR$/build/bootstrap/jdk.javadoc/gensrc" isTestSource="false" />
       <sourceFolder url="file://$MODULE_DIR$/build/bootstrap/jdk.jdeps/gensrc" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/src/jdk.jshell/share/classes" isTestSource="false" />
     </content>
     <orderEntry type="sourceFolder" forTests="false" />
     <orderEntry type="inheritedJdk" />
--- a/make/intellij/workspace.xml	Wed Oct 21 15:15:36 2015 -0700
+++ b/make/intellij/workspace.xml	Wed Oct 21 18:40:01 2015 -0700
@@ -103,6 +103,24 @@
         <option name="AntTarget" enabled="true" antfile="file://$PROJECT_DIR$/.idea/build.xml" target="build-all-classes" />
       </method>
     </configuration>
+    <configuration default="false" name="jshell" type="Application" factoryName="Application">
+      <extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
+      <option name="MAIN_CLASS_NAME" value="jdk.internal.jshell.tool.JShellTool" />
+      <option name="VM_PARAMETERS" value="-Xbootclasspath/p:build/jdk.internal.le/classes:build/jdk.jdi/classes:build/jdk.jshell/classes:build/java.compiler/classes:build/jdk.compiler/classes:build/jdk.javadoc/classes:build/jdk.jdeps/classes" />
+      <option name="PROGRAM_PARAMETERS" value="" />
+      <option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$" />
+      <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
+      <option name="ALTERNATIVE_JRE_PATH" value="$PROJECT_DIR$/../../dev/build/linux-x86_64-normal-server-release/images/jre" />
+      <option name="ENABLE_SWING_INSPECTOR" value="false" />
+      <option name="ENV_VARIABLES" />
+      <option name="PASS_PARENT_ENVS" value="true" />
+      <module name="langtools" />
+      <envs />
+      <method>
+        <option name="Make" enabled="false" />
+        <option name="AntTarget" enabled="true" antfile="file://$PROJECT_DIR$/.idea/build.xml" target="build-all-classes" />
+      </method>
+    </configuration>
     <!-- bootstrap javac -->
     <configuration default="false" name="javac (bootstrap)" type="Application" factoryName="Application">
       <option name="MAIN_CLASS_NAME" value="com.sun.tools.javac.Main" />
--- a/make/launcher.sh-template	Wed Oct 21 15:15:36 2015 -0700
+++ b/make/launcher.sh-template	Wed Oct 21 18:40:01 2015 -0700
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 #
-# Copyright (c) 2006, 2011, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2006, 2015, 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
@@ -71,4 +71,4 @@
 unset DUALCASE
 
 IFS=$nl
-"#TARGET_JAVA#" "${bcp:+-Xbootclasspath/p:"$bcp"}" ${ea} ${javaOpts} com.sun.tools.#PROGRAM#.Main ${toolOpts}
+"#TARGET_JAVA#" "${bcp:+-Xbootclasspath/p:"$bcp"}" ${ea} ${javaOpts} #PROGRAM# ${toolOpts}
--- a/make/netbeans/langtools/build.xml	Wed Oct 21 15:15:36 2015 -0700
+++ b/make/netbeans/langtools/build.xml	Wed Oct 21 18:40:01 2015 -0700
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!--
- Copyright (c) 2007, 2014, Oracle and/or its affiliates. All rights reserved.
+ Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
 
  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions
@@ -81,11 +81,21 @@
         the user.
     -->
 
-    <target name="run" depends="-check-target.java.home,-build-classes,-def-run,-get-tool-and-args,-setup-bootclasspath"
+    <target name="run" depends="-check-target.java.home,-build-classes,-def-run,-get-tool-and-args,-setup-bootclasspath,-def-resolve-main-class"
             description="run tool">
         <echo level="info" message="${with_bootclasspath}"/>
         <echo level="info" message="Run ${use_bootstrap}${langtools.tool.name} with args ${langtools.tool.args}"/>
-        <run bcp="${with_bootclasspath}" mainclass="com.sun.tools.${langtools.tool.name}.Main" args="${langtools.tool.args}"/>
+        <resolve-main-class tool.name="${langtools.tool.name}" />
+        <run bcp="${with_bootclasspath}" mainclass="${langtools.main.class}" args="${langtools.tool.args}"/>
+    </target>
+
+    <target name="-def-resolve-main-class">
+        <macrodef name="resolve-main-class">
+          <attribute name="tool.name"/>
+          <sequential>
+            <property name="langtools.main.class" value="${tool.@{tool.name}.main.class}"/>
+          </sequential>
+        </macrodef>
     </target>
 
     <target name="-build-classes" depends="-get-tool-if-set,-build-classes-bootstrap-javac,-build-classes-all" />
@@ -159,10 +169,11 @@
 
     <!-- Debug tool in NetBeans. -->
 
-    <target name="debug" depends="-check-target.java.home,-def-run,-def-start-debugger,-get-tool-and-args,-setup-bootclasspath,-build-classes" if="netbeans.home">
+    <target name="debug" depends="-check-target.java.home,-def-run,-def-start-debugger,-get-tool-and-args,-setup-bootclasspath,-build-classes,-def-resolve-main-class" if="netbeans.home">
         <echo level="info" message="Debug ${use_bootstrap}${langtools.tool.name} with args ${langtools.tool.args}"/>
         <start-debugger/>
-        <run bcp="${with_bootclasspath}" mainclass="com.sun.tools.${langtools.tool.name}.Main" args="${langtools.tool.args}" jpda.jvmargs="${jpda.jvmargs}"/>
+        <resolve-main-class tool.name="${langtools.tool.name}" />
+        <run bcp="${with_bootclasspath}" mainclass="${langtools.main.class}" args="${langtools.tool.args}" jpda.jvmargs="${jpda.jvmargs}"/>
     </target>
 
     <!-- Debug a selected class . -->
--- a/make/netbeans/langtools/nbproject/project.xml	Wed Oct 21 15:15:36 2015 -0700
+++ b/make/netbeans/langtools/nbproject/project.xml	Wed Oct 21 18:40:01 2015 -0700
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!--
- Copyright (c) 2007, 2014, Oracle and/or its affiliates. All rights reserved.
+ Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
 
  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions
@@ -76,6 +76,11 @@
                     <type>java</type>
                     <location>${root}/src/jdk.javadoc/share/classes</location>
                 </source-folder>
+                <source-folder>
+                    <label>Source files - jdk.jshell</label>
+                    <type>java</type>
+                    <location>${root}/src/jdk.jshell/share/classes</location>
+                </source-folder>
                 <build-file>
                     <location>${root}/build/classes</location>
                 </build-file>
@@ -152,6 +157,19 @@
                         </arity>
                     </context>
                 </action>
+                <action name="compile.single">
+                    <target>compile-single</target>
+                    <property name="module.name">jdk.jshell</property>
+                    <context>
+                        <property>includes</property>
+                        <folder>${root}/src/jdk.jshell/share/classes</folder>
+                        <pattern>\.java$</pattern>
+                        <format>relative-path</format>
+                        <arity>
+                            <separated-files>,</separated-files>
+                        </arity>
+                    </context>
+                </action>
                 <action name="run">
                     <target>run</target>
                 </action>
@@ -215,6 +233,18 @@
                         </arity>
                     </context>
                 </action>
+                <action name="run.single">
+                    <target>run-single</target>
+                    <context>
+                        <property>run.classname</property>
+                        <folder>${root}/src/jdk.jshell/share/classes</folder>
+                        <pattern>\.java$</pattern>
+                        <format>java-name</format>
+                        <arity>
+                            <one-file-only/>
+                        </arity>
+                    </context>
+                </action>
                 <!--
  Note: NetBeans does not appear to support context menu items
  on shell scripts :-(
@@ -285,6 +315,18 @@
                         </arity>
                     </context>
                 </action>
+                <action name="debug.single">
+                    <target>debug-single</target>
+                    <context>
+                        <property>debug.classname</property>
+                        <folder>${root}/src/jdk.jshell/share/classes</folder>
+                        <pattern>\.java$</pattern>
+                        <format>java-name</format>
+                        <arity>
+                            <one-file-only/>
+                        </arity>
+                    </context>
+                </action>
                 <!--
  Note: NetBeans does not appear to support context menu items
  on shell scripts :-(
@@ -353,6 +395,19 @@
                         </arity>
                     </context>
                 </action>
+                <action name="debug.fix">
+                    <target>debug-fix</target>
+                    <property name="module.name">jdk.jshell</property>
+                    <context>
+                        <property>class</property>
+                        <folder>${root}/src/jdk.jshell/share/classes</folder>
+                        <pattern>\.java$</pattern>
+                        <format>relative-path-noext</format>
+                        <arity>
+                            <one-file-only/>
+                        </arity>
+                    </context>
+                </action>
                 <action name="javadoc">
                     <target>javadoc</target>
                 </action>
@@ -390,6 +445,10 @@
                         <location>${root}/src/jdk.javadoc/share/classes</location>
                     </source-folder>
                     <source-folder style="tree">
+                        <label>Source files - jdk.jshell</label>
+                        <location>${root}/src/jdk.jshell/share/classes</location>
+                    </source-folder>
+                    <source-folder style="tree">
                         <label>Test files</label>
                         <location>${root}/test</location>
                     </source-folder>
@@ -456,6 +515,15 @@
                 <built-to>${root}/build/jdk.javadoc/classes</built-to>
                 <source-level>1.8</source-level>
             </compilation-unit>
+            <compilation-unit>
+                <package-root>${root}/src/jdk.jshell/share/classes</package-root>
+                <package-root>${root}/build/bootstrap/jdk.jshell/gensrc</package-root>
+                <package-root>${root}/../jdk/src/jdk.internal.le/share/classes</package-root>
+                <package-root>${root}/../jdk/src/jdk.jdi/share/classes</package-root>
+                <classpath mode="compile">${root}/build/java.compiler/classes:${root}/build/jdk.compiler/classes:${root}/build/jdk.internal.le/aux:${root}/build/jdk.jdi/aux:${root}/build/jdk.internal.le/classes:${root}/build/jdk.jdi/classes</classpath>
+                <built-to>${root}/build/jdk.jshell/classes</built-to>
+                <source-level>1.8</source-level>
+            </compilation-unit>
         </java-data>
     </configuration>
 </project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/tools/anttasks/DumpClassesTask.java	Wed Oct 21 18:40:01 2015 -0700
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2015, 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 anttasks;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.Collections;
+import java.util.stream.Stream;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Task;
+
+public class DumpClassesTask extends Task {
+
+    private String moduleName;
+    private File dir;
+
+    public void setModuleName(String moduleName) {
+        this.moduleName = moduleName;
+    }
+
+    public void setDestDir(File dir) {
+        this.dir = dir;
+    }
+
+    @Override
+    public void execute() {
+        try (FileSystem fs = FileSystems.newFileSystem(new URI("jrt:/"), Collections.emptyMap(), DumpClassesTask.class.getClassLoader())) {
+            Path source = fs.getPath("modules", moduleName);
+            Path target = dir.toPath();
+
+            try (Stream<Path> content = Files.walk(source)) {
+                content.filter(Files :: isRegularFile)
+                       .forEach(p -> {
+                    try {
+                        Path targetFile = target.resolve(source.relativize(p).toString());
+                        if (!Files.exists(targetFile) || Files.getLastModifiedTime(targetFile).compareTo(Files.getLastModifiedTime(source)) < 0) {
+                            Files.createDirectories(targetFile.getParent());
+                            Files.copy(p, targetFile, StandardCopyOption.REPLACE_EXISTING);
+                        }
+                    } catch (IOException ex) {
+                        throw new UncheckedIOException(ex);
+                    }
+                });
+            }
+        } catch (URISyntaxException | IOException | UncheckedIOException ex) {
+            throw new BuildException(ex);
+        }
+    }
+}
--- a/make/tools/anttasks/SelectToolTask.java	Wed Oct 21 15:15:36 2015 -0700
+++ b/make/tools/anttasks/SelectToolTask.java	Wed Oct 21 18:40:01 2015 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2008, 2015, 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
@@ -88,7 +88,8 @@
         },
         JAVADOC("javadoc"),
         JAVAH("javah"),
-        JAVAP("javap");
+        JAVAP("javap"),
+        JSHELL("jshell");
 
         String toolName;
         boolean bootstrap;
--- a/src/jdk.compiler/share/classes/com/sun/source/doctree/DocCommentTree.java	Wed Oct 21 15:15:36 2015 -0700
+++ b/src/jdk.compiler/share/classes/com/sun/source/doctree/DocCommentTree.java	Wed Oct 21 18:40:01 2015 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2015, 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
@@ -25,6 +25,7 @@
 
 package com.sun.source.doctree;
 
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -44,6 +45,20 @@
     List<? extends DocTree> getFirstSentence();
 
     /**
+     * Returns the entire body of a documentation comment, appearing
+     * before any block tags, including the first sentence.
+     * @return body of a documentation comment first sentence inclusive
+     *
+     * @since 1.9
+     */
+    default List<? extends DocTree> getFullBody() {
+        ArrayList<DocTree> bodyList = new ArrayList<>();
+        bodyList.addAll(getFirstSentence());
+        bodyList.addAll(getBody());
+        return bodyList;
+    }
+
+    /**
      * Returns the body of a documentation comment,
      * appearing after the first sentence, and before any block tags.
      * @return the body of a documentation comment
--- a/src/jdk.compiler/share/classes/com/sun/source/util/DocTrees.java	Wed Oct 21 15:15:36 2015 -0700
+++ b/src/jdk.compiler/share/classes/com/sun/source/util/DocTrees.java	Wed Oct 21 18:40:01 2015 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2015, 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
@@ -25,12 +25,15 @@
 
 package com.sun.source.util;
 
+import java.util.List;
+
 import javax.annotation.processing.ProcessingEnvironment;
 import javax.lang.model.element.Element;
+import javax.tools.Diagnostic;
 import javax.tools.JavaCompiler.CompilationTask;
 
 import com.sun.source.doctree.DocCommentTree;
-import javax.tools.Diagnostic;
+import com.sun.source.doctree.DocTree;
 
 /**
  * Provides access to syntax trees for doc comments.
@@ -78,6 +81,17 @@
     public abstract Element getElement(DocTreePath path);
 
     /**
+     * Returns the list of {@link DocTree} representing the first sentence of
+     * a comment.
+     *
+     * @param list the DocTree list to interrogate
+     * @return the first sentence
+     *
+     * @since 1.9
+     */
+    public abstract List<DocTree> getFirstSentence(List<? extends DocTree> list);
+
+    /**
      * Returns a utility object for accessing the source positions
      * of documentation tree nodes.
      * @return the utility object
--- a/src/jdk.compiler/share/classes/com/sun/tools/doclint/HtmlTag.java	Wed Oct 21 15:15:36 2015 -0700
+++ b/src/jdk.compiler/share/classes/com/sun/tools/doclint/HtmlTag.java	Wed Oct 21 18:40:01 2015 -0700
@@ -25,19 +25,18 @@
 
 package com.sun.tools.doclint;
 
-import java.util.Set;
 import java.util.Collections;
 import java.util.EnumMap;
 import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.Map;
-
+import java.util.Set;
 import javax.lang.model.element.Name;
 
+import com.sun.tools.javac.util.StringUtils;
+
 import static com.sun.tools.doclint.HtmlTag.Attr.*;
 
-import com.sun.tools.javac.util.StringUtils;
-
 /**
  * Enum representing HTML tags.
  *
@@ -646,15 +645,14 @@
         return map;
     }
 
-    private static final Map<String,HtmlTag> index = new HashMap<>();
+    private static final Map<String, HtmlTag> index = new HashMap<>();
     static {
         for (HtmlTag t: values()) {
             index.put(t.getText(), t);
         }
     }
 
-    static HtmlTag get(Name tagName) {
+    public static HtmlTag get(Name tagName) {
         return index.get(StringUtils.toLowerCase(tagName.toString()));
     }
-
 }
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java	Wed Oct 21 15:15:36 2015 -0700
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java	Wed Oct 21 18:40:01 2015 -0700
@@ -25,7 +25,6 @@
 
 package com.sun.tools.javac.api;
 
-import java.io.IOException;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -86,9 +85,17 @@
 import com.sun.tools.javac.tree.DCTree.DCParam;
 import com.sun.tools.javac.tree.DCTree.DCReference;
 import com.sun.tools.javac.tree.DCTree.DCText;
+import com.sun.tools.javac.tree.DocTreeMaker;
 import com.sun.tools.javac.tree.EndPosTable;
 import com.sun.tools.javac.tree.JCTree;
-import com.sun.tools.javac.tree.JCTree.*;
+import com.sun.tools.javac.tree.JCTree.JCBlock;
+import com.sun.tools.javac.tree.JCTree.JCCatch;
+import com.sun.tools.javac.tree.JCTree.JCClassDecl;
+import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
+import com.sun.tools.javac.tree.JCTree.JCExpression;
+import com.sun.tools.javac.tree.JCTree.JCIdent;
+import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
+import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
 import com.sun.tools.javac.tree.TreeCopier;
 import com.sun.tools.javac.tree.TreeInfo;
 import com.sun.tools.javac.tree.TreeMaker;
@@ -106,6 +113,7 @@
 import com.sun.tools.javac.util.Names;
 import com.sun.tools.javac.util.Pair;
 import com.sun.tools.javac.util.Position;
+
 import static com.sun.tools.javac.code.Kinds.Kind.*;
 import static com.sun.tools.javac.code.TypeTag.*;
 
@@ -132,6 +140,7 @@
     private JavacTaskImpl javacTaskImpl;
     private Names names;
     private Types types;
+    private DocTreeMaker doctreeMaker;
 
     // called reflectively from Trees.instance(CompilationTask task)
     public static JavacTrees instance(JavaCompiler.CompilationTask task) {
@@ -173,6 +182,7 @@
         memberEnter = MemberEnter.instance(context);
         names = Names.instance(context);
         types = Types.instance(context);
+        doctreeMaker = DocTreeMaker.instance(context);
 
         JavacTask t = context.get(JavacTask.class);
         if (t instanceof JavacTaskImpl)
@@ -259,7 +269,7 @@
 
         tree.accept(new DocTreeScanner<Void, Void>() {
             @Override @DefinedBy(Api.COMPILER_TREE)
- public Void scan(DocTree node, Void p) {
+            public Void scan(DocTree node, Void p) {
                 if (node != null) last[0] = node;
                 return null;
             }
@@ -356,6 +366,11 @@
         return null;
     }
 
+    @Override @DefinedBy(Api.COMPILER_TREE)
+    public java.util.List<DocTree> getFirstSentence(java.util.List<? extends DocTree> list) {
+        return doctreeMaker.getFirstSentence(list);
+    }
+
     private Symbol attributeDocReference(TreePath path, DCReference ref) {
         Env<AttrContext> env = getAttrContext(path);
 
@@ -763,7 +778,6 @@
             javacTaskImpl.enter(null);
         }
 
-
         JCCompilationUnit unit = (JCCompilationUnit) path.getCompilationUnit();
         Copier copier = createCopier(treeMaker.forToplevel(unit));
 
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java	Wed Oct 21 15:15:36 2015 -0700
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java	Wed Oct 21 18:40:01 2015 -0700
@@ -1597,6 +1597,10 @@
             }
         }
 
+        public boolean isLambdaMethod() {
+            return (flags() & LAMBDA_METHOD) == LAMBDA_METHOD;
+        }
+
         /** The implementation of this (abstract) symbol in class origin;
          *  null if none exists. Synthetic methods are not considered
          *  as possible implementations.
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ArgumentAttr.java	Wed Oct 21 15:15:36 2015 -0700
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ArgumentAttr.java	Wed Oct 21 18:40:01 2015 -0700
@@ -263,6 +263,8 @@
                 attr.memberReferenceQualifierResult(tree));
         JCMemberReference mref2 = new TreeCopier<Void>(attr.make).copy(tree);
         mref2.expr = exprTree;
+        Symbol lhsSym = TreeInfo.symbol(exprTree);
+        localEnv.info.selectSuper = lhsSym != null && lhsSym.name == lhsSym.name.table.names._super;
         Symbol res =
                 attr.rs.getMemberReference(tree, localEnv, mref2,
                         exprTree.type, tree.name);
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java	Wed Oct 21 15:15:36 2015 -0700
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java	Wed Oct 21 18:40:01 2015 -0700
@@ -2817,8 +2817,10 @@
                 //omitted as we don't know at this stage as to whether this is a
                 //raw selector (because of inference)
                 chk.validate(that.expr, env, false);
+            } else {
+                Symbol lhsSym = TreeInfo.symbol(that.expr);
+                localEnv.info.selectSuper = lhsSym != null && lhsSym.name == names._super;
             }
-
             //attrib type-arguments
             List<Type> typeargtypes = List.nil();
             if (that.typeargs != null) {
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassWriter.java	Wed Oct 21 15:15:36 2015 -0700
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassWriter.java	Wed Oct 21 18:40:01 2015 -0700
@@ -1107,8 +1107,10 @@
             endAttr(alenIdx);
             acount++;
         }
-        if (options.isSet(PARAMETERS))
-            acount += writeMethodParametersAttr(m);
+        if (options.isSet(PARAMETERS)) {
+            if (!m.isLambdaMethod()) // Per JDK-8138729, do not emit parameters table for lambda bodies.
+                acount += writeMethodParametersAttr(m);
+        }
         acount += writeMemberAttrs(m);
         acount += writeParameterAttrs(m);
         endAttrs(acountIdx, acount);
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/DocCommentParser.java	Wed Oct 21 15:15:36 2015 -0700
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/DocCommentParser.java	Wed Oct 21 18:40:01 2015 -0700
@@ -26,12 +26,8 @@
 package com.sun.tools.javac.parser;
 
 import java.text.BreakIterator;
-import java.util.Arrays;
 import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Locale;
 import java.util.Map;
-import java.util.Set;
 
 import com.sun.source.doctree.AttributeTree.ValueKind;
 import com.sun.tools.javac.parser.DocCommentParser.TagParser.Kind;
@@ -40,12 +36,10 @@
 import com.sun.tools.javac.tree.DCTree;
 import com.sun.tools.javac.tree.DCTree.DCAttribute;
 import com.sun.tools.javac.tree.DCTree.DCDocComment;
-import com.sun.tools.javac.tree.DCTree.DCEndElement;
 import com.sun.tools.javac.tree.DCTree.DCEndPosTree;
 import com.sun.tools.javac.tree.DCTree.DCErroneous;
 import com.sun.tools.javac.tree.DCTree.DCIdentifier;
 import com.sun.tools.javac.tree.DCTree.DCReference;
-import com.sun.tools.javac.tree.DCTree.DCStartElement;
 import com.sun.tools.javac.tree.DCTree.DCText;
 import com.sun.tools.javac.tree.DocTreeMaker;
 import com.sun.tools.javac.tree.JCTree;
@@ -55,9 +49,8 @@
 import com.sun.tools.javac.util.Log;
 import com.sun.tools.javac.util.Name;
 import com.sun.tools.javac.util.Names;
-import com.sun.tools.javac.util.Options;
 import com.sun.tools.javac.util.Position;
-import com.sun.tools.javac.util.StringUtils;
+
 import static com.sun.tools.javac.util.LayoutCharacters.*;
 
 /**
@@ -100,24 +93,20 @@
 
     Map<Name, TagParser> tagParsers;
 
-    DocCommentParser(ParserFactory fac, DiagnosticSource diagSource, Comment comment) {
+    public DocCommentParser(ParserFactory fac, DiagnosticSource diagSource, Comment comment) {
         this.fac = fac;
         this.diagSource = diagSource;
         this.comment = comment;
         names = fac.names;
         m = fac.docTreeMaker;
-
-        Locale locale = (fac.locale == null) ? Locale.getDefault() : fac.locale;
-
-        Options options = fac.options;
-        boolean useBreakIterator = options.isSet("breakIterator");
-        if (useBreakIterator || !locale.getLanguage().equals(Locale.ENGLISH.getLanguage()))
-            sentenceBreaker = BreakIterator.getSentenceInstance(locale);
-
         initTagParsers();
     }
 
-    DCDocComment parse() {
+    public DocCommentParser(ParserFactory fac) {
+        this(fac, null, null);
+    }
+
+    public DCDocComment parse() {
         String c = comment.getText();
         buf = new char[c.length() + 1];
         c.getChars(0, c.length(), buf, 0);
@@ -128,54 +117,11 @@
 
         List<DCTree> body = blockContent();
         List<DCTree> tags = blockTags();
+        int pos = !body.isEmpty()
+                ? body.head.pos
+                : !tags.isEmpty() ? tags.head.pos : Position.NOPOS;
 
-        // split body into first sentence and body
-        ListBuffer<DCTree> fs = new ListBuffer<>();
-        loop:
-        for (; body.nonEmpty(); body = body.tail) {
-            DCTree t = body.head;
-            switch (t.getKind()) {
-                case TEXT:
-                    String s = ((DCText) t).getBody();
-                    int i = getSentenceBreak(s);
-                    if (i > 0) {
-                        int i0 = i;
-                        while (i0 > 0 && isWhitespace(s.charAt(i0 - 1)))
-                            i0--;
-                        fs.add(m.at(t.pos).Text(s.substring(0, i0)));
-                        int i1 = i;
-                        while (i1 < s.length() && isWhitespace(s.charAt(i1)))
-                            i1++;
-                        body = body.tail;
-                        if (i1 < s.length())
-                            body = body.prepend(m.at(t.pos + i1).Text(s.substring(i1)));
-                        break loop;
-                    } else if (body.tail.nonEmpty()) {
-                        if (isSentenceBreak(body.tail.head)) {
-                            int i0 = s.length() - 1;
-                            while (i0 > 0 && isWhitespace(s.charAt(i0)))
-                                i0--;
-                            fs.add(m.at(t.pos).Text(s.substring(0, i0 + 1)));
-                            body = body.tail;
-                            break loop;
-                        }
-                    }
-                    break;
-
-                case START_ELEMENT:
-                case END_ELEMENT:
-                    if (isSentenceBreak(t))
-                        break loop;
-                    break;
-            }
-            fs.add(t);
-        }
-
-        @SuppressWarnings("unchecked")
-        DCTree first = getFirst(fs.toList(), body, tags);
-        int pos = (first == null) ? Position.NOPOS : first.pos;
-
-        DCDocComment dc = m.at(pos).DocComment(comment, fs.toList(), body, tags);
+        DCDocComment dc = m.at(pos).DocComment(comment, body, tags);
         return dc;
     }
 
@@ -331,23 +277,28 @@
             nextChar();
             if (isIdentifierStart(ch)) {
                 Name name = readTagName();
-                skipWhitespace();
+                TagParser tp = tagParsers.get(name);
 
-                TagParser tp = tagParsers.get(name);
                 if (tp == null) {
-                    DCTree text = inlineText();
+                    skipWhitespace();
+                    DCTree text = inlineText(WhitespaceRetentionPolicy.REMOVE_ALL);
                     if (text != null) {
                         nextChar();
                         return m.at(p).UnknownInlineTag(name, List.of(text)).setEndPos(bp);
                     }
-                } else if (tp.getKind() == TagParser.Kind.INLINE) {
-                    DCEndPosTree<?> tree = (DCEndPosTree<?>) tp.parse(p);
-                    if (tree != null) {
-                        return tree.setEndPos(bp);
+                } else {
+                    if (!tp.retainWhiteSpace) {
+                        skipWhitespace();
                     }
-                } else {
-                    inlineText(); // skip content
-                    nextChar();
+                    if (tp.getKind() == TagParser.Kind.INLINE) {
+                        DCEndPosTree<?> tree = (DCEndPosTree<?>) tp.parse(p);
+                        if (tree != null) {
+                            return tree.setEndPos(bp);
+                        }
+                    } else { // handle block tags (ex: @see) in inline content
+                        inlineText(WhitespaceRetentionPolicy.REMOVE_ALL); // skip content
+                        nextChar();
+                    }
                 }
             }
             return erroneous("dc.no.tag.name", p);
@@ -356,13 +307,32 @@
         }
     }
 
+    private static enum WhitespaceRetentionPolicy {
+        RETAIN_ALL,
+        REMOVE_FIRST_SPACE,
+        REMOVE_ALL
+    }
+
     /**
      * Read plain text content of an inline tag.
      * Matching pairs of { } are skipped; the text is terminated by the first
      * unmatched }. It is an error if the beginning of the next tag is detected.
      */
-    protected DCTree inlineText() throws ParseException {
-        skipWhitespace();
+    private DCTree inlineText(WhitespaceRetentionPolicy whitespacePolicy) throws ParseException {
+        switch (whitespacePolicy) {
+            case REMOVE_ALL:
+                skipWhitespace();
+                break;
+            case REMOVE_FIRST_SPACE:
+                if (ch == ' ')
+                    nextChar();
+                break;
+            case RETAIN_ALL:
+            default:
+                // do nothing
+                break;
+
+        }
         int pos = bp;
         int depth = 1;
 
@@ -742,7 +712,8 @@
                 }
                 if (ch == '>') {
                     nextChar();
-                    return m.at(p).StartElement(name, attrs, selfClosing).setEndPos(bp);
+                    DCTree dctree = m.at(p).StartElement(name, attrs, selfClosing).setEndPos(bp);
+                    return dctree;
                 }
             }
         } else if (ch == '/') {
@@ -884,15 +855,6 @@
         return m.at(pos).Erroneous(newString(pos, i + 1), diagSource, code);
     }
 
-    @SuppressWarnings("unchecked")
-    <T> T getFirst(List<T>... lists) {
-        for (List<T> list: lists) {
-            if (list.nonEmpty())
-                return list.head;
-        }
-        return null;
-    }
-
     protected boolean isIdentifierStart(char ch) {
         return Character.isUnicodeIdentifierStart(ch);
     }
@@ -916,8 +878,11 @@
     protected Name readTagName() {
         int start = bp;
         nextChar();
-        while (bp < buflen && (Character.isUnicodeIdentifierPart(ch) || ch == '.'))
+        while (bp < buflen
+                && (Character.isUnicodeIdentifierPart(ch) || ch == '.'
+                || ch == '-' || ch == ':')) {
             nextChar();
+        }
         return names.fromChars(buf, start, bp - start);
     }
 
@@ -960,59 +925,9 @@
     }
 
     protected void skipWhitespace() {
-        while (isWhitespace(ch))
+        while (isWhitespace(ch)) {
             nextChar();
-    }
-
-    protected int getSentenceBreak(String s) {
-        if (sentenceBreaker != null) {
-            sentenceBreaker.setText(s);
-            int i = sentenceBreaker.next();
-            return (i == s.length()) ? -1 : i;
         }
-
-        // scan for period followed by whitespace
-        boolean period = false;
-        for (int i = 0; i < s.length(); i++) {
-            switch (s.charAt(i)) {
-                case '.':
-                    period = true;
-                    break;
-
-                case ' ':
-                case '\f':
-                case '\n':
-                case '\r':
-                case '\t':
-                    if (period)
-                        return i;
-                    break;
-
-                default:
-                    period = false;
-                    break;
-            }
-        }
-        return -1;
-    }
-
-
-    Set<String> htmlBlockTags = new HashSet<>(Arrays.asList(
-                    "h1", "h2", "h3", "h4", "h5", "h6", "p", "pre"));
-
-    protected boolean isSentenceBreak(Name n) {
-        return htmlBlockTags.contains(StringUtils.toLowerCase(n.toString()));
-    }
-
-    protected boolean isSentenceBreak(DCTree t) {
-        switch (t.getKind()) {
-            case START_ELEMENT:
-                return isSentenceBreak(((DCStartElement) t).getName());
-
-            case END_ELEMENT:
-                return isSentenceBreak(((DCEndElement) t).getName());
-        }
-        return false;
     }
 
     /**
@@ -1026,12 +941,21 @@
     static abstract class TagParser {
         enum Kind { INLINE, BLOCK }
 
-        Kind kind;
-        DCTree.Kind treeKind;
+        final Kind kind;
+        final DCTree.Kind treeKind;
+        final boolean retainWhiteSpace;
+
 
         TagParser(Kind k, DCTree.Kind tk) {
             kind = k;
             treeKind = tk;
+            retainWhiteSpace = false;
+        }
+
+        TagParser(Kind k, DCTree.Kind tk, boolean retainWhiteSpace) {
+            kind = k;
+            treeKind = tk;
+            this.retainWhiteSpace = retainWhiteSpace;
         }
 
         Kind getKind() {
@@ -1059,9 +983,9 @@
             },
 
             // {@code text}
-            new TagParser(Kind.INLINE, DCTree.Kind.CODE) {
+            new TagParser(Kind.INLINE, DCTree.Kind.CODE, true) {
                 public DCTree parse(int pos) throws ParseException {
-                    DCTree text = inlineText();
+                    DCTree text = inlineText(WhitespaceRetentionPolicy.REMOVE_FIRST_SPACE);
                     nextChar();
                     return m.at(pos).Code((DCText) text);
                 }
@@ -1082,7 +1006,7 @@
                         nextChar();
                         return m.at(pos).DocRoot();
                     }
-                    inlineText(); // skip unexpected content
+                    inlineText(WhitespaceRetentionPolicy.REMOVE_ALL); // skip unexpected content
                     nextChar();
                     throw new ParseException("dc.unexpected.content");
                 }
@@ -1105,7 +1029,7 @@
                         nextChar();
                         return m.at(pos).InheritDoc();
                     }
-                    inlineText(); // skip unexpected content
+                    inlineText(WhitespaceRetentionPolicy.REMOVE_ALL); // skip unexpected content
                     nextChar();
                     throw new ParseException("dc.unexpected.content");
                 }
@@ -1130,9 +1054,9 @@
             },
 
             // {@literal text}
-            new TagParser(Kind.INLINE, DCTree.Kind.LITERAL) {
+            new TagParser(Kind.INLINE, DCTree.Kind.LITERAL, true) {
                 public DCTree parse(int pos) throws ParseException {
-                    DCTree text = inlineText();
+                    DCTree text = inlineText(WhitespaceRetentionPolicy.REMOVE_FIRST_SPACE);
                     nextChar();
                     return m.at(pos).Literal((DCText) text);
                 }
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java	Wed Oct 21 15:15:36 2015 -0700
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java	Wed Oct 21 18:40:01 2015 -0700
@@ -973,7 +973,7 @@
          */
         protected JCExpression foldStrings(JCExpression tree) {
             if (!allowStringFolding)
-                return null;
+                return tree;
             ListBuffer<JCExpression> opStack = new ListBuffer<>();
             ListBuffer<JCLiteral> litBuf = new ListBuffer<>();
             boolean needsFolding = false;
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DCTree.java	Wed Oct 21 15:15:36 2015 -0700
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DCTree.java	Wed Oct 21 18:40:01 2015 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2015, 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
@@ -25,7 +25,6 @@
 
 package com.sun.tools.javac.tree;
 
-
 import javax.tools.Diagnostic;
 
 import com.sun.source.doctree.*;
@@ -39,8 +38,10 @@
 import com.sun.tools.javac.util.List;
 import com.sun.tools.javac.util.Name;
 import com.sun.tools.javac.util.Position;
+
 import java.io.IOException;
 import java.io.StringWriter;
+
 import javax.tools.JavaFileObject;
 
 /**
@@ -104,14 +105,19 @@
     public static class DCDocComment extends DCTree implements DocCommentTree {
         public final Comment comment; // required for the implicit source pos table
 
+        public final List<DCTree> fullBody;
         public final List<DCTree> firstSentence;
         public final List<DCTree> body;
         public final List<DCTree> tags;
 
         public DCDocComment(Comment comment,
-                List<DCTree> firstSentence, List<DCTree> body, List<DCTree> tags) {
+                            List<DCTree> fullBody,
+                            List<DCTree> firstSentence,
+                            List<DCTree> body,
+                            List<DCTree> tags) {
             this.comment = comment;
             this.firstSentence = firstSentence;
+            this.fullBody = fullBody;
             this.body = body;
             this.tags = tags;
         }
@@ -132,6 +138,11 @@
         }
 
         @DefinedBy(Api.COMPILER_TREE)
+        public List<? extends DocTree> getFullBody() {
+            return fullBody;
+        }
+
+        @DefinedBy(Api.COMPILER_TREE)
         public List<? extends DocTree> getBody() {
             return body;
         }
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocPretty.java	Wed Oct 21 15:15:36 2015 -0700
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocPretty.java	Wed Oct 21 18:40:01 2015 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1999, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2015, 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
@@ -25,15 +25,15 @@
 
 package com.sun.tools.javac.tree;
 
+import java.io.IOException;
 import java.io.Writer;
+import java.util.List;
 
 import com.sun.source.doctree.*;
 import com.sun.source.doctree.AttributeTree.ValueKind;
 import com.sun.tools.javac.util.Convert;
 import com.sun.tools.javac.util.DefinedBy;
 import com.sun.tools.javac.util.DefinedBy.Api;
-import java.io.IOException;
-import java.util.List;
 
 /**
  * Prints out a doc comment tree.
@@ -201,14 +201,10 @@
     @DefinedBy(Api.COMPILER_TREE)
     public Void visitDocComment(DocCommentTree node, Void p) {
         try {
-            List<? extends DocTree> fs = node.getFirstSentence();
-            List<? extends DocTree> b = node.getBody();
+            List<? extends DocTree> b = node.getFullBody();
             List<? extends DocTree> t = node.getBlockTags();
-            print(fs);
-            if (!fs.isEmpty() && !b.isEmpty())
-                print(" ");
             print(b);
-            if ((!fs.isEmpty() || !b.isEmpty()) && !t.isEmpty())
+            if (!b.isEmpty() && !t.isEmpty())
                 print("\n");
             print(t, "\n");
         } catch (IOException e) {
@@ -308,7 +304,10 @@
         try {
             print("{");
             printTagName(node);
-            print(" ");
+            String body = node.getBody().getBody();
+            if (!body.isEmpty() && !Character.isWhitespace(body.charAt(0))) {
+                print(" ");
+            }
             print(node.getBody());
             print("}");
         } catch (IOException e) {
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocTreeMaker.java	Wed Oct 21 15:15:36 2015 -0700
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocTreeMaker.java	Wed Oct 21 18:40:01 2015 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2015, 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
@@ -25,19 +25,62 @@
 
 package com.sun.tools.javac.tree;
 
+import java.text.BreakIterator;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.ListIterator;
+import java.util.Locale;
+
 import com.sun.source.doctree.AttributeTree.ValueKind;
+import com.sun.source.doctree.DocTree;
 import com.sun.source.doctree.DocTree.Kind;
+import com.sun.source.doctree.EndElementTree;
+import com.sun.source.doctree.StartElementTree;
+import com.sun.tools.doclint.HtmlTag;
 
 import com.sun.tools.javac.parser.Tokens.Comment;
-import com.sun.tools.javac.tree.DCTree.*;
+import com.sun.tools.javac.tree.DCTree.DCAttribute;
+import com.sun.tools.javac.tree.DCTree.DCAuthor;
+import com.sun.tools.javac.tree.DCTree.DCComment;
+import com.sun.tools.javac.tree.DCTree.DCDeprecated;
+import com.sun.tools.javac.tree.DCTree.DCDocComment;
+import com.sun.tools.javac.tree.DCTree.DCDocRoot;
+import com.sun.tools.javac.tree.DCTree.DCEndElement;
+import com.sun.tools.javac.tree.DCTree.DCEntity;
+import com.sun.tools.javac.tree.DCTree.DCErroneous;
+import com.sun.tools.javac.tree.DCTree.DCIdentifier;
+import com.sun.tools.javac.tree.DCTree.DCInheritDoc;
+import com.sun.tools.javac.tree.DCTree.DCLink;
+import com.sun.tools.javac.tree.DCTree.DCLiteral;
+import com.sun.tools.javac.tree.DCTree.DCParam;
+import com.sun.tools.javac.tree.DCTree.DCReference;
+import com.sun.tools.javac.tree.DCTree.DCReturn;
+import com.sun.tools.javac.tree.DCTree.DCSee;
+import com.sun.tools.javac.tree.DCTree.DCSerial;
+import com.sun.tools.javac.tree.DCTree.DCSerialData;
+import com.sun.tools.javac.tree.DCTree.DCSerialField;
+import com.sun.tools.javac.tree.DCTree.DCSince;
+import com.sun.tools.javac.tree.DCTree.DCStartElement;
+import com.sun.tools.javac.tree.DCTree.DCText;
+import com.sun.tools.javac.tree.DCTree.DCThrows;
+import com.sun.tools.javac.tree.DCTree.DCUnknownBlockTag;
+import com.sun.tools.javac.tree.DCTree.DCUnknownInlineTag;
+import com.sun.tools.javac.tree.DCTree.DCValue;
+import com.sun.tools.javac.tree.DCTree.DCVersion;
 import com.sun.tools.javac.util.Context;
 import com.sun.tools.javac.util.DiagnosticSource;
 import com.sun.tools.javac.util.JCDiagnostic;
 import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
 import com.sun.tools.javac.util.List;
+import com.sun.tools.javac.util.ListBuffer;
 import com.sun.tools.javac.util.Name;
+import com.sun.tools.javac.util.Options;
+import com.sun.tools.javac.util.Pair;
 import com.sun.tools.javac.util.Position;
 
+import static com.sun.tools.doclint.HtmlTag.*;
+
 /**
  *
  *  <p><b>This is NOT part of any supported API.
@@ -50,6 +93,12 @@
     /** The context key for the tree factory. */
     protected static final Context.Key<DocTreeMaker> treeMakerKey = new Context.Key<>();
 
+    // A subset of block tags, which acts as sentence breakers, appearing
+    // anywhere but the zero'th position in the first sentence.
+    final EnumSet<HtmlTag> sentenceBreakTags;
+
+    private final BreakIterator sentenceBreaker;
+
     /** Get the TreeMaker instance. */
     public static DocTreeMaker instance(Context context) {
         DocTreeMaker instance = context.get(treeMakerKey);
@@ -71,6 +120,15 @@
         context.put(treeMakerKey, this);
         diags = JCDiagnostic.Factory.instance(context);
         this.pos = Position.NOPOS;
+        sentenceBreakTags = EnumSet.of(H1, H2, H3, H4, H5, H6, PRE, P);
+        Locale locale = (context.get(Locale.class) != null)
+                ? context.get(Locale.class)
+                : Locale.getDefault();
+        Options options = Options.instance(context);
+        boolean useBreakIterator = options.isSet("breakiterator");
+        sentenceBreaker = (useBreakIterator || !locale.getLanguage().equals(Locale.ENGLISH.getLanguage()))
+                ? BreakIterator.getSentenceInstance(locale)
+                : null;
     }
 
     /** Reassign current position.
@@ -117,9 +175,11 @@
         return tree;
     }
 
-    public DCDocComment DocComment(Comment comment, List<DCTree> firstSentence, List<DCTree> body, List<DCTree> tags) {
-        DCDocComment tree = new DCDocComment(comment, firstSentence, body, tags);
-        tree.pos = pos;
+    public DCDocComment DocComment(Comment comment, List<DCTree> fullBody, List<DCTree> tags) {
+        final int savepos = pos;
+        Pair<List<DCTree>, List<DCTree>> pair = splitBody(fullBody);
+        DCDocComment tree = new DCDocComment(comment, fullBody, pair.fst, pair.snd, tags);
+        this.pos = tree.pos = savepos;
         return tree;
     }
 
@@ -273,4 +333,155 @@
         tree.pos = pos;
         return tree;
     }
+
+    public java.util.List<DocTree> getFirstSentence(java.util.List<? extends DocTree> list) {
+        Pair<List<DCTree>, List<DCTree>> pair = splitBody(list);
+        return new ArrayList<>(pair.fst);
+    }
+
+    /*
+     * Breaks up the body tags into the first sentence and its successors.
+     * The first sentence is determined with the presence of a period, block tag,
+     * or a sentence break, as returned by the BreakIterator. Trailing
+     * whitespaces are trimmed.
+     */
+    Pair<List<DCTree>, List<DCTree>> splitBody(Collection<? extends DocTree> list) {
+        ListBuffer<DCTree> body = new ListBuffer<>();
+        // split body into first sentence and body
+        ListBuffer<DCTree> fs = new ListBuffer<>();
+        if (list.isEmpty()) {
+            return new Pair<>(fs.toList(), body.toList());
+        }
+        boolean foundFirstSentence = false;
+        ArrayList<DocTree> alist = new ArrayList<>(list);
+        ListIterator<DocTree> itr = alist.listIterator();
+        while (itr.hasNext()) {
+            boolean isFirst = itr.previousIndex() == -1;
+            DocTree dt = itr.next();
+            int spos = ((DCTree)dt).pos;
+            if (foundFirstSentence) {
+                body.add((DCTree) dt);
+                continue;
+            }
+            switch (dt.getKind()) {
+                case TEXT:
+                    DCText tt = (DCText)dt;
+                    String s = tt.getBody();
+                    int sbreak = getSentenceBreak(s);
+                    if (sbreak > 0) {
+                        s = removeTrailingWhitespace(s.substring(0, sbreak));
+                        DCText text = this.at(spos).Text(s);
+                        fs.add(text);
+                        foundFirstSentence = true;
+                        int nwPos = skipWhiteSpace(tt.getBody(), sbreak);
+                        if (nwPos > 0) {
+                            DCText text2 = this.at(spos + nwPos).Text(tt.getBody().substring(nwPos));
+                            body.add(text2);
+                        }
+                        continue;
+                    } else if (itr.hasNext()) {
+                        // if the next doctree is a break, remove trailing spaces
+                        DocTree next = itr.next();
+                        boolean sbrk = isSentenceBreak(next, false);
+                        if (sbrk) {
+                            s = removeTrailingWhitespace(s);
+                            DCText text = this.at(spos).Text(s);
+                            fs.add(text);
+                            body.add((DCTree)next);
+                            foundFirstSentence = true;
+                            continue;
+                        }
+                        // reset to previous for further processing
+                        itr.previous();
+                    }
+                    break;
+                default:
+                    if (isSentenceBreak(dt, isFirst)) {
+                        body.add((DCTree)dt);
+                        foundFirstSentence = true;
+                        continue;
+                    }
+            }
+            fs.add((DCTree)dt);
+        }
+        return new Pair<>(fs.toList(), body.toList());
+    }
+
+    /*
+     * Computes the first sentence break.
+     */
+    int defaultSentenceBreak(String s) {
+        // scan for period followed by whitespace
+        int period = -1;
+        for (int i = 0; i < s.length(); i++) {
+            switch (s.charAt(i)) {
+                case '.':
+                    period = i;
+                    break;
+
+                case ' ':
+                case '\f':
+                case '\n':
+                case '\r':
+                case '\t':
+                    if (period >= 0) {
+                        return i;
+                    }
+                    break;
+
+                default:
+                    period = -1;
+                    break;
+            }
+        }
+        return -1;
+    }
+
+    int getSentenceBreak(String s) {
+        if (sentenceBreaker == null) {
+            return defaultSentenceBreak(s);
+        }
+        sentenceBreaker.setText(s);
+        return sentenceBreaker.first();
+    }
+
+    boolean isSentenceBreak(javax.lang.model.element.Name tagName) {
+        return sentenceBreakTags.contains(get(tagName));
+    }
+
+    boolean isSentenceBreak(DocTree dt, boolean isFirstDocTree) {
+        switch (dt.getKind()) {
+            case START_ELEMENT:
+                    StartElementTree set = (StartElementTree)dt;
+                    return !isFirstDocTree && ((DCTree) dt).pos > 1 && isSentenceBreak(set.getName());
+            case END_ELEMENT:
+                    EndElementTree eet = (EndElementTree)dt;
+                    return !isFirstDocTree && ((DCTree) dt).pos > 1 && isSentenceBreak(eet.getName());
+            default:
+                return false;
+        }
+    }
+
+    /*
+     * Returns the position of the the first non-white space
+     */
+    int skipWhiteSpace(String s, int start) {
+        for (int i = start; i < s.length(); i++) {
+            char c = s.charAt(i);
+            if (!Character.isWhitespace(c)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    String removeTrailingWhitespace(String s) {
+        for (int i = s.length() - 1 ; i > 0 ; i--) {
+            char ch = s.charAt(i);
+            if (!Character.isWhitespace(ch)) {
+                return s.substring(0, i + 1);
+            }
+        }
+        return s;
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/internal/jshell/debug/InternalDebugControl.java	Wed Oct 21 18:40:01 2015 -0700
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2015, 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.
+ */
+
+package jdk.internal.jshell.debug;
+
+import java.util.HashMap;
+import java.util.Map;
+import jdk.jshell.JShell;
+
+/**
+ * Used to externally control output messages for debugging the implementation
+ * of the JShell API.  This is NOT a supported interface,
+ * @author Robert Field
+ */
+public class InternalDebugControl {
+    public static final int DBG_GEN   = 0b0000001;
+    public static final int DBG_FMGR  = 0b0000010;
+    public static final int DBG_COMPA = 0b0000100;
+    public static final int DBG_DEP   = 0b0001000;
+    public static final int DBG_EVNT  = 0b0010000;
+
+    private static Map<JShell, Integer> debugMap = null;
+
+    public static void setDebugFlags(JShell state, int flags) {
+        if (debugMap == null) {
+            debugMap = new HashMap<>();
+        }
+        debugMap.put(state, flags);
+    }
+
+    public static boolean debugEnabled(JShell state, int flag) {
+        if (debugMap == null) {
+            return false;
+        }
+        Integer flags = debugMap.get(state);
+        if (flags == null) {
+            return false;
+        }
+        return (flags & flag) != 0;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/internal/jshell/remote/RemoteAgent.java	Wed Oct 21 18:40:01 2015 -0700
@@ -0,0 +1,263 @@
+/*
+ * Copyright (c) 2014, 2015, 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.remote;
+import java.io.File;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.Socket;
+
+import java.util.ArrayList;
+import java.util.List;
+import static jdk.internal.jshell.remote.RemoteCodes.*;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * The remote agent runs in the execution process (separate from the main JShell
+ * process.  This agent loads code over a socket from the main JShell process,
+ * executes the code, and other misc,
+ * @author Robert Field
+ */
+class RemoteAgent {
+
+    private final RemoteClassLoader loader = new RemoteClassLoader();
+    private final Map<String, Class<?>> klasses = new TreeMap<>();
+
+    public static void main(String[] args) throws Exception {
+        String loopBack = null;
+        Socket socket = new Socket(loopBack, Integer.parseInt(args[0]));
+        (new RemoteAgent()).commandLoop(socket);
+    }
+
+    void commandLoop(Socket socket) throws IOException {
+        // in before out -- so we don't hang the controlling process
+        ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
+        ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
+        while (true) {
+            int cmd = in.readInt();
+            switch (cmd) {
+                case CMD_EXIT:
+                    // Terminate this process
+                    return;
+                case CMD_LOAD:
+                    // Load a generated class file over the wire
+                    try {
+                        int count = in.readInt();
+                        List<String> names = new ArrayList<>(count);
+                        for (int i = 0; i < count; ++i) {
+                            String name = in.readUTF();
+                            byte[] kb = (byte[]) in.readObject();
+                            loader.delare(name, kb);
+                            names.add(name);
+                        }
+                        for (String name : names) {
+                            Class<?> klass = loader.loadClass(name);
+                            klasses.put(name, klass);
+                            // Get class loaded to the point of, at least, preparation
+                            klass.getDeclaredMethods();
+                        }
+                        out.writeInt(RESULT_SUCCESS);
+                        out.flush();
+                    } catch (IOException | ClassNotFoundException | ClassCastException ex) {
+                        debug("*** Load failure: %s\n", ex);
+                        out.writeInt(RESULT_FAIL);
+                        out.writeUTF(ex.toString());
+                        out.flush();
+                    }
+                    break;
+                case CMD_INVOKE: {
+                    // Invoke executable entry point in loaded code
+                    String name = in.readUTF();
+                    Class<?> klass = klasses.get(name);
+                    if (klass == null) {
+                        debug("*** Invoke failure: no such class loaded %s\n", name);
+                        out.writeInt(RESULT_FAIL);
+                        out.writeUTF("no such class loaded: " + name);
+                        out.flush();
+                        break;
+                    }
+                    Method doitMethod;
+                    try {
+                        doitMethod = klass.getDeclaredMethod(DOIT_METHOD_NAME, new Class<?>[0]);
+                        doitMethod.setAccessible(true);
+                        Object res;
+                        try {
+                            clientCodeEnter();
+                            res = doitMethod.invoke(null, new Object[0]);
+                        } catch (InvocationTargetException ex) {
+                            if (ex.getCause() instanceof StopExecutionException) {
+                                expectingStop = false;
+                                throw (StopExecutionException) ex.getCause();
+                            }
+                            throw ex;
+                        } catch (StopExecutionException ex) {
+                            expectingStop = false;
+                            throw ex;
+                        } finally {
+                            clientCodeLeave();
+                        }
+                        out.writeInt(RESULT_SUCCESS);
+                        out.writeUTF(valueString(res));
+                        out.flush();
+                    } catch (InvocationTargetException ex) {
+                        Throwable cause = ex.getCause();
+                        StackTraceElement[] elems = cause.getStackTrace();
+                        if (cause instanceof RemoteResolutionException) {
+                            out.writeInt(RESULT_CORRALLED);
+                            out.writeInt(((RemoteResolutionException) cause).id);
+                        } else {
+                            out.writeInt(RESULT_EXCEPTION);
+                            out.writeUTF(cause.getClass().getName());
+                            out.writeUTF(cause.getMessage() == null ? "<none>" : cause.getMessage());
+                        }
+                        out.writeInt(elems.length);
+                        for (StackTraceElement ste : elems) {
+                            out.writeUTF(ste.getClassName());
+                            out.writeUTF(ste.getMethodName());
+                            out.writeUTF(ste.getFileName() == null ? "<none>" : ste.getFileName());
+                            out.writeInt(ste.getLineNumber());
+                        }
+                        out.flush();
+                    } catch (NoSuchMethodException | IllegalAccessException ex) {
+                        debug("*** Invoke failure: %s -- %s\n", ex, ex.getCause());
+                        out.writeInt(RESULT_FAIL);
+                        out.writeUTF(ex.toString());
+                        out.flush();
+                    } catch (StopExecutionException ex) {
+                        try {
+                            out.writeInt(RESULT_KILLED);
+                            out.flush();
+                        } catch (IOException err) {
+                            debug("*** Error writing killed result: %s -- %s\n", ex, ex.getCause());
+                        }
+                    }
+                    System.out.flush();
+                    break;
+                }
+                case CMD_VARVALUE: {
+                    // Retrieve a variable value
+                    String classname = in.readUTF();
+                    String varname = in.readUTF();
+                    Class<?> klass = klasses.get(classname);
+                    if (klass == null) {
+                        debug("*** Var value failure: no such class loaded %s\n", classname);
+                        out.writeInt(RESULT_FAIL);
+                        out.writeUTF("no such class loaded: " + classname);
+                        out.flush();
+                        break;
+                    }
+                    try {
+                        Field var = klass.getDeclaredField(varname);
+                        var.setAccessible(true);
+                        Object res = var.get(null);
+                        out.writeInt(RESULT_SUCCESS);
+                        out.writeUTF(valueString(res));
+                        out.flush();
+                    } catch (Exception ex) {
+                        debug("*** Var value failure: no such field %s.%s\n", classname, varname);
+                        out.writeInt(RESULT_FAIL);
+                        out.writeUTF("no such field loaded: " + varname + " in class: " + classname);
+                        out.flush();
+                    }
+                    break;
+                }
+                case CMD_CLASSPATH: {
+                    // Append to the claspath
+                    String cp = in.readUTF();
+                    for (String path : cp.split(File.pathSeparator)) {
+                        loader.addURL(new File(path).toURI().toURL());
+                    }
+                    out.writeInt(RESULT_SUCCESS);
+                    out.flush();
+                    break;
+                }
+                default:
+                    debug("*** Bad command code: %d\n", cmd);
+                    break;
+            }
+        }
+    }
+
+    // These three variables are used by the main JShell process in interrupting
+    // the running process.  Access is via JDI, so the reference is not visible
+    // to code inspection.
+    private boolean inClientCode; // Queried by the main process
+    private boolean expectingStop; // Set by the main process
+
+    // thrown by the main process via JDI:
+    private final StopExecutionException stopException = new StopExecutionException();
+
+    @SuppressWarnings("serial")             // serialVersionUID intentionally omitted
+    private class StopExecutionException extends ThreadDeath {
+        @Override public synchronized Throwable fillInStackTrace() {
+            return this;
+        }
+    }
+
+    void clientCodeEnter() {
+        expectingStop = false;
+        inClientCode = true;
+    }
+
+    void clientCodeLeave() {
+        inClientCode = false;
+        while (expectingStop) {
+            try {
+                Thread.sleep(0);
+            } catch (InterruptedException ex) {
+                debug("*** Sleep interrupted while waiting for stop exception: %s\n", ex);
+            }
+        }
+    }
+
+    private void debug(String format, Object... args) {
+        System.err.printf("REMOTE: "+format, args);
+    }
+
+    static String valueString(Object value) {
+        if (value == null) {
+            return "null";
+        } else if (value instanceof String) {
+            return "\"" + expunge((String)value) + "\"";
+        } else if (value instanceof Character) {
+            return "'" + value + "'";
+        } else {
+            return expunge(value.toString());
+        }
+    }
+
+    static String expunge(String s) {
+        StringBuilder sb = new StringBuilder();
+        for (String comp : prefixPattern.split(s)) {
+            sb.append(comp);
+        }
+        return sb.toString();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/internal/jshell/remote/RemoteClassLoader.java	Wed Oct 21 18:40:01 2015 -0700
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2014, 2015, 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.remote;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.CodeSource;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * Class loader wrapper which caches class files by name until requested.
+ * @author Robert Field
+ */
+class RemoteClassLoader extends URLClassLoader {
+
+    private final Map<String, byte[]> classObjects = new TreeMap<>();
+
+    RemoteClassLoader() {
+        super(new URL[0]);
+    }
+
+    void delare(String name, byte[] bytes) {
+        classObjects.put(name, bytes);
+    }
+
+    @Override
+    protected Class<?> findClass(String name) throws ClassNotFoundException {
+        byte[] b = classObjects.get(name);
+        if (b == null) {
+            return super.findClass(name);
+        }
+        return super.defineClass(name, b, 0, b.length, (CodeSource) null);
+    }
+
+    @Override
+    public void addURL(URL url) {
+        super.addURL(url);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/internal/jshell/remote/RemoteCodes.java	Wed Oct 21 18:40:01 2015 -0700
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2014, 2015, 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.remote;
+
+import java.util.regex.Pattern;
+
+/**
+ * Communication constants shared between the main process and the remote
+ * execution process
+ * @author Robert Field
+ */
+public class RemoteCodes {
+    // Command codes
+    public static final int CMD_EXIT       = 0;
+    public static final int CMD_LOAD       = 1;
+    public static final int CMD_INVOKE     = 3;
+    public static final int CMD_CLASSPATH  = 4;
+    public static final int CMD_VARVALUE   = 5;
+
+    // Return result codes
+    public static final int RESULT_SUCCESS   = 100;
+    public static final int RESULT_FAIL      = 101;
+    public static final int RESULT_EXCEPTION = 102;
+    public static final int RESULT_CORRALLED = 103;
+    public static final int RESULT_KILLED    = 104;
+
+    public static final String DOIT_METHOD_NAME = "do_it$";
+    public static final String replClass = "\\$REPL(?<num>\\d+)[A-Z]*";
+    public static final Pattern prefixPattern = Pattern.compile("(REPL\\.)?" + replClass + "[\\$\\.]?");
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/internal/jshell/remote/RemoteResolutionException.java	Wed Oct 21 18:40:01 2015 -0700
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2015, 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.remote;
+
+/**
+ * The exception thrown on the remote side upon executing a
+ * {@link jdk.jshell.Snippet.Status#RECOVERABLE_DEFINED RECOVERABLE_DEFINED}
+ * user method. This exception is not seen by the end user nor through the API.
+ * @author Robert Field
+ */
+@SuppressWarnings("serial")             // serialVersionUID intentionally omitted
+public class RemoteResolutionException extends RuntimeException {
+
+    final int id;
+
+    /**
+     * The throw of this exception is generated into the body of a
+     * {@link jdk.jshell.Snippet.Status#RECOVERABLE_DEFINED RECOVERABLE_DEFINED}
+     * method.
+     * @param id An internal identifier of the specific method
+     */
+    public RemoteResolutionException(int id) {
+        super("RemoteResolutionException");
+        this.id = id;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java	Wed Oct 21 18:40:01 2015 -0700
@@ -0,0 +1,344 @@
+/*
+ * Copyright (c) 2015, 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 jdk.jshell.SourceCodeAnalysis.CompletionInfo;
+import jdk.jshell.SourceCodeAnalysis.Suggestion;
+
+import java.awt.event.ActionListener;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.io.UncheckedIOException;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Supplier;
+
+import jdk.internal.jline.NoInterruptUnixTerminal;
+import jdk.internal.jline.Terminal;
+import jdk.internal.jline.TerminalFactory;
+import jdk.internal.jline.WindowsTerminal;
+import jdk.internal.jline.console.ConsoleReader;
+import jdk.internal.jline.console.KeyMap;
+import jdk.internal.jline.console.UserInterruptException;
+import jdk.internal.jline.console.completer.Completer;
+import jdk.internal.jshell.tool.StopDetectingInputStream.State;
+
+class ConsoleIOContext extends IOContext {
+
+    final JShellTool repl;
+    final StopDetectingInputStream input;
+    final ConsoleReader in;
+    final EditingHistory history;
+
+    String prefix = "";
+
+    ConsoleIOContext(JShellTool repl, InputStream cmdin, PrintStream cmdout) throws Exception {
+        this.repl = repl;
+        this.input = new StopDetectingInputStream(() -> repl.state.stop(), ex -> repl.hard("Error on input: %s", ex));
+        Terminal term;
+        if (System.getProperty("os.name").toLowerCase(Locale.US).contains(TerminalFactory.WINDOWS)) {
+            term = new JShellWindowsTerminal(input);
+        } else {
+            term = new JShellUnixTerminal(input);
+        }
+        term.init();
+        in = new ConsoleReader(cmdin, cmdout, term);
+        in.setExpandEvents(false);
+        in.setHandleUserInterrupt(true);
+        in.setHistory(history = new EditingHistory(JShellTool.PREFS) {
+            @Override protected CompletionInfo analyzeCompletion(String input) {
+                return repl.analysis.analyzeCompletion(input);
+            }
+        });
+        in.setBellEnabled(true);
+        in.addCompleter(new Completer() {
+            private String lastTest;
+            private int lastCursor;
+            private boolean allowSmart = false;
+            @Override public int complete(String test, int cursor, List<CharSequence> result) {
+                int[] anchor = new int[] {-1};
+                List<Suggestion> suggestions;
+                if (prefix.isEmpty() && test.trim().startsWith("/")) {
+                    suggestions = repl.commandCompletionSuggestions(test, cursor, anchor);
+                } else {
+                    int prefixLength = prefix.length();
+                    suggestions = repl.analysis.completionSuggestions(prefix + test, cursor + prefixLength, anchor);
+                    anchor[0] -= prefixLength;
+                }
+                if (!Objects.equals(lastTest, test) || lastCursor != cursor)
+                    allowSmart = true;
+
+                boolean smart = allowSmart &&
+                                suggestions.stream()
+                                           .anyMatch(s -> s.isSmart);
+
+                lastTest = test;
+                lastCursor = cursor;
+                allowSmart = !allowSmart;
+
+                suggestions.stream()
+                           .filter(s -> !smart || s.isSmart)
+                           .map(s -> s.continuation)
+                           .forEach(result::add);
+
+                boolean onlySmart = suggestions.stream()
+                                               .allMatch(s -> s.isSmart);
+
+                if (smart && !onlySmart) {
+                    Optional<String> prefix =
+                            suggestions.stream()
+                                       .map(s -> s.continuation)
+                                       .reduce(ConsoleIOContext::commonPrefix);
+
+                    String prefixStr = prefix.orElse("").substring(cursor - anchor[0]);
+                    try {
+                        in.putString(prefixStr);
+                        cursor += prefixStr.length();
+                    } catch (IOException ex) {
+                        throw new IllegalStateException(ex);
+                    }
+                    result.add("<press tab to see more>");
+                    return cursor; //anchor should not be used.
+                }
+
+                if (result.isEmpty()) {
+                    try {
+                        //provide "empty completion" feedback
+                        //XXX: this only works correctly when there is only one Completer:
+                        in.beep();
+                    } catch (IOException ex) {
+                        throw new UncheckedIOException(ex);
+                    }
+                }
+
+                return anchor[0];
+            }
+        });
+        bind(DOCUMENTATION_SHORTCUT, (ActionListener) evt -> documentation(repl));
+        bind(CTRL_UP, (ActionListener) evt -> moveHistoryToSnippet(((EditingHistory) in.getHistory())::previousSnippet));
+        bind(CTRL_DOWN, (ActionListener) evt -> moveHistoryToSnippet(((EditingHistory) in.getHistory())::nextSnippet));
+    }
+
+    @Override
+    public String readLine(String prompt, String prefix) throws IOException, InputInterruptedException {
+        this.prefix = prefix;
+        try {
+            return in.readLine(prompt);
+        } catch (UserInterruptException ex) {
+            throw (InputInterruptedException) new InputInterruptedException().initCause(ex);
+        }
+    }
+
+    @Override
+    public boolean interactiveOutput() {
+        return true;
+    }
+
+    @Override
+    public Iterable<String> currentSessionHistory() {
+        return history.currentSessionEntries();
+    }
+
+    @Override
+    public void close() throws IOException {
+        history.save();
+        in.shutdown();
+        try {
+            in.getTerminal().restore();
+        } catch (Exception ex) {
+            throw new IOException(ex);
+        }
+    }
+
+    private void moveHistoryToSnippet(Supplier<Boolean> action) {
+        if (!action.get()) {
+            try {
+                in.beep();
+            } catch (IOException ex) {
+                throw new IllegalStateException(ex);
+            }
+        } else {
+            try {
+                //could use:
+                //in.resetPromptLine(in.getPrompt(), in.getHistory().current().toString(), -1);
+                //but that would mean more re-writing on the screen, (and prints an additional
+                //empty line), so using setBuffer directly:
+                Method setBuffer = in.getClass().getDeclaredMethod("setBuffer", String.class);
+
+                setBuffer.setAccessible(true);
+                setBuffer.invoke(in, in.getHistory().current().toString());
+                in.flush();
+            } catch (ReflectiveOperationException | IOException ex) {
+                throw new IllegalStateException(ex);
+            }
+        }
+    }
+
+    private void bind(String shortcut, Object action) {
+        KeyMap km = in.getKeys();
+        for (int i = 0; i < shortcut.length(); i++) {
+            Object value = km.getBound(Character.toString(shortcut.charAt(i)));
+            if (value instanceof KeyMap) {
+                km = (KeyMap) value;
+            } else {
+                km.bind(shortcut.substring(i), action);
+            }
+        }
+    }
+
+    private static final String DOCUMENTATION_SHORTCUT = "\033\133\132"; //Shift-TAB
+    private static final String CTRL_UP = "\033\133\061\073\065\101"; //Ctrl-UP
+    private static final String CTRL_DOWN = "\033\133\061\073\065\102"; //Ctrl-DOWN
+
+    private void documentation(JShellTool repl) {
+        String buffer = in.getCursorBuffer().buffer.toString();
+        int cursor = in.getCursorBuffer().cursor;
+        String doc;
+        if (prefix.isEmpty() && buffer.trim().startsWith("/")) {
+            doc = repl.commandDocumentation(buffer, cursor);
+        } else {
+            doc = repl.analysis.documentation(prefix + buffer, cursor + prefix.length());
+        }
+
+        try {
+            if (doc != null) {
+                in.println();
+                in.println(doc);
+                in.redrawLine();
+                in.flush();
+            } else {
+                in.beep();
+            }
+        } catch (IOException ex) {
+            throw new IllegalStateException(ex);
+        }
+    }
+
+    private static String commonPrefix(String str1, String str2) {
+        for (int i = 0; i < str2.length(); i++) {
+            if (!str1.startsWith(str2.substring(0, i + 1))) {
+                return str2.substring(0, i);
+            }
+        }
+
+        return str2;
+    }
+
+    @Override
+    public boolean terminalEditorRunning() {
+        Terminal terminal = in.getTerminal();
+        if (terminal instanceof JShellUnixTerminal)
+            return ((JShellUnixTerminal) terminal).isRaw();
+        return false;
+    }
+
+    @Override
+    public void suspend() {
+        try {
+            in.getTerminal().restore();
+        } catch (Exception ex) {
+            throw new IllegalStateException(ex);
+        }
+    }
+
+    @Override
+    public void resume() {
+        try {
+            in.getTerminal().init();
+        } catch (Exception ex) {
+            throw new IllegalStateException(ex);
+        }
+    }
+
+    public void beforeUserCode() {
+        input.setState(State.BUFFER);
+    }
+
+    public void afterUserCode() {
+        input.setState(State.WAIT);
+    }
+
+    @Override
+    public void replaceLastHistoryEntry(String source) {
+        history.fullHistoryReplace(source);
+    }
+
+    private static final class JShellUnixTerminal extends NoInterruptUnixTerminal {
+
+        private final StopDetectingInputStream input;
+
+        public JShellUnixTerminal(StopDetectingInputStream input) throws Exception {
+            this.input = input;
+        }
+
+        public boolean isRaw() {
+            try {
+                return getSettings().get("-a").contains("-icanon");
+            } catch (IOException | InterruptedException ex) {
+                return false;
+            }
+        }
+
+        @Override
+        public InputStream wrapInIfNeeded(InputStream in) throws IOException {
+            return input.setInputStream(super.wrapInIfNeeded(in));
+        }
+
+        @Override
+        public void disableInterruptCharacter() {
+        }
+
+        @Override
+        public void enableInterruptCharacter() {
+        }
+
+    }
+
+    private static final class JShellWindowsTerminal extends WindowsTerminal {
+
+        private final StopDetectingInputStream input;
+
+        public JShellWindowsTerminal(StopDetectingInputStream input) throws Exception {
+            this.input = input;
+        }
+
+        @Override
+        public void init() throws Exception {
+            super.init();
+            setAnsiSupported(false);
+        }
+
+        @Override
+        public InputStream wrapInIfNeeded(InputStream in) throws IOException {
+            return input.setInputStream(super.wrapInIfNeeded(in));
+        }
+
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/EditPad.java	Wed Oct 21 18:40:01 2015 -0700
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2015, 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.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.event.KeyEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.util.concurrent.CountDownLatch;
+import java.util.function.Consumer;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.SwingUtilities;
+
+/**
+ * A minimal Swing editor as a fallback when the user does not specify an
+ * external editor.
+ */
+@SuppressWarnings("serial")             // serialVersionUID intentionally omitted
+public class EditPad extends JFrame implements Runnable {
+    private final Consumer<String> errorHandler; // For possible future error handling
+    private final String initialText;
+    private final CountDownLatch closeLock;
+    private final Consumer<String> saveHandler;
+
+    EditPad(Consumer<String> errorHandler, String initialText,
+            CountDownLatch closeLock, Consumer<String> saveHandler) {
+        super("JShell Edit Pad");
+        this.errorHandler = errorHandler;
+        this.initialText = initialText;
+        this.closeLock = closeLock;
+        this.saveHandler = saveHandler;
+    }
+
+    @Override
+    public void run() {
+        addWindowListener(new WindowAdapter() {
+            @Override
+            public void windowClosing(WindowEvent e) {
+                EditPad.this.dispose();
+                closeLock.countDown();
+            }
+        });
+        setLocationRelativeTo(null);
+        setLayout(new BorderLayout());
+        JTextArea textArea = new JTextArea(initialText);
+        add(new JScrollPane(textArea), BorderLayout.CENTER);
+        add(buttons(textArea), BorderLayout.SOUTH);
+
+        setSize(800, 600);
+        setVisible(true);
+    }
+
+    private JPanel buttons(JTextArea textArea) {
+        FlowLayout flow = new FlowLayout();
+        flow.setHgap(35);
+        JPanel buttons = new JPanel(flow);
+        JButton cancel = new JButton("Cancel");
+        cancel.setMnemonic(KeyEvent.VK_C);
+        JButton accept = new JButton("Accept");
+        accept.setMnemonic(KeyEvent.VK_A);
+        JButton exit = new JButton("Exit");
+        exit.setMnemonic(KeyEvent.VK_X);
+        buttons.add(cancel);
+        buttons.add(accept);
+        buttons.add(exit);
+
+        cancel.addActionListener(e -> {
+            close();
+        });
+        accept.addActionListener(e -> {
+            saveHandler.accept(textArea.getText());
+        });
+        exit.addActionListener(e -> {
+            saveHandler.accept(textArea.getText());
+            close();
+        });
+
+        return buttons;
+    }
+
+    private void close() {
+        setVisible(false);
+        dispose();
+        closeLock.countDown();
+    }
+
+    public static void edit(Consumer<String> errorHandler, String initialText,
+            Consumer<String> saveHandler) {
+        CountDownLatch closeLock = new CountDownLatch(1);
+        SwingUtilities.invokeLater(
+                new EditPad(errorHandler, initialText, closeLock, saveHandler));
+        do {
+            try {
+                closeLock.await();
+                break;
+            } catch (InterruptedException ex) {
+                // ignore and loop
+            }
+        } while (true);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/EditingHistory.java	Wed Oct 21 18:40:01 2015 -0700
@@ -0,0 +1,381 @@
+/*
+ * Copyright (c) 2015, 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.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Set;
+import java.util.prefs.BackingStoreException;
+import java.util.prefs.Preferences;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import jdk.internal.jline.console.history.History;
+import jdk.internal.jline.console.history.History.Entry;
+import jdk.internal.jline.console.history.MemoryHistory;
+import jdk.jshell.SourceCodeAnalysis.CompletionInfo;
+
+/*Public for tests (HistoryTest).
+ */
+public abstract class EditingHistory implements History {
+
+    private final Preferences prefs;
+    private final History fullHistory;
+    private History currentDelegate;
+
+    protected EditingHistory(Preferences prefs) {
+        this.prefs = prefs;
+        this.fullHistory = new MemoryHistory();
+        this.currentDelegate = fullHistory;
+        load();
+    }
+
+    @Override
+    public int size() {
+        return currentDelegate.size();
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return currentDelegate.isEmpty();
+    }
+
+    @Override
+    public int index() {
+        return currentDelegate.index();
+    }
+
+    @Override
+    public void clear() {
+        if (currentDelegate != fullHistory)
+            throw new IllegalStateException("narrowed");
+        currentDelegate.clear();
+    }
+
+    @Override
+    public CharSequence get(int index) {
+        return currentDelegate.get(index);
+    }
+
+    @Override
+    public void add(CharSequence line) {
+        NarrowingHistoryLine currentLine = null;
+        int origIndex = fullHistory.index();
+        int fullSize;
+        try {
+            fullHistory.moveToEnd();
+            fullSize = fullHistory.index();
+            if (currentDelegate == fullHistory) {
+                if (origIndex < fullHistory.index()) {
+                    for (Entry entry : fullHistory) {
+                        if (!(entry.value() instanceof NarrowingHistoryLine))
+                            continue;
+                        int[] cluster = ((NarrowingHistoryLine) entry.value()).span;
+                        if (cluster[0] == origIndex && cluster[1] > cluster[0]) {
+                            currentDelegate = new MemoryHistory();
+                            for (int i = cluster[0]; i <= cluster[1]; i++) {
+                                currentDelegate.add(fullHistory.get(i));
+                            }
+                        }
+                    }
+                }
+            }
+            fullHistory.moveToEnd();
+            while (fullHistory.previous()) {
+                CharSequence c = fullHistory.current();
+                if (c instanceof NarrowingHistoryLine) {
+                    currentLine = (NarrowingHistoryLine) c;
+                    break;
+                }
+            }
+        } finally {
+            fullHistory.moveTo(origIndex);
+        }
+        if (currentLine == null || currentLine.span[1] != (-1)) {
+            line = currentLine = new NarrowingHistoryLine(line, fullSize);
+        }
+        StringBuilder complete = new StringBuilder();
+        for (int i = currentLine.span[0]; i < fullSize; i++) {
+            complete.append(fullHistory.get(i));
+        }
+        complete.append(line);
+        if (analyzeCompletion(complete.toString()).completeness.isComplete) {
+            currentLine.span[1] = fullSize; //TODO: +1?
+            currentDelegate = fullHistory;
+        }
+        fullHistory.add(line);
+    }
+
+    protected abstract CompletionInfo analyzeCompletion(String input);
+
+    @Override
+    public void set(int index, CharSequence item) {
+        if (currentDelegate != fullHistory)
+            throw new IllegalStateException("narrowed");
+        currentDelegate.set(index, item);
+    }
+
+    @Override
+    public CharSequence remove(int i) {
+        if (currentDelegate != fullHistory)
+            throw new IllegalStateException("narrowed");
+        return currentDelegate.remove(i);
+    }
+
+    @Override
+    public CharSequence removeFirst() {
+        if (currentDelegate != fullHistory)
+            throw new IllegalStateException("narrowed");
+        return currentDelegate.removeFirst();
+    }
+
+    @Override
+    public CharSequence removeLast() {
+        if (currentDelegate != fullHistory)
+            throw new IllegalStateException("narrowed");
+        return currentDelegate.removeLast();
+    }
+
+    @Override
+    public void replace(CharSequence item) {
+        if (currentDelegate != fullHistory)
+            throw new IllegalStateException("narrowed");
+        currentDelegate.replace(item);
+    }
+
+    @Override
+    public ListIterator<Entry> entries(int index) {
+        return currentDelegate.entries(index);
+    }
+
+    @Override
+    public ListIterator<Entry> entries() {
+        return currentDelegate.entries();
+    }
+
+    @Override
+    public Iterator<Entry> iterator() {
+        return currentDelegate.iterator();
+    }
+
+    @Override
+    public CharSequence current() {
+        return currentDelegate.current();
+    }
+
+    @Override
+    public boolean previous() {
+        return currentDelegate.previous();
+    }
+
+    @Override
+    public boolean next() {
+        return currentDelegate.next();
+    }
+
+    @Override
+    public boolean moveToFirst() {
+        return currentDelegate.moveToFirst();
+    }
+
+    @Override
+    public boolean moveToLast() {
+        return currentDelegate.moveToLast();
+    }
+
+    @Override
+    public boolean moveTo(int index) {
+        return currentDelegate.moveTo(index);
+    }
+
+    @Override
+    public void moveToEnd() {
+        currentDelegate.moveToEnd();
+    }
+
+    public boolean previousSnippet() {
+        for (int i = index() - 1; i >= 0; i--) {
+            if (get(i) instanceof NarrowingHistoryLine) {
+                moveTo(i);
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    public boolean nextSnippet() {
+        for (int i = index() + 1; i < size(); i++) {
+            if (get(i) instanceof NarrowingHistoryLine) {
+                moveTo(i);
+                return true;
+            }
+        }
+
+        if (index() < size()) {
+            moveToEnd();
+            return true;
+        }
+
+        return false;
+    }
+
+    private static final String HISTORY_LINE_PREFIX = "HISTORY_LINE_";
+    private static final String HISTORY_SNIPPET_START = "HISTORY_SNIPPET";
+
+    public final void load() {
+        try {
+            Set<Integer> snippetsStart = new HashSet<>();
+            for (String start : prefs.get(HISTORY_SNIPPET_START, "").split(";")) {
+                if (!start.isEmpty())
+                    snippetsStart.add(Integer.parseInt(start));
+            }
+            List<String> keys = Stream.of(prefs.keys()).sorted().collect(Collectors.toList());
+            NarrowingHistoryLine currentHistoryLine = null;
+            int currentLine = 0;
+            for (String key : keys) {
+                if (!key.startsWith(HISTORY_LINE_PREFIX))
+                    continue;
+                CharSequence line = prefs.get(key, "");
+                if (snippetsStart.contains(currentLine)) {
+                    class PersistentNarrowingHistoryLine extends NarrowingHistoryLine implements PersistentEntryMarker {
+                        public PersistentNarrowingHistoryLine(CharSequence delegate, int start) {
+                            super(delegate, start);
+                        }
+                    }
+                    line = currentHistoryLine = new PersistentNarrowingHistoryLine(line, currentLine);
+                } else {
+                    class PersistentLine implements CharSequence, PersistentEntryMarker {
+                        private final CharSequence delegate;
+                        public PersistentLine(CharSequence delegate) {
+                            this.delegate = delegate;
+                        }
+                        @Override public int length() {
+                            return delegate.length();
+                        }
+                        @Override public char charAt(int index) {
+                            return delegate.charAt(index);
+                        }
+                        @Override public CharSequence subSequence(int start, int end) {
+                            return delegate.subSequence(start, end);
+                        }
+                        @Override public String toString() {
+                            return delegate.toString();
+                        }
+                    }
+                    line = new PersistentLine(line);
+                }
+                if (currentHistoryLine != null)
+                    currentHistoryLine.span[1] = currentLine;
+                currentLine++;
+                fullHistory.add(line);
+            }
+            currentLine = 0;
+        } catch (BackingStoreException ex) {
+            throw new IllegalStateException(ex);
+        }
+    }
+
+    public void save() {
+        try {
+            for (String key : prefs.keys()) {
+                if (key.startsWith(HISTORY_LINE_PREFIX))
+                    prefs.remove(key);
+            }
+            Iterator<Entry> entries = fullHistory.iterator();
+            if (entries.hasNext()) {
+                int len = (int) Math.ceil(Math.log10(fullHistory.size()+1));
+                String format = HISTORY_LINE_PREFIX + "%0" + len + "d";
+                StringBuilder snippetStarts = new StringBuilder();
+                String snippetStartDelimiter = "";
+                while (entries.hasNext()) {
+                    Entry entry = entries.next();
+                    prefs.put(String.format(format, entry.index()), entry.value().toString());
+                    if (entry.value() instanceof NarrowingHistoryLine) {
+                        snippetStarts.append(snippetStartDelimiter);
+                        snippetStarts.append(entry.index());
+                        snippetStartDelimiter = ";";
+                    }
+                }
+                prefs.put(HISTORY_SNIPPET_START, snippetStarts.toString());
+            }
+        } catch (BackingStoreException ex) {
+            throw new IllegalStateException(ex);
+        }
+    }
+
+    public List<String> currentSessionEntries() {
+        List<String> result = new ArrayList<>();
+
+        for (Entry e : fullHistory) {
+            if (!(e.value() instanceof PersistentEntryMarker)) {
+                result.add(e.value().toString());
+            }
+        }
+
+        return result;
+    }
+
+    void fullHistoryReplace(String source) {
+        fullHistory.replace(source);
+    }
+
+    private class NarrowingHistoryLine implements CharSequence {
+        private final CharSequence delegate;
+        private final int[] span;
+
+        public NarrowingHistoryLine(CharSequence delegate, int start) {
+            this.delegate = delegate;
+            this.span = new int[] {start, -1};
+        }
+
+        @Override
+        public int length() {
+            return delegate.length();
+        }
+
+        @Override
+        public char charAt(int index) {
+            return delegate.charAt(index);
+        }
+
+        @Override
+        public CharSequence subSequence(int start, int end) {
+            return delegate.subSequence(start, end);
+        }
+
+        @Override
+        public String toString() {
+            return delegate.toString();
+        }
+
+    }
+
+    private interface PersistentEntryMarker {}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ExternalEditor.java	Wed Oct 21 18:40:01 2015 -0700
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2015, 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.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.ClosedWatchServiceException;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.WatchKey;
+import java.nio.file.WatchService;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
+import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
+import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
+
+/**
+ * Wrapper for controlling an external editor.
+ */
+public class ExternalEditor {
+    private final Consumer<String> errorHandler;
+    private final Consumer<String> saveHandler;
+    private final IOContext input;
+
+    private WatchService watcher;
+    private Thread watchedThread;
+    private Path dir;
+    private Path tmpfile;
+
+    ExternalEditor(Consumer<String> errorHandler, Consumer<String> saveHandler, IOContext input) {
+        this.errorHandler = errorHandler;
+        this.saveHandler = saveHandler;
+        this.input = input;
+    }
+
+    private void edit(String cmd, String initialText) {
+        try {
+            setupWatch(initialText);
+            launch(cmd);
+        } catch (IOException ex) {
+            errorHandler.accept(ex.getMessage());
+        }
+    }
+
+    /**
+     * Creates a WatchService and registers the given directory
+     */
+    private void setupWatch(String initialText) throws IOException {
+        this.watcher = FileSystems.getDefault().newWatchService();
+        this.dir = Files.createTempDirectory("REPL");
+        this.tmpfile = Files.createTempFile(dir, null, ".repl");
+        Files.write(tmpfile, initialText.getBytes(Charset.forName("UTF-8")));
+        dir.register(watcher,
+                ENTRY_CREATE,
+                ENTRY_DELETE,
+                ENTRY_MODIFY);
+        watchedThread = new Thread(() -> {
+            for (;;) {
+                WatchKey key;
+                try {
+                    key = watcher.take();
+                } catch (ClosedWatchServiceException ex) {
+                    break;
+                } catch (InterruptedException ex) {
+                    continue; // tolerate an intrupt
+                }
+
+                if (!key.pollEvents().isEmpty()) {
+                    if (!input.terminalEditorRunning()) {
+                        saveFile();
+                    }
+                }
+
+                boolean valid = key.reset();
+                if (!valid) {
+                    errorHandler.accept("Invalid key");
+                    break;
+                }
+            }
+        });
+        watchedThread.start();
+    }
+
+    private void launch(String cmd) throws IOException {
+        ProcessBuilder pb = new ProcessBuilder(cmd, tmpfile.toString());
+        pb = pb.inheritIO();
+
+        try {
+            input.suspend();
+            Process process = pb.start();
+            process.waitFor();
+        } catch (IOException ex) {
+            errorHandler.accept("process IO failure: " + ex.getMessage());
+        } catch (InterruptedException ex) {
+            errorHandler.accept("process interrupt: " + ex.getMessage());
+        } finally {
+            try {
+                watcher.close();
+                watchedThread.join(); //so that saveFile() is finished.
+                saveFile();
+            } catch (InterruptedException ex) {
+                errorHandler.accept("process interrupt: " + ex.getMessage());
+            } finally {
+                input.resume();
+            }
+        }
+    }
+
+    private void saveFile() {
+        try {
+            saveHandler.accept(Files.lines(tmpfile).collect(Collectors.joining("\n", "", "\n")));
+        } catch (IOException ex) {
+            errorHandler.accept("Failure in read edit file: " + ex.getMessage());
+        }
+    }
+
+    static void edit(String cmd, Consumer<String> errorHandler, String initialText,
+            Consumer<String> saveHandler, IOContext input) {
+        ExternalEditor ed = new ExternalEditor(errorHandler,  saveHandler, input);
+        ed.edit(cmd, initialText);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/IOContext.java	Wed Oct 21 18:40:01 2015 -0700
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2015, 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.IOException;
+
+/**
+ * Interface for defining user interaction with the shell.
+ * @author Robert Field
+ */
+abstract class IOContext implements AutoCloseable {
+
+    @Override
+    public abstract void close() throws IOException;
+
+    public abstract String readLine(String prompt, String prefix) throws IOException, InputInterruptedException;
+
+    public abstract boolean interactiveOutput();
+
+    public abstract Iterable<String> currentSessionHistory();
+
+    public abstract  boolean terminalEditorRunning();
+
+    public abstract void suspend();
+
+    public abstract void resume();
+
+    public abstract void beforeUserCode();
+
+    public abstract void afterUserCode();
+
+    public abstract void replaceLastHistoryEntry(String source);
+
+    class InputInterruptedException extends Exception {
+        private static final long serialVersionUID = 1L;
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java	Wed Oct 21 18:40:01 2015 -0700
@@ -0,0 +1,1804 @@
+/*
+ * Copyright (c) 2014, 2015, 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.BufferedWriter;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.nio.charset.Charset;
+import java.nio.file.AccessDeniedException;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+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.Diag;
+import jdk.jshell.EvalException;
+import jdk.jshell.JShell;
+import jdk.jshell.Snippet;
+import jdk.jshell.DeclarationSnippet;
+import jdk.jshell.TypeDeclSnippet;
+import jdk.jshell.MethodSnippet;
+import jdk.jshell.PersistentSnippet;
+import jdk.jshell.VarSnippet;
+import jdk.jshell.ExpressionSnippet;
+import jdk.jshell.Snippet.Status;
+import jdk.jshell.SourceCodeAnalysis;
+import jdk.jshell.SourceCodeAnalysis.CompletionInfo;
+import jdk.jshell.SourceCodeAnalysis.Suggestion;
+import jdk.jshell.SnippetEvent;
+import jdk.jshell.UnresolvedReferenceException;
+import jdk.jshell.Snippet.SubKind;
+import jdk.jshell.JShell.Subscription;
+
+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.MissingResourceException;
+import java.util.ResourceBundle;
+import static java.util.stream.Collectors.toList;
+
+/**
+ * Command line REPL tool for Java using the JShell API.
+ * @author Robert Field
+ */
+public class JShellTool {
+
+    private static final Pattern LINEBREAK = Pattern.compile("\\R");
+    private static final Pattern HISTORY_ALL_FILENAME = Pattern.compile(
+            "((?<cmd>(all|history))(\\z|\\p{javaWhitespace}+))?(?<filename>.*)");
+
+    final InputStream cmdin;
+    final PrintStream cmdout;
+    final PrintStream cmderr;
+    final PrintStream console;
+    final InputStream userin;
+    final PrintStream userout;
+    final PrintStream usererr;
+
+    /**
+     * The constructor for the tool (used by tool launch via main and by test
+     * harnesses to capture ins and outs.
+     * @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 (not yet functional)
+     * @param userout code execution output  -- System.out.printf("hi")
+     * @param usererr code execution error stream  -- System.err.printf("Oops")
+     */
+    public JShellTool(InputStream cmdin, PrintStream cmdout, PrintStream cmderr,
+            PrintStream console,
+            InputStream userin, PrintStream userout, PrintStream usererr) {
+        this.cmdin = cmdin;
+        this.cmdout = cmdout;
+        this.cmderr = cmderr;
+        this.console = console;
+        this.userin = userin;
+        this.userout = userout;
+        this.usererr = usererr;
+    }
+
+    private IOContext input = null;
+    private boolean regenerateOnDeath = true;
+    private boolean live = false;
+
+    SourceCodeAnalysis analysis;
+    JShell state = null;
+    Subscription shutdownSubscription = null;
+
+    private boolean debug = false;
+    private boolean displayPrompt = true;
+    public boolean testPrompt = false;
+    private Feedback feedback = Feedback.Default;
+    private String cmdlineClasspath = null;
+    private String cmdlineStartup = null;
+    private String editor = null;
+
+    static final Preferences PREFS = Preferences.userRoot().node("tool/REPL");
+
+    static final String STARTUP_KEY = "STARTUP";
+
+    static final String DEFAULT_STARTUP =
+            "\n" +
+            "import java.util.*;\n" +
+            "import java.io.*;\n" +
+            "import java.math.*;\n" +
+            "import java.net.*;\n" +
+            "import java.util.concurrent.*;\n" +
+            "import java.util.prefs.*;\n" +
+            "import java.util.regex.*;\n" +
+            "void printf(String format, Object... args) { System.out.printf(format, args); }\n";
+
+    // Tool id (tid) mapping
+    NameSpace mainNamespace;
+    NameSpace startNamespace;
+    NameSpace errorNamespace;
+    NameSpace currentNameSpace;
+    Map<Snippet,SnippetInfo> mapSnippet;
+
+    void debug(String format, Object... args) {
+        if (debug) {
+            cmderr.printf(format + "\n", args);
+        }
+    }
+
+    /**
+     * For more verbose feedback modes
+     * @param format printf format
+     * @param args printf args
+     */
+    void fluff(String format, Object... args) {
+        if (feedback() != Feedback.Off && feedback() != Feedback.Concise) {
+            hard(format, args);
+        }
+    }
+
+    /**
+     * For concise feedback mode only
+     * @param format printf format
+     * @param args printf args
+     */
+    void concise(String format, Object... args) {
+        if (feedback() == Feedback.Concise) {
+            hard(format, args);
+        }
+    }
+
+    /**
+     * For all feedback modes -- must show
+     * @param format printf format
+     * @param args printf args
+     */
+    void hard(String format, Object... args) {
+        cmdout.printf("|  " + format + "\n", args);
+    }
+
+    /**
+     * 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;
+        }
+    }
+
+    /**
+     * Normal start entry point
+     * @param args
+     * @throws Exception
+     */
+    public static void main(String[] args) throws Exception {
+        new JShellTool(System.in, System.out, System.err, System.out,
+                 new ByteArrayInputStream(new byte[0]), System.out, System.err)
+                .start(args);
+    }
+
+    public void start(String[] args) throws Exception {
+        List<String> loadList = processCommandArgs(args);
+        if (loadList == null) {
+            // Abort
+            return;
+        }
+        try (IOContext in = new ConsoleIOContext(this, cmdin, console)) {
+            start(in, loadList);
+        }
+    }
+
+    private void start(IOContext in, List<String> loadList) {
+        resetState(); // Initialize
+
+        for (String loadFile : loadList) {
+            cmdOpen(loadFile);
+        }
+
+        if (regenerateOnDeath) {
+            fluff("Welcome to JShell -- Version %s", version());
+            fluff("Type /help for help");
+        }
+
+        try {
+            while (regenerateOnDeath) {
+                if (!live) {
+                    resetState();
+                }
+                run(in);
+            }
+        } finally {
+            closeState();
+        }
+    }
+
+    /**
+     * Process the command line arguments.
+     * Set options.
+     * @param args the command line arguments
+     * @return the list of files to be loaded
+     */
+    private List<String> processCommandArgs(String[] args) {
+        List<String> loadList = new ArrayList<>();
+        Iterator<String> ai = Arrays.asList(args).iterator();
+        while (ai.hasNext()) {
+            String arg = ai.next();
+            if (arg.startsWith("-")) {
+                switch (arg) {
+                    case "-classpath":
+                    case "-cp":
+                        if (cmdlineClasspath != null) {
+                            cmderr.printf("Conflicting -classpath option.\n");
+                            return null;
+                        }
+                        if (ai.hasNext()) {
+                            cmdlineClasspath = ai.next();
+                        } else {
+                            cmderr.printf("Argument to -classpath missing.\n");
+                            return null;
+                        }
+                        break;
+                    case "-help":
+                        printUsage();
+                        return null;
+                    case "-version":
+                        cmdout.printf("jshell %s\n", version());
+                        return null;
+                    case "-fullversion":
+                        cmdout.printf("jshell %s\n", fullVersion());
+                        return null;
+                    case "-startup":
+                        if (cmdlineStartup != null) {
+                            cmderr.printf("Conflicting -startup or -nostartup option.\n");
+                            return null;
+                        }
+                        if (ai.hasNext()) {
+                            String filename = ai.next();
+                            try {
+                                byte[] encoded = Files.readAllBytes(Paths.get(filename));
+                                cmdlineStartup = new String(encoded);
+                            } catch (AccessDeniedException e) {
+                                hard("File '%s' for start-up is not accessible.", filename);
+                            } catch (NoSuchFileException e) {
+                                hard("File '%s' for start-up is not found.", filename);
+                            } catch (Exception e) {
+                                hard("Exception while reading start-up file: %s", e);
+                            }
+                        } else {
+                            cmderr.printf("Argument to -startup missing.\n");
+                            return null;
+                        }
+                        break;
+                    case "-nostartup":
+                        if (cmdlineStartup != null && !cmdlineStartup.isEmpty()) {
+                            cmderr.printf("Conflicting -startup option.\n");
+                            return null;
+                        }
+                        cmdlineStartup = "";
+                        break;
+                    default:
+                        cmderr.printf("Unknown option: %s\n", arg);
+                        printUsage();
+                        return null;
+                }
+            } else {
+                loadList.add(arg);
+            }
+        }
+        return loadList;
+    }
+
+    private void printUsage() {
+        cmdout.printf("Usage:   jshell <options> <load files>\n");
+        cmdout.printf("where possible options include:\n");
+        cmdout.printf("  -classpath <path>          Specify where to find user class files\n");
+        cmdout.printf("  -cp <path>                 Specify where to find user class files\n");
+        cmdout.printf("  -startup <file>            One run replacement for the start-up definitions\n");
+        cmdout.printf("  -nostartup                 Do not run the start-up definitions\n");
+        cmdout.printf("  -help                      Print a synopsis of standard options\n");
+        cmdout.printf("  -version                   Version information\n");
+    }
+
+    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;
+
+        state = 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))
+                .build();
+        analysis = state.sourceCodeAnalysis();
+        shutdownSubscription = state.onShutdown((JShell deadState) -> {
+            if (deadState == state) {
+                hard("State engine terminated.  See /history");
+                live = false;
+            }
+        });
+        live = true;
+
+        if (cmdlineClasspath != null) {
+            state.addToClasspath(cmdlineClasspath);
+        }
+
+
+        String start;
+        if (cmdlineStartup == null) {
+            start = PREFS.get(STARTUP_KEY, "<nada>");
+            if (start.equals("<nada>")) {
+                start = DEFAULT_STARTUP;
+                PREFS.put(STARTUP_KEY, DEFAULT_STARTUP);
+            }
+        } else {
+            start = cmdlineStartup;
+        }
+        try (IOContext suin = new FileScannerIOContext(new StringReader(start))) {
+            run(suin);
+        } catch (Exception ex) {
+            hard("Unexpected exception reading start-up: %s\n", ex);
+        }
+        currentNameSpace = mainNamespace;
+    }
+
+    private void closeState() {
+        live = false;
+        JShell oldState = state;
+        if (oldState != 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 {
+            String incomplete = "";
+            while (live) {
+                String prompt;
+                if (in.interactiveOutput() && displayPrompt) {
+                    prompt = testPrompt
+                                    ? incomplete.isEmpty()
+                                            ? "\u0005" //ENQ
+                                            : "\u0006" //ACK
+                                    : incomplete.isEmpty()
+                                            ? feedback() == Feedback.Concise
+                                                    ? "-> "
+                                                    : "\n-> "
+                                            : ">> "
+                    ;
+                } else {
+                    prompt = "";
+                }
+                String raw;
+                try {
+                    raw = in.readLine(prompt, incomplete);
+                } catch (InputInterruptedException ex) {
+                    //input interrupted - clearing current state
+                    incomplete = "";
+                    continue;
+                }
+                if (raw == null) {
+                    //EOF
+                    if (in.interactiveOutput()) {
+                        // End after user ctrl-D
+                        regenerateOnDeath = false;
+                    }
+                    break;
+                }
+                String trimmed = trimEnd(raw);
+                if (!trimmed.isEmpty()) {
+                    String line = incomplete + trimmed;
+
+                    // No commands in the middle of unprocessed source
+                    if (incomplete.isEmpty() && line.startsWith("/") && !line.startsWith("//") && !line.startsWith("/*")) {
+                        processCommand(line.trim());
+                    } else {
+                        incomplete = processSourceCatchingReset(line);
+                    }
+                }
+            }
+        } catch (IOException ex) {
+            hard("Unexpected exception: %s\n", ex);
+        } finally {
+            input = oldInput;
+        }
+    }
+
+    private String processSourceCatchingReset(String src) {
+        try {
+            input.beforeUserCode();
+            return processSource(src);
+        } catch (IllegalStateException ex) {
+            hard("Resetting...");
+            live = false; // Make double sure
+            return "";
+        } finally {
+            input.afterUserCode();
+        }
+    }
+
+    private void processCommand(String cmd) {
+        try {
+            //handle "/[number]"
+            cmdUseHistoryEntry(Integer.parseInt(cmd.substring(1)));
+            return ;
+        } catch (NumberFormatException ex) {
+            //ignore
+        }
+        String arg = "";
+        int idx = cmd.indexOf(' ');
+        if (idx > 0) {
+            arg = cmd.substring(idx + 1).trim();
+            cmd = cmd.substring(0, idx);
+        }
+        Command command = commands.get(cmd);
+        if (command == null || command.kind == CommandKind.HELP_ONLY) {
+            hard("No such command: %s", cmd);
+            fluff("Type /help for help.");
+        } else {
+            command.run.accept(arg);
+        }
+    }
+
+    private 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[] aliases;
+        public final String params;
+        public final String description;
+        public final Consumer<String> run;
+        public final CompletionProvider completions;
+        public final CommandKind kind;
+
+        public Command(String command, String alias, String params, String description, Consumer<String> run, CompletionProvider completions) {
+            this(command, alias, params, description, run, completions, CommandKind.NORMAL);
+        }
+
+        public Command(String command, String alias, String params, String description, Consumer<String> run, CompletionProvider completions, CommandKind kind) {
+            this.aliases = alias != null ? new String[] {command, alias} : new String[] {command};
+            this.params = params;
+            this.description = description;
+            this.run = run;
+            this.completions = completions;
+            this.kind = kind;
+        }
+
+    }
+
+    interface CompletionProvider {
+        List<Suggestion> completionSuggestions(String input, int cursor, int[] anchor);
+    }
+
+    enum CommandKind {
+        NORMAL,
+        HIDDEN,
+        HELP_ONLY;
+    }
+
+    static final class FixedCompletionProvider implements CompletionProvider {
+
+        private final String[] alternatives;
+
+        public FixedCompletionProvider(String... alternatives) {
+            this.alternatives = alternatives;
+        }
+
+        @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 Suggestion(alternative, false));
+                }
+            }
+
+            anchor[0] = 0;
+
+            return result;
+        }
+
+    }
+
+    private static final CompletionProvider EMPTY_COMPLETION_PROVIDER = new FixedCompletionProvider();
+    private static final CompletionProvider FILE_COMPLETION_PROVIDER = fileCompletions(p -> true);
+    private final Map<String, Command> commands = new LinkedHashMap<>();
+    private void registerCommand(Command cmd) {
+        for (String str : cmd.aliases) {
+            commands.put(str, cmd);
+        }
+    }
+    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 Suggestion(f.getFileName() + (Files.isDirectory(f) ? "/" : ""), false))
+                   .forEach(result::add);
+            } catch (IOException ex) {
+                //ignore...
+            }
+            if (path.isEmpty()) {
+                StreamSupport.stream(FileSystems.getDefault().getRootDirectories().spliterator(), false)
+                             .filter(root -> accept.test(root) && root.toString().startsWith(prefix))
+                             .map(root -> new Suggestion(root.toString(), false))
+                             .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"));
+    }
+
+    private CompletionProvider editCompletion() {
+        return (prefix, cursor, anchor) -> {
+            anchor[0] = 0;
+            return state.snippets()
+                        .stream()
+                        .flatMap(k -> (k instanceof DeclarationSnippet)
+                                ? Stream.of(String.valueOf(k.id()), ((DeclarationSnippet) k).name())
+                                : Stream.of(String.valueOf(k.id())))
+                        .filter(k -> k.startsWith(prefix))
+                        .map(k -> new Suggestion(k, false))
+                        .collect(Collectors.toList());
+        };
+    }
+
+    private static CompletionProvider saveCompletion() {
+        CompletionProvider keyCompletion = new FixedCompletionProvider("all ", "history ");
+        return (code, cursor, anchor) -> {
+            List<Suggestion> result = new ArrayList<>();
+            int space = code.indexOf(' ');
+            if (space == (-1)) {
+                result.addAll(keyCompletion.completionSuggestions(code, cursor, anchor));
+            }
+            result.addAll(FILE_COMPLETION_PROVIDER.completionSuggestions(code.substring(space + 1), cursor - space - 1, anchor));
+            anchor[0] += space + 1;
+            return result;
+        };
+    }
+
+    // Table of commands -- with command forms, argument kinds, help message, implementation, ...
+
+    {
+        registerCommand(new Command("/list", "/l", "[all]", "list the source you have typed",
+                                    arg -> cmdList(arg),
+                                    new FixedCompletionProvider("all")));
+        registerCommand(new Command("/seteditor", null, "<executable>", "set the external editor command to use",
+                                    arg -> cmdSetEditor(arg),
+                                    EMPTY_COMPLETION_PROVIDER));
+        registerCommand(new Command("/edit", "/e", "<name or id>", "edit a source entry referenced by name or id",
+                                    arg -> cmdEdit(arg),
+                                    editCompletion()));
+        registerCommand(new Command("/drop", "/d", "<name or id>", "delete a source entry referenced by name or id",
+                                    arg -> cmdDrop(arg),
+                                    editCompletion()));
+        registerCommand(new Command("/save", "/s", "[all|history] <file>", "save the source you have typed",
+                                    arg -> cmdSave(arg),
+                                    saveCompletion()));
+        registerCommand(new Command("/open", "/o", "<file>", "open a file as source input",
+                                    arg -> cmdOpen(arg),
+                                    FILE_COMPLETION_PROVIDER));
+        registerCommand(new Command("/vars", "/v", null, "list the declared variables and their values",
+                                    arg -> cmdVars(),
+                                    EMPTY_COMPLETION_PROVIDER));
+        registerCommand(new Command("/methods", "/m", null, "list the declared methods and their signatures",
+                                    arg -> cmdMethods(),
+                                    EMPTY_COMPLETION_PROVIDER));
+        registerCommand(new Command("/classes", "/c", null, "list the declared classes",
+                                    arg -> cmdClasses(),
+                                    EMPTY_COMPLETION_PROVIDER));
+        registerCommand(new Command("/exit", "/x", null, "exit the REPL",
+                                    arg -> cmdExit(),
+                                    EMPTY_COMPLETION_PROVIDER));
+        registerCommand(new Command("/reset", "/r", null, "reset everything in the REPL",
+                                    arg -> cmdReset(),
+                                    EMPTY_COMPLETION_PROVIDER));
+        registerCommand(new Command("/feedback", "/f", "<level>", "feedback information: off, concise, normal, verbose, default, or ?",
+                                    arg -> cmdFeedback(arg),
+                                    new FixedCompletionProvider("off", "concise", "normal", "verbose", "default", "?")));
+        registerCommand(new Command("/prompt", "/p", null, "toggle display of a prompt",
+                                    arg -> cmdPrompt(),
+                                    EMPTY_COMPLETION_PROVIDER));
+        registerCommand(new Command("/classpath", "/cp", "<path>", "add a path to the classpath",
+                                    arg -> cmdClasspath(arg),
+                                    classPathCompletion()));
+        registerCommand(new Command("/history", "/h", null, "history of what you have typed",
+                                    arg -> cmdHistory(),
+                                    EMPTY_COMPLETION_PROVIDER));
+        registerCommand(new Command("/setstart", null, "<file>", "read file and set as the new start-up definitions",
+                                    arg -> cmdSetStart(arg),
+                                    FILE_COMPLETION_PROVIDER));
+        registerCommand(new Command("/savestart", null, "<file>", "save the default start-up definitions to the file",
+                                    arg -> cmdSaveStart(arg),
+                                    FILE_COMPLETION_PROVIDER));
+        registerCommand(new Command("/debug", "/db", "", "toggle debugging of the REPL",
+                                    arg -> cmdDebug(arg),
+                                    EMPTY_COMPLETION_PROVIDER,
+                                    CommandKind.HIDDEN));
+        registerCommand(new Command("/help", "/?", "", "this help message",
+                                    arg -> cmdHelp(),
+                                    EMPTY_COMPLETION_PROVIDER));
+        registerCommand(new Command("/!", null, "", "re-run last snippet",
+                                    arg -> cmdUseHistoryEntry(-1),
+                                    EMPTY_COMPLETION_PROVIDER));
+        registerCommand(new Command("/<n>", null, "", "re-run n-th snippet",
+                                    arg -> { throw new IllegalStateException(); },
+                                    EMPTY_COMPLETION_PROVIDER,
+                                    CommandKind.HELP_ONLY));
+        registerCommand(new Command("/-<n>", null, "", "re-run n-th previous snippet",
+                                    arg -> { throw new IllegalStateException(); },
+                                    EMPTY_COMPLETION_PROVIDER,
+                                    CommandKind.HELP_ONLY));
+    }
+
+    public List<Suggestion> commandCompletionSuggestions(String code, int cursor, int[] anchor) {
+        String prefix = code.substring(0, cursor);
+        int space = prefix.indexOf(' ');
+        Stream<Suggestion> result;
+
+        if (space == (-1)) {
+            result = commands.values()
+                             .stream()
+                             .distinct()
+                             .filter(cmd -> cmd.kind != CommandKind.HIDDEN && cmd.kind != CommandKind.HELP_ONLY)
+                             .map(cmd -> cmd.aliases[0])
+                             .filter(key -> key.startsWith(prefix))
+                             .map(key -> new Suggestion(key + " ", false));
+            anchor[0] = 0;
+        } else {
+            String arg = prefix.substring(space + 1);
+            String cmd = prefix.substring(0, space);
+            Command command = commands.get(cmd);
+            if (command != null) {
+                result = command.completions.completionSuggestions(arg, cursor - space, anchor).stream();
+                anchor[0] += space + 1;
+            } else {
+                result = Stream.empty();
+            }
+        }
+
+        return result.sorted((s1, s2) -> s1.continuation.compareTo(s2.continuation))
+                     .collect(Collectors.toList());
+    }
+
+    public String commandDocumentation(String code, int cursor) {
+        code = code.substring(0, cursor);
+        int space = code.indexOf(' ');
+
+        if (space != (-1)) {
+            String cmd = code.substring(0, space);
+            Command command = commands.get(cmd);
+            if (command != null) {
+                return command.description;
+            }
+        }
+
+        return null;
+    }
+
+    // --- Command implementations ---
+
+    void cmdSetEditor(String arg) {
+        if (arg.isEmpty()) {
+            hard("/seteditor requires a path argument");
+        } else {
+            editor = arg;
+            fluff("Editor set to: %s", arg);
+        }
+    }
+
+    void cmdClasspath(String arg) {
+        if (arg.isEmpty()) {
+            hard("/classpath requires a path argument");
+        } else {
+            state.addToClasspath(toPathResolvingUserHome(arg).toString());
+            fluff("Path %s added to classpath", arg);
+        }
+    }
+
+    void cmdDebug(String arg) {
+        if (arg.isEmpty()) {
+            debug = !debug;
+            InternalDebugControl.setDebugFlags(state, debug ? InternalDebugControl.DBG_GEN : 0);
+            fluff("Debugging %s", debug ? "on" : "off");
+        } else {
+            int flags = 0;
+            for (char ch : arg.toCharArray()) {
+                switch (ch) {
+                    case '0':
+                        flags = 0;
+                        debug = false;
+                        fluff("Debugging off");
+                        break;
+                    case 'r':
+                        debug = true;
+                        fluff("REPL tool debugging on");
+                        break;
+                    case 'g':
+                        flags |= InternalDebugControl.DBG_GEN;
+                        fluff("General debugging on");
+                        break;
+                    case 'f':
+                        flags |= InternalDebugControl.DBG_FMGR;
+                        fluff("File manager debugging on");
+                        break;
+                    case 'c':
+                        flags |= InternalDebugControl.DBG_COMPA;
+                        fluff("Completion analysis debugging on");
+                        break;
+                    case 'd':
+                        flags |= InternalDebugControl.DBG_DEP;
+                        fluff("Dependency debugging on");
+                        break;
+                    case 'e':
+                        flags |= InternalDebugControl.DBG_EVNT;
+                        fluff("Event debugging on");
+                        break;
+                    default:
+                        hard("Unknown debugging option: %c", ch);
+                        fluff("Use: 0 r g f c d");
+                        break;
+                }
+            }
+            InternalDebugControl.setDebugFlags(state, flags);
+        }
+    }
+
+    private void cmdExit() {
+        regenerateOnDeath = false;
+        live = false;
+        fluff("Goodbye\n");
+    }
+
+    private void cmdFeedback(String arg) {
+        switch (arg) {
+            case "":
+            case "d":
+            case "default":
+                feedback = Feedback.Default;
+                break;
+            case "o":
+            case "off":
+                feedback = Feedback.Off;
+                break;
+            case "c":
+            case "concise":
+                feedback = Feedback.Concise;
+                break;
+            case "n":
+            case "normal":
+                feedback = Feedback.Normal;
+                break;
+            case "v":
+            case "verbose":
+                feedback = Feedback.Verbose;
+                break;
+            default:
+                hard("Follow /feedback with of the following:");
+                hard("  off       (errors and critical output only)");
+                hard("  concise");
+                hard("  normal");
+                hard("  verbose");
+                hard("  default");
+                hard("You may also use just the first letter, for example: /f c");
+                hard("In interactive mode 'default' is the same as 'normal', from a file it is the same as 'off'");
+                return;
+        }
+        fluff("Feedback mode: %s", feedback.name().toLowerCase());
+    }
+
+    void cmdHelp() {
+        int synopsisLen = 0;
+        Map<String, String> synopsis2Description = new LinkedHashMap<>();
+        for (Command cmd : new LinkedHashSet<>(commands.values())) {
+            if (cmd.kind == CommandKind.HIDDEN)
+                continue;
+            StringBuilder synopsis = new StringBuilder();
+            if (cmd.aliases.length > 1) {
+                synopsis.append(String.format("%-3s or ", cmd.aliases[1]));
+            } else {
+                synopsis.append("       ");
+            }
+            synopsis.append(cmd.aliases[0]);
+            if (cmd.params != null)
+                synopsis.append(" ").append(cmd.params);
+            synopsis2Description.put(synopsis.toString(), cmd.description);
+            synopsisLen = Math.max(synopsisLen, synopsis.length());
+        }
+        cmdout.println("Type a Java language expression, statement, or declaration.");
+        cmdout.println("Or type one of the following commands:\n");
+        for (Entry<String, String> e : synopsis2Description.entrySet()) {
+            cmdout.print(String.format("%-" + synopsisLen + "s", e.getKey()));
+            cmdout.print(" -- ");
+            cmdout.println(e.getValue());
+        }
+        cmdout.println();
+        cmdout.println("Supported shortcuts include:");
+        cmdout.println("<tab>       -- show possible completions for the current text");
+        cmdout.println("Shift-<tab> -- for current method or constructor invocation, show a synopsis of the method/constructor");
+    }
+
+    private void cmdHistory() {
+        cmdout.println();
+        for (String s : input.currentSessionHistory()) {
+            // No number prefix, confusing with snippet ids
+            cmdout.printf("%s\n", s);
+        }
+    }
+
+    /**
+     * Convert a user argument to a list of snippets referenced by that
+     * argument (or lack of argument).
+     * @param arg The user's argument to the command
+     * @return a list of referenced snippets
+     */
+    private List<Snippet> argToSnippets(String arg) {
+        List<Snippet> snippets = new ArrayList<>();
+        if (arg.isEmpty()) {
+            // Default is all user snippets
+            for (Snippet sn : state.snippets()) {
+                if (notInStartUp(sn)) {
+                    snippets.add(sn);
+                }
+            }
+        } else {
+            // Look for all declarations with matching names
+            for (Snippet key : state.snippets()) {
+                switch (key.kind()) {
+                    case METHOD:
+                    case VAR:
+                    case TYPE_DECL:
+                        if (((DeclarationSnippet) key).name().equals(arg)) {
+                            snippets.add(key);
+                        }
+                        break;
+                }
+            }
+            // If no declarations found, look for an id of this name
+            if (snippets.isEmpty()) {
+                for (Snippet sn : state.snippets()) {
+                    if (sn.id().equals(arg)) {
+                        snippets.add(sn);
+                        break;
+                    }
+                }
+            }
+            // If still no matches found, give an error
+            if (snippets.isEmpty()) {
+                hard("No definition or id named %s found.  See /classes /methods /vars or /list", arg);
+                return null;
+            }
+        }
+        return snippets;
+    }
+
+    private void cmdDrop(String arg) {
+        if (arg.isEmpty()) {
+            hard("In the /drop argument, please specify an import, variable, method, or class to drop.");
+            hard("Specify by id or name. Use /list to see ids. Use /reset to reset all state.");
+            return;
+        }
+        List<Snippet> snippetSet = argToSnippets(arg);
+        if (snippetSet == null) {
+            return;
+        }
+        snippetSet = snippetSet.stream()
+                .filter(sn -> state.status(sn).isActive)
+                .collect(toList());
+        snippetSet.removeIf(sn -> !(sn instanceof PersistentSnippet));
+        if (snippetSet.isEmpty()) {
+            hard("The argument did not specify an import, variable, method, or class to drop.");
+            return;
+        }
+        if (snippetSet.size() > 1) {
+            hard("The argument references more than one import, variable, method, or class.");
+            hard("Try again with one of the ids below:");
+            for (Snippet sn : snippetSet) {
+                cmdout.printf("%4s : %s\n", sn.id(), sn.source().replace("\n", "\n       "));
+            }
+            return;
+        }
+        PersistentSnippet psn = (PersistentSnippet) snippetSet.iterator().next();
+        state.drop(psn).forEach(this::handleEvent);
+    }
+
+    private void cmdEdit(String arg) {
+        List<Snippet> snippetSet = argToSnippets(arg);
+        if (snippetSet == null) {
+            return;
+        }
+        Set<String> srcSet = new LinkedHashSet<>();
+        for (Snippet key : snippetSet) {
+            String src = key.source();
+            switch (key.subKind()) {
+                case VAR_VALUE_SUBKIND:
+                    break;
+                case ASSIGNMENT_SUBKIND:
+                case OTHER_EXPRESSION_SUBKIND:
+                case TEMP_VAR_EXPRESSION_SUBKIND:
+                    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 == null) {
+            EditPad.edit(errorHandler, src, saveHandler);
+        } else {
+            ExternalEditor.edit(editor, errorHandler, src, saveHandler, input);
+        }
+    }
+    //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 = processCompleteSource(tsrc);
+                        }
+                        nextSrcs.add(tsrc);
+                        if (an.remaining.isEmpty()) {
+                            break;
+                        }
+                        s = an.remaining;
+                    }
+                    currSrcs = nextSrcs;
+                } catch (IllegalStateException ex) {
+                    hard("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 void cmdList(String arg) {
+        boolean all = false;
+        switch (arg) {
+            case "all":
+                all = true;
+                break;
+            case "history":
+                cmdHistory();
+                return;
+            case "":
+                break;
+            default:
+                hard("Invalid /list argument: %s", arg);
+                return;
+        }
+        boolean hasOutput = false;
+        for (Snippet sn : state.snippets()) {
+            if (all || (notInStartUp(sn) && state.status(sn).isActive)) {
+                if (!hasOutput) {
+                    cmdout.println();
+                    hasOutput = true;
+                }
+                cmdout.printf("%4s : %s\n", sn.id(), sn.source().replace("\n", "\n       "));
+
+            }
+        }
+    }
+
+    private void cmdOpen(String filename) {
+        if (filename.isEmpty()) {
+            hard("The /open command requires a filename argument.");
+        } else {
+            try {
+                run(new FileScannerIOContext(toPathResolvingUserHome(filename).toString()));
+            } catch (FileNotFoundException e) {
+                hard("File '%s' is not found: %s", filename, e.getMessage());
+            } catch (Exception e) {
+                hard("Exception while reading file: %s", e);
+            }
+        }
+    }
+
+    private void cmdPrompt() {
+        displayPrompt = !displayPrompt;
+        fluff("Prompt will %sdisplay. Use /prompt to toggle.", displayPrompt ? "" : "NOT ");
+        concise("Prompt: %s", displayPrompt ? "on" : "off");
+    }
+
+    private void cmdReset() {
+        live = false;
+        fluff("Resetting state.");
+    }
+
+    private void cmdSave(String arg_filename) {
+        Matcher mat = HISTORY_ALL_FILENAME.matcher(arg_filename);
+        if (!mat.find()) {
+            hard("Malformed argument to the /save command: %s", arg_filename);
+            return;
+        }
+        boolean useHistory = false;
+        boolean saveAll = false;
+        String cmd = mat.group("cmd");
+        if (cmd != null) switch (cmd) {
+            case "all":
+                saveAll = true;
+                break;
+            case "history":
+                useHistory = true;
+                break;
+        }
+        String filename = mat.group("filename");
+        if (filename == null ||filename.isEmpty()) {
+            hard("The /save command requires a filename argument.");
+            return;
+        }
+        try (BufferedWriter writer = Files.newBufferedWriter(toPathResolvingUserHome(filename),
+                Charset.defaultCharset(),
+                CREATE, TRUNCATE_EXISTING, WRITE)) {
+            if (useHistory) {
+                for (String s : input.currentSessionHistory()) {
+                    writer.write(s);
+                    writer.write("\n");
+                }
+            } else {
+                for (Snippet sn : state.snippets()) {
+                    if (saveAll || notInStartUp(sn)) {
+                        writer.write(sn.source());
+                        writer.write("\n");
+                    }
+                }
+            }
+        } catch (FileNotFoundException e) {
+            hard("File '%s' for save is not accessible: %s", filename, e.getMessage());
+        } catch (Exception e) {
+            hard("Exception while saving: %s", e);
+        }
+    }
+
+    private void cmdSetStart(String filename) {
+        if (filename.isEmpty()) {
+            hard("The /setstart command requires a filename argument.");
+        } else {
+            try {
+                byte[] encoded = Files.readAllBytes(toPathResolvingUserHome(filename));
+                String init = new String(encoded);
+                PREFS.put(STARTUP_KEY, init);
+            } catch (AccessDeniedException e) {
+                hard("File '%s' for /setstart is not accessible.", filename);
+            } catch (NoSuchFileException e) {
+                hard("File '%s' for /setstart is not found.", filename);
+            } catch (Exception e) {
+                hard("Exception while reading start set file: %s", e);
+            }
+        }
+    }
+
+    private void cmdSaveStart(String filename) {
+        if (filename.isEmpty()) {
+            hard("The /savestart command requires a filename argument.");
+        } else {
+            try {
+                Files.write(toPathResolvingUserHome(filename), DEFAULT_STARTUP.getBytes());
+            } catch (AccessDeniedException e) {
+                hard("File '%s' for /savestart is not accessible.", filename);
+            } catch (NoSuchFileException e) {
+                hard("File '%s' for /savestart cannot be located.", filename);
+            } catch (Exception e) {
+                hard("Exception while saving default startup file: %s", e);
+            }
+        }
+    }
+
+    private void cmdVars() {
+        for (VarSnippet vk : state.variables()) {
+            String val = state.status(vk) == Status.VALID
+                    ? state.varValue(vk)
+                    : "(not-active)";
+            hard("  %s %s = %s", vk.typeName(), vk.name(), val);
+        }
+    }
+
+    private void cmdMethods() {
+        for (MethodSnippet mk : state.methods()) {
+            hard("  %s %s", mk.name(), mk.signature());
+        }
+    }
+
+    private void cmdClasses() {
+        for (TypeDeclSnippet ck : state.types()) {
+            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());
+        }
+    }
+
+    private void cmdUseHistoryEntry(int index) {
+        List<Snippet> keys = state.snippets();
+        if (index < 0)
+            index += keys.size();
+        else
+            index--;
+        if (index >= 0 && index < keys.size()) {
+            String source = keys.get(index).source();
+            cmdout.printf("%s\n", source);
+            input.replaceLastHistoryEntry(source);
+            processSourceCatchingReset(source);
+        } else {
+            hard("Cannot find snippet %d", index + 1);
+        }
+    }
+
+    /**
+     * Filter diagnostics for only errors (no warnings, ...)
+     * @param diagnostics input list
+     * @return filtered list
+     */
+    List<Diag> errorsOnly(List<Diag> diagnostics) {
+        return diagnostics.stream()
+                .filter(d -> d.isError())
+                .collect(toList());
+    }
+
+    void printDiagnostics(String source, List<Diag> diagnostics, boolean embed) {
+        String padding = embed? "    " : "";
+        for (Diag diag : diagnostics) {
+            //assert diag.getSource().equals(source);
+
+            if (!embed) {
+                if (diag.isError()) {
+                    hard("Error:");
+                } else {
+                    hard("Warning:");
+                }
+            }
+
+            for (String line : diag.getMessage(null).split("\\r?\\n")) {
+                if (!line.trim().startsWith("location:")) {
+                    hard("%s%s", padding, 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();
+            }
+            fluff("%s%s", padding, 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('^');
+                }
+            }
+            fluff("%s%s", padding, 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());
+        }
+    }
+
+    private String processSource(String srcInput) throws IllegalStateException {
+        while (true) {
+            CompletionInfo an = analysis.analyzeCompletion(srcInput);
+            if (!an.completeness.isComplete) {
+                return an.remaining;
+            }
+            boolean failed = processCompleteSource(an.source);
+            if (failed || an.remaining.isEmpty()) {
+                return "";
+            }
+            srcInput = an.remaining;
+        }
+    }
+    //where
+    private boolean processCompleteSource(String source) throws IllegalStateException {
+        debug("Compiling: %s", source);
+        boolean failed = false;
+        List<SnippetEvent> events = state.eval(source);
+        for (SnippetEvent e : events) {
+            failed |= handleEvent(e);
+        }
+        return failed;
+    }
+
+    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);
+        String source = sn.source();
+        if (ste.causeSnippet() == null) {
+            // main event
+            printDiagnostics(source, diagnostics, false);
+            if (ste.status().isActive) {
+                if (ste.exception() != null) {
+                    if (ste.exception() instanceof EvalException) {
+                        printEvalException((EvalException) ste.exception());
+                        return true;
+                    } else if (ste.exception() instanceof UnresolvedReferenceException) {
+                        printUnresolved((UnresolvedReferenceException) ste.exception());
+                    } else {
+                        hard("Unexpected execution exception: %s", ste.exception());
+                        return true;
+                    }
+                } else {
+                    displayDeclarationAndValue(ste, false, ste.value());
+                }
+            } else if (ste.status() == Status.REJECTED) {
+                if (diagnostics.isEmpty()) {
+                    hard("Failed.");
+                }
+                return true;
+            }
+        } else if (ste.status() == Status.REJECTED) {
+            //TODO -- I don't believe updates can cause failures any more
+            hard("Caused failure of dependent %s --", ((DeclarationSnippet) sn).name());
+            printDiagnostics(source, diagnostics, true);
+        } else {
+            // Update
+            SubKind subkind = sn.subKind();
+            if (sn instanceof DeclarationSnippet
+                    && (feedback() == Feedback.Verbose
+                    || ste.status() == Status.OVERWRITTEN
+                    || subkind == SubKind.VAR_DECLARATION_SUBKIND
+                    || subkind == SubKind.VAR_DECLARATION_WITH_INITIALIZER_SUBKIND)) {
+                // Under the conditions, display update information
+                displayDeclarationAndValue(ste, true, null);
+                List<Diag> other = errorsOnly(diagnostics);
+                if (other.size() > 0) {
+                    printDiagnostics(source, other, true);
+                }
+            }
+        }
+        return false;
+    }
+
+    @SuppressWarnings("fallthrough")
+    private void displayDeclarationAndValue(SnippetEvent ste, boolean update, String value) {
+        Snippet key = ste.snippet();
+        String declared;
+        Status status = ste.status();
+        switch (status) {
+            case VALID:
+            case RECOVERABLE_DEFINED:
+            case RECOVERABLE_NOT_DEFINED:
+                if (ste.previousStatus().isActive) {
+                    declared = ste.isSignatureChange()
+                        ? "Replaced"
+                        : "Modified";
+                } else {
+                    declared = "Added";
+                }
+                break;
+            case OVERWRITTEN:
+                declared = "Overwrote";
+                break;
+            case DROPPED:
+                declared = "Dropped";
+                break;
+            case REJECTED:
+                declared = "Rejected";
+                break;
+            case NONEXISTENT:
+            default:
+                // Should not occur
+                declared = ste.previousStatus().toString() + "=>" + status.toString();
+        }
+        if (update) {
+            declared = "  Update " + declared.toLowerCase();
+        }
+        String however;
+        if (key instanceof DeclarationSnippet && (status == Status.RECOVERABLE_DEFINED || status == Status.RECOVERABLE_NOT_DEFINED)) {
+            String cannotUntil = (status == Status.RECOVERABLE_NOT_DEFINED)
+                    ? " cannot be referenced until"
+                    : " cannot be invoked until";
+            however = (update? " which" : ", however, it") + cannotUntil + unresolved((DeclarationSnippet) key);
+        } else {
+            however = "";
+        }
+        switch (key.subKind()) {
+            case CLASS_SUBKIND:
+                fluff("%s class %s%s", declared, ((TypeDeclSnippet) key).name(), however);
+                break;
+            case INTERFACE_SUBKIND:
+                fluff("%s interface %s%s", declared, ((TypeDeclSnippet) key).name(), however);
+                break;
+            case ENUM_SUBKIND:
+                fluff("%s enum %s%s", declared, ((TypeDeclSnippet) key).name(), however);
+                break;
+            case ANNOTATION_TYPE_SUBKIND:
+                fluff("%s annotation interface %s%s", declared, ((TypeDeclSnippet) key).name(), however);
+                break;
+            case METHOD_SUBKIND:
+                fluff("%s method %s(%s)%s", declared, ((MethodSnippet) key).name(),
+                        ((MethodSnippet) key).parameterTypes(), however);
+                break;
+            case VAR_DECLARATION_SUBKIND:
+                if (!update) {
+                    VarSnippet vk = (VarSnippet) key;
+                    if (status == Status.RECOVERABLE_NOT_DEFINED) {
+                        fluff("%s variable %s%s", declared, vk.name(), however);
+                    } else {
+                        fluff("%s variable %s of type %s%s", declared, vk.name(), vk.typeName(), however);
+                    }
+                    break;
+                }
+            // Fall through
+            case VAR_DECLARATION_WITH_INITIALIZER_SUBKIND: {
+                VarSnippet vk = (VarSnippet) key;
+                if (status == Status.RECOVERABLE_NOT_DEFINED) {
+                    if (!update) {
+                        fluff("%s variable %s%s", declared, vk.name(), however);
+                        break;
+                    }
+                } else if (update) {
+                    if (ste.isSignatureChange()) {
+                        hard("%s variable %s, reset to null", declared, vk.name());
+                    }
+                } else {
+                    fluff("%s variable %s of type %s with initial value %s",
+                            declared, vk.name(), vk.typeName(), value);
+                    concise("%s : %s", vk.name(), value);
+                }
+                break;
+            }
+            case TEMP_VAR_EXPRESSION_SUBKIND: {
+                VarSnippet vk = (VarSnippet) key;
+                if (update) {
+                    hard("%s temporary variable %s, reset to null", declared, vk.name());
+                 } else {
+                    fluff("Expression value is: %s", (value));
+                    fluff("  assigned to temporary variable %s of type %s", vk.name(), vk.typeName());
+                    concise("%s : %s", vk.name(), value);
+                }
+                break;
+            }
+            case OTHER_EXPRESSION_SUBKIND:
+                fluff("Expression value is: %s", (value));
+                break;
+            case VAR_VALUE_SUBKIND: {
+                ExpressionSnippet ek = (ExpressionSnippet) key;
+                fluff("Variable %s of type %s has value %s", ek.name(), ek.typeName(), (value));
+                concise("%s : %s", ek.name(), value);
+                break;
+            }
+            case ASSIGNMENT_SUBKIND: {
+                ExpressionSnippet ek = (ExpressionSnippet) key;
+                fluff("Variable %s has been assigned the value %s", ek.name(), (value));
+                concise("%s : %s", ek.name(), value);
+                break;
+            }
+        }
+    }
+    //where
+    void printStackTrace(StackTraceElement[] stes) {
+        for (StackTraceElement ste : stes) {
+            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()
+                    ? "Native Method"
+                    : fileName == null
+                            ? "Unknown Source"
+                            : lineNumber >= 0
+                                    ? fileName + ":" + lineNumber
+                                    : fileName;
+            hard("      at %s(%s)", sb, loc);
+
+        }
+    }
+    //where
+    void printUnresolved(UnresolvedReferenceException ex) {
+        MethodSnippet corralled =  ex.getMethodSnippet();
+        List<Diag> otherErrors = errorsOnly(state.diagnostics(corralled));
+        StringBuilder sb = new StringBuilder();
+        if (otherErrors.size() > 0) {
+            if (state.unresolvedDependencies(corralled).size() > 0) {
+                sb.append(" and");
+            }
+            if (otherErrors.size() == 1) {
+                sb.append(" this error is addressed --");
+            } else {
+                sb.append(" these errors are addressed --");
+            }
+        } else {
+            sb.append(".");
+        }
+
+        hard("Attempted to call %s which cannot be invoked until%s", corralled.name(),
+                unresolved(corralled), sb.toString());
+        if (otherErrors.size() > 0) {
+            printDiagnostics(corralled.source(), otherErrors, true);
+        }
+    }
+    //where
+    void printEvalException(EvalException ex) {
+        if (ex.getMessage() == null) {
+            hard("%s thrown", ex.getExceptionClassName());
+        } else {
+            hard("%s thrown: %s", ex.getExceptionClassName(), ex.getMessage());
+        }
+        printStackTrace(ex.getStackTrace());
+    }
+    //where
+    String unresolved(DeclarationSnippet key) {
+        List<String> unr = state.unresolvedDependencies(key);
+        StringBuilder sb = new StringBuilder();
+        int fromLast = unr.size();
+        if (fromLast > 0) {
+            sb.append(" ");
+        }
+        for (String u : unr) {
+            --fromLast;
+            sb.append(u);
+            if (fromLast == 0) {
+                // No suffix
+            } else if (fromLast == 1) {
+                sb.append(", and ");
+            } else {
+                sb.append(", ");
+            }
+        }
+        switch (unr.size()) {
+            case 0:
+                break;
+            case 1:
+                sb.append(" is declared");
+                break;
+            default:
+                sb.append(" are declared");
+                break;
+        }
+        return sb.toString();
+    }
+
+    enum Feedback {
+        Default,
+        Off,
+        Concise,
+        Normal,
+        Verbose
+    }
+
+    Feedback feedback() {
+        if (feedback == Feedback.Default) {
+            return input == null || input.interactiveOutput() ? Feedback.Normal : Feedback.Off;
+        }
+        return feedback;
+    }
+
+    boolean notInStartUp(Snippet sn) {
+        return mapSnippet.get(sn).space != startNamespace;
+    }
+
+    /** The current version number as a string.
+     */
+    static String version() {
+        return version("release");  // mm.nn.oo[-milestone]
+    }
+
+    /** The current full version number as a string.
+     */
+    static String fullVersion() {
+        return version("full"); // mm.mm.oo[-milestone]-build
+    }
+
+    private static final String versionRBName = "jdk.internal.jshell.tool.resources.version";
+    private static ResourceBundle versionRB;
+
+    private static String version(String key) {
+        if (versionRB == null) {
+            try {
+                versionRB = ResourceBundle.getBundle(versionRBName);
+            } 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;
+        }
+    }
+}
+
+class ScannerIOContext extends IOContext {
+
+    private final Scanner scannerIn;
+    private final PrintStream pStream;
+
+    public ScannerIOContext(Scanner scannerIn, PrintStream pStream) {
+        this.scannerIn = scannerIn;
+        this.pStream = pStream;
+    }
+
+    @Override
+    public String readLine(String prompt, String prefix) {
+        if (pStream != null && prompt != null) {
+            pStream.print(prompt);
+        }
+        if (scannerIn.hasNextLine()) {
+            return scannerIn.nextLine();
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public boolean interactiveOutput() {
+        return true;
+    }
+
+    @Override
+    public Iterable<String> currentSessionHistory() {
+        return Collections.emptyList();
+    }
+
+    @Override
+    public void close() {
+        scannerIn.close();
+    }
+
+    @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 FileScannerIOContext extends ScannerIOContext {
+
+    public FileScannerIOContext(String fn) throws FileNotFoundException {
+        this(new FileReader(fn));
+    }
+
+    public FileScannerIOContext(Reader rdr) throws FileNotFoundException {
+        super(new Scanner(rdr), null);
+    }
+
+    @Override
+    public boolean interactiveOutput() {
+        return false;
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/StopDetectingInputStream.java	Wed Oct 21 18:40:01 2015 -0700
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2015, 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.IOException;
+import java.io.InputStream;
+import java.util.function.Consumer;
+
+public final class StopDetectingInputStream extends InputStream {
+    public static final int INITIAL_SIZE = 128;
+
+    private final Runnable stop;
+    private final Consumer<Exception> errorHandler;
+
+    private boolean initialized;
+    private int[] buffer = new int[INITIAL_SIZE];
+    private int start;
+    private int end;
+    private State state = State.WAIT;
+
+    public StopDetectingInputStream(Runnable stop, Consumer<Exception> errorHandler) {
+        this.stop = stop;
+        this.errorHandler = errorHandler;
+    }
+
+    public synchronized InputStream setInputStream(InputStream input) {
+        if (initialized)
+            throw new IllegalStateException("Already initialized.");
+        initialized = true;
+
+        Thread reader = new Thread() {
+            @Override
+            public void run() {
+                try {
+                    int read;
+                    while (true) {
+                        //to support external terminal editors, the "cmdin.read" must not run when
+                        //an external editor is running. At the same time, it needs to run while the
+                        //user's code is running (so Ctrl-C is detected). Hence waiting here until
+                        //there is a confirmed need for input.
+                        waitInputNeeded();
+                        if ((read = input.read()) == (-1)) {
+                            break;
+                        }
+                        if (read == 3 && state == StopDetectingInputStream.State.BUFFER) {
+                            stop.run();
+                        } else {
+                            write(read);
+                        }
+                    }
+                } catch (IOException ex) {
+                    errorHandler.accept(ex);
+                } finally {
+                    state = StopDetectingInputStream.State.CLOSED;
+                }
+            }
+        };
+        reader.setDaemon(true);
+        reader.start();
+
+        return this;
+    }
+
+    @Override
+    public synchronized int read() {
+        while (start == end) {
+            if (state == State.CLOSED) {
+                return -1;
+            }
+            if (state == State.WAIT) {
+                state = State.READ;
+            }
+            notifyAll();
+            try {
+                wait();
+            } catch (InterruptedException ex) {
+                //ignore
+            }
+        }
+        try {
+            return buffer[start];
+        } finally {
+            start = (start + 1) % buffer.length;
+        }
+    }
+
+    public synchronized void write(int b) {
+        if (state != State.BUFFER) {
+            state = State.WAIT;
+        }
+        int newEnd = (end + 1) % buffer.length;
+        if (newEnd == start) {
+            //overflow:
+            int[] newBuffer = new int[buffer.length * 2];
+            int rightPart = (end > start ? end : buffer.length) - start;
+            int leftPart = end > start ? 0 : start - 1;
+            System.arraycopy(buffer, start, newBuffer, 0, rightPart);
+            System.arraycopy(buffer, 0, newBuffer, rightPart, leftPart);
+            buffer = newBuffer;
+            start = 0;
+            end = rightPart + leftPart;
+            newEnd = end + 1;
+        }
+        buffer[end] = b;
+        end = newEnd;
+        notifyAll();
+    }
+
+    public synchronized void setState(State state) {
+        this.state = state;
+        notifyAll();
+    }
+
+    private synchronized void waitInputNeeded() {
+        while (state == State.WAIT) {
+            try {
+                wait();
+            } catch (InterruptedException ex) {
+                //ignore
+            }
+        }
+    }
+
+    public enum State {
+        /* No reading from the input should happen. This is the default state. The StopDetectingInput
+         * must be in this state when an external editor is being run, so that the external editor
+         * can read from the input.
+         */
+        WAIT,
+        /* A single input character should be read. Reading from the StopDetectingInput will move it
+         * into this state, and the state will be automatically changed back to WAIT when a single
+         * input character is obtained. This state is typically used while the user is editing the
+         * input line.
+         */
+        READ,
+        /* Continuously read from the input. Forward Ctrl-C ((int) 3) to the "stop" Runnable, buffer
+         * all other input. This state is typically used while the user's code is running, to provide
+         * the ability to detect Ctrl-C in order to stop the execution.
+         */
+        BUFFER,
+        /* The input is closed.
+        */
+        CLOSED
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/version.properties-template	Wed Oct 21 18:40:01 2015 -0700
@@ -0,0 +1,28 @@
+#
+# Copyright (c) 2015, 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.
+#
+
+jdk=$(JDK_VERSION)
+full=$(FULL_VERSION)
+release=$(RELEASE)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/jshell/ClassTracker.java	Wed Oct 21 18:40:01 2015 -0700
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2015, 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;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import com.sun.jdi.ReferenceType;
+
+/**
+ * Tracks the state of a class through compilation and loading in the remote
+ * environment.
+ *
+ * @author Robert Field
+ */
+class ClassTracker {
+
+    private final JShell state;
+    private final HashMap<String, ClassInfo> map;
+
+    ClassTracker(JShell state) {
+        this.state = state;
+        this.map = new HashMap<>();
+    }
+
+    class ClassInfo {
+
+        private final String className;
+        private byte[] bytes;
+        private byte[] loadedBytes;
+        private ReferenceType rt;
+
+        private ClassInfo(String className) {
+            this.className = className;
+        }
+
+        String getClassName() {
+            return className;
+        }
+
+        byte[] getBytes() {
+            return bytes;
+        }
+
+        void setBytes(byte[] bytes) {
+            this.bytes = bytes;
+        }
+
+        void setLoaded() {
+            loadedBytes = bytes;
+        }
+
+        boolean isLoaded() {
+            return Arrays.equals(loadedBytes, bytes);
+        }
+
+        ReferenceType getReferenceTypeOrNull() {
+            if (rt == null) {
+                rt = state.executionControl().nameToRef(className);
+            }
+            return rt;
+        }
+    }
+
+    ClassInfo classInfo(String className, byte[] bytes) {
+        ClassInfo ci = map.computeIfAbsent(className, k -> new ClassInfo(k));
+        ci.setBytes(bytes);
+        return ci;
+    }
+
+    ClassInfo get(String className) {
+        return map.get(className);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/jshell/CompletenessAnalyzer.java	Wed Oct 21 18:40:01 2015 -0700
@@ -0,0 +1,885 @@
+/*
+ * Copyright (c) 2015, 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;
+
+import com.sun.tools.javac.code.Source;
+import com.sun.tools.javac.parser.Scanner;
+import com.sun.tools.javac.parser.ScannerFactory;
+import com.sun.tools.javac.parser.Tokens.Token;
+import com.sun.tools.javac.parser.Tokens.TokenKind;
+import com.sun.tools.javac.util.Context;
+import com.sun.tools.javac.util.JCDiagnostic;
+import com.sun.tools.javac.util.JCDiagnostic.DiagnosticFlag;
+import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
+import com.sun.tools.javac.util.Log;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.EnumMap;
+import java.util.Iterator;
+import jdk.jshell.SourceCodeAnalysis.Completeness;
+import com.sun.source.tree.Tree;
+import static jdk.jshell.CompletenessAnalyzer.TK.*;
+import jdk.jshell.TaskFactory.ParseTask;
+import java.util.List;
+
+/**
+ * Low level scanner to determine completeness of input.
+ * @author Robert Field
+ */
+class CompletenessAnalyzer {
+
+    private final ScannerFactory scannerFactory;
+    private final JShell proc;
+
+    private static Completeness error() {
+        return Completeness.UNKNOWN;  // For breakpointing
+    }
+
+    static class CaInfo {
+
+        CaInfo(Completeness status, int unitEndPos) {
+            this.status = status;
+            this.unitEndPos = unitEndPos;
+        }
+        final int unitEndPos;
+        final Completeness status;
+    }
+
+    CompletenessAnalyzer(JShell proc) {
+        this.proc = proc;
+        Context context = new Context();
+        Log log = CaLog.createLog(context);
+        context.put(Log.class, log);
+        context.put(Source.class, Source.JDK1_9);
+        scannerFactory = ScannerFactory.instance(context);
+    }
+
+    CaInfo scan(String s) {
+        try {
+            Scanner scanner = scannerFactory.newScanner(s, false);
+            Matched in = new Matched(scanner);
+            Parser parser = new Parser(in, proc, s);
+            Completeness stat = parser.parseUnit();
+            int endPos = stat == Completeness.UNKNOWN
+                    ? s.length()
+                    : in.prevCT.endPos;
+            return new CaInfo(stat, endPos);
+        } catch (SyntaxException ex) {
+            return new CaInfo(error(), s.length());
+        }
+    }
+
+    @SuppressWarnings("serial")             // serialVersionUID intentionally omitted
+    private static class SyntaxException extends RuntimeException {
+    }
+
+    private static void die() {
+        throw new SyntaxException();
+    }
+
+    /**
+     * Subclass of Log used by compiler API to die on error and ignore
+     * other messages
+     */
+    private static class CaLog extends Log {
+
+        private static CaLog createLog(Context context) {
+            PrintWriter pw = new PrintWriter(new StringWriter());
+            CaLog log = new CaLog(context, pw, pw, pw);
+            context.put(outKey, pw);
+            context.put(logKey, log);
+            return log;
+        }
+
+        private CaLog(Context context, PrintWriter errWriter, PrintWriter warnWriter, PrintWriter noticeWriter) {
+            super(context, errWriter, warnWriter, noticeWriter);
+        }
+
+        @Override
+        public void error(String key, Object... args) {
+            die();
+        }
+
+        @Override
+        public void error(DiagnosticPosition pos, String key, Object... args) {
+            die();
+        }
+
+        @Override
+        public void error(DiagnosticFlag flag, DiagnosticPosition pos, String key, Object... args) {
+            die();
+        }
+
+        @Override
+        public void error(int pos, String key, Object... args) {
+            die();
+        }
+
+        @Override
+        public void error(DiagnosticFlag flag, int pos, String key, Object... args) {
+            die();
+        }
+
+        @Override
+        public void report(JCDiagnostic diagnostic) {
+            // Ignore
+        }
+    }
+
+    // Location position kinds -- a token is ...
+    private static final int XEXPR         = 0b1;                       // OK in expression (not first)
+    private static final int XDECL         = 0b10;                      // OK in declaration (not first)
+    private static final int XSTMT         = 0b100;                     // OK in statement framework (not first)
+    private static final int XEXPR1o       = 0b1000;                    // OK first in expression
+    private static final int XDECL1o       = 0b10000;                   // OK first in declaration
+    private static final int XSTMT1o       = 0b100000;                  // OK first or only in statement framework
+    private static final int XEXPR1        = XEXPR1o | XEXPR;           // OK in expression (anywhere)
+    private static final int XDECL1        = XDECL1o | XDECL;           // OK in declaration (anywhere)
+    private static final int XSTMT1        = XSTMT1o | XSTMT;           // OK in statement framework (anywhere)
+    private static final int XANY1         = XEXPR1o | XDECL1o | XSTMT1o;  // Mask: first in statement, declaration, or expression
+    private static final int XTERM         = 0b100000000;               // Can terminate (last before EOF)
+    private static final int XSTART        = 0b1000000000;              // Boundary, must be XTERM before
+    private static final int XERRO         = 0b10000000000;             // Is an error
+
+    /**
+     * An extension of the compiler's TokenKind which adds our combined/processed
+     * kinds. Also associates each TK with a union of acceptable kinds of code
+     * position it can occupy.  For example: IDENTIFER is XEXPR1|XDECL1|XTERM,
+     * meaning it can occur in expressions or declarations (but not in the
+     * framework of a statement and that can be the final (terminating) token
+     * in a snippet.
+     * <P>
+     * There must be a TK defined for each compiler TokenKind, an exception
+     * will
+     * be thrown if a TokenKind is defined and a corresponding TK is not. Add a
+     * new TK in the appropriate category. If it is like an existing category
+     * (e.g. a new modifier or type this may be all that is needed.  If it
+     * is bracketing or modifies the acceptable positions of other tokens,
+     * please closely examine the needed changes to this scanner.
+     */
+    static enum TK {
+
+        // Special
+        EOF(TokenKind.EOF, 0),  //
+        ERROR(TokenKind.ERROR, XERRO),  //
+        IDENTIFIER(TokenKind.IDENTIFIER, XEXPR1|XDECL1|XTERM),  //
+        UNDERSCORE(TokenKind.UNDERSCORE, XERRO),  //  _
+        CLASS(TokenKind.CLASS, XEXPR|XDECL1|XTERM),  //  class decl and .class
+        MONKEYS_AT(TokenKind.MONKEYS_AT, XEXPR|XDECL1),  //  @
+        IMPORT(TokenKind.IMPORT, XDECL1|XSTART),  //  import -- consider declaration
+        SEMI(TokenKind.SEMI, XSTMT1|XTERM|XSTART),  //  ;
+
+        // Shouldn't see -- error
+        PACKAGE(TokenKind.PACKAGE, XERRO),  //  package
+        CONST(TokenKind.CONST, XERRO),  //  reserved keyword -- const
+        GOTO(TokenKind.GOTO, XERRO),  //  reserved keyword -- goto
+        CUSTOM(TokenKind.CUSTOM, XERRO),  // No uses
+
+        // Declarations
+        ENUM(TokenKind.ENUM, XDECL1),  //  enum
+        IMPLEMENTS(TokenKind.IMPLEMENTS, XDECL),  //  implements
+        INTERFACE(TokenKind.INTERFACE, XDECL1),  //  interface
+        THROWS(TokenKind.THROWS, XDECL),  //  throws
+
+        // Primarive type names
+        BOOLEAN(TokenKind.BOOLEAN, XEXPR|XDECL1),  //  boolean
+        BYTE(TokenKind.BYTE, XEXPR|XDECL1),  //  byte
+        CHAR(TokenKind.CHAR, XEXPR|XDECL1),  //  char
+        DOUBLE(TokenKind.DOUBLE, XEXPR|XDECL1),  //  double
+        FLOAT(TokenKind.FLOAT, XEXPR|XDECL1),  //  float
+        INT(TokenKind.INT, XEXPR|XDECL1),  //  int
+        LONG(TokenKind.LONG, XEXPR|XDECL1),  //  long
+        SHORT(TokenKind.SHORT, XEXPR|XDECL1),  //  short
+        VOID(TokenKind.VOID, XEXPR|XDECL1),  //  void
+
+        // Modifiers keywords
+        ABSTRACT(TokenKind.ABSTRACT, XDECL1),  //  abstract
+        FINAL(TokenKind.FINAL, XDECL1),  //  final
+        NATIVE(TokenKind.NATIVE, XDECL1),  //  native
+        STATIC(TokenKind.STATIC, XDECL1),  //  static
+        STRICTFP(TokenKind.STRICTFP, XDECL1),  //  strictfp
+        PRIVATE(TokenKind.PRIVATE, XDECL1),  //  private
+        PROTECTED(TokenKind.PROTECTED, XDECL1),  //  protected
+        PUBLIC(TokenKind.PUBLIC, XDECL1),  //  public
+        TRANSIENT(TokenKind.TRANSIENT, XDECL1),  //  transient
+        VOLATILE(TokenKind.VOLATILE, XDECL1),  //  volatile
+
+        // Declarations and type parameters (thus expressions)
+        EXTENDS(TokenKind.EXTENDS, XEXPR|XDECL),  //  extends
+        COMMA(TokenKind.COMMA, XEXPR|XDECL|XSTART),  //  ,
+        AMP(TokenKind.AMP, XEXPR|XDECL),  //  &
+        GT(TokenKind.GT, XEXPR|XDECL),  //  >
+        LT(TokenKind.LT, XEXPR|XDECL1),  //  <
+        LTLT(TokenKind.LTLT, XEXPR|XDECL1),  //  <<
+        GTGT(TokenKind.GTGT, XEXPR|XDECL),  //  >>
+        GTGTGT(TokenKind.GTGTGT, XEXPR|XDECL),  //  >>>
+        QUES(TokenKind.QUES, XEXPR|XDECL),  //  ?
+        DOT(TokenKind.DOT, XEXPR|XDECL),  //  .
+        STAR(TokenKind.STAR, XEXPR|XDECL|XTERM),  //  * -- import foo.* //TODO handle these case separately, XTERM
+
+        // Statement keywords
+        ASSERT(TokenKind.ASSERT, XSTMT1|XSTART),  //  assert
+        BREAK(TokenKind.BREAK, XSTMT1|XTERM|XSTART),  //  break
+        CATCH(TokenKind.CATCH, XSTMT1|XSTART),  //  catch
+        CONTINUE(TokenKind.CONTINUE, XSTMT1|XTERM|XSTART),  //  continue
+        DO(TokenKind.DO, XSTMT1|XSTART),  //  do
+        ELSE(TokenKind.ELSE, XSTMT1|XTERM|XSTART),  //  else
+        FINALLY(TokenKind.FINALLY, XSTMT1|XSTART),  //  finally
+        FOR(TokenKind.FOR, XSTMT1|XSTART),  //  for
+        IF(TokenKind.IF, XSTMT1|XSTART),  //  if
+        RETURN(TokenKind.RETURN, XSTMT1|XTERM|XSTART),  //  return
+        SWITCH(TokenKind.SWITCH, XSTMT1|XSTART),  //  switch
+        SYNCHRONIZED(TokenKind.SYNCHRONIZED, XSTMT1|XDECL),  //  synchronized
+        THROW(TokenKind.THROW, XSTMT1|XSTART),  //  throw
+        TRY(TokenKind.TRY, XSTMT1|XSTART),  //  try
+        WHILE(TokenKind.WHILE, XSTMT1|XSTART),  //  while
+
+        // Statement keywords that we shouldn't see -- inside braces
+        CASE(TokenKind.CASE, XSTMT|XSTART),  //  case
+        DEFAULT(TokenKind.DEFAULT, XSTMT|XSTART),  //  default method, default case -- neither we should see
+
+        // Expressions (can terminate)
+        INTLITERAL(TokenKind.INTLITERAL, XEXPR1|XTERM),  //
+        LONGLITERAL(TokenKind.LONGLITERAL, XEXPR1|XTERM),  //
+        FLOATLITERAL(TokenKind.FLOATLITERAL, XEXPR1|XTERM),  //
+        DOUBLELITERAL(TokenKind.DOUBLELITERAL, XEXPR1|XTERM),  //
+        CHARLITERAL(TokenKind.CHARLITERAL, XEXPR1|XTERM),  //
+        STRINGLITERAL(TokenKind.STRINGLITERAL, XEXPR1|XTERM),  //
+        TRUE(TokenKind.TRUE, XEXPR1|XTERM),  //  true
+        FALSE(TokenKind.FALSE, XEXPR1|XTERM),  //  false
+        NULL(TokenKind.NULL, XEXPR1|XTERM),  //  null
+        THIS(TokenKind.THIS, XEXPR1|XTERM),  //  this  -- shouldn't see
+
+        // Expressions maybe terminate  //TODO handle these case separately
+        PLUSPLUS(TokenKind.PLUSPLUS, XEXPR1|XTERM),  //  ++
+        SUBSUB(TokenKind.SUBSUB, XEXPR1|XTERM),  //  --
+
+        // Expressions cannot terminate
+        INSTANCEOF(TokenKind.INSTANCEOF, XEXPR),  //  instanceof
+        NEW(TokenKind.NEW, XEXPR1),  //  new
+        SUPER(TokenKind.SUPER, XEXPR1|XDECL),  //  super -- shouldn't see as rec. But in type parameters
+        ARROW(TokenKind.ARROW, XEXPR),  //  ->
+        COLCOL(TokenKind.COLCOL, XEXPR),  //  ::
+        LPAREN(TokenKind.LPAREN, XEXPR),  //  (
+        RPAREN(TokenKind.RPAREN, XEXPR),  //  )
+        LBRACE(TokenKind.LBRACE, XEXPR),  //  {
+        RBRACE(TokenKind.RBRACE, XEXPR),  //  }
+        LBRACKET(TokenKind.LBRACKET, XEXPR),  //  [
+        RBRACKET(TokenKind.RBRACKET, XEXPR),  //  ]
+        ELLIPSIS(TokenKind.ELLIPSIS, XEXPR),  //  ...
+        EQ(TokenKind.EQ, XEXPR),  //  =
+        BANG(TokenKind.BANG, XEXPR1),  //  !
+        TILDE(TokenKind.TILDE, XEXPR1),  //  ~
+        COLON(TokenKind.COLON, XEXPR|XTERM),  //  :
+        EQEQ(TokenKind.EQEQ, XEXPR),  //  ==
+        LTEQ(TokenKind.LTEQ, XEXPR),  //  <=
+        GTEQ(TokenKind.GTEQ, XEXPR),  //  >=
+        BANGEQ(TokenKind.BANGEQ, XEXPR),  //  !=
+        AMPAMP(TokenKind.AMPAMP, XEXPR),  //  &&
+        BARBAR(TokenKind.BARBAR, XEXPR),  //  ||
+        PLUS(TokenKind.PLUS, XEXPR1),  //  +
+        SUB(TokenKind.SUB, XEXPR1),  //  -
+        SLASH(TokenKind.SLASH, XEXPR),  //  /
+        BAR(TokenKind.BAR, XEXPR),  //  |
+        CARET(TokenKind.CARET, XEXPR),  //  ^
+        PERCENT(TokenKind.PERCENT, XEXPR),  //  %
+        PLUSEQ(TokenKind.PLUSEQ, XEXPR),  //  +=
+        SUBEQ(TokenKind.SUBEQ, XEXPR),  //  -=
+        STAREQ(TokenKind.STAREQ, XEXPR),  //  *=
+        SLASHEQ(TokenKind.SLASHEQ, XEXPR),  //  /=
+        AMPEQ(TokenKind.AMPEQ, XEXPR),  //  &=
+        BAREQ(TokenKind.BAREQ, XEXPR),  //  |=
+        CARETEQ(TokenKind.CARETEQ, XEXPR),  //  ^=
+        PERCENTEQ(TokenKind.PERCENTEQ, XEXPR),  //  %=
+        LTLTEQ(TokenKind.LTLTEQ, XEXPR),  //  <<=
+        GTGTEQ(TokenKind.GTGTEQ, XEXPR),  //  >>=
+        GTGTGTEQ(TokenKind.GTGTGTEQ, XEXPR),  //  >>>=
+
+        // combined/processed kinds
+        UNMATCHED(XERRO),
+        PARENS(XEXPR1|XDECL|XSTMT|XTERM),
+        BRACKETS(XEXPR|XDECL|XTERM),
+        BRACES(XSTMT1|XEXPR|XTERM);
+
+        static final EnumMap<TokenKind,TK> tokenKindToTKMap = new EnumMap<>(TokenKind.class);
+
+        final TokenKind tokenKind;
+        final int belongs;
+
+        TK(int b) {
+            this.tokenKind = null;
+            this.belongs = b;
+        }
+
+        TK(TokenKind tokenKind, int b) {
+            this.tokenKind = tokenKind;
+            this.belongs = b;
+        }
+
+        private static TK tokenKindToTK(TokenKind kind) {
+            TK tk = tokenKindToTKMap.get(kind);
+            if (tk == null) {
+                System.err.printf("No corresponding %s for %s: %s\n",
+                        TK.class.getCanonicalName(),
+                        TokenKind.class.getCanonicalName(),
+                        kind);
+                throw new InternalError("No corresponding TK for TokenKind: " + kind);
+            }
+            return tk;
+        }
+
+        boolean isOkToTerminate() {
+            return (belongs & XTERM) != 0;
+        }
+
+        boolean isExpression() {
+            return (belongs & XEXPR) != 0;
+        }
+
+        boolean isDeclaration() {
+            return (belongs & XDECL) != 0;
+        }
+
+        boolean isError() {
+            return (belongs & XERRO) != 0;
+        }
+
+        boolean isStart() {
+            return (belongs & XSTART) != 0;
+        }
+
+        /**
+         * After construction, check that all compiler TokenKind values have
+         * corresponding TK values.
+         */
+        static {
+            for (TK tk : TK.values()) {
+                if (tk.tokenKind != null) {
+                    tokenKindToTKMap.put(tk.tokenKind, tk);
+                }
+            }
+            for (TokenKind kind : TokenKind.values()) {
+                tokenKindToTK(kind); // assure they can be retrieved without error
+            }
+        }
+    }
+
+    /**
+     * A completeness scanner token.
+     */
+    private static class CT {
+
+        /** The token kind */
+        public final TK kind;
+
+        /** The end position of this token */
+        public final int endPos;
+
+        /** The error message **/
+        public final String message;
+
+        private CT(TK tk, Token tok, String msg) {
+            this.kind = tk;
+            this.endPos = tok.endPos;
+            this.message = msg;
+            //throw new InternalError(msg); /* for debugging */
+        }
+
+        private CT(TK tk, Token tok) {
+            this.kind = tk;
+            this.endPos = tok.endPos;
+            this.message = null;
+        }
+
+        private CT(TK tk, int endPos) {
+            this.kind = tk;
+            this.endPos = endPos;
+            this.message = null;
+        }
+    }
+
+    /**
+     * Look for matching tokens (like parens) and other special cases, like "new"
+     */
+    private static class Matched implements Iterator<CT> {
+
+        private final Scanner scanner;
+        private Token current;
+        private CT prevCT;
+        private CT currentCT;
+        private final Deque<Token> stack = new ArrayDeque<>();
+
+        Matched(Scanner scanner) {
+            this.scanner = scanner;
+            advance();
+            prevCT = currentCT = new CT(SEMI, 0); // So is valid for testing
+        }
+
+        @Override
+        public boolean hasNext() {
+            return currentCT.kind != EOF;
+        }
+
+        private Token advance() {
+            Token prev = current;
+            scanner.nextToken();
+            current = scanner.token();
+            return prev;
+        }
+
+        @Override
+        public CT next() {
+            prevCT = currentCT;
+            currentCT = nextCT();
+            return currentCT;
+        }
+
+        private CT match(TK tk, TokenKind open) {
+            Token tok = advance();
+            db("match desired-tk=%s, open=%s, seen-tok=%s", tk, open, tok.kind);
+            if (stack.isEmpty()) {
+                return new CT(ERROR, tok, "Encountered '" + tok + "' with no opening '" + open + "'");
+            }
+            Token p = stack.pop();
+            if (p.kind != open) {
+                return new CT(ERROR, tok, "No match for '" + p + "' instead encountered '" + tok + "'");
+            }
+            return new CT(tk, tok);
+        }
+
+        private void db(String format, Object ... args) {
+//            System.err.printf(format, args);
+//            System.err.printf(" -- stack(");
+//            if (stack.isEmpty()) {
+//
+//            } else {
+//                for (Token tok : stack) {
+//                    System.err.printf("%s ", tok.kind);
+//                }
+//            }
+//            System.err.printf(") current=%s / currentCT=%s\n", current.kind, currentCT.kind);
+        }
+
+        /**
+         * @return the next scanner token
+         */
+        private CT nextCT() {
+            // TODO Annotations?
+            TK prevTK = currentCT.kind;
+            while (true) {
+                db("nextCT");
+                CT ct;
+                switch (current.kind) {
+                    case EOF:
+                        db("eof");
+                        if (stack.isEmpty()) {
+                            ct = new CT(EOF, current);
+                        } else {
+                            TokenKind unmatched = stack.pop().kind;
+                            stack.clear(); // So we will get EOF next time
+                            ct = new CT(UNMATCHED, current, "Unmatched " + unmatched);
+                        }
+                        break;
+                    case LPAREN:
+                    case LBRACE:
+                    case LBRACKET:
+                        stack.push(advance());
+                        prevTK = SEMI; // new start
+                        continue;
+                    case RPAREN:
+                        ct = match(PARENS, TokenKind.LPAREN);
+                        break;
+                    case RBRACE:
+                        ct = match(BRACES, TokenKind.LBRACE);
+                        break;
+                    case RBRACKET:
+                        ct = match(BRACKETS, TokenKind.LBRACKET);
+                        break;
+                    default:
+                        ct = new CT(TK.tokenKindToTK(current.kind), advance());
+                        break;
+                }
+                if (ct.kind.isStart() && !prevTK.isOkToTerminate()) {
+                    return new CT(ERROR, current, "No '" + prevTK + "' before '" + ct.kind + "'");
+                }
+                if (stack.isEmpty() || ct.kind.isError()) {
+                    return ct;
+                }
+                prevTK = ct.kind;
+            }
+        }
+    }
+
+    /**
+     * Fuzzy parser based on token kinds
+     */
+    private static class Parser {
+
+        final Matched in;
+        CT token;
+        Completeness checkResult;
+
+        final JShell proc;
+        final String scannedInput;
+
+
+
+        Parser(Matched in, JShell proc, String scannedInput) {
+            this.in = in;
+            nextToken();
+
+            this.proc = proc;
+            this.scannedInput = scannedInput;
+        }
+
+        final void nextToken() {
+            in.next();
+            token = in.currentCT;
+        }
+
+        boolean shouldAbort(TK tk) {
+            if (token.kind == tk) {
+                nextToken();
+                return false;
+            }
+            switch (token.kind) {
+                case EOF:
+                    checkResult = ((tk == SEMI) && in.prevCT.kind.isOkToTerminate())
+                            ? Completeness.COMPLETE_WITH_SEMI
+                            : Completeness.DEFINITELY_INCOMPLETE;
+                    return true;
+                case UNMATCHED:
+                    checkResult = Completeness.DEFINITELY_INCOMPLETE;
+                    return true;
+                default:
+                    checkResult = error();
+                    return true;
+
+            }
+        }
+
+        Completeness lastly(TK tk) {
+            if (shouldAbort(tk))  return checkResult;
+            return Completeness.COMPLETE;
+        }
+
+        Completeness optionalFinalSemi() {
+            if (!shouldAbort(SEMI)) return Completeness.COMPLETE;
+            if (checkResult == Completeness.COMPLETE_WITH_SEMI) return Completeness.COMPLETE;
+            return checkResult;
+        }
+
+        boolean shouldAbort(Completeness flags) {
+            checkResult = flags;
+            return flags != Completeness.COMPLETE;
+        }
+
+        public Completeness parseUnit() {
+            //System.err.printf("%s:  belongs %o  XANY1 %o\n", token.kind, token.kind.belongs, token.kind.belongs & XANY1);
+            switch (token.kind.belongs & XANY1) {
+                case XEXPR1o:
+                    return parseExpressionOptionalSemi();
+                case XSTMT1o: {
+                    Completeness stat = parseSimpleStatement();
+                    return stat==null? error() : stat;
+                }
+                case XDECL1o:
+                    return parseDeclaration();
+                case XSTMT1o | XDECL1o:
+                case XEXPR1o | XDECL1o:
+                    return disambiguateDeclarationVsExpression();
+                case 0:
+                    if ((token.kind.belongs & XERRO) != 0) {
+                        return parseExpressionStatement(); // Let this gen the status
+                    }
+                    return error();
+                default:
+                    throw new InternalError("Case not covered " + token.kind.belongs + " in " + token.kind);
+            }
+        }
+
+        public Completeness parseDeclaration() {
+            boolean isImport = token.kind == IMPORT;
+            while (token.kind.isDeclaration()) {
+                nextToken();
+            }
+            switch (token.kind) {
+                case EQ:
+                    // Check for array initializer
+                    nextToken();
+                    if (token.kind == BRACES) {
+                        nextToken();
+                        return lastly(SEMI);
+                    }
+                    return parseExpressionStatement();
+                case BRACES:
+                case SEMI:
+                    nextToken();
+                    return Completeness.COMPLETE;
+                case UNMATCHED:
+                    nextToken();
+                    return Completeness.DEFINITELY_INCOMPLETE;
+                case EOF:
+                    switch (in.prevCT.kind) {
+                        case BRACES:
+                        case SEMI:
+                            return Completeness.COMPLETE;
+                        case IDENTIFIER:
+                        case BRACKETS:
+                            return Completeness.COMPLETE_WITH_SEMI;
+                        case STAR:
+                            if (isImport) {
+                                return Completeness.COMPLETE_WITH_SEMI;
+                            } else {
+                                return Completeness.DEFINITELY_INCOMPLETE;
+                            }
+                        default:
+                            return Completeness.DEFINITELY_INCOMPLETE;
+                    }
+                default:
+                    return error();
+            }
+        }
+
+        public Completeness disambiguateDeclarationVsExpression() {
+            // String folding messes up position information.
+            ParseTask pt = proc.taskFactory.new ParseTask(scannedInput);
+            List<? extends Tree> units = pt.units();
+            if (units.isEmpty()) {
+                return error();
+            }
+            Tree unitTree = units.get(0);
+            switch (unitTree.getKind()) {
+                case EXPRESSION_STATEMENT:
+                    return parseExpressionOptionalSemi();
+                case LABELED_STATEMENT:
+                    if (shouldAbort(IDENTIFIER))  return checkResult;
+                    if (shouldAbort(COLON))  return checkResult;
+                    return parseStatement();
+                case VARIABLE:
+                case IMPORT:
+                case CLASS:
+                case ENUM:
+                case ANNOTATION_TYPE:
+                case INTERFACE:
+                case METHOD:
+                    return parseDeclaration();
+                default:
+                    return error();
+            }
+        }
+
+//        public Status parseExpressionOrDeclaration() {
+//            if (token.kind == IDENTIFIER) {
+//                nextToken();
+//                switch (token.kind) {
+//                    case IDENTIFIER:
+//                        return parseDeclaration();
+//                }
+//            }
+//            while (token.kind.isExpressionOrDeclaration()) {
+//                if (!token.kind.isExpression()) {
+//                    return parseDeclaration();
+//                }
+//                if (!token.kind.isDeclaration()) {
+//                    // Expression not declaration
+//                    if (token.kind == EQ) {
+//                        // Check for array initializer
+//                        nextToken();
+//                        if (token.kind == BRACES) {
+//                            nextToken();
+//                            return lastly(SEMI);
+//                        }
+//                    }
+//                    return parseExpressionStatement();
+//                }
+//                nextToken();
+//            }
+//            switch (token.kind) {
+//                case BRACES:
+//                case SEMI:
+//                    nextToken();
+//                    return Status.COMPLETE;
+//                case UNMATCHED:
+//                    nextToken();
+//                    return Status.DEFINITELY_INCOMPLETE;
+//                case EOF:
+//                    if (in.prevCT.kind.isOkToTerminate()) {
+//                        return Status.COMPLETE_WITH_SEMI;
+//                    } else {
+//                        return Status.DEFINITELY_INCOMPLETE;
+//                    }
+//                default:
+//                    return error();
+//            }
+//        }
+
+        public Completeness parseExpressionStatement() {
+            if (shouldAbort(parseExpression()))  return checkResult;
+            return lastly(SEMI);
+        }
+
+        public Completeness parseExpressionOptionalSemi() {
+            if (shouldAbort(parseExpression())) return checkResult;
+            return optionalFinalSemi();
+        }
+
+        public Completeness parseExpression() {
+            while (token.kind.isExpression())
+                nextToken();
+            return Completeness.COMPLETE;
+        }
+
+        public Completeness parseStatement() {
+            Completeness stat = parseSimpleStatement();
+            if (stat == null) {
+                return parseExpressionStatement();
+            }
+            return stat;
+        }
+
+        /**
+         * Statement = Block | IF ParExpression Statement [ELSE Statement] | FOR
+         * "(" ForInitOpt ";" [Expression] ";" ForUpdateOpt ")" Statement | FOR
+         * "(" FormalParameter : Expression ")" Statement | WHILE ParExpression
+         * Statement | DO Statement WHILE ParExpression ";" | TRY Block (
+         * Catches | [Catches] FinallyPart ) | TRY "(" ResourceSpecification
+         * ";"opt ")" Block [Catches] [FinallyPart] | SWITCH ParExpression "{"
+         * SwitchBlockStatementGroups "}" | SYNCHRONIZED ParExpression Block |
+         * RETURN [Expression] ";" | THROW Expression ";" | BREAK [Ident] ";" |
+         * CONTINUE [Ident] ";" | ASSERT Expression [ ":" Expression ] ";" | ";"
+         */
+        public Completeness parseSimpleStatement() {
+            switch (token.kind) {
+                case BRACES:
+                    return lastly(BRACES);
+                case IF: {
+                    nextToken();
+                    if (shouldAbort(PARENS))  return checkResult;
+                    Completeness thenpart = parseStatement();
+                    if (shouldAbort(thenpart)) return thenpart;
+                    if (token.kind == ELSE) {
+                        nextToken();
+                        return parseStatement();
+                    }
+                    return thenpart;
+
+                }
+                case FOR: {
+                    nextToken();
+                    if (shouldAbort(PARENS))  return checkResult;
+                    if (shouldAbort(parseStatement()))  return checkResult;
+                    return Completeness.COMPLETE;
+                }
+                case WHILE: {
+                    nextToken();
+                    if (shouldAbort(PARENS))  return error();
+                    return parseStatement();
+                }
+                case DO: {
+                    nextToken();
+                    switch (parseStatement()) {
+                        case DEFINITELY_INCOMPLETE:
+                        case CONSIDERED_INCOMPLETE:
+                        case COMPLETE_WITH_SEMI:
+                            return Completeness.DEFINITELY_INCOMPLETE;
+                        case UNKNOWN:
+                            return error();
+                        case COMPLETE:
+                            break;
+                    }
+                    if (shouldAbort(WHILE))  return checkResult;
+                    if (shouldAbort(PARENS)) return checkResult;
+                    return lastly(SEMI);
+                }
+                case TRY: {
+                    boolean hasResources = false;
+                    nextToken();
+                    if (token.kind == PARENS) {
+                        nextToken();
+                        hasResources = true;
+                    }
+                    if (shouldAbort(BRACES))  return checkResult;
+                    if (token.kind == CATCH || token.kind == FINALLY) {
+                        while (token.kind == CATCH) {
+                            if (shouldAbort(CATCH))  return checkResult;
+                            if (shouldAbort(PARENS)) return checkResult;
+                            if (shouldAbort(BRACES)) return checkResult;
+                        }
+                        if (token.kind == FINALLY) {
+                            if (shouldAbort(FINALLY))  return checkResult;
+                            if (shouldAbort(BRACES)) return checkResult;
+                        }
+                    } else if (!hasResources) {
+                        if (token.kind == EOF) {
+                            return Completeness.DEFINITELY_INCOMPLETE;
+                        } else {
+                            return error();
+                        }
+                    }
+                    return Completeness.COMPLETE;
+                }
+                case SWITCH: {
+                    nextToken();
+                    if (shouldAbort(PARENS))  return checkResult;
+                    return lastly(BRACES);
+                }
+                case SYNCHRONIZED: {
+                    nextToken();
+                    if (shouldAbort(PARENS))  return checkResult;
+                    return lastly(BRACES);
+                }
+                case THROW: {
+                    nextToken();
+                    if (shouldAbort(parseExpression()))  return checkResult;
+                    return lastly(SEMI);
+                }
+                case SEMI:
+                    return lastly(SEMI);
+                case ASSERT:
+                    nextToken();
+                    // Crude expression parsing just happily eats the optional colon
+                    return parseExpressionStatement();
+                case RETURN:
+                case BREAK:
+                case CONTINUE:
+                    nextToken();
+                    return parseExpressionStatement();
+                // What are these doing here?
+                case ELSE:
+                case FINALLY:
+                case CATCH:
+                    return error();
+                case EOF:
+                    return Completeness.CONSIDERED_INCOMPLETE;
+                default:
+                    return null;
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/jshell/DeclarationSnippet.java	Wed Oct 21 18:40:01 2015 -0700
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2015, 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;
+
+import java.util.Collection;
+import jdk.jshell.Key.DeclarationKey;
+
+/**
+ * Grouping for all declaration Snippets: variable declarations
+ * ({@link jdk.jshell.VarSnippet}), method declarations
+ * ({@link jdk.jshell.MethodSnippet}), and type declarations
+ * ({@link jdk.jshell.TypeDeclSnippet}).
+ * <p>
+ * Declaration snippets are unique in that they can be active
+ *  with unresolved references:
+ * {@link jdk.jshell.Snippet.Status#RECOVERABLE_DEFINED RECOVERABLE_DEFINED} or
+ * {@link jdk.jshell.Snippet.Status#RECOVERABLE_NOT_DEFINED RECOVERABLE_NOT_DEFINED}.
+ * Unresolved references can be queried with
+ * {@link jdk.jshell.JShell#unresolvedDependencies(jdk.jshell.DeclarationSnippet)
+ * JShell.unresolvedDependencies(DeclarationSnippet)}.
+ * <p>
+ * <code>DeclarationSnippet</code> is immutable: an access to
+ * any of its methods will always return the same result.
+ * and thus is thread-safe.
+ */
+public abstract class DeclarationSnippet extends PersistentSnippet {
+
+    private final Wrap corralled;
+    private final Collection<String> declareReferences;
+    private final Collection<String> bodyReferences;
+
+    DeclarationSnippet(DeclarationKey key, String userSource, Wrap guts,
+            String unitName, SubKind subkind, Wrap corralled,
+            Collection<String> declareReferences,
+            Collection<String> bodyReferences) {
+        super(key, userSource, guts, unitName, subkind);
+        this.corralled = corralled;
+        this.declareReferences = declareReferences;
+        this.bodyReferences = bodyReferences;
+    }
+
+    /**** internal access ****/
+
+    /**
+     * @return the corralled guts
+     */
+    @Override
+    Wrap corralled() {
+        return corralled;
+    }
+
+    @Override
+    Collection<String> declareReferences() {
+        return declareReferences;
+    }
+
+    @Override
+    Collection<String> bodyReferences() {
+        return bodyReferences;
+    }
+
+    @Override
+    String importLine(JShell state) {
+        return "import static " + state.maps.classFullName(this) + "." + name() + ";\n";
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/jshell/Diag.java	Wed Oct 21 18:40:01 2015 -0700
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2015, 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;
+
+import java.util.Locale;
+import javax.tools.Diagnostic;
+
+/**
+ * Diagnostic information for a Snippet.
+ * @see jdk.jshell.JShell#diagnostics(jdk.jshell.Snippet)
+ */
+public abstract class Diag {
+    // Simplified view on compiler Diagnostic.
+
+    /**
+     * Used to signal that no position is available.
+     */
+    public final static long NOPOS = Diagnostic.NOPOS;
+
+    /**
+     * Is this diagnostic and error (as opposed to a warning or note)
+     * @return true if this diagnostic is an error
+     */
+    public abstract boolean isError();
+
+    /**
+     * Returns a character offset from the beginning of the source object
+     * associated with this diagnostic that indicates the location of
+     * the problem.  In addition, the following must be true:
+     *
+     * <p>{@code getStartPostion() <= getPosition()}
+     * <p>{@code getPosition() <= getEndPosition()}
+     *
+     * @return character offset from beginning of source; {@link
+     * #NOPOS} if {@link #getSource()} would return {@code null} or if
+     * no location is suitable
+     */
+    public abstract long getPosition();
+
+    /**
+     * Returns the character offset from the beginning of the file
+     * associated with this diagnostic that indicates the start of the
+     * problem.
+     *
+     * @return offset from beginning of file; {@link #NOPOS} if and
+     * only if {@link #getPosition()} returns {@link #NOPOS}
+     */
+    public abstract long getStartPosition();
+
+    /**
+     * Returns the character offset from the beginning of the file
+     * associated with this diagnostic that indicates the end of the
+     * problem.
+     *
+     * @return offset from beginning of file; {@link #NOPOS} if and
+     * only if {@link #getPosition()} returns {@link #NOPOS}
+     */
+    public abstract long getEndPosition();
+
+    /**
+     * Returns a diagnostic code indicating the type of diagnostic.  The
+     * code is implementation-dependent and might be {@code null}.
+     *
+     * @return a diagnostic code
+     */
+    public abstract String getCode();
+
+    /**
+     * Returns a localized message for the given locale.  The actual
+     * message is implementation-dependent.  If the locale is {@code
+     * null} use the default locale.
+     *
+     * @param locale a locale; might be {@code null}
+     * @return a localized message
+     */
+    public abstract String getMessage(Locale locale);
+
+    // *** Internal support ***
+
+    /**
+     * Internal: If this is from a compile, extract the compilation Unit.
+     * Otherwise null.
+     */
+    abstract Unit unitOrNull();
+
+    /**
+     * This is an unreachable-statement error
+     */
+    boolean isUnreachableError() {
+        return getCode().equals("compiler.err.unreachable.stmt");
+    }
+
+    /**
+     * This is a not-a-statement error
+     */
+    boolean isNotAStatementError() {
+        return getCode().equals("compiler.err.not.stmt");
+    }
+
+    /**
+     * This is a resolution error.
+     */
+    boolean isResolutionError() {
+        //TODO: try javac RESOLVE_ERROR flag
+        return getCode().startsWith("compiler.err.cant.resolve");
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/jshell/DiagList.java	Wed Oct 21 18:40:01 2015 -0700
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2015, 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;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.stream.Collectors;
+
+/**
+ * List of diagnostics, with convenient operations.
+ *
+ * @author Robert Field
+ */
+@SuppressWarnings("serial")             // serialVersionUID intentionally omitted
+final class DiagList extends ArrayList<Diag> {
+
+    private int cntNotStmt = 0;
+    private int cntUnreach = 0;
+    private int cntResolve = 0;
+    private int cntOther = 0;
+
+    DiagList() {
+        super();
+    }
+
+    DiagList(Diag d) {
+        super();
+        add(d);
+    }
+
+    DiagList(Collection<? extends Diag> c) {
+        super();
+        addAll(c);
+    }
+
+    private void tally(Diag d) {
+        if (d.isError()) {
+            if (d.isUnreachableError()) {
+                ++cntUnreach;
+            } else if (d.isNotAStatementError()) {
+                ++cntNotStmt;
+            } else if (d.isResolutionError()) {
+                ++cntResolve;
+            } else {
+                ++cntOther;
+            }
+        }
+    }
+
+    @Override
+    public boolean addAll(Collection<? extends Diag> c) {
+        return c.stream().filter(d -> add(d)).count() > 0;
+    }
+
+    @Override
+    public Diag set(int index, Diag element) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void add(int index, Diag element) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean add(Diag d) {
+        boolean added = super.add(d);
+        if (added) {
+            tally(d);
+        }
+        return added;
+    }
+
+    @Override
+    public boolean addAll(int index, Collection<? extends Diag> c) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean remove(Object o) {
+        throw new UnsupportedOperationException();
+    }
+
+    DiagList ofUnit(Unit u) {
+        return this.stream()
+                .filter(d -> d.unitOrNull() == u)
+                .collect(Collectors.toCollection(() -> new DiagList()));
+    }
+
+    boolean hasErrors() {
+        return (cntNotStmt + cntResolve + cntUnreach + cntOther) > 0;
+    }
+
+    boolean hasResolutionErrorsAndNoOthers() {
+        return cntResolve > 0 && (cntNotStmt + cntUnreach + cntOther) == 0;
+    }
+
+    boolean hasUnreachableError() {
+        return cntUnreach > 0;
+    }
+
+    boolean hasNotStatement() {
+        return cntNotStmt > 0;
+    }
+
+    boolean hasOtherThanNotStatementErrors() {
+        return (cntResolve + cntUnreach + cntOther) > 0;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/jshell/ErroneousSnippet.java	Wed Oct 21 18:40:01 2015 -0700
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2015, 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;
+
+import jdk.jshell.Key.ErroneousKey;
+
+/**
+ * A snippet of code that is not valid Java programming language code, and for
+ * which the kind of snippet could not be determined.
+ * The Kind is {@link jdk.jshell.Snippet.Kind#ERRONEOUS ERRONEOUS}.
+ * <p>
+ * <code>ErroneousSnippet</code> is immutable: an access to
+ * any of its methods will always return the same result.
+ * and thus is thread-safe.
+ */
+public class ErroneousSnippet extends Snippet {
+
+    ErroneousSnippet(ErroneousKey key, String userSource, Wrap guts, SubKind subkind) {
+        super(key, userSource, guts, null, subkind);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/jshell/Eval.java	Wed Oct 21 18:40:01 2015 -0700
@@ -0,0 +1,730 @@
+/*
+ * Copyright (c) 2014, 2015, 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;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import javax.lang.model.element.Modifier;
+import com.sun.source.tree.ArrayTypeTree;
+import com.sun.source.tree.AssignmentTree;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.ModifiersTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.VariableTree;
+import com.sun.tools.javac.tree.JCTree;
+import com.sun.tools.javac.tree.Pretty;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import jdk.jshell.ClassTracker.ClassInfo;
+import jdk.jshell.Key.ErroneousKey;
+import jdk.jshell.Key.MethodKey;
+import jdk.jshell.Snippet.SubKind;
+import jdk.jshell.TaskFactory.AnalyzeTask;
+import jdk.jshell.TaskFactory.BaseTask;
+import jdk.jshell.TaskFactory.CompileTask;
+import jdk.jshell.TaskFactory.ParseTask;
+import jdk.jshell.TreeDissector.ExpressionInfo;
+import jdk.jshell.Wrap.Range;
+import jdk.jshell.Snippet.Status;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
+import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
+import static jdk.jshell.Util.*;
+import static jdk.internal.jshell.remote.RemoteCodes.DOIT_METHOD_NAME;
+import static jdk.internal.jshell.remote.RemoteCodes.prefixPattern;
+import static jdk.jshell.Snippet.SubKind.SINGLE_TYPE_IMPORT_SUBKIND;
+import static jdk.jshell.Snippet.SubKind.SINGLE_STATIC_IMPORT_SUBKIND;
+import static jdk.jshell.Snippet.SubKind.TYPE_IMPORT_ON_DEMAND_SUBKIND;
+import static jdk.jshell.Snippet.SubKind.STATIC_IMPORT_ON_DEMAND_SUBKIND;
+
+/**
+ * The Evaluation Engine. Source internal analysis, wrapping control,
+ * compilation, declaration. redefinition, replacement, and execution.
+ *
+ * @author Robert Field
+ */
+class Eval {
+
+    private static final Pattern IMPORT_PATTERN = Pattern.compile("import\\p{javaWhitespace}+(?<static>static\\p{javaWhitespace}+)?(?<fullname>[\\p{L}\\p{N}_\\$\\.]+\\.(?<name>[\\p{L}\\p{N}_\\$]+|\\*))");
+
+    private int varNumber = 0;
+
+    private final JShell state;
+
+    Eval(JShell state) {
+        this.state = state;
+    }
+
+    List<SnippetEvent> eval(String userSource) throws IllegalStateException {
+        String compileSource = Util.trimEnd(new MaskCommentsAndModifiers(userSource, false).cleared());
+        if (compileSource.length() == 0) {
+            return Collections.emptyList();
+        }
+        // String folding messes up position information.
+        ParseTask pt = state.taskFactory.new ParseTask(compileSource);
+        if (pt.getDiagnostics().hasOtherThanNotStatementErrors()) {
+            return compileFailResult(pt, userSource);
+        }
+
+        List<? extends Tree> units = pt.units();
+        if (units.isEmpty()) {
+            return compileFailResult(pt, userSource);
+        }
+        // Erase illegal modifiers
+        compileSource = new MaskCommentsAndModifiers(compileSource, true).cleared();
+        Tree unitTree = units.get(0);
+        state.debug(DBG_GEN, "Kind: %s -- %s\n", unitTree.getKind(), unitTree);
+        switch (unitTree.getKind()) {
+            case IMPORT:
+                return processImport(userSource, compileSource);
+            case VARIABLE:
+                return processVariables(userSource, units, compileSource, pt);
+            case EXPRESSION_STATEMENT:
+                return processExpression(userSource, compileSource);
+            case CLASS:
+                return processClass(userSource, unitTree, compileSource, SubKind.CLASS_SUBKIND, pt);
+            case ENUM:
+                return processClass(userSource, unitTree, compileSource, SubKind.ENUM_SUBKIND, pt);
+            case ANNOTATION_TYPE:
+                return processClass(userSource, unitTree, compileSource, SubKind.ANNOTATION_TYPE_SUBKIND, pt);
+            case INTERFACE:
+                return processClass(userSource, unitTree, compileSource, SubKind.INTERFACE_SUBKIND, pt);
+            case METHOD:
+                return processMethod(userSource, unitTree, compileSource, pt);
+            default:
+                return processStatement(userSource, compileSource);
+        }
+    }
+
+    private List<SnippetEvent> processImport(String userSource, String compileSource) {
+        Wrap guts = Wrap.importWrap(compileSource);
+        Matcher mat = IMPORT_PATTERN.matcher(compileSource);
+        String fullname;
+        String name;
+        boolean isStatic;
+        if (mat.find()) {
+            isStatic = mat.group("static") != null;
+            name = mat.group("name");
+            fullname = mat.group("fullname");
+        } else {
+            // bad import -- fake it
+            isStatic = compileSource.contains("static");
+            name = fullname = compileSource;
+        }
+        String fullkey = (isStatic ? "static-" : "") + fullname;
+        boolean isStar = name.equals("*");
+        String keyName = isStar
+                ? fullname
+                : name;
+        SubKind snippetKind = isStar
+                ? (isStatic ? STATIC_IMPORT_ON_DEMAND_SUBKIND : TYPE_IMPORT_ON_DEMAND_SUBKIND)
+                : (isStatic ? SINGLE_STATIC_IMPORT_SUBKIND : SINGLE_TYPE_IMPORT_SUBKIND);
+        Snippet snip = new ImportSnippet(state.keyMap.keyForImport(keyName, snippetKind),
+                userSource, guts, fullname, name, snippetKind, fullkey, isStatic, isStar);
+        return declare(snip);
+    }
+
+    private static class EvalPretty extends Pretty {
+
+        private final Writer out;
+
+        public EvalPretty(Writer writer, boolean bln) {
+            super(writer, bln);
+            this.out = writer;
+        }
+
+        /**
+         * Print string, DO NOT replacing all non-ascii character with unicode
+         * escapes.
+         */
+        @Override
+        public void print(Object o) throws IOException {
+            out.write(o.toString());
+        }
+
+        static String prettyExpr(JCTree tree, boolean bln) {
+            StringWriter out = new StringWriter();
+            try {
+                new EvalPretty(out, bln).printExpr(tree);
+            } catch (IOException e) {
+                throw new AssertionError(e);
+            }
+            return out.toString();
+        }
+    }
+
+    private List<SnippetEvent> processVariables(String userSource, List<? extends Tree> units, String compileSource, ParseTask pt) {
+        List<SnippetEvent> allEvents = new ArrayList<>();
+        TreeDissector dis = new TreeDissector(pt);
+        for (Tree unitTree : units) {
+            VariableTree vt = (VariableTree) unitTree;
+            String name = vt.getName().toString();
+            String typeName = EvalPretty.prettyExpr((JCTree) vt.getType(), false);
+            Tree baseType = vt.getType();
+            TreeDependencyScanner tds = new TreeDependencyScanner();
+            tds.scan(baseType); // Not dependent on initializer
+            StringBuilder sbBrackets = new StringBuilder();
+            while (baseType instanceof ArrayTypeTree) {
+                //TODO handle annotations too
+                baseType = ((ArrayTypeTree) baseType).getType();
+                sbBrackets.append("[]");
+            }
+            Range rtype = dis.treeToRange(baseType);
+            Range runit = dis.treeToRange(vt);
+            runit = new Range(runit.begin, runit.end - 1);
+            ExpressionTree it = vt.getInitializer();
+            Range rinit = null;
+            int nameMax = runit.end - 1;
+            SubKind subkind;
+            if (it != null) {
+                subkind = SubKind.VAR_DECLARATION_WITH_INITIALIZER_SUBKIND;
+                rinit = dis.treeToRange(it);
+                nameMax = rinit.begin - 1;
+            } else {
+                subkind = SubKind.VAR_DECLARATION_SUBKIND;
+            }
+            int nameStart = compileSource.lastIndexOf(name, nameMax);
+            if (nameStart < 0) {
+                throw new AssertionError("Name '" + name + "' not found");
+            }
+            int nameEnd = nameStart + name.length();
+            Range rname = new Range(nameStart, nameEnd);
+            Wrap guts = Wrap.varWrap(compileSource, rtype, sbBrackets.toString(), rname, rinit);
+            Snippet snip = new VarSnippet(state.keyMap.keyForVariable(name), userSource, guts,
+                    name, subkind, typeName,
+                    tds.declareReferences());
+            DiagList modDiag = modifierDiagnostics(vt.getModifiers(), dis, true);
+            List<SnippetEvent> res1 = declare(snip, modDiag);
+            allEvents.addAll(res1);
+        }
+
+        return allEvents;
+    }
+
+    private List<SnippetEvent> processExpression(String userSource, String compileSource) {
+        String name = null;
+        ExpressionInfo ei = typeOfExpression(compileSource);
+        ExpressionTree assignVar;
+        Wrap guts;
+        Snippet snip;
+        if (ei != null && ei.isNonVoid) {
+            String typeName = ei.typeName;
+            SubKind subkind;
+            if (ei.tree instanceof IdentifierTree) {
+                IdentifierTree id = (IdentifierTree) ei.tree;
+                name = id.getName().toString();
+                subkind = SubKind.VAR_VALUE_SUBKIND;
+
+            } else if (ei.tree instanceof AssignmentTree
+                    && (assignVar = ((AssignmentTree) ei.tree).getVariable()) instanceof IdentifierTree) {
+                name = assignVar.toString();
+                subkind = SubKind.ASSIGNMENT_SUBKIND;
+            } else {
+                subkind = SubKind.OTHER_EXPRESSION_SUBKIND;
+            }
+            if (shouldGenTempVar(subkind)) {
+                if (state.tempVariableNameGenerator != null) {
+                    name = state.tempVariableNameGenerator.get();
+                }
+                while (name == null || state.keyMap.doesVariableNameExist(name)) {
+                    name = "$" + ++varNumber;
+                }
+                guts = Wrap.tempVarWrap(compileSource, typeName, name);
+                Collection<String> declareReferences = null; //TODO
+                snip = new VarSnippet(state.keyMap.keyForVariable(name), userSource, guts,
+                        name, SubKind.TEMP_VAR_EXPRESSION_SUBKIND, typeName, declareReferences);
+            } else {
+                guts = Wrap.methodReturnWrap(compileSource);
+                snip = new ExpressionSnippet(state.keyMap.keyForExpression(name, typeName), userSource, guts,
+                        name, subkind);
+            }
+        } else {
+            guts = Wrap.methodWrap(compileSource);
+            if (ei == null) {
+                // We got no type info, check for not a statement by trying
+                AnalyzeTask at = trialCompile(guts);
+                if (at.getDiagnostics().hasNotStatement()) {
+                    guts = Wrap.methodReturnWrap(compileSource);
+                    at = trialCompile(guts);
+                }
+                if (at.hasErrors()) {
+                    return compileFailResult(at, userSource);
+                }
+            }
+            snip = new StatementSnippet(state.keyMap.keyForStatement(), userSource, guts);
+        }
+        return declare(snip);
+    }
+
+    private List<SnippetEvent> processClass(String userSource, Tree unitTree, String compileSource, SubKind snippetKind, ParseTask pt) {
+        TreeDependencyScanner tds = new TreeDependencyScanner();
+        tds.scan(unitTree);
+
+        TreeDissector dis = new TreeDissector(pt);
+
+        ClassTree klassTree = (ClassTree) unitTree;
+        String name = klassTree.getSimpleName().toString();
+        Wrap guts = Wrap.classMemberWrap(compileSource);
+        Wrap corralled = null; //TODO
+        Snippet snip = new TypeDeclSnippet(state.keyMap.keyForClass(name), userSource, guts,
+                name, snippetKind,
+                corralled, tds.declareReferences(), tds.bodyReferences());
+        DiagList modDiag = modifierDiagnostics(klassTree.getModifiers(), dis, false);
+        return declare(snip, modDiag);
+    }
+
+    private List<SnippetEvent> processStatement(String userSource, String compileSource) {
+        Wrap guts = Wrap.methodWrap(compileSource);
+        // Check for unreachable by trying
+        AnalyzeTask at = trialCompile(guts);
+        if (at.hasErrors()) {
+            if (at.getDiagnostics().hasUnreachableError()) {
+                guts = Wrap.methodUnreachableSemiWrap(compileSource);
+                at = trialCompile(guts);
+                if (at.hasErrors()) {
+                    if (at.getDiagnostics().hasUnreachableError()) {
+                        // Without ending semicolon
+                        guts = Wrap.methodUnreachableWrap(compileSource);
+                        at = trialCompile(guts);
+                    }
+                    if (at.hasErrors()) {
+                        return compileFailResult(at, userSource);
+                    }
+                }
+            } else {
+                return compileFailResult(at, userSource);
+            }
+        }
+        Snippet snip = new StatementSnippet(state.keyMap.keyForStatement(), userSource, guts);
+        return declare(snip);
+    }
+
+    private OuterWrap wrapInClass(String className, Set<Key> except, String userSource, Wrap guts, Collection<Snippet> plus) {
+        String imports = state.maps.packageAndImportsExcept(except, plus);
+        return OuterWrap.wrapInClass(state.maps.packageName(), className, imports, userSource, guts);
+    }
+
+    OuterWrap wrapInClass(Snippet snip, Set<Key> except, Wrap guts, Collection<Snippet> plus) {
+        return wrapInClass(snip.className(), except, snip.source(), guts, plus);
+    }
+
+    private AnalyzeTask trialCompile(Wrap guts) {
+        OuterWrap outer = wrapInClass(REPL_DOESNOTMATTER_CLASS_NAME,
+                Collections.emptySet(), "", guts, null);
+        return state.taskFactory.new AnalyzeTask(outer);
+    }
+
+    private List<SnippetEvent> processMethod(String userSource, Tree unitTree, String compileSource, ParseTask pt) {
+        TreeDependencyScanner tds = new TreeDependencyScanner();
+        tds.scan(unitTree);
+
+        MethodTree mt = (MethodTree) unitTree;
+        TreeDissector dis = new TreeDissector(pt);
+        DiagList modDiag = modifierDiagnostics(mt.getModifiers(), dis, true);
+        if (modDiag.hasErrors()) {
+            return compileFailResult(modDiag, userSource);
+        }
+        String unitName = mt.getName().toString();
+        Wrap guts = Wrap.classMemberWrap(compileSource);
+
+        Range modRange = dis.treeToRange(mt.getModifiers());
+        Range tpRange = dis.treeListToRange(mt.getTypeParameters());
+        Range typeRange = dis.treeToRange(mt.getReturnType());
+        String name = mt.getName().toString();
+        Range paramRange = dis.treeListToRange(mt.getParameters());
+        Range throwsRange = dis.treeListToRange(mt.getThrows());
+
+        String parameterTypes
+                = mt.getParameters()
+                .stream()
+                .map(param -> dis.treeToRange(param.getType()).part(compileSource))
+                .collect(Collectors.joining(","));
+        String signature = "(" + parameterTypes + ")" + typeRange.part(compileSource);
+
+        MethodKey key = state.keyMap.keyForMethod(name, parameterTypes);
+        // rewrap with correct Key index
+        Wrap corralled = Wrap.corralledMethod(compileSource,
+                modRange, tpRange, typeRange, name, paramRange, throwsRange, key.index());
+        Snippet snip = new MethodSnippet(key, userSource, guts,
+                unitName, signature,
+                corralled, tds.declareReferences(), tds.bodyReferences());
+        return declare(snip, modDiag);
+    }
+
+    /**
+     * The snippet has failed, return with the rejected event
+     *
+     * @param xt the task from which to extract the failure diagnostics
+     * @param userSource the incoming bad user source
+     * @return a rejected snippet event
+     */
+    private List<SnippetEvent> compileFailResult(BaseTask xt, String userSource) {
+        return compileFailResult(xt.getDiagnostics(), userSource);
+    }
+
+    /**
+     * The snippet has failed, return with the rejected event
+     *
+     * @param diags the failure diagnostics
+     * @param userSource the incoming bad user source
+     * @return a rejected snippet event
+     */
+    private List<SnippetEvent> compileFailResult(DiagList diags, String userSource) {
+        ErroneousKey key = state.keyMap.keyForErroneous();
+        Snippet snip = new ErroneousSnippet(key, userSource, null, SubKind.UNKNOWN_SUBKIND);
+        snip.setFailed(diags);
+        state.maps.installSnippet(snip);
+        return Collections.singletonList(new SnippetEvent(
+                snip, Status.NONEXISTENT, Status.REJECTED,
+                false, null, null, null)
+        );
+    }
+
+    private ExpressionInfo typeOfExpression(String expression) {
+        Wrap guts = Wrap.methodReturnWrap(expression);
+        TaskFactory.AnalyzeTask at = trialCompile(guts);
+        if (!at.hasErrors() && at.cuTree() != null) {
+            return new TreeDissector(at)
+                    .typeOfReturnStatement(at.messages(), state.maps::fullClassNameAndPackageToClass);
+        }
+        return null;
+    }
+
+    /**
+     * Should a temp var wrap the expression. TODO make this user configurable.
+     *
+     * @param snippetKind
+     * @return
+     */
+    private boolean shouldGenTempVar(SubKind snippetKind) {
+        return snippetKind == SubKind.OTHER_EXPRESSION_SUBKIND;
+    }
+
+    List<SnippetEvent> drop(Snippet si) {
+        Unit c = new Unit(state, si);
+
+        Set<Unit> ins = c.dependents().collect(toSet());
+        Set<Unit> outs = compileAndLoad(ins);
+
+        return events(c, outs, null, null);
+    }
+
+    private List<SnippetEvent> declare(Snippet si) {
+        return declare(si, new DiagList());
+    }
+
+    private List<SnippetEvent> declare(Snippet si, DiagList generatedDiagnostics) {
+        Unit c = new Unit(state, si, null, generatedDiagnostics);
+
+        // Ignores duplicates
+        //TODO: remove, modify, or move to edit
+        if (c.isRedundant()) {
+            return Collections.emptyList();
+        }
+
+        Set<Unit> ins = new LinkedHashSet<>();
+        ins.add(c);
+        Set<Unit> outs = compileAndLoad(ins);
+
+        if (!si.status().isDefined
+                && si.diagnostics().isEmpty()
+                && si.unresolved().isEmpty()) {
+            // did not succeed, but no record of it, extract from others
+            si.setDiagnostics(outs.stream()
+                    .flatMap(u -> u.snippet().diagnostics().stream())
+                    .collect(Collectors.toCollection(DiagList::new)));
+        }
+
+        // If appropriate, execute the snippet
+        String value = null;
+        Exception exception = null;
+        if (si.isExecutable() && si.status().isDefined) {
+            try {
+                value = state.executionControl().commandInvoke(state.maps.classFullName(si));
+                value = si.subKind().hasValue()
+                        ? expunge(value)
+                        : "";
+            } catch (EvalException ex) {
+                exception = translateExecutionException(ex);
+            } catch (UnresolvedReferenceException ex) {
+                exception = ex;
+            }
+        }
+        return events(c, outs, value, exception);
+    }
+
+    private List<SnippetEvent> events(Unit c, Collection<Unit> outs, String value, Exception exception) {
+        List<SnippetEvent> events = new ArrayList<>();
+        events.add(c.event(value, exception));
+        events.addAll(outs.stream()
+                .filter(u -> u != c)
+                .map(u -> u.event(null, null))
+                .collect(Collectors.toList()));
+        events.addAll(outs.stream()
+                .flatMap(u -> u.secondaryEvents().stream())
+                .collect(Collectors.toList()));
+        //System.err.printf("Events: %s\n", events);
+        return events;
+    }
+
+    private Set<Unit> compileAndLoad(Set<Unit> ins) {
+        if (ins.isEmpty()) {
+            return ins;
+        }
+        Set<Unit> replaced = new LinkedHashSet<>();
+        while (true) {
+            state.debug(DBG_GEN, "compileAndLoad  %s\n", ins);
+
+            ins.stream().forEach(u -> u.initialize(ins));
+            AnalyzeTask at = state.taskFactory.new AnalyzeTask(ins);
+            ins.stream().forEach(u -> u.setDiagnostics(at));
+            // corral any Snippets that need it
+            if (ins.stream().filter(u -> u.corralIfNeeded(ins)).count() > 0) {
+                // if any were corralled, re-analyze everything
+                AnalyzeTask cat = state.taskFactory.new AnalyzeTask(ins);
+                ins.stream().forEach(u -> u.setCorralledDiagnostics(cat));
+            }
+            ins.stream().forEach(u -> u.setStatus());
+            // compile and load the legit snippets
+            boolean success;
+            while (true) {
+                List<Unit> legit = ins.stream()
+                        .filter(u -> u.isDefined())
+                        .collect(toList());
+                state.debug(DBG_GEN, "compileAndLoad ins = %s -- legit = %s\n",
+                        ins, legit);
+                if (legit.isEmpty()) {
+                    // no class files can be generated
+                    success = true;
+                } else {
+                    // re-wrap with legit imports
+                    legit.stream().forEach(u -> u.setWrap(ins, legit));
+
+                    // generate class files for those capable
+                    CompileTask ct = state.taskFactory.new CompileTask(legit);
+                    if (!ct.compile()) {
+                        // oy! compile failed because of recursive new unresolved
+                        if (legit.stream()
+                                .filter(u -> u.smashingErrorDiagnostics(ct))
+                                .count() > 0) {
+                            // try again, with the erroreous removed
+                            continue;
+                        } else {
+                            state.debug(DBG_GEN, "Should never happen error-less failure - %s\n",
+                                    legit);
+                        }
+                    }
+
+                    // load all new classes
+                    load(legit.stream()
+                            .flatMap(u -> u.classesToLoad(ct.classInfoList(u)))
+                            .collect(toList()));
+                    // attempt to redefine the remaining classes
+                    List<Unit> toReplace = legit.stream()
+                            .filter(u -> !u.doRedefines())
+                            .collect(toList());
+
+                    // prevent alternating redefine/replace cyclic dependency
+                    // loop by replacing all that have been replaced
+                    if (!toReplace.isEmpty()) {
+                        replaced.addAll(toReplace);
+                        replaced.stream().forEach(u -> u.markForReplacement());
+                    }
+
+                    success = toReplace.isEmpty();
+                }
+                break;
+            }
+
+            // add any new dependencies to the working set
+            List<Unit> newDependencies = ins.stream()
+                    .flatMap(u -> u.effectedDependents())
+                    .collect(toList());
+            state.debug(DBG_GEN, "compileAndLoad %s -- deps: %s  success: %s\n",
+                    ins, newDependencies, success);
+            if (!ins.addAll(newDependencies) && success) {
+                // all classes that could not be directly loaded (because they
+                // are new) have been redefined, and no new dependnencies were
+                // identified
+                ins.stream().forEach(u -> u.finish());
+                return ins;
+            }
+        }
+    }
+
+    private void load(List<ClassInfo> cil) {
+        if (!cil.isEmpty()) {
+            state.executionControl().commandLoad(cil);
+        }
+    }
+
+    private EvalException translateExecutionException(EvalException ex) {
+        StackTraceElement[] raw = ex.getStackTrace();
+        int last = raw.length;
+        do {
+            if (last == 0) {
+                last = raw.length - 1;
+                break;
+            }
+        } while (!isWrap(raw[--last]));
+        StackTraceElement[] elems = new StackTraceElement[last + 1];
+        for (int i = 0; i <= last; ++i) {
+            StackTraceElement r = raw[i];
+            String rawKlass = r.getClassName();
+            Matcher matcher = prefixPattern.matcher(rawKlass);
+            String num;
+            if (matcher.find() && (num = matcher.group("num")) != null) {
+                int end = matcher.end();
+                if (rawKlass.charAt(end - 1) == '$') {
+                    --end;
+                }
+                int id = Integer.parseInt(num);
+                Snippet si = state.maps.getSnippet(id);
+                String klass = expunge(rawKlass);
+                String method = r.getMethodName().equals(DOIT_METHOD_NAME) ? "" : r.getMethodName();
+                String file = "#" + id;
+                int line = si.outerWrap().wrapLineToSnippetLine(r.getLineNumber() - 1) + 1;
+                elems[i] = new StackTraceElement(klass, method, file, line);
+            } else if (r.getFileName().equals("<none>")) {
+                elems[i] = new StackTraceElement(r.getClassName(), r.getMethodName(), null, r.getLineNumber());
+            } else {
+                elems[i] = r;
+            }
+        }
+        String msg = ex.getMessage();
+        if (msg.equals("<none>")) {
+            msg = null;
+        }
+        return new EvalException(msg, ex.getExceptionClassName(), elems);
+    }
+
+    private boolean isWrap(StackTraceElement ste) {
+        return prefixPattern.matcher(ste.getClassName()).find();
+    }
+
+    private DiagList modifierDiagnostics(ModifiersTree modtree,
+            final TreeDissector dis, boolean isAbstractProhibited) {
+
+        class ModifierDiagnostic extends Diag {
+
+            final boolean fatal;
+            final String message;
+
+            ModifierDiagnostic(List<Modifier> list, boolean fatal) {
+                this.fatal = fatal;
+                StringBuilder sb = new StringBuilder();
+                sb.append((list.size() > 1) ? "Modifiers " : "Modifier ");
+                for (Modifier mod : list) {
+                    sb.append("'");
+                    sb.append(mod.toString());
+                    sb.append("' ");
+                }
+                sb.append("not permitted in top-level declarations");
+                if (!fatal) {
+                    sb.append(", ignored");
+                }
+                this.message = sb.toString();
+            }
+
+            @Override
+            public boolean isError() {
+                return fatal;
+            }
+
+            @Override
+            public long getPosition() {
+                return dis.getStartPosition(modtree);
+            }
+
+            @Override
+            public long getStartPosition() {
+                return dis.getStartPosition(modtree);
+            }
+
+            @Override
+            public long getEndPosition() {
+                return dis.getEndPosition(modtree);
+            }
+
+            @Override
+            public String getCode() {
+                return fatal
+                        ? "jdk.eval.error.illegal.modifiers"
+                        : "jdk.eval.warn.illegal.modifiers";
+            }
+
+            @Override
+            public String getMessage(Locale locale) {
+                return message;
+            }
+
+            @Override
+            Unit unitOrNull() {
+                return null;
+            }
+        }
+
+        List<Modifier> list = new ArrayList<>();
+        boolean fatal = false;
+        for (Modifier mod : modtree.getFlags()) {
+            switch (mod) {
+                case SYNCHRONIZED:
+                case NATIVE:
+                    list.add(mod);
+                    fatal = true;
+                    break;
+                case ABSTRACT:
+                    if (isAbstractProhibited) {
+                        list.add(mod);
+                        fatal = true;
+                    }
+                    break;
+                case PUBLIC:
+                case PROTECTED:
+                case PRIVATE:
+                case STATIC:
+                case FINAL:
+                    list.add(mod);
+                    break;
+            }
+        }
+        return list.isEmpty()
+                ? new DiagList()
+                : new DiagList(new ModifierDiagnostic(list, fatal));
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/jshell/EvalException.java	Wed Oct 21 18:40:01 2015 -0700
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2015, 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;
+
+/**
+ * Wraps an exception thrown in the remotely executing client.
+ * An instance of <code>EvalException</code> can be returned in the
+ * {@link jdk.jshell.SnippetEvent#exception()} query.
+ * The name of the exception thrown is available from
+ * {@link jdk.jshell.EvalException#getExceptionClassName()}.
+ * Message and stack can be queried by methods on <code>Exception</code>.
+ * <p>
+ * Note that in stack trace frames representing JShell Snippets,
+ * <code>StackTraceElement.getFileName()</code> will return "#" followed by
+ * the Snippet id and for snippets without a method name (for example an
+ * expression) <code>StackTraceElement.getMethodName()</code> will be the
+ * empty string.
+ */
+@SuppressWarnings("serial")             // serialVersionUID intentionally omitted
+public class EvalException extends Exception {
+    private final String exceptionClass;
+
+    EvalException(String message, String exceptionClass, StackTraceElement[] stackElements) {
+        super(message);
+        this.exceptionClass = exceptionClass;
+        this.setStackTrace(stackElements);
+    }
+
+    /**
+     * Returns the name of the Throwable subclass which was thrown in the
+     * executing client. Note this class may not be loaded in the controlling
+     * process.
+     * See
+     * {@link java.lang.Class#getName() Class.getName()} for the format of the string.
+     * @return the name of the exception class as a String
+     */
+    public String getExceptionClassName() {
+        return exceptionClass;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/jshell/ExecutionControl.java	Wed Oct 21 18:40:01 2015 -0700
@@ -0,0 +1,311 @@
+/*
+ * Copyright (c) 2014, 2015, 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;
+
+import static jdk.internal.jshell.remote.RemoteCodes.*;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.ServerSocket;
+import java.net.Socket;
+import com.sun.jdi.*;
+import java.io.EOFException;
+import java.util.List;
+import java.util.Map;
+import jdk.jshell.ClassTracker.ClassInfo;
+import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
+
+/**
+ * Controls the remote execution environment.
+ *
+ * @author Robert Field
+ */
+class ExecutionControl {
+
+    private final JDIEnv env;
+    private final SnippetMaps maps;
+    private JDIEventHandler handler;
+    private Socket socket;
+    private ObjectInputStream in;
+    private ObjectOutputStream out;
+    private final JShell proc;
+
+    ExecutionControl(JDIEnv env, SnippetMaps maps, JShell proc) {
+        this.env = env;
+        this.maps = maps;
+        this.proc = proc;
+    }
+
+    void launch() throws IOException {
+        try (ServerSocket listener = new ServerSocket(0)) {
+            // timeout after 60 seconds
+            listener.setSoTimeout(60000);
+            int port = listener.getLocalPort();
+            jdiGo(port);
+            socket = listener.accept();
+            // out before in -- match remote creation so we don't hang
+            out = new ObjectOutputStream(socket.getOutputStream());
+            in = new ObjectInputStream(socket.getInputStream());
+        }
+    }
+
+    void commandExit() {
+        try {
+            if (out != null) {
+                out.writeInt(CMD_EXIT);
+                out.flush();
+            }
+            JDIConnection c = env.connection();
+            if (c != null) {
+                c.disposeVM();
+            }
+        } catch (IOException ex) {
+            proc.debug(DBG_GEN, "Exception on JDI exit: %s\n", ex);
+        }
+    }
+
+
+    boolean commandLoad(List<ClassInfo> cil) {
+        try {
+            out.writeInt(CMD_LOAD);
+            out.writeInt(cil.size());
+            for (ClassInfo ci : cil) {
+                out.writeUTF(ci.getClassName());
+                out.writeObject(ci.getBytes());
+            }
+            out.flush();
+            return readAndReportResult();
+        } catch (IOException ex) {
+            proc.debug(DBG_GEN, "IOException on remote load operation: %s\n", ex);
+            return false;
+        }
+    }
+
+    String commandInvoke(String classname) throws EvalException, UnresolvedReferenceException {
+        try {
+            synchronized (STOP_LOCK) {
+                userCodeRunning = true;
+            }
+            out.writeInt(CMD_INVOKE);
+            out.writeUTF(classname);
+            out.flush();
+            if (readAndReportExecutionResult()) {
+                String result = in.readUTF();
+                return result;
+            }
+        } catch (EOFException ex) {
+            env.shutdown();
+        } catch (IOException | ClassNotFoundException ex) {
+            proc.debug(DBG_GEN, "Exception on remote invoke: %s\n", ex);
+            return "Execution failure: " + ex.getMessage();
+        } finally {
+            synchronized (STOP_LOCK) {
+                userCodeRunning = false;
+            }
+        }
+        return "";
+    }
+
+    String commandVarValue(String classname, String varname) {
+        try {
+            out.writeInt(CMD_VARVALUE);
+            out.writeUTF(classname);
+            out.writeUTF(varname);
+            out.flush();
+            if (readAndReportResult()) {
+                String result = in.readUTF();
+                return result;
+            }
+        } catch (EOFException ex) {
+            env.shutdown();
+        } catch (IOException ex) {
+            proc.debug(DBG_GEN, "Exception on remote var value: %s\n", ex);
+            return "Execution failure: " + ex.getMessage();
+        }
+        return "";
+    }
+
+    boolean commandAddToClasspath(String cp) {
+        try {
+            out.writeInt(CMD_CLASSPATH);
+            out.writeUTF(cp);
+            out.flush();
+            return readAndReportResult();
+        } catch (IOException ex) {
+            throw new InternalError("Classpath addition failed: " + cp, ex);
+        }
+    }
+
+    boolean commandRedefine(Map<ReferenceType, byte[]> mp) {
+        try {
+            env.vm().redefineClasses(mp);
+            return true;
+        } catch (UnsupportedOperationException ex) {
+            return false;
+        } catch (Exception ex) {
+            proc.debug(DBG_GEN, "Exception on JDI redefine: %s\n", ex);
+            return false;
+        }
+    }
+
+    ReferenceType nameToRef(String name) {
+        List<ReferenceType> rtl = env.vm().classesByName(name);
+        if (rtl.size() != 1) {
+            return null;
+        }
+        return rtl.get(0);
+    }
+
+    private boolean readAndReportResult() throws IOException {
+        int ok = in.readInt();
+        switch (ok) {
+            case RESULT_SUCCESS:
+                return true;
+            case RESULT_FAIL: {
+                String ex = in.readUTF();
+                proc.debug(DBG_GEN, "Exception on remote operation: %s\n", ex);
+                return false;
+            }
+            default: {
+                proc.debug(DBG_GEN, "Bad remote result code: %s\n", ok);
+                return false;
+            }
+        }
+    }
+
+    private boolean readAndReportExecutionResult() throws IOException, ClassNotFoundException, EvalException, UnresolvedReferenceException {
+        int ok = in.readInt();
+        switch (ok) {
+            case RESULT_SUCCESS:
+                return true;
+            case RESULT_FAIL: {
+                String ex = in.readUTF();
+                proc.debug(DBG_GEN, "Exception on remote operation: %s\n", ex);
+                return false;
+            }
+            case RESULT_EXCEPTION: {
+                String exceptionClassName = in.readUTF();
+                String message = in.readUTF();
+                StackTraceElement[] elems = readStackTrace();
+                EvalException ee = new EvalException(message, exceptionClassName, elems);
+                throw ee;
+            }
+            case RESULT_CORRALLED: {
+                int id = in.readInt();
+                StackTraceElement[] elems = readStackTrace();
+                Snippet si = maps.getSnippet(id);
+                throw new UnresolvedReferenceException((MethodSnippet) si, elems);
+            }
+            case RESULT_KILLED: {
+                proc.out.println("Killed.");
+                return false;
+            }
+            default: {
+                proc.debug(DBG_GEN, "Bad remote result code: %s\n", ok);
+                return false;
+            }
+        }
+    }
+
+    private StackTraceElement[] readStackTrace() throws IOException {
+        int elemCount = in.readInt();
+        StackTraceElement[] elems = new StackTraceElement[elemCount];
+        for (int i = 0; i < elemCount; ++i) {
+            String className = in.readUTF();
+            String methodName = in.readUTF();
+            String fileName = in.readUTF();
+            int line = in.readInt();
+            elems[i] = new StackTraceElement(className, methodName, fileName, line);
+        }
+        return elems;
+    }
+
+    private void jdiGo(int port) {
+        //MessageOutput.textResources = ResourceBundle.getBundle("impl.TTYResources",
+        //        Locale.getDefault());
+
+        String connect = "com.sun.jdi.CommandLineLaunch:";
+        String cmdLine = "jdk.internal.jshell.remote.RemoteAgent";
+        String classPath = System.getProperty("java.class.path");
+        String bootclassPath = System.getProperty("sun.boot.class.path");
+        String javaArgs = "-classpath " + classPath + " -Xbootclasspath:" + bootclassPath;
+
+        String connectSpec = connect + "main=" + cmdLine + " " + port + ",options=" + javaArgs + ",";
+        boolean launchImmediately = true;
+        int traceFlags = 0;// VirtualMachine.TRACE_SENDS | VirtualMachine.TRACE_EVENTS;
+
+        env.init(connectSpec, launchImmediately, traceFlags);
+
+        if (env.connection().isOpen() && env.vm().canBeModified()) {
+            /*
+             * Connection opened on startup. Start event handler
+             * immediately, telling it (through arg 2) to stop on the
+             * VM start event.
+             */
+            handler = new JDIEventHandler(env);
+        }
+    }
+
+    private final Object STOP_LOCK = new Object();
+    private boolean userCodeRunning = false;
+
+    void commandStop() {
+        synchronized (STOP_LOCK) {
+            if (!userCodeRunning)
+                return ;
+
+            VirtualMachine vm = handler.env.vm();
+            vm.suspend();
+            try {
+                OUTER: for (ThreadReference thread : vm.allThreads()) {
+                    // could also tag the thread (e.g. using name), to find it easier
+                    for (StackFrame frame : thread.frames()) {
+                        String remoteAgentName = "jdk.internal.jshell.remote.RemoteAgent";
+                        if (remoteAgentName.equals(frame.location().declaringType().name()) &&
+                            "commandLoop".equals(frame.location().method().name())) {
+                            ObjectReference thiz = frame.thisObject();
+                            if (((BooleanValue) thiz.getValue(thiz.referenceType().fieldByName("inClientCode"))).value()) {
+                                thiz.setValue(thiz.referenceType().fieldByName("expectingStop"), vm.mirrorOf(true));
+                                ObjectReference stopInstance = (ObjectReference) thiz.getValue(thiz.referenceType().fieldByName("stopException"));
+
+                                vm.resume();
+                                proc.debug(DBG_GEN, "Attempting to stop the client code...\n");
+                                thread.stop(stopInstance);
+                                thiz.setValue(thiz.referenceType().fieldByName("expectingStop"), vm.mirrorOf(false));
+                            }
+
+                            break OUTER;
+                        }
+                    }
+                }
+            } catch (ClassNotLoadedException | IncompatibleThreadStateException | InvalidTypeException  ex) {
+                proc.debug(DBG_GEN, "Exception on remote stop: %s\n", ex);
+            } finally {
+                vm.resume();
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/jshell/ExpressionSnippet.java	Wed Oct 21 18:40:01 2015 -0700
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2015, 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;
+
+import jdk.jshell.Key.ExpressionKey;
+
+/**
+ * Snippet for an assignment or variable-value expression.
+ * The Kind is {@link jdk.jshell.Snippet.Kind#EXPRESSION}.
+ * <p>
+ * <code>ExpressionSnippet</code> is immutable: an access to
+ * any of its methods will always return the same result.
+ * and thus is thread-safe.
+ * @jls 15: Expression.
+ */
+public class ExpressionSnippet extends Snippet {
+
+    ExpressionSnippet(ExpressionKey key, String userSource, Wrap guts, String name, SubKind subkind) {
+        super(key, userSource, guts, name, subkind);
+    }
+
+    /**
+     * Variable name which is the value of the expression. Since the expression
+     * is either just a variable identifier or it is an assignment
+     * to a variable, there is always a variable which is the subject of the
+     * expression. All other forms of expression become temporary variables
+     * which are instead referenced by a {@link VarSnippet}.
+     * @return the name of the variable which is the subject of the expression.
+     */
+    @Override
+    public String name() {
+        return key().name();
+    }
+
+    /**
+     * Type of the expression
+     * @return String representation of the type of the expression.
+     */
+    public String typeName() {
+        return key().typeName();
+    }
+
+    /**** internal access ****/
+
+    @Override
+    ExpressionKey key() {
+        return (ExpressionKey) super.key();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/jshell/GeneralWrap.java	Wed Oct 21 18:40:01 2015 -0700
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2015, 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;
+
+/**
+ * Common interface for all wrappings of snippet source to Java source.
+ *
+ * @author Robert Field
+ */
+interface GeneralWrap {
+
+    String wrapped();
+
+    int snippetIndexToWrapIndex(int sni);
+
+    int wrapIndexToSnippetIndex(int wi);
+
+    default int wrapIndexToSnippetIndex(long wi) {
+        return wrapIndexToSnippetIndex((int) wi);
+    }
+
+    int firstSnippetIndex();
+
+    int lastSnippetIndex();
+
+    int snippetLineToWrapLine(int snline);
+
+    int wrapLineToSnippetLine(int wline);
+
+    int firstSnippetLine();
+
+    int lastSnippetLine();
+
+    default String debugPos(long lpos) {
+        int pos = (int) lpos;
+        int len = wrapped().length();
+        return wrapped().substring(Math.max(0, pos - 10), Math.max(0, Math.min(len, pos)))
+                + "###"
+                + wrapped().substring(Math.max(0, Math.min(len, pos)), Math.max(0, Math.min(len, pos + 10)));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/jshell/ImportSnippet.java	Wed Oct 21 18:40:01 2015 -0700
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2015, 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;
+
+import jdk.jshell.Key.ImportKey;
+
+/**
+ * Snippet for an import declaration.
+ * The Kind is {@link jdk.jshell.Snippet.Kind#IMPORT}.
+ * <p>
+ * <code>ImportSnippet</code> is immutable: an access to
+ * any of its methods will always return the same result.
+ * and thus is thread-safe.
+ * @jls 8.3: importDeclaration.
+ */
+public class ImportSnippet extends PersistentSnippet {
+
+    final String fullname;
+    final String fullkey;
+    final boolean isStatic;
+    final boolean isStar;
+
+    ImportSnippet(ImportKey key, String userSource, Wrap guts,
+            String fullname, String name, SubKind subkind, String fullkey,
+            boolean isStatic, boolean isStar) {
+        super(key, userSource, guts, name, subkind);
+        this.fullname = fullname;
+        this.fullkey = fullkey;
+        this.isStatic = isStatic;
+        this.isStar = isStar;
+    }
+
+    /**
+     * The identifying name of the import. For on-demand imports
+     * ({@link jdk.jshell.Snippet.SubKind#TYPE_IMPORT_ON_DEMAND_SUBKIND} or
+     * ({@link jdk.jshell.Snippet.SubKind#STATIC_IMPORT_ON_DEMAND_SUBKIND})
+     * that is the full specifier including any
+     * qualifiers and the asterisks. For single imports
+     * ({@link jdk.jshell.Snippet.SubKind#SINGLE_TYPE_IMPORT_SUBKIND} or
+     * ({@link jdk.jshell.Snippet.SubKind#SINGLE_STATIC_IMPORT_SUBKIND}),
+     * it is the imported name. That is, the unqualified name.
+     * @return the name of the import.
+     */
+    @Override
+    public String name() {
+        return key().name();
+    }
+
+    /**** internal access ****/
+
+    @Override
+    ImportKey key() {
+        return (ImportKey) super.key();
+    }
+
+    boolean isStatic() {
+        return isStatic;
+    }
+
+    @Override
+    String importLine(JShell state) {
+        return source();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/jshell/JDIConnection.java	Wed Oct 21 18:40:01 2015 -0700
@@ -0,0 +1,546 @@
+/*
+ * Copyright (c) 1998, 2015, 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.
+ */
+
+/*
+ * This source code is provided to illustrate the usage of a given feature
+ * or technique and has been deliberately simplified. Additional steps
+ * required for a production-quality application, such as security checks,
+ * input validation and proper error handling, might not be present in
+ * this sample code.
+ */
+
+
+package jdk.jshell;
+
+import com.sun.jdi.*;
+import com.sun.jdi.connect.*;
+
+import java.util.*;
+import java.util.regex.*;
+import java.io.*;
+import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
+
+/**
+ * Connection to a Java Debug Interface VirtualMachine instance.
+ * Adapted from jdb VMConnection. Message handling, exception handling, and I/O
+ * redirection changed.  Interface to JShell added.
+ */
+class JDIConnection {
+
+    private VirtualMachine vm;
+    private Process process = null;
+    private int outputCompleteCount = 0;
+
+    private final JShell proc;
+    private final JDIEnv env;
+    private final Connector connector;
+    private final Map<String, com.sun.jdi.connect.Connector.Argument> connectorArgs;
+    private final int traceFlags;
+
+    synchronized void notifyOutputComplete() {
+        outputCompleteCount++;
+        notifyAll();
+    }
+
+    synchronized void waitOutputComplete() {
+        // Wait for stderr and stdout
+        if (process != null) {
+            while (outputCompleteCount < 2) {
+                try {wait();} catch (InterruptedException e) {}
+            }
+        }
+    }
+
+    private Connector findConnector(String name) {
+        for (Connector cntor :
+                 Bootstrap.virtualMachineManager().allConnectors()) {
+            if (cntor.name().equals(name)) {
+                return cntor;
+            }
+        }
+        return null;
+    }
+
+    private Map <String, com.sun.jdi.connect.Connector.Argument> parseConnectorArgs(Connector connector, String argString) {
+        Map<String, com.sun.jdi.connect.Connector.Argument> arguments = connector.defaultArguments();
+
+        /*
+         * We are parsing strings of the form:
+         *    name1=value1,[name2=value2,...]
+         * However, the value1...valuen substrings may contain
+         * embedded comma(s), so make provision for quoting inside
+         * the value substrings. (Bug ID 4285874)
+         */
+        String regexPattern =
+            "(quote=[^,]+,)|" +           // special case for quote=.,
+            "(\\w+=)" +                   // name=
+            "(((\"[^\"]*\")|" +           //   ( "l , ue"
+            "('[^']*')|" +                //     'l , ue'
+            "([^,'\"]+))+,)";             //     v a l u e )+ ,
+        Pattern p = Pattern.compile(regexPattern);
+        Matcher m = p.matcher(argString);
+        while (m.find()) {
+            int startPosition = m.start();
+            int endPosition = m.end();
+            if (startPosition > 0) {
+                /*
+                 * It is an error if parsing skips over any part of argString.
+                 */
+                throw new IllegalArgumentException("Illegal connector argument" +
+                                          argString);
+            }
+
+            String token = argString.substring(startPosition, endPosition);
+            int index = token.indexOf('=');
+            String name = token.substring(0, index);
+            String value = token.substring(index + 1,
+                                           token.length() - 1); // Remove comma delimiter
+
+            /*
+             * for values enclosed in quotes (single and/or double quotes)
+             * strip off enclosing quote chars
+             * needed for quote enclosed delimited substrings
+             */
+            if (name.equals("options")) {
+                StringBuilder sb = new StringBuilder();
+                for (String s : splitStringAtNonEnclosedWhiteSpace(value)) {
+                    while (isEnclosed(s, "\"") || isEnclosed(s, "'")) {
+                        s = s.substring(1, s.length() - 1);
+                    }
+                    sb.append(s);
+                    sb.append(" ");
+                }
+                value = sb.toString();
+            }
+
+            Connector.Argument argument = arguments.get(name);
+            if (argument == null) {
+                throw new IllegalArgumentException("Argument is not defined for connector:" +
+                                          name + " -- " + connector.name());
+            }
+            argument.setValue(value);
+
+            argString = argString.substring(endPosition); // Remove what was just parsed...
+            m = p.matcher(argString);                     //    and parse again on what is left.
+        }
+        if ((! argString.equals(",")) && (argString.length() > 0)) {
+            /*
+             * It is an error if any part of argString is left over,
+             * unless it was empty to begin with.
+             */
+            throw new IllegalArgumentException("Illegal connector argument" + argString);
+        }
+        return arguments;
+    }
+
+    private static boolean isEnclosed(String value, String enclosingChar) {
+        if (value.indexOf(enclosingChar) == 0) {
+            int lastIndex = value.lastIndexOf(enclosingChar);
+            if (lastIndex > 0 && lastIndex  == value.length() - 1) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static List<String> splitStringAtNonEnclosedWhiteSpace(String value) throws IllegalArgumentException {
+        List<String> al = new ArrayList<>();
+        char[] arr;
+        int startPosition = 0;
+        int endPosition;
+        final char SPACE = ' ';
+        final char DOUBLEQ = '"';
+        final char SINGLEQ = '\'';
+
+        /*
+         * An "open" or "active" enclosing state is where
+         * the first valid start quote qualifier is found,
+         * and there is a search in progress for the
+         * relevant end matching quote
+         *
+         * enclosingTargetChar set to SPACE
+         * is used to signal a non open enclosing state
+         */
+        char enclosingTargetChar = SPACE;
+
+        if (value == null) {
+            throw new IllegalArgumentException("value string is null");
+        }
+
+        // split parameter string into individual chars
+        arr = value.toCharArray();
+
+        for (int i = 0; i < arr.length; i++) {
+            switch (arr[i]) {
+                case SPACE: {
+                    // do nothing for spaces
+                    // unless last in array
+                    if (isLastChar(arr, i)) {
+                        endPosition = i;
+                        // break for substring creation
+                        break;
+                    }
+                    continue;
+                }
+                case DOUBLEQ:
+                case SINGLEQ: {
+                    if (enclosingTargetChar == arr[i]) {
+                        // potential match to close open enclosing
+                        if (isNextCharWhitespace(arr, i)) {
+                            // if peek next is whitespace
+                            // then enclosing is a valid substring
+                            endPosition = i;
+                            // reset enclosing target char
+                            enclosingTargetChar = SPACE;
+                            // break for substring creation
+                            break;
+                        }
+                    }
+                    if (enclosingTargetChar == SPACE) {
+                        // no open enclosing state
+                        // handle as normal char
+                        if (isPreviousCharWhitespace(arr, i)) {
+                            startPosition = i;
+                            // peek forward for end candidates
+                            if (value.indexOf(arr[i], i + 1) >= 0) {
+                                // set open enclosing state by
+                                // setting up the target char
+                                enclosingTargetChar = arr[i];
+                            } else {
+                                // no more target chars left to match
+                                // end enclosing, handle as normal char
+                                if (isNextCharWhitespace(arr, i)) {
+                                    endPosition = i;
+                                    // break for substring creation
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                    continue;
+                }
+                default: {
+                    // normal non-space, non-" and non-' chars
+                    if (enclosingTargetChar == SPACE) {
+                        // no open enclosing state
+                        if (isPreviousCharWhitespace(arr, i)) {
+                            // start of space delim substring
+                            startPosition = i;
+                        }
+                        if (isNextCharWhitespace(arr, i)) {
+                            // end of space delim substring
+                            endPosition = i;
+                            // break for substring creation
+                            break;
+                        }
+                    }
+                    continue;
+                }
+            }
+
+            // break's end up here
+            if (startPosition > endPosition) {
+                throw new IllegalArgumentException("Illegal option values");
+            }
+
+            // extract substring and add to List<String>
+            al.add(value.substring(startPosition, ++endPosition));
+
+            // set new start position
+            i = startPosition = endPosition;
+
+        } // for loop
+
+        return al;
+    }
+
+    static private boolean isPreviousCharWhitespace(char[] arr, int curr_pos) {
+        return isCharWhitespace(arr, curr_pos - 1);
+    }
+
+    static private boolean isNextCharWhitespace(char[] arr, int curr_pos) {
+        return isCharWhitespace(arr, curr_pos + 1);
+    }
+
+    static private boolean isCharWhitespace(char[] arr, int pos) {
+        if (pos < 0 || pos >= arr.length) {
+            // outside arraybounds is considered an implicit space
+            return true;
+        }
+        return (arr[pos] == ' ');
+    }
+
+    static private boolean isLastChar(char[] arr, int pos) {
+        return (pos + 1 == arr.length);
+    }
+
+    JDIConnection(JDIEnv env, String connectSpec, int traceFlags, JShell proc) {
+        this.env = env;
+        this.proc = proc;
+        String nameString;
+        String argString;
+        int index = connectSpec.indexOf(':');
+        if (index == -1) {
+            nameString = connectSpec;
+            argString = "";
+        } else {
+            nameString = connectSpec.substring(0, index);
+            argString = connectSpec.substring(index + 1);
+        }
+
+        connector = findConnector(nameString);
+        if (connector == null) {
+            throw new IllegalArgumentException("No connector named: " + nameString);
+        }
+
+        connectorArgs = parseConnectorArgs(connector, argString);
+        this.traceFlags = traceFlags;
+    }
+
+    synchronized VirtualMachine open() {
+        if (connector instanceof LaunchingConnector) {
+            vm = launchTarget();
+        } else if (connector instanceof AttachingConnector) {
+            vm = attachTarget();
+        } else if (connector instanceof ListeningConnector) {
+            vm = listenTarget();
+        } else {
+            throw new InternalError("Invalid connect type");
+        }
+        vm.setDebugTraceMode(traceFlags);
+        // Uncomment here and below to enable event requests
+        // installEventRequests(vm);
+
+        return vm;
+    }
+
+    boolean setConnectorArg(String name, String value) {
+        /*
+         * Too late if the connection already made
+         */
+        if (vm != null) {
+            return false;
+        }
+
+        Connector.Argument argument = connectorArgs.get(name);
+        if (argument == null) {
+            return false;
+        }
+        argument.setValue(value);
+        return true;
+    }
+
+    String connectorArg(String name) {
+        Connector.Argument argument = connectorArgs.get(name);
+        if (argument == null) {
+            return "";
+        }
+        return argument.value();
+    }
+
+    public synchronized VirtualMachine vm() {
+        if (vm == null) {
+            throw new JDINotConnectedException();
+        } else {
+            return vm;
+        }
+    }
+
+    boolean isOpen() {
+        return (vm != null);
+    }
+
+    boolean isLaunch() {
+        return (connector instanceof LaunchingConnector);
+    }
+
+    public void disposeVM() {
+        try {
+            if (vm != null) {
+                vm.dispose(); // This could NPE, so it is caught below
+                vm = null;
+            }
+        } catch (VMDisconnectedException | NullPointerException ex) {
+            // Ignore if already closed
+        } finally {
+            if (process != null) {
+                process.destroy();
+                process = null;
+            }
+            waitOutputComplete();
+        }
+    }
+
+/*** Preserved for possible future support of event requests
+
+    private void installEventRequests(VirtualMachine vm) {
+        if (vm.canBeModified()){
+            setEventRequests(vm);
+            resolveEventRequests();
+        }
+    }
+
+    private void setEventRequests(VirtualMachine vm) {
+        EventRequestManager erm = vm.eventRequestManager();
+
+        // Normally, we want all uncaught exceptions.  We request them
+        // via the same mechanism as Commands.commandCatchException()
+        // so the user can ignore them later if they are not
+        // interested.
+        // FIXME: this works but generates spurious messages on stdout
+        //        during startup:
+        //          Set uncaught java.lang.Throwable
+        //          Set deferred uncaught java.lang.Throwable
+        Commands evaluator = new Commands();
+        evaluator.commandCatchException
+            (new StringTokenizer("uncaught java.lang.Throwable"));
+
+        ThreadStartRequest tsr = erm.createThreadStartRequest();
+        tsr.enable();
+        ThreadDeathRequest tdr = erm.createThreadDeathRequest();
+        tdr.enable();
+    }
+
+    private void resolveEventRequests() {
+        Env.specList.resolveAll();
+    }
+***/
+
+    private void dumpStream(InputStream inStream, final PrintStream pStream) throws IOException {
+        BufferedReader in =
+            new BufferedReader(new InputStreamReader(inStream));
+        int i;
+        try {
+            while ((i = in.read()) != -1) {
+                pStream.print((char) i);
+            }
+        } catch (IOException ex) {
+            String s = ex.getMessage();
+            if (!s.startsWith("Bad file number")) {
+                throw ex;
+            }
+            // else we got a Bad file number IOException which just means
+            // that the debuggee has gone away.  We'll just treat it the
+            // same as if we got an EOF.
+        }
+    }
+
+    /**
+     *  Create a Thread that will retrieve and display any output.
+     *  Needs to be high priority, else debugger may exit before
+     *  it can be displayed.
+     */
+    private void displayRemoteOutput(final InputStream inStream, final PrintStream pStream) {
+        Thread thr = new Thread("output reader") {
+            @Override
+            public void run() {
+                try {
+                    dumpStream(inStream, pStream);
+                } catch (IOException ex) {
+                    proc.debug(ex, "Failed reading output");
+                    env.shutdown();
+                } finally {
+                    notifyOutputComplete();
+                }
+            }
+        };
+        thr.setPriority(Thread.MAX_PRIORITY-1);
+        thr.start();
+    }
+
+    /**
+     *  Create a Thread that will ship all input to remote.
+     *  Does it need be high priority?
+     */
+    private void readRemoteInput(final OutputStream outStream, final InputStream inputStream) {
+        Thread thr = new Thread("input reader") {
+            @Override
+            public void run() {
+                try {
+                    byte[] buf = new byte[256];
+                    int cnt;
+                    while ((cnt = inputStream.read(buf)) != -1) {
+                        outStream.write(buf, 0, cnt);
+                        outStream.flush();
+                    }
+                } catch (IOException ex) {
+                    proc.debug(ex, "Failed reading output");
+                    env.shutdown();
+                }
+            }
+        };
+        thr.setPriority(Thread.MAX_PRIORITY-1);
+        thr.start();
+    }
+
+    /* launch child target vm */
+    private VirtualMachine launchTarget() {
+        LaunchingConnector launcher = (LaunchingConnector)connector;
+        try {
+            VirtualMachine new_vm = launcher.launch(connectorArgs);
+            process = new_vm.process();
+            displayRemoteOutput(process.getErrorStream(), proc.err);
+            displayRemoteOutput(process.getInputStream(), proc.out);
+            readRemoteInput(process.getOutputStream(), proc.in);
+            return new_vm;
+        } catch (Exception ex) {
+            reportLaunchFail(ex, "launch");
+        }
+        return null;
+    }
+
+    /* JShell currently uses only launch, preserved for futures: */
+    /* attach to running target vm */
+    private VirtualMachine attachTarget() {
+        AttachingConnector attacher = (AttachingConnector)connector;
+        try {
+            return attacher.attach(connectorArgs);
+        } catch (Exception ex) {
+            reportLaunchFail(ex, "attach");
+        }
+        return null;
+    }
+
+    /* JShell currently uses only launch, preserved for futures: */
+    /* listen for connection from target vm */
+    private VirtualMachine listenTarget() {
+        ListeningConnector listener = (ListeningConnector)connector;
+        try {
+            String retAddress = listener.startListening(connectorArgs);
+            proc.debug(DBG_GEN, "Listening at address: " + retAddress);
+            vm = listener.accept(connectorArgs);
+            listener.stopListening(connectorArgs);
+            return vm;
+        } catch (Exception ex) {
+            reportLaunchFail(ex, "listen");
+        }
+        return null;
+    }
+
+    private void reportLaunchFail(Exception ex, String context) {
+        throw new InternalError("Failed remote " + context + ": " + connector +
+                " -- " + connectorArgs, ex);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/jshell/JDIEnv.java	Wed Oct 21 18:40:01 2015 -0700
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 1998, 2015, 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;
+
+import com.sun.jdi.*;
+import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
+
+/**
+ * Representation of a Java Debug Interface environment
+ * Select methods extracted from jdb Env; shutdown() adapted to JShell shutdown.
+ */
+class JDIEnv {
+
+    private JDIConnection connection;
+    private final JShell state;
+
+    JDIEnv(JShell state) {
+        this.state = state;
+    }
+
+    void init(String connectSpec, boolean openNow, int flags) {
+        connection = new JDIConnection(this, connectSpec, flags, state);
+        if (!connection.isLaunch() || openNow) {
+            connection.open();
+        }
+    }
+
+    JDIConnection connection() {
+        return connection;
+    }
+
+    VirtualMachine vm() {
+        return connection.vm();
+    }
+
+    void shutdown() {
+        if (connection != null) {
+            try {
+                connection.disposeVM();
+            } catch (VMDisconnectedException e) {
+                // Shutting down after the VM has gone away. This is
+                // not an error, and we just ignore it.
+            } catch (Throwable e) {
+                state.debug(DBG_GEN, null, "disposeVM threw: " + e);
+            }
+        }
+        if (state != null) { // If state has been set-up
+            try {
+                state.closeDown();
+            } catch (Throwable e) {
+                state.debug(DBG_GEN, null, "state().closeDown() threw: " + e);
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/jshell/JDIEventHandler.java	Wed Oct 21 18:40:01 2015 -0700
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 1998, 2011, 2014, 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;
+
+import com.sun.jdi.*;
+import com.sun.jdi.event.*;
+
+/**
+ * Handler of Java Debug Interface events.
+ * Adapted from jdb EventHandler; Handling of events not used by JShell stubbed out.
+ */
+class JDIEventHandler implements Runnable {
+
+    Thread thread;
+    volatile boolean connected = true;
+    boolean completed = false;
+    String shutdownMessageKey;
+    final JDIEnv env;
+
+    JDIEventHandler(JDIEnv env) {
+        this.env = env;
+        this.thread = new Thread(this, "event-handler");
+        this.thread.start();
+    }
+
+    synchronized void shutdown() {
+        connected = false;  // force run() loop termination
+        thread.interrupt();
+        while (!completed) {
+            try {wait();} catch (InterruptedException exc) {}
+        }
+    }
+
+    @Override
+    public void run() {
+        EventQueue queue = env.vm().eventQueue();
+        while (connected) {
+            try {
+                EventSet eventSet = queue.remove();
+                boolean resumeStoppedApp = false;
+                EventIterator it = eventSet.eventIterator();
+                while (it.hasNext()) {
+                    resumeStoppedApp |= handleEvent(it.nextEvent());
+                }
+
+                if (resumeStoppedApp) {
+                    eventSet.resume();
+                }
+            } catch (InterruptedException exc) {
+                // Do nothing. Any changes will be seen at top of loop.
+            } catch (VMDisconnectedException discExc) {
+                handleDisconnectedException();
+                break;
+            }
+        }
+        synchronized (this) {
+            completed = true;
+            notifyAll();
+        }
+    }
+
+    private boolean handleEvent(Event event) {
+        if (event instanceof ExceptionEvent) {
+            exceptionEvent(event);
+        } else if (event instanceof WatchpointEvent) {
+            fieldWatchEvent(event);
+        } else if (event instanceof MethodEntryEvent) {
+            methodEntryEvent(event);
+        } else if (event instanceof MethodExitEvent) {
+            methodExitEvent(event);
+        } else if (event instanceof ClassPrepareEvent) {
+            classPrepareEvent(event);
+        } else if (event instanceof ThreadStartEvent) {
+            threadStartEvent(event);
+        } else if (event instanceof ThreadDeathEvent) {
+            threadDeathEvent(event);
+        } else if (event instanceof VMStartEvent) {
+            vmStartEvent(event);
+            return true;
+        } else {
+            handleExitEvent(event);
+        }
+        return true;
+    }
+
+    private boolean vmDied = false;
+
+    private void handleExitEvent(Event event) {
+        if (event instanceof VMDeathEvent) {
+            vmDied = true;
+            shutdownMessageKey = "The application exited";
+        } else if (event instanceof VMDisconnectEvent) {
+            connected = false;
+            if (!vmDied) {
+                shutdownMessageKey = "The application has been disconnected";
+            }
+        } else {
+            throw new InternalError("Unexpected event type: " +
+                    event.getClass());
+        }
+        env.shutdown();
+    }
+
+    synchronized void handleDisconnectedException() {
+        /*
+         * A VMDisconnectedException has happened while dealing with
+         * another event. We need to flush the event queue, dealing only
+         * with exit events (VMDeath, VMDisconnect) so that we terminate
+         * correctly.
+         */
+        EventQueue queue = env.vm().eventQueue();
+        while (connected) {
+            try {
+                EventSet eventSet = queue.remove();
+                EventIterator iter = eventSet.eventIterator();
+                while (iter.hasNext()) {
+                    handleExitEvent(iter.next());
+                }
+            } catch (InterruptedException exc) {
+                // ignore
+            } catch (InternalError exc) {
+                // ignore
+            }
+        }
+    }
+
+    private void vmStartEvent(Event event)  {
+        VMStartEvent se = (VMStartEvent)event;
+    }
+
+    private void methodEntryEvent(Event event)  {
+        MethodEntryEvent me = (MethodEntryEvent)event;
+    }
+
+    private void methodExitEvent(Event event)  {
+        MethodExitEvent me = (MethodExitEvent)event;
+    }
+
+    private void fieldWatchEvent(Event event)  {
+        WatchpointEvent fwe = (WatchpointEvent)event;
+    }
+
+    private void classPrepareEvent(Event event)  {
+        ClassPrepareEvent cle = (ClassPrepareEvent)event;
+    }
+
+    private void exceptionEvent(Event event) {
+        ExceptionEvent ee = (ExceptionEvent)event;
+    }
+
+    private void threadDeathEvent(Event event) {
+        ThreadDeathEvent tee = (ThreadDeathEvent)event;
+    }
+
+    private void threadStartEvent(Event event) {
+        ThreadStartEvent tse = (ThreadStartEvent)event;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/jshell/JDINotConnectedException.java	Wed Oct 21 18:40:01 2015 -0700
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 1999, 2015, 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;
+
+/**
+ * Internal exception when Java Debug Interface VirtualMacine is not connected.
+ * Copy of jdb VMNotConnectedException.
+ */
+class JDINotConnectedException extends RuntimeException {
+
+    private static final long serialVersionUID = -7433430494903950165L;
+
+    public JDINotConnectedException() {
+        super();
+    }
+
+    public JDINotConnectedException(String s) {
+        super(s);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/jshell/JShell.java	Wed Oct 21 18:40:01 2015 -0700
@@ -0,0 +1,676 @@
+/*
+ * Copyright (c) 2015, 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;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+
+import java.util.function.Supplier;
+import jdk.internal.jshell.debug.InternalDebugControl;
+import static java.util.stream.Collectors.collectingAndThen;
+import static java.util.stream.Collectors.toList;
+import static jdk.internal.jshell.debug.InternalDebugControl.DBG_EVNT;
+import static jdk.jshell.Util.expunge;
+import jdk.jshell.Snippet.Status;
+
+/**
+ * The JShell evaluation state engine.  This is the central class in the JShell
+ * API.  A <code>JShell</code> instance holds the evolving compilation and
+ * execution state.  The state is changed with the instance methods
+ * {@link jdk.jshell.JShell#eval(java.lang.String) eval(String)},
+ * {@link jdk.jshell.JShell#drop(jdk.jshell.PersistentSnippet) drop(PersistentSnippet)} and
+ * {@link jdk.jshell.JShell#addToClasspath(java.lang.String) addToClasspath(String)}.
+ * The majority of methods query the state.
+ * A <code>JShell</code> instance also allows registering for events with
+ * {@link jdk.jshell.JShell#onSnippetEvent(java.util.function.Consumer) onSnippetEvent(Consumer)}
+ * and {@link jdk.jshell.JShell#onShutdown(java.util.function.Consumer) onShutdown(Consumer)}, which
+ * are unregistered with
+ * {@link jdk.jshell.JShell#unsubscribe(jdk.jshell.JShell.Subscription) unsubscribe(Subscription)}.
+ * Access to the source analysis utilities is via
+ * {@link jdk.jshell.JShell#sourceCodeAnalysis()}.
+ * When complete the instance should be closed to free resources --
+ * {@link jdk.jshell.JShell#close()}.
+ * <p>
+ * An instance of <code>JShell</code> is created with
+ * <code>JShell.create()</code>.
+ * <p>
+ * This class is not thread safe, except as noted, all access should be through
+ * a single thread.
+ * @see jdk.jshell
+ * @author Robert Field
+ */
+public class JShell implements AutoCloseable {
+
+    final SnippetMaps maps;
+    final KeyMap keyMap;
+    final TaskFactory taskFactory;
+    final InputStream in;
+    final PrintStream out;
+    final PrintStream err;
+    final Supplier<String> tempVariableNameGenerator;
+    final BiFunction<Snippet, Integer, String> idGenerator;
+
+    private int nextKeyIndex = 1;
+
+    final Eval eval;
+    final ClassTracker classTracker;
+    private final Map<Subscription, Consumer<JShell>> shutdownListeners = new HashMap<>();
+    private final Map<Subscription, Consumer<SnippetEvent>> keyStatusListeners = new HashMap<>();
+    private boolean closed = false;
+
+
+    private ExecutionControl executionControl = null;
+    private SourceCodeAnalysis sourceCodeAnalysis = null;
+
+
+    JShell(Builder b) {
+        this.in = b.in;
+        this.out = b.out;
+        this.err = b.err;
+        this.tempVariableNameGenerator = b.tempVariableNameGenerator;
+        this.idGenerator = b.idGenerator;
+
+        this.maps = new SnippetMaps(this);
+        maps.setPackageName("REPL");
+        this.keyMap = new KeyMap(this);
+        this.taskFactory = new TaskFactory(this);
+        this.eval = new Eval(this);
+        this.classTracker = new ClassTracker(this);
+    }
+
+    /**
+     * Builder for <code>JShell</code> instances.
+     * Create custom instances of <code>JShell</code> by using the setter
+     * methods on this class.  After zero or more of these, use the
+     * {@link #build()} method to create a <code>JShell</code> instance.
+     * These can all be chained. For example, setting the remote output and
+     * error streams:
+     * <pre>
+     * <code>
+     *     JShell myShell =
+     *       JShell.builder()
+     *         .out(myOutStream)
+     *         .err(myErrStream)
+     *         .build(); </code> </pre>
+     * If no special set-up is needed, just use
+     * <code>JShell.builder().build()</code> or the short-cut equivalent
+     * <code>JShell.create()</code>.
+     */
+    public static class Builder {
+
+        InputStream in = new ByteArrayInputStream(new byte[0]);
+        PrintStream out = System.out;
+        PrintStream err = System.err;
+        Supplier<String> tempVariableNameGenerator = null;
+        BiFunction<Snippet, Integer, String> idGenerator = null;
+
+        Builder() { }
+
+        /**
+         * Input for the running evaluation (it's <code>System.in</code>). Note:
+         * applications that use <code>System.in</code> for snippet or other
+         * user input cannot use <code>System.in</code> as the input stream for
+         * the remote process.
+         * <p>
+         * The default, if this is not set, is to provide an empty input stream
+         * -- <code>new ByteArrayInputStream(new byte[0])</code>.
+         *
+         * @param in the <code>InputStream</code> to be channelled to
+         * <code>System.in</code> in the remote execution process.
+         * @return the <code>Builder</code> instance (for use in chained
+         * initialization).
+         */
+        public Builder in(InputStream in) {
+            this.in = in;
+            return this;
+        }
+
+        /**
+         * Output for the running evaluation (it's <code>System.out</code>).
+         * The controlling process and
+         * the remote process can share <code>System.out</code>.
+         * <p>
+         * The default, if this is not set, is <code>System.out</code>.
+         *
+         * @param out the <code>PrintStream</code> to be channelled to
+         * <code>System.out</code> in the remote execution process.
+         * @return the <code>Builder</code> instance (for use in chained
+         * initialization).
+         */
+        public Builder out(PrintStream out) {
+            this.out = out;
+            return this;
+        }
+
+        /**
+         * Error output for the running evaluation (it's
+         * <code>System.err</code>). The controlling process and the remote
+         * process can share <code>System.err</code>.
+         * <p>
+         * The default, if this is not set, is <code>System.err</code>.
+         *
+         * @param err the <code>PrintStream</code> to be channelled to
+         * <code>System.err</code> in the remote execution process.
+         * @return the <code>Builder</code> instance (for use in chained
+         * initialization).
+         */
+        public Builder err(PrintStream err) {
+            this.err = err;
+            return this;
+        }
+
+        /**
+         * Set a generator of temp variable names for
+         * {@link jdk.jshell.VarSnippet} of
+         * {@link jdk.jshell.Snippet.SubKind#TEMP_VAR_EXPRESSION_SUBKIND}.
+         * <p>
+         * Do not use this method unless you have explicit need for it.
+         * <p>
+         * The generator will be used for newly created VarSnippet
+         * instances. The name of a variable is queried with
+         * {@link jdk.jshell.VarSnippet#name()}.
+         * <p>
+         * The callback is sent during the processing of the snippet, the
+         * JShell state is not stable. No calls whatsoever on the
+         * <code>JShell</code> instance may be made from the callback.
+         * <p>
+         * The generated name must be unique within active snippets.
+         * <p>
+         * The default behavior (if this is not set or <code>generator</code>
+         * is null) is to generate the name as a sequential number with a
+         * prefixing dollar sign ("$").
+         *
+         * @param generator the <code>Supplier</code> to generate the temporary
+         * variable name string or <code>null</code>.
+         * @return the <code>Builder</code> instance (for use in chained
+         * initialization).
+         */
+        public Builder tempVariableNameGenerator(Supplier<String> generator) {
+            this.tempVariableNameGenerator = generator;
+            return this;
+        }
+
+        /**
+         * Set the generator of identifying names for Snippets.
+         * <p>
+         * Do not use this method unless you have explicit need for it.
+         * <p>
+         * The generator will be used for newly created Snippet instances. The
+         * identifying name (id) is accessed with
+         * {@link jdk.jshell.Snippet#id()} and can be seen in the
+         * <code>StackTraceElement.getFileName()</code> for a
+         * {@link jdk.jshell.EvalException} and
+         * {@link jdk.jshell.UnresolvedReferenceException}.
+         * <p>
+         * The inputs to the generator are the {@link jdk.jshell.Snippet} and an
+         * integer. The integer will be the same for two Snippets which would
+         * overwrite one-another, but otherwise is unique.
+         * <p>
+         * The callback is sent during the processing of the snippet and the
+         * Snippet and the state as a whole are not stable. No calls to change
+         * system state (including Snippet state) should be made. Queries of
+         * Snippet may be made except to {@link jdk.jshell.Snippet#id()}. No
+         * calls on the <code>JShell</code> instance may be made from the
+         * callback, except to
+         * {@link #status(jdk.jshell.Snippet) status(Snippet)}.
+         * <p>
+         * The default behavior (if this is not set or <code>generator</code>
+         * is null) is to generate the id as the integer converted to a string.
+         *
+         * @param generator the <code>BiFunction</code> to generate the id
+         * string or <code>null</code>.
+         * @return the <code>Builder</code> instance (for use in chained
+         * initialization).
+         */
+        public Builder idGenerator(BiFunction<Snippet, Integer, String> generator) {
+            this.idGenerator = generator;
+            return this;
+        }
+
+        /**
+         * Build a JShell state engine. This is the entry-point to all JShell
+         * functionality. This creates a remote process for execution. It is
+         * thus important to close the returned instance.
+         *
+         * @return the state engine.
+         */
+        public JShell build() {
+            return new JShell(this);
+        }
+    }
+
+    // --- public API ---
+
+    /**
+     * Create a new JShell state engine.
+     * That is, create an instance of <code>JShell</code>.
+     * <p>
+     * Equivalent to {@link JShell#builder() JShell.builder()}{@link JShell.Builder#build() .build()}.
+     * @return an instance of <code>JShell</code>.
+     */
+    public static JShell create() {
+        return builder().build();
+    }
+
+    /**
+     * Factory method for <code>JShell.Builder</code> which, in-turn, is used
+     * for creating instances of <code>JShell</code>.
+     * Create a default instance of <code>JShell</code> with
+     * <code>JShell.builder().build()</code>. For more construction options
+     * see {@link jdk.jshell.JShell.Builder}.
+     * @return an instance of <code>Builder</code>.
+     * @see jdk.jshell.JShell.Builder
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    /**
+     * Access to source code analysis functionality.
+     * An instance of <code>JShell</code> will always return the same
+     * <code>SourceCodeAnalysis</code> instance from
+     * <code>sourceCodeAnalysis()</code>.
+     * @return an instance of {@link SourceCodeAnalysis SourceCodeAnalysis}
+     * which can be used for source analysis such as completion detection and
+     * completion suggestions.
+     */
+    public SourceCodeAnalysis sourceCodeAnalysis() {
+        if (sourceCodeAnalysis == null) {
+            sourceCodeAnalysis = new SourceCodeAnalysisImpl(this);
+        }
+        return sourceCodeAnalysis;
+    }
+
+    /**
+     * Evaluate the input String, including definition and/or execution, if
+     * applicable. The input is checked for errors, unless the errors can be
+     * deferred (as is the case with some unresolvedDependencies references),
+     * errors will abort evaluation. The input should be
+     * exactly one complete snippet of source code, that is, one expression,
+     * statement, variable declaration, method declaration, class declaration,
+     * or import.
+     * To break arbitrary input into individual complete snippets, use
+     * {@link SourceCodeAnalysis#analyzeCompletion(String)}.
+     * <p>
+     * For imports, the import is added.  Classes, interfaces. methods,
+     * and variables are defined.  The initializer of variables, statements,
+     * and expressions are executed.
+     * The modifiers public, protected, private, static, and final are not
+     * allowed on op-level declarations and are ignored with a warning.
+     * Synchronized, native, abstract, and default top-level methods are not
+     * allowed and are errors.
+     * If a previous definition of a declaration is overwritten then there will
+     * be an event showing its status changed to OVERWRITTEN, this will not
+     * occur for dropped, rejected, or already overwritten declarations.
+     * <p>
+     * The execution environment is out of process.  If the evaluated code
+     * causes the execution environment to terminate, this <code>JShell</code>
+     * instance will be closed but the calling process and VM remain valid.
+     * @param input The input String to evaluate
+     * @return the list of events directly or indirectly caused by this evaluation.
+     * @throws IllegalStateException if this <code>JShell</code> instance is closed.
+     * @see SourceCodeAnalysis#analyzeCompletion(String)
+     * @see JShell#onShutdown(java.util.function.Consumer)
+     */
+    public List<SnippetEvent> eval(String input) throws IllegalStateException {
+        checkIfAlive();
+        List<SnippetEvent> events = eval.eval(input);
+        events.forEach(this::notifyKeyStatusEvent);
+        return Collections.unmodifiableList(events);
+    }
+
+    /**
+     * Remove a declaration from the state.
+     * @param snippet The snippet to remove
+     * @return The list of events from updating declarations dependent on the
+     * dropped snippet.
+     * @throws IllegalStateException if this <code>JShell</code> instance is closed.
+     * @throws IllegalArgumentException if the snippet is not associated with
+     * this <code>JShell</code> instance.
+     */
+    public List<SnippetEvent> drop(PersistentSnippet snippet) throws IllegalStateException {
+        checkIfAlive();
+        checkValidSnippet(snippet);
+        List<SnippetEvent> events = eval.drop(snippet);
+        events.forEach(this::notifyKeyStatusEvent);
+        return Collections.unmodifiableList(events);
+    }
+
+    /**
+     * The specified path is added to the end of the classpath used in eval().
+     * Note that the unnamed package is not accessible from the package in which
+     * {@link JShell#eval()} code is placed.
+     * @param path the path to add to the classpath.
+     */
+    public void addToClasspath(String path) {
+        taskFactory.addToClasspath(path);  // Compiler
+        executionControl().commandAddToClasspath(path);       // Runtime
+    }
+
+    /**
+     * Attempt to stop currently running evaluation. When called while
+     * the {@link #eval(java.lang.String) } method is running and the
+     * user's code being executed, an attempt will be made to stop user's code.
+     * Note that typically this method needs to be called from a different thread
+     * than the one running the {@code eval} method.
+     * <p>
+     * If the {@link #eval(java.lang.String) } method is not running, does nothing.
+     * <p>
+     * The attempt to stop the user's code may fail in some case, which may include
+     * when the execution is blocked on an I/O operation, or when the user's code is
+     * catching the {@link ThreadDeath} exception.
+     */
+    public void stop() {
+        if (executionControl != null)
+            executionControl.commandStop();
+    }
+
+    /**
+     * Close this state engine. Frees resources. Should be called when this
+     * state engine is no longer needed.
+     */
+    @Override
+    public void close() {
+        if (!closed) {
+            closeDown();
+            executionControl().commandExit();
+        }
+    }
+
+    /**
+     * Return all snippets.
+     * @return the snippets for all current snippets in id order.
+     * @throws IllegalStateException if this JShell instance is closed.
+     */
+    public List<Snippet> snippets() throws IllegalStateException {
+        checkIfAlive();
+        return Collections.unmodifiableList(maps.snippetList());
+    }
+
+    /**
+     * Returns the active variable snippets.
+     * This convenience method is equivalent to <code>snippets()</code> filtered for
+     * {@link jdk.jshell.Snippet.Status#isActive status(snippet).isActive}
+     * <code>&amp;&amp; snippet.kind() == Kind.VARIABLE</code>
+     * and cast to <code>VarSnippet</code>.
+     * @return the active declared variables.
+     * @throws IllegalStateException if this JShell instance is closed.
+     */
+    public List<VarSnippet> variables() throws IllegalStateException {
+        return snippets().stream()
+                     .filter(sn -> status(sn).isActive && sn.kind() == Snippet.Kind.VAR)
+                     .map(sn -> (VarSnippet) sn)
+                     .collect(collectingAndThen(toList(), Collections::unmodifiableList));
+    }
+
+    /**
+     * Returns the active method snippets.
+     * This convenience method is equivalent to <code>snippets()</code> filtered for
+     * {@link jdk.jshell.Snippet.Status#isActive status(snippet).isActive}
+     * <code>&amp;&amp; snippet.kind() == Kind.METHOD</code>
+     * and cast to MethodSnippet.
+     * @return the active declared methods.
+     * @throws IllegalStateException if this JShell instance is closed.
+     */
+    public List<MethodSnippet> methods() throws IllegalStateException {
+        return snippets().stream()
+                     .filter(sn -> status(sn).isActive && sn.kind() == Snippet.Kind.METHOD)
+                     .map(sn -> (MethodSnippet)sn)
+                     .collect(collectingAndThen(toList(), Collections::unmodifiableList));
+    }
+
+    /**
+     * Returns the active type declaration (class, interface, annotation type, and enum) snippets.
+     * This convenience method is equivalent to <code>snippets()</code> filtered for
+     * {@link jdk.jshell.Snippet.Status#isActive status(snippet).isActive}
+     * <code>&amp;&amp; snippet.kind() == Kind.TYPE_DECL</code>
+     * and cast to TypeDeclSnippet.
+     * @return the active declared type declarations.
+     * @throws IllegalStateException if this JShell instance is closed.
+     */
+    public List<TypeDeclSnippet> types() throws IllegalStateException {
+        return snippets().stream()
+                .filter(sn -> status(sn).isActive && sn.kind() == Snippet.Kind.TYPE_DECL)
+                .map(sn -> (TypeDeclSnippet) sn)
+                .collect(collectingAndThen(toList(), Collections::unmodifiableList));
+    }
+
+    /**
+     * Return the status of the snippet.
+     * This is updated either because of an explicit <code>eval()</code> call or
+     * an automatic update triggered by a dependency.
+     * @param snippet the <code>Snippet</code> to look up
+     * @return the status corresponding to this snippet
+     * @throws IllegalStateException if this <code>JShell</code> instance is closed.
+     * @throws IllegalArgumentException if the snippet is not associated with
+     * this <code>JShell</code> instance.
+     */
+    public Status status(Snippet snippet) {
+        return checkValidSnippet(snippet).status();
+    }
+
+    /**
+     * Return the diagnostics of the most recent evaluation of the snippet.
+     * The evaluation can either because of an explicit <code>eval()</code> call or
+     * an automatic update triggered by a dependency.
+     * @param snippet the <code>Snippet</code> to look up
+     * @return the diagnostics corresponding to this snippet.  This does not
+     * include unresolvedDependencies references reported in <code>unresolvedDependencies()</code>.
+     * @throws IllegalStateException if this <code>JShell</code> instance is closed.
+     * @throws IllegalArgumentException if the snippet is not associated with
+     * this <code>JShell</code> instance.
+     */
+    public List<Diag> diagnostics(Snippet snippet) {
+        return Collections.unmodifiableList(checkValidSnippet(snippet).diagnostics());
+    }
+
+    /**
+     * For {@link jdk.jshell.Snippet.Status#RECOVERABLE_DEFINED RECOVERABLE_DEFINED} or
+     * {@link jdk.jshell.Snippet.Status#RECOVERABLE_NOT_DEFINED RECOVERABLE_NOT_DEFINED}
+     * declarations, the names of current unresolved dependencies for
+     * the snippet.
+     * The returned value of this method, for a given method may change when an
+     * <code>eval()</code> or <code>drop()</code> of another snippet causes
+     * an update of a dependency.
+     * @param snippet the declaration <code>Snippet</code> to look up
+     * @return the list of symbol names that are currently unresolvedDependencies.
+     * @throws IllegalStateException if this <code>JShell</code> instance is closed.
+     * @throws IllegalArgumentException if the snippet is not associated with
+     * this <code>JShell</code> instance.
+     */
+    public List<String> unresolvedDependencies(DeclarationSnippet snippet) {
+        return Collections.unmodifiableList(checkValidSnippet(snippet).unresolved());
+    }
+
+    /**
+     * Get the current value of a variable.
+     * @param snippet the variable Snippet whose value is queried.
+     * @return the current value of the variable referenced by snippet.
+     * @throws IllegalStateException if this <code>JShell</code> instance is closed.
+     * @throws IllegalArgumentException if the snippet is not associated with
+     * this <code>JShell</code> instance.
+     * @throws IllegalArgumentException if the variable's status is anything but
+     * {@link jdk.jshell.Snippet.Status#VALID}.
+     */
+    public String varValue(VarSnippet snippet) throws IllegalStateException {
+        checkIfAlive();
+        checkValidSnippet(snippet);
+        if (snippet.status() != Status.VALID) {
+            throw new IllegalArgumentException("Snippet parameter of varValue() '" +
+                    snippet + "' must be VALID, it is: " + snippet.status());
+        }
+        String value = executionControl().commandVarValue(maps.classFullName(snippet), snippet.name());
+        return expunge(value);
+    }
+
+    /**
+     * Register a callback to be called when the Status of a snippet changes.
+     * Each call adds a new subscription.
+     * @param listener Action to perform when the Status changes.
+     * @return A token which can be used to {@linkplain JShell#unsubscribe unsubscribe} this subscription.
+     * @throws IllegalStateException if this <code>JShell</code> instance is closed.
+     */
+    public Subscription onSnippetEvent(Consumer<SnippetEvent> listener)
+            throws IllegalStateException {
+        return onX(keyStatusListeners, listener);
+    }
+
+    /**
+     * Register a callback to be called when this JShell instance terminates.
+     * This occurs either because the client process has ended (e.g. called System.exit(0))
+     * or the connection has been shutdown, as by close().
+     * Each call adds a new subscription.
+     * @param listener Action to perform when the state terminates.
+     * @return A token which can be used to {@linkplain JShell#unsubscribe unsubscribe} this subscription.
+     * @throws IllegalStateException if this JShell instance is closed
+     */
+    public Subscription onShutdown(Consumer<JShell> listener)
+            throws IllegalStateException {
+        return onX(shutdownListeners, listener);
+    }
+
+    /**
+     * Cancel a callback subscription.
+     * @param token The token corresponding to the subscription to be unsubscribed.
+     */
+    public void unsubscribe(Subscription token) {
+        synchronized (this) {
+            token.remover.accept(token);
+        }
+    }
+
+    /**
+     * Subscription is a token for referring to subscriptions so they can
+     * be {@linkplain JShell#unsubscribe unsubscribed}.
+     */
+    public class Subscription {
+
+        Consumer<Subscription> remover;
+
+        Subscription(Consumer<Subscription> remover) {
+            this.remover = remover;
+        }
+    }
+
+    // --- private / package-private implementation support ---
+
+    ExecutionControl executionControl() {
+        if (executionControl == null) {
+            this.executionControl = new ExecutionControl(new JDIEnv(this), maps, this);
+            try {
+                executionControl.launch();
+            } catch (IOException ex) {
+                throw new InternalError("Launching JDI execution engine threw: " + ex.getMessage(), ex);
+            }
+        }
+        return executionControl;
+    }
+
+    void debug(int flags, String format, Object... args) {
+        if (InternalDebugControl.debugEnabled(this, flags)) {
+            err.printf(format, args);
+        }
+    }
+
+    void debug(Exception ex, String where) {
+        if (InternalDebugControl.debugEnabled(this, 0xFFFFFFFF)) {
+            err.printf("Fatal error: %s: %s\n", where, ex.getMessage());
+            ex.printStackTrace(err);
+        }
+    }
+
+    /**
+     * Generate the next key index, indicating a unique snippet signature.
+     * @return the next key index
+     */
+    int nextKeyIndex() {
+        return nextKeyIndex++;
+    }
+
+    private synchronized <T> Subscription onX(Map<Subscription, Consumer<T>> map, Consumer<T> listener)
+            throws IllegalStateException {
+        Objects.requireNonNull(listener);
+        checkIfAlive();
+        Subscription token = new Subscription(map::remove);
+        map.put(token, listener);
+        return token;
+    }
+
+    private synchronized void notifyKeyStatusEvent(SnippetEvent event) {
+        keyStatusListeners.values().forEach(l -> l.accept(event));
+    }
+
+    private synchronized void notifyShutdownEvent(JShell state) {
+        shutdownListeners.values().forEach(l -> l.accept(state));
+    }
+
+    void closeDown() {
+        if (!closed) {
+            // Send only once
+            closed = true;
+            notifyShutdownEvent(this);
+        }
+    }
+
+    /**
+     * Check if this JShell has been closed
+     * @throws IllegalStateException if it is closed
+     */
+    private void checkIfAlive()  throws IllegalStateException {
+        if (closed) {
+            throw new IllegalStateException("JShell (" + this + ") has been closed.");
+        }
+    }
+
+    /**
+     * Check a Snippet parameter coming from the API user
+     * @param sn the Snippet to check
+     * @throws NullPointerException if Snippet parameter is null
+     * @throws IllegalArgumentException if Snippet is not from this JShell
+     * @return the input Snippet (for chained calls)
+     */
+    private Snippet checkValidSnippet(Snippet sn) {
+        if (sn == null) {
+            throw new NullPointerException("Snippet must not be null");
+        } else {
+            if (sn.key().state() != this) {
+                throw new IllegalArgumentException("Snippet not from this JShell");
+            }
+            return sn;
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jshell/share/classes/jdk/jshell/Key.java	Wed Oct 21 18:40:01 2015 -0700
@@ -0,0 +1,290 @@
+/*
+ * Copyright (c) 2015, 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;
+
+import jdk.jshell.Snippet.Kind;
+import jdk.jshell.Snippet.SubKind;
+
+/**
+ * The Key is unique for a given signature.  Used internal to the implementation
+ * to group signature matched Snippets.  Snippet kinds without a signature
+ * (for example expressions) each have their own UniqueKey.
+ * @author Robert Field
+ */
+abstract class Key {
+
+    /**
+     * The unique index for this Key
+     */
+    private final int index;
+
+    /**
+     * The JShell corresponding to this Key
+     */
+    private final JShell state;
+
+    Key(JShell state) {