--- a/langtools/make/build.properties Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/make/build.properties Thu Oct 22 11:12:55 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/langtools/make/build.xml Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/make/build.xml Thu Oct 22 11:12:55 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">
@@ -417,10 +418,14 @@
<copy todir=".idea" >
<fileset dir="make/intellij" excludes="**/src/**"/>
</copy>
- <condition property="jtreg.idea.home" value="${jtreg.home}" else = "[jtreg.home]">
+ <condition property="idea.jtreg.home" value="${jtreg.home}" else = "[jtreg.home]">
<isset property="jtreg.home"/>
</condition>
- <replace file=".idea/ant.xml" token="@@@" value="${jtreg.idea.home}"/>
+ <condition property="idea.target.jdk" value="${target.java.home}" else = "$JDKPath$">
+ <isset property="target.java.home"/>
+ </condition>
+ <replace file=".idea/ant.xml" token="@IDEA_JTREG_HOME@" value="${idea.jtreg.home}"/>
+ <replace file=".idea/ant.xml" token="@IDEA_TARGET_JDK@" value="${idea.target.jdk}"/>
<replace file=".idea/workspace.xml" token="@FILE_SEP@" value="${file.separator}"/>
<replace file=".idea/workspace.xml" token="@PATH_SEP@" value="${path.separator}"/>
<mkdir dir=".idea/classes"/>
@@ -464,6 +469,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 +510,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 +538,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 +661,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 +697,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/langtools/make/gensrc/Gensrc-jdk.jshell.gmk Thu Oct 22 11:12:55 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/langtools/make/intellij/ant.xml Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/make/intellij/ant.xml Thu Oct 22 11:12:55 2015 -0700
@@ -5,8 +5,8 @@
<properties>
<property name="boot.java.home" value="$JDKPath$" />
<property name="jtreg.tests" value="$FilePath$" />
- <property name="target.java.home" value="$JDKPath$" />
- <property name="jtreg.home" value="@@@" />
+ <property name="target.java.home" value="@IDEA_TARGET_JDK@" />
+ <property name="jtreg.home" value="@IDEA_JTREG_HOME@" />
<property name="javac.debuglevel" value="source,lines,vars" />
<property name="jtreg.jpda.jvmargs" value="-agentlib:jdwp=transport=dt_socket,server=n,address=localhost:5900,suspend=y" />
</properties>
--- a/langtools/make/intellij/langtools.iml Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/make/intellij/langtools.iml Thu Oct 22 11:12:55 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/langtools/make/intellij/workspace.xml Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/make/intellij/workspace.xml Thu Oct 22 11:12:55 2015 -0700
@@ -103,6 +103,23 @@
<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="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/langtools/make/launcher.sh-template Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/make/launcher.sh-template Thu Oct 22 11:12:55 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/langtools/make/netbeans/langtools/build.xml Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/make/netbeans/langtools/build.xml Thu Oct 22 11:12:55 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/langtools/make/netbeans/langtools/nbproject/project.xml Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/make/netbeans/langtools/nbproject/project.xml Thu Oct 22 11:12:55 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/langtools/make/tools/anttasks/DumpClassesTask.java Thu Oct 22 11:12:55 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/langtools/make/tools/anttasks/SelectToolTask.java Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/make/tools/anttasks/SelectToolTask.java Thu Oct 22 11:12:55 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/langtools/src/jdk.compiler/share/classes/com/sun/source/doctree/DocCommentTree.java Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/source/doctree/DocCommentTree.java Thu Oct 22 11:12:55 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/langtools/src/jdk.compiler/share/classes/com/sun/source/util/DocTrees.java Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/source/util/DocTrees.java Thu Oct 22 11:12:55 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/langtools/src/jdk.compiler/share/classes/com/sun/tools/doclint/HtmlTag.java Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/doclint/HtmlTag.java Thu Oct 22 11:12:55 2015 -0700
@@ -25,18 +25,17 @@
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 static com.sun.tools.doclint.HtmlTag.Attr.*;
+import com.sun.tools.javac.util.StringUtils;
-import com.sun.tools.javac.util.StringUtils;
+import static com.sun.tools.doclint.HtmlTag.Attr.*;
/**
* 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/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java Thu Oct 22 11:12:55 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/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java Thu Oct 22 11:12:55 2015 -0700
@@ -220,11 +220,7 @@
*/
public static final long UNION = 1L<<39;
- /**
- * Flag that marks a special kind of bridge method (the ones that
- * come from restricted supertype bounds).
- */
- public static final long OVERRIDE_BRIDGE = 1L<<40;
+ // Flag bit (1L << 40) is available.
/**
* Flag that marks an 'effectively final' local variable.
@@ -383,7 +379,6 @@
HYPOTHETICAL(Flags.HYPOTHETICAL),
PROPRIETARY(Flags.PROPRIETARY),
UNION(Flags.UNION),
- OVERRIDE_BRIDGE(Flags.OVERRIDE_BRIDGE),
EFFECTIVELY_FINAL(Flags.EFFECTIVELY_FINAL),
CLASH(Flags.CLASH),
AUXILIARY(Flags.AUXILIARY),
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java Thu Oct 22 11:12:55 2015 -0700
@@ -1528,9 +1528,33 @@
* It is assumed that both symbols have the same name. The static
* modifier is ignored for this test.
*
+ * A quirk in the works is that if the receiver is a method symbol for
+ * an inherited abstract method we answer false summarily all else being
+ * immaterial. Abstract "own" methods (i.e `this' is a direct member of
+ * origin) don't get rejected as summarily and are put to test against the
+ * suitable criteria.
+ *
* See JLS 8.4.6.1 (without transitivity) and 8.4.6.4
*/
public boolean overrides(Symbol _other, TypeSymbol origin, Types types, boolean checkResult) {
+ return overrides(_other, origin, types, checkResult, true);
+ }
+
+ /** Does this symbol override `other' symbol, when both are seen as
+ * members of class `origin'? It is assumed that _other is a member
+ * of origin.
+ *
+ * Caveat: If `this' is an abstract inherited member of origin, it is
+ * deemed to override `other' only when `requireConcreteIfInherited'
+ * is false.
+ *
+ * It is assumed that both symbols have the same name. The static
+ * modifier is ignored for this test.
+ *
+ * See JLS 8.4.6.1 (without transitivity) and 8.4.6.4
+ */
+ public boolean overrides(Symbol _other, TypeSymbol origin, Types types, boolean checkResult,
+ boolean requireConcreteIfInherited) {
if (isConstructor() || _other.kind != MTH) return false;
if (this == _other) return true;
@@ -1550,7 +1574,7 @@
}
// check for an inherited implementation
- if ((flags() & ABSTRACT) != 0 ||
+ if (((flags() & ABSTRACT) != 0 && requireConcreteIfInherited) ||
((other.flags() & ABSTRACT) == 0 && (other.flags() & DEFAULT) == 0) ||
!other.isOverridableIn(origin) ||
!this.isMemberOf(origin, types))
@@ -1597,6 +1621,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/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ArgumentAttr.java Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ArgumentAttr.java Thu Oct 22 11:12:55 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/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java Thu Oct 22 11:12:55 2015 -0700
@@ -56,7 +56,6 @@
import com.sun.tools.javac.tree.JCTree.JCPolyExpression.*;
import com.sun.tools.javac.util.*;
import com.sun.tools.javac.util.DefinedBy.Api;
-import com.sun.tools.javac.util.Dependencies.AttributionKind;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
import com.sun.tools.javac.util.JCDiagnostic.Fragment;
import com.sun.tools.javac.util.List;
@@ -760,7 +759,6 @@
*/
void attribTypeVariables(List<JCTypeParameter> typarams, Env<AttrContext> env) {
for (JCTypeParameter tvar : typarams) {
- dependencies.push(AttributionKind.TVAR, tvar);
TypeVar a = (TypeVar)tvar.type;
a.tsym.flags_field |= UNATTRIBUTED;
a.bound = Type.noType;
@@ -775,7 +773,6 @@
types.setBounds(a, List.of(syms.objectType));
}
a.tsym.flags_field &= ~UNATTRIBUTED;
- dependencies.pop();
}
for (JCTypeParameter tvar : typarams) {
chk.checkNonCyclic(tvar.pos(), (TypeVar)tvar.type);
@@ -2817,8 +2814,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/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransTypes.java Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransTypes.java Thu Oct 22 11:12:55 2015 -0700
@@ -86,7 +86,7 @@
log = Log.instance(context);
syms = Symtab.instance(context);
enter = Enter.instance(context);
- overridden = new HashMap<>();
+ bridgeSpans = new HashMap<>();
types = Types.instance(context);
make = TreeMaker.instance(context);
resolve = Resolve.instance(context);
@@ -96,10 +96,12 @@
annotate = Annotate.instance(context);
}
- /** A hashtable mapping bridge methods to the methods they override after
- * type erasure.
+ /** A hashtable mapping bridge methods to the pair of methods they bridge.
+ * The bridge overrides the first of the pair after type erasure and deflects
+ * to the second of the pair (which differs in type erasure from the one
+ * it overrides thereby necessitating the bridge)
*/
- Map<MethodSymbol,MethodSymbol> overridden;
+ Map<MethodSymbol, Pair<MethodSymbol, MethodSymbol>> bridgeSpans;
/** Construct an attributed tree for a cast of expression to target type,
* unless it already has precisely that type.
@@ -294,9 +296,9 @@
bridges.append(md);
}
- // Add bridge to scope of enclosing class and `overridden' table.
+ // Add bridge to scope of enclosing class and keep track of the bridge span.
origin.members().enter(bridge);
- overridden.put(bridge, meth);
+ bridgeSpans.put(bridge, new Pair<>(meth, impl));
}
private List<VarSymbol> createBridgeParams(MethodSymbol impl, MethodSymbol bridge,
@@ -344,12 +346,12 @@
if (sym.kind == MTH &&
sym.name != names.init &&
(sym.flags() & (PRIVATE | STATIC)) == 0 &&
- (sym.flags() & (SYNTHETIC | OVERRIDE_BRIDGE)) != SYNTHETIC &&
+ (sym.flags() & SYNTHETIC) != SYNTHETIC &&
sym.isMemberOf(origin, types))
{
MethodSymbol meth = (MethodSymbol)sym;
MethodSymbol bridge = meth.binaryImplementation(origin, types);
- MethodSymbol impl = meth.implementation(origin, types, true, overrideBridgeFilter);
+ MethodSymbol impl = meth.implementation(origin, types, true);
if (bridge == null ||
bridge == meth ||
(impl != null && !bridge.owner.isSubClass(impl.owner, types))) {
@@ -365,14 +367,19 @@
// reflection design error.
addBridge(pos, meth, impl, origin, false, bridges);
}
- } else if ((bridge.flags() & (SYNTHETIC | OVERRIDE_BRIDGE)) == SYNTHETIC) {
- MethodSymbol other = overridden.get(bridge);
+ } else if ((bridge.flags() & SYNTHETIC) == SYNTHETIC) {
+ final Pair<MethodSymbol, MethodSymbol> bridgeSpan = bridgeSpans.get(bridge);
+ MethodSymbol other = bridgeSpan == null ? null : bridgeSpan.fst;
if (other != null && other != meth) {
if (impl == null || !impl.overrides(other, origin, types, true)) {
- // Bridge for other symbol pair was added
- log.error(pos, "name.clash.same.erasure.no.override",
- other, other.location(origin.type, types),
- meth, meth.location(origin.type, types));
+ // Is bridge effectively also the bridge for `meth', if so no clash.
+ MethodSymbol target = bridgeSpan == null ? null : bridgeSpan.snd;
+ if (target == null || !target.overrides(meth, origin, types, true, false)) {
+ // Bridge for other symbol pair was added
+ log.error(pos, "name.clash.same.erasure.no.override",
+ other, other.location(origin.type, types),
+ meth, meth.location(origin.type, types));
+ }
}
}
} else if (!bridge.overrides(meth, origin, types, true)) {
@@ -388,11 +395,6 @@
}
}
// where
- private Filter<Symbol> overrideBridgeFilter = new Filter<Symbol>() {
- public boolean accepts(Symbol s) {
- return (s.flags() & (SYNTHETIC | OVERRIDE_BRIDGE)) != SYNTHETIC;
- }
- };
/**
* @param method The symbol for which a bridge might have to be added
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TypeEnter.java Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TypeEnter.java Thu Oct 22 11:12:55 2015 -0700
@@ -54,7 +54,6 @@
import static com.sun.tools.javac.code.TypeTag.ERROR;
import static com.sun.tools.javac.tree.JCTree.Tag.*;
-import com.sun.tools.javac.util.Dependencies.AttributionKind;
import com.sun.tools.javac.util.Dependencies.CompletionCause;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticFlag;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
@@ -372,7 +371,6 @@
}
private void doImport(JCImport tree) {
- dependencies.push(AttributionKind.IMPORT, tree);
JCFieldAccess imp = (JCFieldAccess)tree.qualid;
Name name = TreeInfo.name(imp);
@@ -399,7 +397,6 @@
importNamed(tree.pos(), c, env, tree);
}
}
- dependencies.pop();
}
Type attribImportType(JCTree tree, Env<AttrContext> env) {
@@ -647,13 +644,7 @@
if (tree.extending != null) {
extending = clearTypeParams(tree.extending);
- dependencies.push(AttributionKind.EXTENDS, tree.extending);
- try {
- supertype = attr.attribBase(extending, baseEnv,
- true, false, true);
- } finally {
- dependencies.pop();
- }
+ supertype = attr.attribBase(extending, baseEnv, true, false, true);
} else {
extending = null;
supertype = ((tree.mods.flags & Flags.ENUM) != 0)
@@ -671,20 +662,14 @@
List<JCExpression> interfaceTrees = tree.implementing;
for (JCExpression iface : interfaceTrees) {
iface = clearTypeParams(iface);
- dependencies.push(AttributionKind.IMPLEMENTS, iface);
- try {
- Type it = attr.attribBase(iface, baseEnv, false, true, true);
- if (it.hasTag(CLASS)) {
- interfaces.append(it);
- if (all_interfaces != null) all_interfaces.append(it);
- } else {
- if (all_interfaces == null)
- all_interfaces = new ListBuffer<Type>().appendList(interfaces);
- all_interfaces.append(modelMissingTypes(it, iface, true));
-
- }
- } finally {
- dependencies.pop();
+ Type it = attr.attribBase(iface, baseEnv, false, true, true);
+ if (it.hasTag(CLASS)) {
+ interfaces.append(it);
+ if (all_interfaces != null) all_interfaces.append(it);
+ } else {
+ if (all_interfaces == null)
+ all_interfaces = new ListBuffer<Type>().appendList(interfaces);
+ all_interfaces.append(modelMissingTypes(it, iface, true));
}
}
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassWriter.java Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassWriter.java Thu Oct 22 11:12:55 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/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/DocCommentParser.java Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/DocCommentParser.java Thu Oct 22 11:12:55 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/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java Thu Oct 22 11:12:55 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/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DCTree.java Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DCTree.java Thu Oct 22 11:12:55 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/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocPretty.java Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocPretty.java Thu Oct 22 11:12:55 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/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocTreeMaker.java Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocTreeMaker.java Thu Oct 22 11:12:55 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;
+ }
}
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Dependencies.java Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Dependencies.java Thu Oct 22 11:12:55 2015 -0700
@@ -30,7 +30,7 @@
import com.sun.tools.javac.code.Symbol.Completer;
import com.sun.tools.javac.code.Symbol.CompletionFailure;
import com.sun.tools.javac.main.JavaCompiler;
-import com.sun.tools.javac.tree.JCTree;
+import com.sun.tools.javac.util.GraphUtils.DependencyKind;
import com.sun.tools.javac.util.GraphUtils.DotVisitor;
import com.sun.tools.javac.util.GraphUtils.NodeVisitor;
@@ -42,13 +42,13 @@
import java.util.Collection;
import java.util.EnumMap;
import java.util.EnumSet;
-import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
-import java.util.Set;
import java.util.Stack;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import javax.tools.JavaFileObject;
@@ -78,69 +78,23 @@
}
/**
- * This enum models different kinds of attribution actions triggered during
- * symbol completion.
- */
- public enum AttributionKind {
- /**
- * Attribution of superclass (i.e. @{code extends} clause).
- */
- EXTENDS {
- @Override
- String format(JCTree tree) {
- return "extends " + super.format(tree);
- }
- },
- /**
- * Attribution of superinterface (i.e. an type in the @{code interface} clause).
- */
- IMPLEMENTS {
- @Override
- String format(JCTree tree) {
- return "implements " + super.format(tree);
- }
- },
- /**
- * Attribution of an import statement
- */
- IMPORT,
- /**
- * Attribution of type-variable bound
- */
- TVAR {
- @Override
- String format(JCTree tree) {
- return "<" + super.format(tree) + ">";
- }
- };
-
- String format(JCTree tree) {
- return tree.toString();
- }
- }
-
- /**
* Push a new completion node on the stack.
*/
abstract public void push(ClassSymbol s, CompletionCause phase);
/**
- * Push a new attribution node on the stack.
- */
- abstract public void push(AttributionKind ak, JCTree t);
-
- /**
* Remove current dependency node from the stack.
*/
abstract public void pop();
- public enum CompletionCause {
+ public enum CompletionCause implements GraphUtils.DependencyKind {
CLASS_READER,
HEADER_PHASE,
HIERARCHY_PHASE,
IMPORTS_PHASE,
MEMBER_ENTER,
- MEMBERS_PHASE;
+ MEMBERS_PHASE,
+ OTHER;
}
/**
@@ -163,13 +117,8 @@
/**
* Register a Context.Factory to create a Dependencies.
*/
- public static void preRegister(final Context context) {
- context.put(dependenciesKey, new Context.Factory<Dependencies>() {
- public Dependencies make(Context c) {
- Dependencies deps = new GraphDependencies(context);
- return deps;
- }
- });
+ public static void preRegister(Context context) {
+ context.put(dependenciesKey, (Context.Factory<Dependencies>) GraphDependencies::new);
}
/**
@@ -195,12 +144,11 @@
enum DependenciesMode {
SOURCE("source"),
CLASS("class"),
- REDUNDANT("redundant"),
- SIDE_EFFECTS("side-effects");
+ REDUNDANT("redundant");
final String opt;
- private DependenciesMode(String opt) {
+ DependenciesMode(String opt) {
this.opt = opt;
}
@@ -228,46 +176,19 @@
}
/**
- * Class representing a node in the dependency graph. Nodes are of two main
- * kinds: (i) symbol nodes, corresponding to symbol completion requests
- * (either from source or classfile); (ii) attribution nodes, corresponding to
- * attribution actions triggered during (source) completion.
+ * Class representing a node in the dependency graph.
*/
- public static abstract class Node extends GraphUtils.AbstractNode<String, Node>
- implements GraphUtils.DottableNode<String, Node> {
-
- /**
- * Model the dependencies between nodes.
- */
- public enum DependencyKind implements GraphUtils.DependencyKind {
- /**
- * standard dependency - i.e. completion of the source node depends
- * on completion of the sink node.
- */
- REQUIRES("solid"),
- /**
- * soft dependencies - i.e. completion of the source node depends
- * on side-effects of the source node. These dependencies are meant
- * to capture the order in which javac processes all dependants of a given node.
- */
- SIDE_EFFECTS("dashed");
-
- final String dotStyle;
-
- DependencyKind(String dotStyle) {
- this.dotStyle = dotStyle;
- }
- }
-
+ public static abstract class Node extends GraphUtils.AbstractNode<ClassSymbol, Node>
+ implements GraphUtils.DottableNode<ClassSymbol, Node> {
/**
* dependant nodes grouped by kind
*/
- EnumMap<DependencyKind, List<Node>> depsByKind;
+ EnumMap<CompletionCause, List<Node>> depsByKind;
- Node(String value) {
+ Node(ClassSymbol value) {
super(value);
- this.depsByKind = new EnumMap<>(DependencyKind.class);
- for (DependencyKind depKind : DependencyKind.values()) {
+ this.depsByKind = new EnumMap<>(CompletionCause.class);
+ for (CompletionCause depKind : CompletionCause.values()) {
depsByKind.put(depKind, new ArrayList<Node>());
}
}
@@ -281,8 +202,7 @@
@Override
public boolean equals(Object obj) {
- return obj instanceof Node &&
- data.equals(((Node) obj).data);
+ return obj instanceof Node && data.equals(((Node) obj).data);
}
@Override
@@ -292,19 +212,12 @@
@Override
public GraphUtils.DependencyKind[] getSupportedDependencyKinds() {
- return DependencyKind.values();
+ return CompletionCause.values();
}
@Override
- public java.util.Collection<? extends Node> getDependenciesByKind(GraphUtils.DependencyKind dk) {
- List<Node> deps = depsByKind.get(dk);
- if (dk == DependencyKind.REQUIRES) {
- return deps;
- } else {
- Set<Node> temp = new HashSet<>(deps);
- temp.removeAll(depsByKind.get(DependencyKind.REQUIRES));
- return temp;
- }
+ public java.util.Collection<? extends Node> getDependenciesByKind(DependencyKind dk) {
+ return depsByKind.get(dk);
}
@Override
@@ -317,9 +230,14 @@
@Override
public Properties dependencyAttributes(Node to, GraphUtils.DependencyKind dk) {
Properties p = new Properties();
- p.put("style", ((DependencyKind) dk).dotStyle);
+ p.put("label", dk);
return p;
}
+
+ @Override
+ public String toString() {
+ return data.getQualifiedName().toString();
+ }
}
/**
@@ -349,11 +267,9 @@
}
final Kind ck;
- final ClassSymbol sym;
CompletionNode(ClassSymbol sym) {
- super(sym.getQualifiedName().toString());
- this.sym = sym;
+ super(sym);
//infer completion kind by looking at the symbol fields
boolean fromClass = (sym.classfile == null && sym.sourcefile == null) ||
(sym.classfile != null && sym.classfile.getKind() == JavaFileObject.Kind.CLASS);
@@ -371,27 +287,7 @@
}
public ClassSymbol getClassSymbol() {
- return sym;
- }
- }
-
- /**
- * This is a dependency node used to model attribution actions triggered during
- * source symbol completion. The possible kinds of attribution actions are
- * captured in {@link AttributionNode}.
- */
- static class AttributionNode extends Node {
-
- AttributionNode(AttributionKind ak, JCTree tree) {
- super(ak.format(tree));
- }
-
- @Override
- public Properties nodeAttributes() {
- Properties p = super.nodeAttributes();
- p.put("shape", "box");
- p.put("style", "solid");
- return p;
+ return data;
}
}
@@ -403,25 +299,20 @@
/**
* map containing all dependency nodes seen so far
*/
- Map<String, Node> dependencyNodeMap = new LinkedHashMap<>();
+ Map<ClassSymbol, Node> dependencyNodeMap = new LinkedHashMap<>();
@Override
public void push(ClassSymbol s, CompletionCause phase) {
Node n = new CompletionNode(s);
- if (n == push(n)) {
+ if (n == push(n, phase)) {
s.completer = this;
}
}
- @Override
- public void push(AttributionKind ak, JCTree t) {
- push(new AttributionNode(ak, t));
- }
-
/**
* Push a new dependency on the stack.
*/
- protected Node push(Node newNode) {
+ protected Node push(Node newNode, CompletionCause cc) {
Node cachedNode = dependencyNodeMap.get(newNode.data);
if (cachedNode == null) {
dependencyNodeMap.put(newNode.data, newNode);
@@ -430,7 +321,7 @@
}
if (!nodeStack.isEmpty()) {
Node currentNode = nodeStack.peek();
- currentNode.addDependency(Node.DependencyKind.REQUIRES, newNode);
+ currentNode.addDependency(cc, newNode);
}
nodeStack.push(newNode);
return newNode;
@@ -455,10 +346,6 @@
//filter source completions
new FilterVisitor(CompletionNode.Kind.CLASS).visit(dependencyNodeMap.values(), null);
}
- if (dependenciesModes.contains(DependenciesMode.SIDE_EFFECTS)) {
- //add side-effects edges
- new SideEffectVisitor().visit(dependencyNodeMap.values(), null);
- }
if (dependenciesFile != null) {
//write to file
try (FileWriter fw = new FileWriter(dependenciesFile)) {
@@ -469,7 +356,7 @@
@Override
public void complete(Symbol sym) throws CompletionFailure {
- push((ClassSymbol) sym, null);
+ push((ClassSymbol)sym, CompletionCause.OTHER);
pop();
sym.completer = this;
}
@@ -484,31 +371,9 @@
}
/**
- * This visitor is used to generate the special side-effect dependencies
- * given a graph containing only standard dependencies.
- */
- private static class SideEffectVisitor extends NodeVisitor<String, Node, Void> {
- @Override
- public void visitNode(Node node, Void arg) {
- //do nothing
- }
-
- @Override
- public void visitDependency(GraphUtils.DependencyKind dk, Node from, Node to, Void arg) {
- //if we are adding multiple dependencies to same node
- //make order explicit via special 'side-effect' dependencies
- List<Node> deps = from.depsByKind.get(dk);
- int pos = deps.indexOf(to);
- if (dk == Node.DependencyKind.REQUIRES && pos > 0) {
- to.addDependency(Node.DependencyKind.SIDE_EFFECTS, deps.get(pos - 1));
- }
- }
- }
-
- /**
* This visitor is used to prune the graph from spurious edges using some heuristics.
*/
- private static class PruneVisitor extends NodeVisitor<String, Node, Void> {
+ private static class PruneVisitor extends NodeVisitor<ClassSymbol, Node, Void> {
@Override
public void visitNode(Node node, Void arg) {
//do nothing
@@ -517,8 +382,7 @@
@Override
public void visitDependency(GraphUtils.DependencyKind dk, Node from, Node to, Void arg) {
//heuristic - skips dependencies that are likely to be fake
- if (from.equals(to) ||
- from.depsByKind.get(Node.DependencyKind.REQUIRES).contains(to)) {
+ if (from.equals(to)) {
to.depsByKind.get(dk).remove(from);
}
}
@@ -527,7 +391,7 @@
/**
* This visitor is used to retain only completion nodes with given kind.
*/
- private class FilterVisitor extends NodeVisitor<String, Node, Void> {
+ private class FilterVisitor extends NodeVisitor<ClassSymbol, Node, Void> {
CompletionNode.Kind ck;
@@ -571,11 +435,6 @@
}
@Override
- public void push(AttributionKind ak, JCTree t) {
- //do nothing
- }
-
- @Override
public void pop() {
//do nothing
}
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/dependencies/NewDependencyCollector.java Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/dependencies/NewDependencyCollector.java Thu Oct 22 11:12:55 2015 -0700
@@ -26,11 +26,13 @@
package com.sun.tools.sjavac.comp.dependencies;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import javax.tools.JavaFileManager.Location;
import javax.tools.JavaFileObject;
@@ -38,7 +40,11 @@
import com.sun.source.util.TaskEvent;
import com.sun.source.util.TaskListener;
+import com.sun.tools.javac.code.Kinds.Kind;
+import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
+import com.sun.tools.javac.code.Symbol.TypeSymbol;
+import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.DefinedBy;
import com.sun.tools.javac.util.DefinedBy.Api;
@@ -85,7 +91,6 @@
return deps.getNodes()
.stream()
- .filter(n -> n instanceof CompletionNode)
.map(n -> (CompletionNode) n)
.filter(n -> n.getClassSymbol().fullname != null)
.filter(n -> explicits == explicitJFOs.contains(n.getClassSymbol().classfile))
@@ -128,31 +133,66 @@
String depPkg = Util.pkgNameOfClassName(fqDep);
Map<String, Set<String>> depsForThisClass = result.get(depPkg);
- if (depsForThisClass == null)
+ if (depsForThisClass == null) {
result.put(depPkg, depsForThisClass = new HashMap<>());
+ }
+
+ Set<String> fqDeps = depsForThisClass.get(fqDep);
+ if (fqDeps == null) {
+ depsForThisClass.put(fqDep, fqDeps = new HashSet<>());
+ }
- for (Node<?,?> depNode : cnode.getDependenciesByKind(GraphDependencies.Node.DependencyKind.REQUIRES)) {
- boolean isCompletionNode = depNode instanceof CompletionNode;
- if (isCompletionNode) {
- CompletionNode cDepNode = (CompletionNode) depNode;
- if (cDepNode == cnode)
- continue;
- if (cDepNode.getClassSymbol().fullname == null) // Anonymous class
- continue;
- Location depLoc = getLocationOf(cDepNode.getClassSymbol());
- boolean relevant = (cp && depLoc == StandardLocation.CLASS_PATH)
- || (!cp && depLoc == StandardLocation.SOURCE_PATH);
- if (!relevant)
- continue;
-
- Set<String> fqDeps = depsForThisClass.get(fqDep);
- if (fqDeps == null)
- depsForThisClass.put(fqDep, fqDeps = new HashSet<>());
+ for (Node<?,?> depNode : getAllDependencies(cnode)) {
+ CompletionNode cDepNode = (CompletionNode) depNode;
+ // Symbol is not regarded to depend on itself.
+ if (cDepNode == cnode) {
+ continue;
+ }
+ // Skip anonymous classes
+ if (cDepNode.getClassSymbol().fullname == null) {
+ continue;
+ }
+ if (isSymbolRelevant(cp, cDepNode.getClassSymbol())) {
fqDeps.add(cDepNode.getClassSymbol().outermostClass().flatname.toString());
}
}
+
+ // The completion dependency graph is not transitively closed for inheritance relations.
+ // For sjavac's purposes however, a class depends on it's super super type, so below we
+ // make sure that we include super types.
+ for (ClassSymbol cs : allSupertypes(cnode.getClassSymbol())) {
+ if (isSymbolRelevant(cp, cs)) {
+ fqDeps.add(cs.outermostClass().flatname.toString());
+ }
+ }
+
}
return result;
}
+ public boolean isSymbolRelevant(boolean cp, ClassSymbol cs) {
+ Location csLoc = getLocationOf(cs);
+ Location relevantLocation = cp ? StandardLocation.CLASS_PATH : StandardLocation.SOURCE_PATH;
+ return csLoc == relevantLocation;
+ }
+
+ private Set<ClassSymbol> allSupertypes(TypeSymbol t) {
+ if (t == null || !(t instanceof ClassSymbol)) {
+ return Collections.emptySet();
+ }
+ Set<ClassSymbol> result = new HashSet<>();
+ ClassSymbol cs = (ClassSymbol) t;
+ result.add(cs);
+ result.addAll(allSupertypes(cs.getSuperclass().tsym));
+ for (Type it : cs.getInterfaces()) {
+ result.addAll(allSupertypes(it.tsym));
+ }
+ return result;
+ }
+
+ private Collection<? extends Node<?, ?>> getAllDependencies(CompletionNode cnode) {
+ return Stream.of(cnode.getSupportedDependencyKinds())
+ .flatMap(dk -> cnode.getDependenciesByKind(dk).stream())
+ .collect(Collectors.toSet());
+ }
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/debug/InternalDebugControl.java Thu Oct 22 11:12:55 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/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/remote/RemoteAgent.java Thu Oct 22 11:12:55 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/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/remote/RemoteClassLoader.java Thu Oct 22 11:12:55 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/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/remote/RemoteCodes.java Thu Oct 22 11:12:55 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/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/remote/RemoteResolutionException.java Thu Oct 22 11:12:55 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/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ConsoleIOContext.java Thu Oct 22 11:12:55 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/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/EditPad.java Thu Oct 22 11:12:55 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/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/EditingHistory.java Thu Oct 22 11:12:55 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/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/ExternalEditor.java Thu Oct 22 11:12:55 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/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/IOContext.java Thu Oct 22 11:12:55 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/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/JShellTool.java Thu Oct 22 11:12:55 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/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/StopDetectingInputStream.java Thu Oct 22 11:12:55 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/langtools/src/jdk.jshell/share/classes/jdk/internal/jshell/tool/resources/version.properties-template Thu Oct 22 11:12:55 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/langtools/src/jdk.jshell/share/classes/jdk/jshell/ClassTracker.java Thu Oct 22 11:12:55 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/langtools/src/jdk.jshell/share/classes/jdk/jshell/CompletenessAnalyzer.java Thu Oct 22 11:12:55 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/langtools/src/jdk.jshell/share/classes/jdk/jshell/DeclarationSnippet.java Thu Oct 22 11:12:55 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/langtools/src/jdk.jshell/share/classes/jdk/jshell/Diag.java Thu Oct 22 11:12:55 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/langtools/src/jdk.jshell/share/classes/jdk/jshell/DiagList.java Thu Oct 22 11:12:55 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/langtools/src/jdk.jshell/share/classes/jdk/jshell/ErroneousSnippet.java Thu Oct 22 11:12:55 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/langtools/src/jdk.jshell/share/classes/jdk/jshell/Eval.java Thu Oct 22 11:12:55 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/langtools/src/jdk.jshell/share/classes/jdk/jshell/EvalException.java Thu Oct 22 11:12:55 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/langtools/src/jdk.jshell/share/classes/jdk/jshell/ExecutionControl.java Thu Oct 22 11:12:55 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/langtools/src/jdk.jshell/share/classes/jdk/jshell/ExpressionSnippet.java Thu Oct 22 11:12:55 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/langtools/src/jdk.jshell/share/classes/jdk/jshell/GeneralWrap.java Thu Oct 22 11:12:55 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/langtools/src/jdk.jshell/share/classes/jdk/jshell/ImportSnippet.java Thu Oct 22 11:12:55 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/langtools/src/jdk.jshell/share/classes/jdk/jshell/JDIConnection.java Thu Oct 22 11:12:55 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/langtools/src/jdk.jshell/share/classes/jdk/jshell/JDIEnv.java Thu Oct 22 11:12:55 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/langtools/src/jdk.jshell/share/classes/jdk/jshell/JDIEventHandler.java Thu Oct 22 11:12:55 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/langtools/src/jdk.jshell/share/classes/jdk/jshell/JDINotConnectedException.java Thu Oct 22 11:12:55 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/langtools/src/jdk.jshell/share/classes/jdk/jshell/JShell.java Thu Oct 22 11:12:55 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>&& 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>&& 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>&& 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/langtools/src/jdk.jshell/share/classes/jdk/jshell/Key.java Thu Oct 22 11:12:55 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) {
+ this.index = state.nextKeyIndex();
+ this.state = state;
+ }
+
+ /**
+ * The unique numeric identifier for the snippet. Snippets which replace or
+ * redefine existing snippets take on the same key index as the replaced or
+ * redefined snippet.
+ * The index values are monotonically increasing integers.
+ * @return the Key index
+ */
+ int index() { return index; }
+
+ /**
+ * The kind for the key. Indicates the subinterface of Key.
+ * @return the Kind of the Key
+ */
+ abstract Kind kind();
+
+ /**
+ * For foreign key testing.
+ */
+ JShell state() { return state; }
+
+ /**
+ * Grouping for snippets which persist and influence future code.
+ * They are keyed off at least the name. They may be Modified/Replaced
+ * with new input and can be dropped (JShell#drop).
+ */
+ static abstract class PersistentKey extends Key {
+
+ private final String name;
+
+ PersistentKey(JShell state, String name) {
+ super(state);
+ this.name = name;
+ }
+ /**
+ * Name of the snippet.
+ *
+ * @return the name of the snippet.
+ */
+ String name() { return name; }
+ }
+
+ /**
+ * Grouping for snippets which reference declarations
+ */
+ static abstract class DeclarationKey extends PersistentKey {
+
+ DeclarationKey(JShell state, String name) {
+ super(state, name);
+ }
+ }
+
+ /**
+ * Key for a class definition Keys of TYPE_DECL. Keyed off class name.
+ */
+ static class TypeDeclKey extends DeclarationKey {
+
+ TypeDeclKey(JShell state, String name) {
+ super(state, name);
+ }
+
+ @Override
+ Kind kind() { return Kind.TYPE_DECL; }
+
+ @Override
+ public String toString() { return "ClassKey(" + name() + ")#" + index(); }
+ }
+
+ /**
+ * Key for a method definition Keys of METHOD. Keyed off method name and
+ * parameter types.
+ */
+ static class MethodKey extends DeclarationKey {
+
+ private final String parameterTypes;
+
+ MethodKey(JShell state, String name, String parameterTypes) {
+ super(state, name);
+ this.parameterTypes = parameterTypes;
+ }
+
+ @Override
+ Kind kind() { return Kind.METHOD; }
+
+ /**
+ * The parameter types of the method. Because of overloading of methods,
+ * the parameter types are part of the key.
+ *
+ * @return a String representation of the parameter types of the method.
+ */
+ String parameterTypes() { return parameterTypes; }
+
+
+ @Override
+ public String toString() { return "MethodKey(" + name() +
+ "(" + parameterTypes() + "))#" + index(); }
+ }
+
+ /**
+ * Key for a variable definition Keys of VARIABLE. Keyed off variable
+ * name.
+ */
+ static class VarKey extends DeclarationKey {
+
+ VarKey(JShell state, String name) {
+ super(state, name);
+ }
+
+ @Override
+ public Kind kind() { return Kind.VAR; }
+
+ @Override
+ public String toString() { return "VariableKey(" + name() + ")#" + index(); }
+ }
+
+ /**
+ * Key for an import. Keys of IMPORT. Keyed off import text and whether
+ * import is static.
+ */
+ static class ImportKey extends PersistentKey {
+
+ private final SubKind snippetKind;
+
+ ImportKey(JShell state, String name,SubKind snippetKind) {
+ super(state, name);
+ this.snippetKind = snippetKind;
+ }
+
+ @Override
+ public Kind kind() { return Kind.IMPORT; }
+
+ /**
+ * Which kind of import.
+ *
+ * @return the appropriate SubKind.
+ */
+ SubKind snippetKind() {
+ return snippetKind;
+ }
+
+
+ @Override
+ public String toString() { return "ImportKey(" + name() + "," +
+ snippetKind + ")#" + index(); }
+ }
+
+ /**
+ * Grouping for snippets which are the key for only one input -- even if the
+ * exactly same entry is made again. The referenced snippets are thus
+ * unmodifiable.
+ */
+ static abstract class UniqueKey extends Key {
+
+ UniqueKey(JShell state) {
+ super(state);
+ }
+ }
+
+ /**
+ * Key for a statement snippet. Keys of STATEMENT. Uniquely keyed, see
+ * UniqueKey.
+ */
+ static class StatementKey extends UniqueKey {
+
+ StatementKey(JShell state) {
+ super(state);
+ }
+
+
+ @Override
+ public Kind kind() {
+ return Kind.STATEMENT;
+ }
+
+ @Override
+ public String toString() { return "StatementKey#" + index(); }
+ }
+
+ /**
+ * Key for an expression. Keys of EXPRESSION. Uniquely keyed, see
+ * UniqueKey.
+ */
+ static class ExpressionKey extends UniqueKey {
+
+ private final String name;
+ private final String typeName;
+
+ ExpressionKey(JShell state, String name, String typeName) {
+ super(state);
+ this.name = name;
+ this.typeName = typeName;
+ }
+
+ @Override
+ public Kind kind() { return Kind.EXPRESSION; }
+
+
+ /**
+ * Variable name which is the value of the expression. Since the
+ * expression is either just an identifier which is a variable or it is
+ * an assignment to a variable, there is always a variable which is the
+ * subject of the expression. All other expression snippets become
+ * temporary variables referenced by a VariableKey.
+ *
+ * @return the name of the variable which is the subject of the
+ * expression.
+ */
+ String name() { return name; }
+
+ /**
+ * Type of the expression
+ *
+ * @return String representation of the type of the expression.
+ */
+ String typeName() { return typeName; }
+
+
+ @Override
+ public String toString() { return "ExpressionKey(" + name() + ")#" + index(); }
+ }
+
+ /**
+ * Key for an erroneous snippet. Keys of ERRONEOUS. Uniquely keyed, see
+ * UniqueKey.
+ */
+ static class ErroneousKey extends UniqueKey {
+
+ ErroneousKey(JShell state) {
+ super(state);
+ }
+
+ @Override
+ Kind kind() { return Kind.ERRONEOUS; }
+
+ @Override
+ public String toString() { return "ErroneousKey#" + index(); }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/KeyMap.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,98 @@
+/*
+ * 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.LinkedHashMap;
+import java.util.Map;
+import java.util.stream.Stream;
+import jdk.jshell.Key.TypeDeclKey;
+import jdk.jshell.Key.ErroneousKey;
+import jdk.jshell.Key.ExpressionKey;
+import jdk.jshell.Key.ImportKey;
+import jdk.jshell.Key.MethodKey;
+import jdk.jshell.Key.StatementKey;
+import jdk.jshell.Key.VarKey;
+import jdk.jshell.Snippet.SubKind;
+import static jdk.jshell.Snippet.SubKind.SINGLE_STATIC_IMPORT_SUBKIND;
+import static jdk.jshell.Snippet.SubKind.SINGLE_TYPE_IMPORT_SUBKIND;
+
+/**
+ * Maps signature to Key by Kind.
+ * @author Robert Field
+ */
+class KeyMap {
+ private final JShell state;
+
+ KeyMap(JShell state) {
+ this.state = state;
+ }
+
+ private final Map<String, TypeDeclKey> classMap = new LinkedHashMap<>();
+ private final Map<String, MethodKey> methodMap = new LinkedHashMap<>();
+ private final Map<String, VarKey> varMap = new LinkedHashMap<>();
+ private final Map<String, ImportKey> importMap = new LinkedHashMap<>();
+
+ TypeDeclKey keyForClass(String name) {
+ return classMap.computeIfAbsent(name, k -> new TypeDeclKey(state, name));
+ }
+
+ MethodKey keyForMethod(String name, String parameterTypes) {
+ return methodMap.computeIfAbsent(name + ":" + parameterTypes,
+ k -> new MethodKey(state, name, parameterTypes));
+ }
+
+ VarKey keyForVariable(String name) {
+ return varMap.computeIfAbsent(name, k -> new VarKey(state, name));
+ }
+
+ ImportKey keyForImport(String name, SubKind snippetKind) {
+ return importMap.computeIfAbsent(name + ":"
+ + ((snippetKind == SINGLE_TYPE_IMPORT_SUBKIND || snippetKind == SINGLE_STATIC_IMPORT_SUBKIND)
+ ? "single"
+ : snippetKind.toString()),
+ k -> new ImportKey(state, name, snippetKind));
+ }
+
+ StatementKey keyForStatement() {
+ return new StatementKey(state);
+ }
+
+ ExpressionKey keyForExpression(String name, String type) {
+ return new ExpressionKey(state, name, type);
+ }
+
+ ErroneousKey keyForErroneous() {
+ return new ErroneousKey(state);
+ }
+
+ boolean doesVariableNameExist(String name) {
+ return varMap.get(name) != null;
+ }
+
+ Stream<ImportKey> importKeys() {
+ return importMap.values().stream();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/MaskCommentsAndModifiers.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,202 @@
+/*
+ * 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.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Within a String, mask code comments and ignored modifiers (within context).
+ *
+ * @author Robert Field
+ */
+class MaskCommentsAndModifiers {
+
+ private final static Set<String> IGNORED_MODIFERS =
+ Stream.of( "public", "protected", "private", "static", "final" )
+ .collect( Collectors.toSet() );
+
+ private final StringBuilder sbCleared = new StringBuilder();
+ private final StringBuilder sbMask = new StringBuilder();
+ private final String str;
+ private final int length;
+ private final boolean maskModifiers;
+ private int next = 0;
+ private boolean wasMasked = false;
+ private boolean inside = false;
+
+ @SuppressWarnings("empty-statement")
+ public MaskCommentsAndModifiers(String s, boolean maskModifiers) {
+ this.str = s;
+ this.length = s.length();
+ this.maskModifiers = maskModifiers;
+ do { } while (next());
+ }
+
+ public String cleared() {
+ return sbCleared.toString();
+ }
+
+ public String mask() {
+ return sbMask.toString();
+ }
+
+ public boolean wasMasked() {
+ return wasMasked;
+ }
+
+ /**
+ * Read the next character
+ */
+ private int read() {
+ if (next >= length) {
+ return -1;
+ }
+ return str.charAt(next++);
+ }
+
+ private void write(StringBuilder sb, int ch) {
+ sb.append((char)ch);
+ }
+
+ private void write(int ch) {
+ write(sbCleared, ch);
+ write(sbMask, Character.isWhitespace(ch) ? ch : ' ');
+ }
+
+ private void writeMask(int ch) {
+ wasMasked = true;
+ write(sbMask, ch);
+ write(sbCleared, Character.isWhitespace(ch) ? ch : ' ');
+ }
+
+ private void write(CharSequence s) {
+ for (int cp : s.chars().toArray()) {
+ write(cp);
+ }
+ }
+
+ private void writeMask(CharSequence s) {
+ for (int cp : s.chars().toArray()) {
+ writeMask(cp);
+ }
+ }
+
+ private boolean next() {
+ return next(read());
+ }
+
+ private boolean next(int c) {
+ if (c < 0) {
+ return false;
+ }
+
+ if (c == '\'' || c == '"') {
+ inside = true;
+ write(c);
+ int match = c;
+ c = read();
+ while (c != match) {
+ if (c < 0) {
+ return false;
+ }
+ if (c == '\n' || c == '\r') {
+ write(c);
+ return true;
+ }
+ if (c == '\\') {
+ write(c);
+ c = read();
+ }
+ write(c);
+ c = read();
+ }
+ write(c);
+ return true;
+ }
+
+ if (c == '/') {
+ c = read();
+ if (c == '*') {
+ writeMask('/');
+ writeMask(c);
+ int prevc = 0;
+ while ((c = read()) != '/' || prevc != '*') {
+ if (c < 0) {
+ return false;
+ }
+ writeMask(c);
+ prevc = c;
+ }
+ writeMask(c);
+ return true;
+ } else if (c == '/') {
+ writeMask('/');
+ writeMask(c);
+ while ((c = read()) != '\n' && c != '\r') {
+ if (c < 0) {
+ return false;
+ }
+ writeMask(c);
+ }
+ writeMask(c);
+ return true;
+ } else {
+ inside = true;
+ write('/');
+ // read character falls through
+ }
+ }
+
+ if (Character.isJavaIdentifierStart(c)) {
+ if (maskModifiers && !inside) {
+ StringBuilder sb = new StringBuilder();
+ do {
+ write(sb, c);
+ c = read();
+ } while (Character.isJavaIdentifierPart(c));
+ String id = sb.toString();
+ if (IGNORED_MODIFERS.contains(id)) {
+ writeMask(sb);
+ } else {
+ write(sb);
+ if (id.equals("import")) {
+ inside = true;
+ }
+ }
+ return next(c); // recurse to handle left-over character
+ }
+ } else if (!Character.isWhitespace(c)) {
+ inside = true;
+ }
+
+ if (c < 0) {
+ return false;
+ }
+ write(c);
+ return true;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/MemoryFileManager.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,651 @@
+/*
+ * 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.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.TreeMap;
+import javax.tools.JavaFileObject.Kind;
+import static javax.tools.StandardLocation.CLASS_PATH;
+import javax.tools.FileObject;
+import javax.tools.JavaFileManager;
+import javax.tools.JavaFileObject;
+import javax.tools.SimpleJavaFileObject;
+import javax.tools.StandardJavaFileManager;
+
+import com.sun.tools.javac.util.DefinedBy;
+import com.sun.tools.javac.util.DefinedBy.Api;
+
+import static jdk.internal.jshell.debug.InternalDebugControl.DBG_FMGR;
+
+/**
+ * File manager for the compiler API. Reads from memory (Strings) and writes
+ * class files to memory (cached OutputMemoryJavaFileObject).
+ *
+ * @author Robert Field
+ */
+class MemoryFileManager implements JavaFileManager {
+
+ private final StandardJavaFileManager stdFileManager;
+
+ private final Map<String, OutputMemoryJavaFileObject> classObjects = new TreeMap<>();
+
+ private ClassFileCreationListener classListener = null;
+
+ private final ClassLoader loader = new REPLClassLoader();
+
+ private final JShell proc;
+
+ // Upcoming Jigsaw
+ private Method inferModuleNameMethod = null;
+ private Method listModuleLocationsMethod = null;
+
+ static abstract class MemoryJavaFileObject extends SimpleJavaFileObject {
+
+ public MemoryJavaFileObject(String name, JavaFileObject.Kind kind) {
+ super(URI.create("string:///" + name.replace('.', '/')
+ + kind.extension), kind);
+ }
+ }
+
+ class SourceMemoryJavaFileObject extends MemoryJavaFileObject {
+ private final String src;
+ private final Object origin;
+
+ SourceMemoryJavaFileObject(Object origin, String className, String code) {
+ super(className, JavaFileObject.Kind.SOURCE);
+ this.origin = origin;
+ this.src = code;
+ }
+
+ public Object getOrigin() {
+ return origin;
+ }
+
+ @Override @DefinedBy(Api.COMPILER)
+ public CharSequence getCharContent(boolean ignoreEncodingErrors) {
+ return src;
+ }
+ }
+
+ static class OutputMemoryJavaFileObject extends MemoryJavaFileObject {
+
+ /**
+ * Byte code created by the compiler will be stored in this
+ * ByteArrayOutputStream.
+ */
+ private ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ private byte[] bytes = null;
+
+ private final String className;
+
+ public OutputMemoryJavaFileObject(String name, JavaFileObject.Kind kind) {
+ super(name, kind);
+ this.className = name;
+ }
+
+ public byte[] getBytes() {
+ if (bytes == null) {
+ bytes = bos.toByteArray();
+ bos = null;
+ }
+ return bytes;
+ }
+
+ public void dump() {
+ try {
+ Path dumpDir = FileSystems.getDefault().getPath("dump");
+ if (Files.notExists(dumpDir)) {
+ Files.createDirectory(dumpDir);
+ }
+ Path file = FileSystems.getDefault().getPath("dump", getName() + ".class");
+ Files.write(file, getBytes());
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ @Override @DefinedBy(Api.COMPILER)
+ public String getName() {
+ return className;
+ }
+
+ /**
+ * Will provide the compiler with an output stream that leads to our
+ * byte array.
+ */
+ @Override @DefinedBy(Api.COMPILER)
+ public OutputStream openOutputStream() throws IOException {
+ return bos;
+ }
+
+ @Override @DefinedBy(Api.COMPILER)
+ public InputStream openInputStream() throws IOException {
+ return new ByteArrayInputStream(getBytes());
+ }
+ }
+
+ // For restoring process-local execution support
+ class REPLClassLoader extends ClassLoader {
+
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ OutputMemoryJavaFileObject fo = classObjects.get(name);
+ proc.debug(DBG_FMGR, "findClass %s = %s\n", name, fo);
+ if (fo == null) {
+ throw new ClassNotFoundException("Not ours");
+ }
+ byte[] b = fo.getBytes();
+ return super.defineClass(name, b, 0, b.length, null);
+ }
+ }
+
+ public MemoryFileManager(StandardJavaFileManager standardManager, JShell proc) {
+ this.stdFileManager = standardManager;
+ this.proc = proc;
+ }
+
+ private Collection<OutputMemoryJavaFileObject> generatedClasses() {
+ return classObjects.values();
+ }
+
+ // For debugging dumps
+ public void dumpClasses() {
+ for (OutputMemoryJavaFileObject co : generatedClasses()) {
+ co.dump();
+ }
+ }
+
+ // For restoring process-local execution support
+ public Class<?> findGeneratedClass(String genClassFullName) throws ClassNotFoundException {
+ for (OutputMemoryJavaFileObject co : generatedClasses()) {
+ if (co.className.equals(genClassFullName)) {
+ Class<?> klass = loadClass(co.className);
+ proc.debug(DBG_FMGR, "Loaded %s\n", klass);
+ return klass;
+ }
+ }
+ return null;
+ }
+
+ // For restoring process-local execution support
+ public byte[] findGeneratedBytes(String genClassFullName) throws ClassNotFoundException {
+ for (OutputMemoryJavaFileObject co : generatedClasses()) {
+ if (co.className.equals(genClassFullName)) {
+ return co.getBytes();
+ }
+ }
+ return null;
+ }
+
+ // For restoring process-local execution support
+ public Class<?> loadClass(String name) throws ClassNotFoundException {
+ return getClassLoader(null).loadClass(name);
+ }
+
+ public JavaFileObject createSourceFileObject(Object origin, String name, String code) {
+ return new SourceMemoryJavaFileObject(origin, name, code);
+ }
+
+ // Make compatible with Jigsaw
+ public String inferModuleName(Location location) {
+ try {
+ if (inferModuleNameMethod == null) {
+ inferModuleNameMethod = JavaFileManager.class.getDeclaredMethod("inferModuleName", Location.class);
+ }
+ @SuppressWarnings("unchecked")
+ String result = (String) inferModuleNameMethod.invoke(stdFileManager, location);
+ return result;
+ } catch (NoSuchMethodException | SecurityException ex) {
+ throw new InternalError("Cannot lookup JavaFileManager method", ex);
+ } catch (IllegalAccessException |
+ IllegalArgumentException |
+ InvocationTargetException ex) {
+ throw new InternalError("Cannot invoke JavaFileManager method", ex);
+ }
+ }
+
+ // Make compatible with Jigsaw
+ public Iterable<Set<Location>> listModuleLocations(Location location) throws IOException {
+ try {
+ if (listModuleLocationsMethod == null) {
+ listModuleLocationsMethod = JavaFileManager.class.getDeclaredMethod("listModuleLocations", Location.class);
+ }
+ @SuppressWarnings("unchecked")
+ Iterable<Set<Location>> result = (Iterable<Set<Location>>) listModuleLocationsMethod.invoke(stdFileManager, location);
+ return result;
+ } catch (NoSuchMethodException | SecurityException ex) {
+ throw new InternalError("Cannot lookup JavaFileManager method", ex);
+ } catch (IllegalAccessException |
+ IllegalArgumentException |
+ InvocationTargetException ex) {
+ throw new InternalError("Cannot invoke JavaFileManager method", ex);
+ }
+ }
+
+
+ /**
+ * Returns a class loader for loading plug-ins from the given location. For
+ * example, to load annotation processors, a compiler will request a class
+ * loader for the {@link
+ * StandardLocation#ANNOTATION_PROCESSOR_PATH
+ * ANNOTATION_PROCESSOR_PATH} location.
+ *
+ * @param location a location
+ * @return a class loader for the given location; or {@code null}
+ * if loading plug-ins from the given location is disabled or if
+ * the location is not known
+ * @throws SecurityException if a class loader can not be created
+ * in the current security context
+ * @throws IllegalStateException if {@link #close} has been called
+ * and this file manager cannot be reopened
+ */
+ @Override @DefinedBy(Api.COMPILER)
+ public ClassLoader getClassLoader(JavaFileManager.Location location) {
+ proc.debug(DBG_FMGR, "getClassLoader: location\n", location);
+ return loader;
+ }
+
+ /**
+ * Lists all file objects matching the given criteria in the given
+ * location. List file objects in "subpackages" if recurse is
+ * true.
+ *
+ * <p>Note: even if the given location is unknown to this file
+ * manager, it may not return {@code null}. Also, an unknown
+ * location may not cause an exception.
+ *
+ * @param location a location
+ * @param packageName a package name
+ * @param kinds return objects only of these kinds
+ * @param recurse if true include "subpackages"
+ * @return an Iterable of file objects matching the given criteria
+ * @throws IOException if an I/O error occurred, or if {@link
+ * #close} has been called and this file manager cannot be
+ * reopened
+ * @throws IllegalStateException if {@link #close} has been called
+ * and this file manager cannot be reopened
+ */
+ @Override @DefinedBy(Api.COMPILER)
+ public Iterable<JavaFileObject> list(JavaFileManager.Location location,
+ String packageName,
+ Set<JavaFileObject.Kind> kinds,
+ boolean recurse)
+ throws IOException {
+ Iterable<JavaFileObject> stdList = stdFileManager.list(location, packageName, kinds, recurse);
+ if (location==CLASS_PATH && packageName.equals("REPL")) {
+ // if the desired list is for our JShell package, lazily iterate over
+ // first the standard list then any generated classes.
+ return () -> new Iterator<JavaFileObject>() {
+ boolean stdDone = false;
+ Iterator<? extends JavaFileObject> it;
+
+ @Override
+ public boolean hasNext() {
+ if (it == null) {
+ it = stdList.iterator();
+ }
+ if (it.hasNext()) {
+ return true;
+ }
+ if (stdDone) {
+ return false;
+ } else {
+ stdDone = true;
+ it = generatedClasses().iterator();
+ return it.hasNext();
+ }
+ }
+
+ @Override
+ public JavaFileObject next() {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+ return it.next();
+ }
+
+ };
+ } else {
+ return stdList;
+ }
+ }
+
+ /**
+ * Infers a binary name of a file object based on a location. The
+ * binary name returned might not be a valid binary name according to
+ * <cite>The Java&trade { throw new UnsupportedOperationException("Not supported yet."); } Language Specification</cite>.
+ *
+ * @param location a location
+ * @param file a file object
+ * @return a binary name or {@code null} the file object is not
+ * found in the given location
+ * @throws IllegalStateException if {@link #close} has been called
+ * and this file manager cannot be reopened
+ */
+ @Override @DefinedBy(Api.COMPILER)
+ public String inferBinaryName(JavaFileManager.Location location, JavaFileObject file) {
+ if (file instanceof OutputMemoryJavaFileObject) {
+ OutputMemoryJavaFileObject ofo = (OutputMemoryJavaFileObject) file;
+ proc.debug(DBG_FMGR, "inferBinaryName %s => %s\n", file, ofo.getName());
+ return ofo.getName();
+ } else {
+ return stdFileManager.inferBinaryName(location, file);
+ }
+ }
+
+ /**
+ * Compares two file objects and return true if they represent the
+ * same underlying object.
+ *
+ * @param a a file object
+ * @param b a file object
+ * @return true if the given file objects represent the same
+ * underlying object
+ *
+ * @throws IllegalArgumentException if either of the arguments
+ * were created with another file manager and this file manager
+ * does not support foreign file objects
+ */
+ @Override @DefinedBy(Api.COMPILER)
+ public boolean isSameFile(FileObject a, FileObject b) {
+ return stdFileManager.isSameFile(b, b);
+ }
+
+ /**
+ * Determines if the given option is supported and if so, the
+ * number of arguments the option takes.
+ *
+ * @param option an option
+ * @return the number of arguments the given option takes or -1 if
+ * the option is not supported
+ */
+ @Override @DefinedBy(Api.COMPILER)
+ public int isSupportedOption(String option) {
+ proc.debug(DBG_FMGR, "isSupportedOption: %s\n", option);
+ return stdFileManager.isSupportedOption(option);
+ }
+
+ /**
+ * Handles one option. If {@code current} is an option to this
+ * file manager it will consume any arguments to that option from
+ * {@code remaining} and return true, otherwise return false.
+ *
+ * @param current current option
+ * @param remaining remaining options
+ * @return true if this option was handled by this file manager,
+ * false otherwise
+ * @throws IllegalArgumentException if this option to this file
+ * manager is used incorrectly
+ * @throws IllegalStateException if {@link #close} has been called
+ * and this file manager cannot be reopened
+ */
+ @Override @DefinedBy(Api.COMPILER)
+ public boolean handleOption(String current, Iterator<String> remaining) {
+ proc.debug(DBG_FMGR, "handleOption: current: %s\n", current +
+ ", remaining: " + remaining);
+ return stdFileManager.handleOption(current, remaining);
+ }
+
+ /**
+ * Determines if a location is known to this file manager.
+ *
+ * @param location a location
+ * @return true if the location is known
+ */
+ @Override @DefinedBy(Api.COMPILER)
+ public boolean hasLocation(JavaFileManager.Location location) {
+ proc.debug(DBG_FMGR, "hasLocation: location: %s\n", location);
+ return stdFileManager.hasLocation(location);
+ }
+
+ interface ClassFileCreationListener {
+ void newClassFile(OutputMemoryJavaFileObject jfo, JavaFileManager.Location location,
+ String className, Kind kind, FileObject sibling);
+ }
+
+ void registerClassFileCreationListener(ClassFileCreationListener listen) {
+ this.classListener = listen;
+ }
+
+ /**
+ * Returns a {@linkplain JavaFileObject file object} for input
+ * representing the specified class of the specified kind in the
+ * given location.
+ *
+ * @param location a location
+ * @param className the name of a class
+ * @param kind the kind of file, must be one of {@link
+ * JavaFileObject.Kind#SOURCE SOURCE} or {@link
+ * JavaFileObject.Kind#CLASS CLASS}
+ * @return a file object, might return {@code null} if the
+ * file does not exist
+ * @throws IllegalArgumentException if the location is not known
+ * to this file manager and the file manager does not support
+ * unknown locations, or if the kind is not valid
+ * @throws IOException if an I/O error occurred, or if {@link
+ * #close} has been called and this file manager cannot be
+ * reopened
+ * @throws IllegalStateException if {@link #close} has been called
+ * and this file manager cannot be reopened
+ */
+ @Override @DefinedBy(Api.COMPILER)
+ public JavaFileObject getJavaFileForInput(JavaFileManager.Location location,
+ String className,
+ JavaFileObject.Kind kind)
+ throws IOException {
+ return stdFileManager.getJavaFileForInput(location, className, kind);
+ }
+
+ /**
+ * Returns a {@linkplain JavaFileObject file object} for output
+ * representing the specified class of the specified kind in the
+ * given location.
+ *
+ * <p>Optionally, this file manager might consider the sibling as
+ * a hint for where to place the output. The exact semantics of
+ * this hint is unspecified. The JDK compiler, javac, for
+ * example, will place class files in the same directories as
+ * originating source files unless a class file output directory
+ * is provided. To facilitate this behavior, javac might provide
+ * the originating source file as sibling when calling this
+ * method.
+ *
+ * @param location a location
+ * @param className the name of a class
+ * @param kind the kind of file, must be one of {@link
+ * JavaFileObject.Kind#SOURCE SOURCE} or {@link
+ * JavaFileObject.Kind#CLASS CLASS}
+ * @param sibling a file object to be used as hint for placement;
+ * might be {@code null}
+ * @return a file object for output
+ * @throws IllegalArgumentException if sibling is not known to
+ * this file manager, or if the location is not known to this file
+ * manager and the file manager does not support unknown
+ * locations, or if the kind is not valid
+ * @throws IOException if an I/O error occurred, or if {@link
+ * #close} has been called and this file manager cannot be
+ * reopened
+ * @throws IllegalStateException {@link #close} has been called
+ * and this file manager cannot be reopened
+ */
+ @Override @DefinedBy(Api.COMPILER)
+ public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location,
+ String className, Kind kind, FileObject sibling) throws IOException {
+
+ OutputMemoryJavaFileObject fo;
+ fo = new OutputMemoryJavaFileObject(className, kind);
+ classObjects.put(className, fo);
+ proc.debug(DBG_FMGR, "Set out file: %s = %s\n", className, fo);
+ if (classListener != null) {
+ classListener.newClassFile(fo, location, className, kind, sibling);
+ }
+ return fo;
+ }
+
+ /**
+ * Returns a {@linkplain FileObject file object} for input
+ * representing the specified <a href="JavaFileManager.html#relative_name">relative
+ * name</a> in the specified package in the given location.
+ *
+ * <p>If the returned object represents a {@linkplain
+ * JavaFileObject.Kind#SOURCE source} or {@linkplain
+ * JavaFileObject.Kind#CLASS class} file, it must be an instance
+ * of {@link JavaFileObject}.
+ *
+ * <p>Informally, the file object returned by this method is
+ * located in the concatenation of the location, package name, and
+ * relative name. For example, to locate the properties file
+ * "resources/compiler.properties" in the package
+ * "com.sun.tools.javac" in the {@linkplain
+ * StandardLocation#SOURCE_PATH SOURCE_PATH} location, this method
+ * might be called like so:
+ *
+ * <pre>getFileForInput(SOURCE_PATH, "com.sun.tools.javac", "resources/compiler.properties");</pre>
+ *
+ * <p>If the call was executed on Windows, with SOURCE_PATH set to
+ * <code>"C:\Documents and Settings\UncleBob\src\share\classes"</code>,
+ * a valid result would be a file object representing the file
+ * <code>"C:\Documents and Settings\UncleBob\src\share\classes\com\sun\tools\javac\resources\compiler.properties"</code>.
+ *
+ * @param location a location
+ * @param packageName a package name
+ * @param relativeName a relative name
+ * @return a file object, might return {@code null} if the file
+ * does not exist
+ * @throws IllegalArgumentException if the location is not known
+ * to this file manager and the file manager does not support
+ * unknown locations, or if {@code relativeName} is not valid
+ * @throws IOException if an I/O error occurred, or if {@link
+ * #close} has been called and this file manager cannot be
+ * reopened
+ * @throws IllegalStateException if {@link #close} has been called
+ * and this file manager cannot be reopened
+ */
+ @Override @DefinedBy(Api.COMPILER)
+ public FileObject getFileForInput(JavaFileManager.Location location,
+ String packageName,
+ String relativeName)
+ throws IOException {
+ proc.debug(DBG_FMGR, "getFileForInput location=%s packageName=%s\n", location, packageName);
+ return stdFileManager.getFileForInput(location, packageName, relativeName);
+ }
+
+ /**
+ * Returns a {@linkplain FileObject file object} for output
+ * representing the specified <a href="JavaFileManager.html#relative_name">relative
+ * name</a> in the specified package in the given location.
+ *
+ * <p>Optionally, this file manager might consider the sibling as
+ * a hint for where to place the output. The exact semantics of
+ * this hint is unspecified. The JDK compiler, javac, for
+ * example, will place class files in the same directories as
+ * originating source files unless a class file output directory
+ * is provided. To facilitate this behavior, javac might provide
+ * the originating source file as sibling when calling this
+ * method.
+ *
+ * <p>If the returned object represents a {@linkplain
+ * JavaFileObject.Kind#SOURCE source} or {@linkplain
+ * JavaFileObject.Kind#CLASS class} file, it must be an instance
+ * of {@link JavaFileObject}.
+ *
+ * <p>Informally, the file object returned by this method is
+ * located in the concatenation of the location, package name, and
+ * relative name or next to the sibling argument. See {@link
+ * #getFileForInput getFileForInput} for an example.
+ *
+ * @param location a location
+ * @param packageName a package name
+ * @param relativeName a relative name
+ * @param sibling a file object to be used as hint for placement;
+ * might be {@code null}
+ * @return a file object
+ * @throws IllegalArgumentException if sibling is not known to
+ * this file manager, or if the location is not known to this file
+ * manager and the file manager does not support unknown
+ * locations, or if {@code relativeName} is not valid
+ * @throws IOException if an I/O error occurred, or if {@link
+ * #close} has been called and this file manager cannot be
+ * reopened
+ * @throws IllegalStateException if {@link #close} has been called
+ * and this file manager cannot be reopened
+ */
+ @Override @DefinedBy(Api.COMPILER)
+ public FileObject getFileForOutput(JavaFileManager.Location location,
+ String packageName,
+ String relativeName,
+ FileObject sibling)
+ throws IOException {
+ throw new UnsupportedOperationException("getFileForOutput: location: " + location +
+ ", packageName: " + packageName +
+ ", relativeName: " + relativeName +
+ ", sibling: " + sibling);
+ }
+
+ /**
+ * Flushes any resources opened for output by this file manager
+ * directly or indirectly. Flushing a closed file manager has no
+ * effect.
+ *
+ * @throws IOException if an I/O error occurred
+ * @see #close
+ */
+ @Override @DefinedBy(Api.COMPILER)
+ public void flush() throws IOException {
+ // Nothing to flush
+ }
+
+ /**
+ * Releases any resources opened by this file manager directly or
+ * indirectly. This might render this file manager useless and
+ * the effect of subsequent calls to methods on this object or any
+ * objects obtained through this object is undefined unless
+ * explicitly allowed. However, closing a file manager which has
+ * already been closed has no effect.
+ *
+ * @throws IOException if an I/O error occurred
+ * @see #flush
+ */
+ @Override @DefinedBy(Api.COMPILER)
+ public void close() throws IOException {
+ // Nothing to close
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/MethodSnippet.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,94 @@
+/*
+ * 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.MethodKey;
+
+/**
+ * Snippet for a method definition.
+ * The Kind is {@link jdk.jshell.Snippet.Kind#METHOD}.
+ * <p>
+ * <code>MethodSnippet</code> is immutable: an access to
+ * any of its methods will always return the same result.
+ * and thus is thread-safe.
+ * @jls 8.4: MethodDeclaration.
+ */
+public class MethodSnippet extends DeclarationSnippet {
+
+ final String signature;
+ private String qualifiedParamaterTypes;
+
+ MethodSnippet(MethodKey key, String userSource, Wrap guts,
+ String name, String signature, Wrap corralled,
+ Collection<String> declareReferences, Collection<String> bodyReferences) {
+ super(key, userSource, guts, name, SubKind.METHOD_SUBKIND, corralled, declareReferences, bodyReferences);
+ this.signature = signature;
+ }
+
+ /**
+ * A String representation of the parameter types of the method.
+ * @return a comma separated list of user entered parameter types for the
+ * method.
+ */
+ public String parameterTypes() {
+ return key().parameterTypes();
+ }
+
+ /**
+ * The full type signature of the method, including return type.
+ * @return A String representation of the parameter and return types
+ */
+ public String signature() {
+ return signature;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("MethodSnippet:");
+ sb.append(name());
+ sb.append('/');
+ sb.append(signature());
+ sb.append('-');
+ sb.append(source());
+ return sb.toString();
+ }
+
+ /**** internal access ****/
+
+ @Override
+ MethodKey key() {
+ return (MethodKey) super.key();
+ }
+
+ String qualifiedParameterTypes() {
+ return qualifiedParamaterTypes;
+ }
+
+ void setQualifiedParamaterTypes(String sig) {
+ qualifiedParamaterTypes = sig;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/OuterWrap.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,189 @@
+/*
+ * 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.Wrap.CompoundWrap;
+import static jdk.jshell.Util.*;
+import java.util.Locale;
+import javax.tools.Diagnostic;
+import javax.tools.JavaFileObject;
+import jdk.jshell.MemoryFileManager.SourceMemoryJavaFileObject;
+
+/**
+ *
+ * @author Robert Field
+ */
+final class OuterWrap implements GeneralWrap {
+
+ private final String packageName;
+ private final String className;
+ private final String userSource;
+ private final GeneralWrap w;
+ private final Wrap guts;
+
+ public static OuterWrap wrapInClass(String packageName, String className,
+ String imports, String userSource, Wrap guts) {
+ GeneralWrap kw = new CompoundWrap(
+ imports
+ + "class " + className + " {\n",
+ guts,
+ "}\n");
+ return new OuterWrap(packageName, className, userSource, kw, guts);
+ }
+
+ public static OuterWrap wrapImport(String userSource, Wrap guts) {
+ return new OuterWrap("", "", userSource, guts, guts);
+ }
+
+ private OuterWrap(String packageName, String className, String userSource,
+ GeneralWrap w, Wrap guts) {
+ this.packageName = packageName;
+ this.className = className;
+ this.userSource = userSource;
+ this.w = w;
+ this.guts = guts;
+ }
+
+ @Override
+ public final String wrapped() {
+ return w.wrapped();
+ }
+
+ @Override
+ public int snippetIndexToWrapIndex(int ui) {
+ return w.snippetIndexToWrapIndex(ui);
+ }
+
+ @Override
+ public int wrapIndexToSnippetIndex(int si) {
+ return w.wrapIndexToSnippetIndex(si);
+ }
+
+ @Override
+ public int firstSnippetIndex() {
+ return w.firstSnippetIndex();
+ }
+
+ @Override
+ public int lastSnippetIndex() {
+ return w.lastSnippetIndex();
+ }
+
+ @Override
+ public int snippetLineToWrapLine(int snline) {
+ return w.snippetLineToWrapLine(snline);
+ }
+
+ @Override
+ public int wrapLineToSnippetLine(int wline) {
+ return w.wrapLineToSnippetLine(wline);
+ }
+
+ @Override
+ public int firstSnippetLine() {
+ return w.firstSnippetLine();
+ }
+
+ @Override
+ public int lastSnippetLine() {
+ return w.lastSnippetLine();
+ }
+
+ public String className() {
+ return className;
+ }
+
+ public String classFullName() {
+ return packageName + "." + className;
+ }
+
+ public String getUserSource() {
+ return userSource;
+ }
+
+ Wrap guts() {
+ return guts;
+ }
+
+ Diag wrapDiag(Diagnostic<? extends JavaFileObject> d) {
+ return new WrappedDiagnostic(d);
+ }
+
+ class WrappedDiagnostic extends Diag {
+
+ private final Diagnostic<? extends JavaFileObject> diag;
+
+ WrappedDiagnostic(Diagnostic<? extends JavaFileObject> diag) {
+ this.diag = diag;
+ }
+
+ @Override
+ public boolean isError() {
+ return diag.getKind() == Diagnostic.Kind.ERROR;
+ }
+
+ @Override
+ public long getPosition() {
+ return wrapIndexToSnippetIndex(diag.getPosition());
+ }
+
+ @Override
+ public long getStartPosition() {
+ return wrapIndexToSnippetIndex(diag.getStartPosition());
+ }
+
+ @Override
+ public long getEndPosition() {
+ return wrapIndexToSnippetIndex(diag.getEndPosition());
+ }
+
+ @Override
+ public String getCode() {
+ return diag.getCode();
+ }
+
+ @Override
+ public String getMessage(Locale locale) {
+ return expunge(diag.getMessage(locale));
+ }
+
+ @Override
+ Unit unitOrNull() {
+ JavaFileObject fo = diag.getSource();
+ if (fo instanceof SourceMemoryJavaFileObject) {
+ SourceMemoryJavaFileObject sfo = (SourceMemoryJavaFileObject) fo;
+ if (sfo.getOrigin() instanceof Unit) {
+ return (Unit) sfo.getOrigin();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return "WrappedDiagnostic(" + getMessage(null) + ":" + getPosition() + ")";
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/PersistentSnippet.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+/**
+ * Grouping for Snippets which persist and influence future code.
+ * A persistent snippet can be
+ * {@linkplain jdk.jshell.Snippet.Status#OVERWRITTEN overwritten)}
+ * with new input and can be dropped {@link JShell#drop}.
+ * <p>
+ * <code>PersistentSnippet</code> is immutable: an access to
+ * any of its methods will always return the same result.
+ * and thus is thread-safe.
+ */
+public abstract class PersistentSnippet extends Snippet {
+
+ PersistentSnippet(Key key, String userSource, Wrap guts, String unitName, SubKind subkind) {
+ super(key, userSource, guts, unitName, subkind);
+ }
+
+ /**
+ * Name of the Snippet.
+ *
+ * @return the name of the snippet.
+ */
+ @Override
+ public String name() {
+ return unitName;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/ReplParser.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,272 @@
+/*
+ * 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 com.sun.tools.javac.code.TypeTag;
+import com.sun.tools.javac.parser.JavacParser;
+import com.sun.tools.javac.parser.ParserFactory;
+import com.sun.tools.javac.parser.Tokens.Comment;
+import com.sun.tools.javac.parser.Tokens.Comment.CommentStyle;
+import com.sun.tools.javac.parser.Tokens.Token;
+import static com.sun.tools.javac.parser.Tokens.TokenKind.CLASS;
+import static com.sun.tools.javac.parser.Tokens.TokenKind.COLON;
+import static com.sun.tools.javac.parser.Tokens.TokenKind.ENUM;
+import static com.sun.tools.javac.parser.Tokens.TokenKind.EOF;
+import static com.sun.tools.javac.parser.Tokens.TokenKind.IMPORT;
+import static com.sun.tools.javac.parser.Tokens.TokenKind.INTERFACE;
+import static com.sun.tools.javac.parser.Tokens.TokenKind.LPAREN;
+import static com.sun.tools.javac.parser.Tokens.TokenKind.MONKEYS_AT;
+import static com.sun.tools.javac.parser.Tokens.TokenKind.PACKAGE;
+import static com.sun.tools.javac.parser.Tokens.TokenKind.SEMI;
+import static com.sun.tools.javac.parser.Tokens.TokenKind.VOID;
+import com.sun.tools.javac.tree.JCTree;
+import com.sun.tools.javac.tree.JCTree.JCAnnotation;
+import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
+import com.sun.tools.javac.tree.JCTree.JCExpression;
+import com.sun.tools.javac.tree.JCTree.JCExpressionStatement;
+import com.sun.tools.javac.tree.JCTree.JCModifiers;
+import com.sun.tools.javac.tree.JCTree.JCPackageDecl;
+import com.sun.tools.javac.tree.JCTree.JCStatement;
+import com.sun.tools.javac.tree.JCTree.JCTypeParameter;
+import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
+import com.sun.tools.javac.tree.JCTree.Tag;
+import static com.sun.tools.javac.tree.JCTree.Tag.IDENT;
+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.Position;
+
+/**
+ * This is a subclass of JavacParser which overrides one method with a modified
+ * verson of that method designed to allow parsing of one "snippet" of Java
+ * code without the surrounding context of class, method, etc.
+ * Accepts an expression, a statement, an import, or the declaration of a
+ * method, variable, or type (class, interface, ...).
+ */
+class ReplParser extends JavacParser {
+
+ public ReplParser(ParserFactory fac,
+ com.sun.tools.javac.parser.Lexer S,
+ boolean keepDocComments,
+ boolean keepLineMap,
+ boolean keepEndPositions) {
+ super(fac, S, keepDocComments, keepLineMap, keepEndPositions);
+ }
+
+ /**
+ * As faithful a clone of the overridden method as possible while still
+ * achieving the goal of allowing the parse of a stand-alone snippet.
+ * As a result, some variables are assigned and never used, tests are
+ * always true, loops don't, etc. This is to allow easy transition as the
+ * underlying method changes.
+ * @return a snippet wrapped in a compilation unit
+ */
+ @Override
+ public JCCompilationUnit parseCompilationUnit() {
+ Token firstToken = token;
+ JCModifiers mods = null;
+ boolean seenImport = false;
+ boolean seenPackage = false;
+ ListBuffer<JCTree> defs = new ListBuffer<>();
+ if (token.kind == MONKEYS_AT) {
+ mods = modifiersOpt();
+ }
+
+ if (token.kind == PACKAGE) {
+ int packagePos = token.pos;
+ List<JCAnnotation> annotations = List.nil();
+ seenPackage = true;
+ if (mods != null) {
+ checkNoMods(mods.flags);
+ annotations = mods.annotations;
+ mods = null;
+ }
+ nextToken();
+ JCExpression pid = qualident(false);
+ accept(SEMI);
+ JCPackageDecl pd = F.at(packagePos).PackageDecl(annotations, pid);
+ attach(pd, firstToken.comment(CommentStyle.JAVADOC));
+ storeEnd(pd, token.pos);
+ defs.append(pd);
+ }
+
+ boolean firstTypeDecl = true;
+ while (token.kind != EOF) {
+ if (token.pos > 0 && token.pos <= endPosTable.errorEndPos) {
+ // error recovery
+ skip(true, false, false, false);
+ if (token.kind == EOF) {
+ break;
+ }
+ }
+ if (mods == null && token.kind == IMPORT) {
+ seenImport = true;
+ defs.append(importDeclaration());
+ } else {
+ Comment docComment = token.comment(CommentStyle.JAVADOC);
+ if (firstTypeDecl && !seenImport && !seenPackage) {
+ docComment = firstToken.comment(CommentStyle.JAVADOC);
+ }
+ List<? extends JCTree> udefs = replUnit(mods, docComment);
+ // if (def instanceof JCExpressionStatement)
+ // def = ((JCExpressionStatement)def).expr;
+ for (JCTree def : udefs) {
+ defs.append(def);
+ }
+ mods = null;
+ firstTypeDecl = false;
+ }
+ break; // Remove to process more than one snippet
+ }
+ List<JCTree> rdefs = defs.toList();
+ class ReplUnit extends JCCompilationUnit {
+
+ public ReplUnit(List<JCTree> defs) {
+ super(defs);
+ }
+ }
+ JCCompilationUnit toplevel = new ReplUnit(rdefs);
+ if (rdefs.isEmpty()) {
+ storeEnd(toplevel, S.prevToken().endPos);
+ }
+ toplevel.lineMap = S.getLineMap();
+ this.endPosTable.setParser(null); // remove reference to parser
+ toplevel.endPositions = this.endPosTable;
+ return toplevel;
+ }
+
+ @SuppressWarnings("fallthrough")
+ List<? extends JCTree> replUnit(JCModifiers pmods, Comment dc) {
+ switch (token.kind) {
+ case EOF:
+ return List.nil();
+ case RBRACE:
+ case CASE:
+ case DEFAULT:
+ // These are illegal, fall through to handle as illegal statement
+ case LBRACE:
+ case IF:
+ case FOR:
+ case WHILE:
+ case DO:
+ case TRY:
+ case SWITCH:
+ case RETURN:
+ case THROW:
+ case BREAK:
+ case CONTINUE:
+ case SEMI:
+ case ELSE:
+ case FINALLY:
+ case CATCH:
+ case ASSERT:
+ return List.<JCTree>of(parseStatement());
+ case SYNCHRONIZED:
+ if (peekToken(LPAREN)) {
+ return List.<JCTree>of(parseStatement());
+ }
+ //fall-through
+ default:
+ JCModifiers mods = modifiersOpt(pmods);
+ if (token.kind == CLASS
+ || token.kind == INTERFACE
+ || token.kind == ENUM) {
+ return List.<JCTree>of(classOrInterfaceOrEnumDeclaration(mods, dc));
+ } else {
+ int pos = token.pos;
+ List<JCTypeParameter> typarams = typeParametersOpt();
+ // if there are type parameters but no modifiers, save the start
+ // position of the method in the modifiers.
+ if (typarams.nonEmpty() && mods.pos == Position.NOPOS) {
+ mods.pos = pos;
+ storeEnd(mods, pos);
+ }
+ List<JCAnnotation> annosAfterParams = annotationsOpt(Tag.ANNOTATION);
+
+ if (annosAfterParams.nonEmpty()) {
+ checkAnnotationsAfterTypeParams(annosAfterParams.head.pos);
+ mods.annotations = mods.annotations.appendList(annosAfterParams);
+ if (mods.pos == Position.NOPOS) {
+ mods.pos = mods.annotations.head.pos;
+ }
+ }
+
+ Token prevToken = token;
+ pos = token.pos;
+ JCExpression t;
+ boolean isVoid = token.kind == VOID;
+ if (isVoid) {
+ t = to(F.at(pos).TypeIdent(TypeTag.VOID));
+ nextToken();
+ } else {
+ // return type of method, declared type of variable, or an expression
+ t = term(EXPR | TYPE);
+ }
+ if (token.kind == COLON && t.hasTag(IDENT)) {
+ // labelled statement
+ nextToken();
+ JCStatement stat = parseStatement();
+ return List.<JCTree>of(F.at(pos).Labelled(prevToken.name(), stat));
+ } else if ((isVoid || (lastmode & TYPE) != 0) && LAX_IDENTIFIER.accepts(token.kind)) {
+ // we have "Type Ident", so we can assume it is variable or method declaration
+ pos = token.pos;
+ Name name = ident();
+ if (token.kind == LPAREN) {
+ // method declaration
+ //mods.flags |= Flags.STATIC;
+ return List.of(methodDeclaratorRest(
+ pos, mods, t, name, typarams,
+ false, isVoid, dc));
+ } else if (!isVoid && typarams.isEmpty()) {
+ // variable declaration
+ //mods.flags |= Flags.STATIC;
+ List<JCTree> defs
+ = variableDeclaratorsRest(pos, mods, t, name, false, dc,
+ new ListBuffer<JCTree>()).toList();
+ accept(SEMI);
+ storeEnd(defs.last(), S.prevToken().endPos);
+ return defs;
+ } else {
+ // malformed declaration, return error
+ pos = token.pos;
+ List<JCTree> err = isVoid
+ ? List.<JCTree>of(toP(F.at(pos).MethodDef(mods, name, t, typarams,
+ List.<JCVariableDecl>nil(), List.<JCExpression>nil(), null, null)))
+ : null;
+ return List.<JCTree>of(syntaxError(token.pos, err, "expected", LPAREN));
+ }
+ } else if (!typarams.isEmpty()) {
+ // type parameters on non-variable non-method -- error
+ return List.<JCTree>of(syntaxError(token.pos, "illegal.start.of.type"));
+ } else {
+ // expression-statement or expression to evaluate
+ JCExpressionStatement expr = toP(F.at(pos).Exec(t));
+ return List.<JCTree>of(expr);
+ }
+
+ }
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/ReplParserFactory.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,58 @@
+/*
+ * 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 com.sun.tools.javac.parser.JavacParser;
+import com.sun.tools.javac.parser.ParserFactory;
+import com.sun.tools.javac.parser.ScannerFactory;
+import com.sun.tools.javac.util.Context;
+
+/**
+ *
+ * @author Robert Field
+ */
+class ReplParserFactory extends ParserFactory {
+
+ public static ParserFactory instance(Context context) {
+ ParserFactory instance = context.get(parserFactoryKey);
+ if (instance == null) {
+ instance = new ReplParserFactory(context);
+ }
+ return instance;
+ }
+
+ private final ScannerFactory scannerFactory;
+
+ protected ReplParserFactory(Context context) {
+ super(context);
+ this.scannerFactory = ScannerFactory.instance(context);
+ }
+
+ @Override
+ public JavacParser newParser(CharSequence input, boolean keepDocComments, boolean keepEndPos, boolean keepLineMap) {
+ com.sun.tools.javac.parser.Lexer lexer = scannerFactory.newScanner(input, keepDocComments);
+ return new ReplParser(this, lexer, keepDocComments, keepLineMap, keepEndPos);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/ReplResolve.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,45 @@
+/*
+ * 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.comp.AttrContext;
+import com.sun.tools.javac.comp.Env;
+import com.sun.tools.javac.comp.Resolve;
+import com.sun.tools.javac.util.Context;
+
+/**
+ * Just need access to isStatic
+ */
+class ReplResolve extends Resolve {
+
+ ReplResolve(Context context) {
+ super(context);
+ }
+
+ public static boolean isStatic(Env<AttrContext> env) {
+ return Resolve.isStatic(env);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/Snippet.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,681 @@
+/*
+ * 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 java.util.Collections;
+import java.util.List;
+import static jdk.jshell.Util.REPL_CLASS_PREFIX;
+import static jdk.jshell.Util.asLetters;
+
+/**
+ * A Snippet represents a snippet of Java source code as passed to
+ * {@link jdk.jshell.JShell#eval}. It is associated only with the
+ * {@link jdk.jshell.JShell JShell} instance that created it.
+ * An instance of Snippet (including its subclasses) is immutable: an access to
+ * any of its methods will always return the same result.
+ * For information about the current state of the snippet within the JShell
+ * state engine, query <code>JShell</code> passing the Snippet.
+ * <p>
+ * Because it is immutable, <code>Snippet</code> (and subclasses) is thread-safe.
+ * @author Robert Field
+ * @see jdk.jshell.JShell#status
+ */
+public abstract class Snippet {
+
+ /**
+ * Describes the general kind of snippet.
+ * The <code>Kind</code> is an immutable property of a Snippet.
+ * It is accessed with {@link jdk.jshell.Snippet#kind()}.
+ * The <code>Kind</code> can be used to determine which
+ * subclass of Snippet it is. For example,
+ * {@link jdk.jshell.JShell#eval eval("int three() { return 3; }")} will
+ * return a snippet creation event. The <code>Kind</code> of that Snippet
+ * will be <code>METHOD</code>, from which you know that the subclass
+ * of <code>Snippet</code> is <code>MethodSnippet</code> and it can be
+ * cast as such.
+ */
+ public enum Kind {
+ /**
+ * An import declaration: <code>import</code> ...
+ * The snippet is an instance of {@link jdk.jshell.ImportSnippet}.
+ * An import can be a single type import
+ * ({@link jdk.jshell.Snippet.SubKind#SINGLE_TYPE_IMPORT_SUBKIND}),
+ * a static single import
+ * ({@link jdk.jshell.Snippet.SubKind#SINGLE_STATIC_IMPORT_SUBKIND}),
+ * an on-demand type import
+ * ({@link jdk.jshell.Snippet.SubKind#TYPE_IMPORT_ON_DEMAND_SUBKIND}),
+ * or a static on-demand type import
+ * ({@link jdk.jshell.Snippet.SubKind#SINGLE_STATIC_IMPORT_SUBKIND}) --
+ * use {@link jdk.jshell.Snippet#subKind()} to distinguish.
+ * @jls 8.3: importDeclaration.
+ */
+ IMPORT(true),
+
+ /**
+ * A type declaration.
+ * Which includes: NormalClassDeclaration, EnumDeclaration,
+ * NormalInterfaceDeclaration, and AnnotationTypeDeclaration.
+ * The snippet is an instance of {@link jdk.jshell.TypeDeclSnippet}.
+ * A type declaration may be an interface
+ * {@link jdk.jshell.Snippet.SubKind#INTERFACE_SUBKIND},
+ * classes {@link jdk.jshell.Snippet.SubKind#CLASS_SUBKIND}, enums, and
+ * annotation interfaces -- see {@link jdk.jshell.Snippet.SubKind} to
+ * differentiate.
+ * @jls 7.6: TypeDeclaration.
+ */
+ TYPE_DECL(true),
+
+ /**
+ * A method declaration.
+ * The snippet is an instance of {@link jdk.jshell.MethodSnippet}.
+ * @jls 8.4: MethodDeclaration.
+ */
+ METHOD(true),
+
+ /**
+ * One variable declaration.
+ * Corresponding to one <i>VariableDeclarator</i>.
+ * The snippet is an instance of {@link jdk.jshell.VarSnippet}.
+ * The variable may be with or without initializer, or be a temporary
+ * variable representing an expression -- see
+ * {@link jdk.jshell.Snippet.SubKind}to differentiate.
+ * @jls 8.3: FieldDeclaration.
+ */
+ VAR(true),
+
+ /**
+ * An expression, with or without side-effects.
+ * The snippet is an instance of {@link jdk.jshell.ExpressionSnippet}.
+ * The expression is currently either a simple named reference to a
+ * variable ({@link jdk.jshell.Snippet.SubKind#VAR_VALUE_SUBKIND}) or an
+ * assignment (both of which have natural referencing
+ * names) -- see {@link jdk.jshell.Snippet.SubKind} to differentiate.
+ * @jls 15: Expression.
+ */
+ EXPRESSION(false),
+
+ /**
+ * A statement.
+ * The snippet is an instance of {@link jdk.jshell.StatementSnippet}.
+ * @jls 14.5: Statement.
+ */
+ STATEMENT(false),
+
+ /**
+ * A syntactically incorrect input for which the specific
+ * kind could not be determined.
+ * The snippet is an instance of {@link jdk.jshell.ErroneousSnippet}.
+ */
+ ERRONEOUS(false);
+
+ /**
+ * True if this kind of snippet adds a declaration or declarations
+ * which are visible to subsequent evaluations.
+ */
+ public final boolean isPersistent;
+
+ Kind(boolean isPersistent) {
+ this.isPersistent = isPersistent;
+ }
+ }
+
+ /**
+ * The detailed variety of a snippet. This is a sub-classification of the
+ * Kind. The Kind of a SubKind is accessible with
+ * {@link jdk.jshell.Snippet.SubKind#kind}.
+ */
+ public enum SubKind {
+
+ /**
+ * Single-Type-Import Declaration.
+ * An import declaration of a single type.
+ * @jls 7.5.1 SingleTypeImportDeclaration.
+ */
+ SINGLE_TYPE_IMPORT_SUBKIND(Kind.IMPORT),
+
+ /**
+ * Type-Import-on-Demand Declaration.
+ * A non-static "star" import.
+ * @jls 7.5.2. TypeImportOnDemandDeclaration.
+ */
+ TYPE_IMPORT_ON_DEMAND_SUBKIND(Kind.IMPORT),
+
+ /**
+ * Single-Static-Import Declaration.
+ * An import of a static member.
+ * @jls 7.5.3 Single-Static-Import.
+ */
+ SINGLE_STATIC_IMPORT_SUBKIND(Kind.IMPORT),
+
+ /**
+ * Static-Import-on-Demand Declaration.
+ * A static "star" import of all static members of a named type.
+ * @jls 7.5.4. Static-Import-on-Demand Static "star" import.
+ */
+ STATIC_IMPORT_ON_DEMAND_SUBKIND(Kind.IMPORT),
+
+ /**
+ * A class declaration.
+ * A <code>SubKind</code> of {@link Kind#TYPE_DECL}.
+ * @jls 8.1. NormalClassDeclaration.
+ */
+ CLASS_SUBKIND(Kind.TYPE_DECL),
+
+ /**
+ * An interface declaration.
+ * A <code>SubKind</code> of {@link Kind#TYPE_DECL}.
+ * @jls 9.1. NormalInterfaceDeclaration.
+ */
+ INTERFACE_SUBKIND(Kind.TYPE_DECL),
+
+ /**
+ * An enum declaration.
+ * A <code>SubKind</code> of {@link Kind#TYPE_DECL}.
+ * @jls 8.9. EnumDeclaration.
+ */
+ ENUM_SUBKIND(Kind.TYPE_DECL),
+
+ /**
+ * An annotation interface declaration. A <code>SubKind</code> of
+ * {@link Kind#TYPE_DECL}.
+ * @jls 9.6. AnnotationTypeDeclaration.
+ */
+ ANNOTATION_TYPE_SUBKIND(Kind.TYPE_DECL),
+
+ /**
+ * A method. The only <code>SubKind</code> for {@link Kind#METHOD}.
+ * @jls 8.4. MethodDeclaration.
+ */
+ METHOD_SUBKIND(Kind.METHOD),
+
+ /**
+ * A variable declaration without initializer.
+ * A <code>SubKind</code> of {@link Kind#VAR}.
+ * @jls 8.3. VariableDeclarator without VariableInitializer in
+ * FieldDeclaration.
+ */
+ VAR_DECLARATION_SUBKIND(Kind.VAR),
+
+ /**
+ * A variable declaration with an initializer expression. A
+ * <code>SubKind</code> of {@link Kind#VAR}.
+ * @jls 8.3. VariableDeclarator with VariableInitializer in
+ * FieldDeclaration.
+ */
+ VAR_DECLARATION_WITH_INITIALIZER_SUBKIND(Kind.VAR, true, true),
+
+ /**
+ * An expression whose value has been stored in a temporary variable. A
+ * <code>SubKind</code> of {@link Kind#VAR}.
+ * @jls 15. Primary.
+ */
+ TEMP_VAR_EXPRESSION_SUBKIND(Kind.VAR, true, true),
+
+ /**
+ * A simple variable reference expression. A <code>SubKind</code> of
+ * {@link Kind#EXPRESSION}.
+ * @jls 15.11. Field Access as 3.8. Identifier.
+ */
+ VAR_VALUE_SUBKIND(Kind.EXPRESSION, true, true),
+
+ /**
+ * An assignment expression. A <code>SubKind</code> of
+ * {@link Kind#EXPRESSION}.
+ * @jls 15.26. Assignment.
+ */
+ ASSIGNMENT_SUBKIND(Kind.EXPRESSION, true, true),
+
+ /**
+ * An expression which has not been wrapped in a temporary variable
+ * (reserved). A <code>SubKind</code> of {@link Kind#EXPRESSION}.
+ */
+ OTHER_EXPRESSION_SUBKIND(Kind.EXPRESSION, true, true),
+
+ /**
+ * A statement. The only <code>SubKind</code> for {@link Kind#STATEMENT}.
+ * @jls 14.5. Statement.
+ */
+ STATEMENT_SUBKIND(Kind.STATEMENT, true, false),
+
+ /**
+ * An unknown snippet. The only <code>SubKind</code> for
+ * {@link Kind#ERRONEOUS}.
+ */
+ UNKNOWN_SUBKIND(Kind.ERRONEOUS, false, false);
+
+ private final boolean isExecutable;
+ private final boolean hasValue;
+ private final Kind kind;
+
+ SubKind(Kind kind) {
+ this.kind = kind;
+ this.isExecutable = false;
+ this.hasValue = false;
+ }
+
+ SubKind(Kind kind, boolean isExecutable, boolean hasValue) {
+ this.kind = kind;
+ this.isExecutable = isExecutable;
+ this.hasValue = hasValue;
+ }
+
+ /**
+ * Is this <code>SubKind</code> executable?
+ *
+ * @return true if this <code>SubKind</code> can be executed.
+ */
+ public boolean isExecutable() {
+ return isExecutable;
+ }
+
+ /**
+ * Is this <code>SubKind</code> executable and is non-<code>void</code>.
+ *
+ * @return true if this <code>SubKind</code> has a value.
+ */
+ public boolean hasValue() {
+ return hasValue;
+ }
+
+ /**
+ * The {@link Snippet.Kind} that corresponds to this <code>SubKind</code>.
+ *
+ * @return the fixed <code>Kind</code> for this <code>SubKind</code>
+ */
+ public Kind kind() {
+ return kind;
+ }
+ }
+
+ /**
+ * Describes the current state of a Snippet.
+ * This is a dynamic property of a Snippet within the JShell state --
+ * thus is retrieved with a {@linkplain
+ * jdk.jshell.JShell#status(jdk.jshell.Snippet) query on <code>JShell</code>}.
+ * <p>
+ * The <code>Status</code> changes as the state changes.
+ * For example, creation of another snippet with
+ * {@link jdk.jshell.JShell#eval(java.lang.String) eval}
+ * may resolve dependencies of this Snippet (or invalidate those dependencies), or
+ * {@linkplain jdk.jshell.Snippet.Status#OVERWRITTEN overwrite}
+ * this Snippet changing its
+ * <code>Status</code>.
+ * <p>
+ * Important properties associated with <code>Status</code> are:
+ * {@link jdk.jshell.Snippet.Status#isDefined}, if it is visible to other
+ * existing and new snippets; and
+ * {@link jdk.jshell.Snippet.Status#isActive}, if, as the
+ * JShell state changes, the snippet will update, possibly
+ * changing <code>Status</code>.
+ * An executable Snippet can only be executed if it is in the the
+ * {@link jdk.jshell.Snippet.Status#VALID} <code>Status</code>.
+ * @see JShell#status(jdk.jshell.Snippet)
+ */
+ public enum Status {
+ /**
+ * The snippet is a valid snippet
+ * (in the context of current <code>JShell</code> state).
+ * Only snippets with <code>VALID</code>
+ * <code>Status</code> can be executed (though not all
+ * <code>VALID</code> snippets have executable code).
+ * If the snippet is a declaration or import, it is visible to other
+ * snippets ({@link Status#isDefined isDefined} <code> == true</code>).
+ * <p>
+ * The snippet will update as dependents change
+ * ({@link Status#isActive isActive} <code> == true</code>), its
+ * status could become <code>RECOVERABLE_DEFINED</code>, <code>RECOVERABLE_NOT_DEFINED</code>,
+ * <code>DROPPED</code>, or <code>OVERWRITTEN</code>.
+ */
+ VALID(true, true),
+
+ /**
+ * The snippet is a declaration snippet with potentially recoverable
+ * unresolved references or other issues in its body
+ * (in the context of current <code>JShell</code> state).
+ * Only a {@link jdk.jshell.DeclarationSnippet} can have this
+ * <code>Status</code>.
+ * The snippet has a valid signature and it is visible to other
+ * snippets ({@link Status#isDefined isDefined} <code> == true</code>)
+ * and thus can be referenced in existing or new snippets
+ * but the snippet cannot be executed.
+ * An {@link UnresolvedReferenceException} will be thrown on an attempt
+ * to execute it.
+ * <p>
+ * The snippet will update as dependents change
+ * ({@link Status#isActive isActive} <code> == true</code>), its
+ * status could become <code>VALID</code>, <code>RECOVERABLE_NOT_DEFINED</code>,
+ * <code>DROPPED</code>, or <code>OVERWRITTEN</code>.
+ * <p>
+ * Note: both <code>RECOVERABLE_DEFINED</code> and <code>RECOVERABLE_NOT_DEFINED</code>
+ * indicate potentially recoverable errors, they differ in that, for
+ * <code>RECOVERABLE_DEFINED</code>, the snippet is
+ * {@linkplain Status#isDefined defined}.
+ */
+ RECOVERABLE_DEFINED(true, true),
+
+ /**
+ * The snippet is a declaration snippet with potentially recoverable
+ * unresolved references or other issues
+ * (in the context of current <code>JShell</code> state).
+ * Only a {@link jdk.jshell.DeclarationSnippet} can have this
+ * <code>Status</code>.
+ * The snippet has an invalid signature or the implementation is
+ * otherwise unable to define it.
+ * The snippet it is not visible to other snippets
+ * ({@link Status#isDefined isDefined} <code> == false</code>)
+ * and thus cannot be referenced or executed.
+ * <p>
+ * The snippet will update as dependents change
+ * ({@link Status#isActive isActive} <code> == true</code>), its
+ * status could become <code>VALID</code>, <code>RECOVERABLE_DEFINED</code>,
+ * <code>DROPPED</code>, or <code>OVERWRITTEN</code>.
+ * <p>
+ * Note: both <code>RECOVERABLE_DEFINED</code> and <code>RECOVERABLE_NOT_DEFINED</code>
+ * indicate potentially recoverable errors, they differ in that, for
+ * <code>RECOVERABLE_DEFINED</code>, the snippet is
+ * {@linkplain Status#isDefined defined}.
+ */
+ RECOVERABLE_NOT_DEFINED(true, false),
+
+ /**
+ * The snippet is inactive because of an explicit call to
+ * the {@link JShell#drop(jdk.jshell.PersistentSnippet)}.
+ * Only a {@link jdk.jshell.PersistentSnippet} can have this
+ * <code>Status</code>.
+ * The snippet is not visible to other snippets
+ * ({@link Status#isDefined isDefined} <code> == false</code>)
+ * and thus cannot be referenced or executed.
+ * <p>
+ * The snippet will not update as dependents change
+ * ({@link Status#isActive isActive} <code> == false</code>), its
+ * <code>Status</code> will never change again.
+ */
+ DROPPED(false, false),
+
+ /**
+ * The snippet is inactive because it has been replaced by a new
+ * snippet. This occurs when the new snippet added with
+ * {@link jdk.jshell.JShell#eval} matches a previous snippet.
+ * A <code>TypeDeclSnippet</code> will match another
+ * <code>TypeDeclSnippet</code> if the names match.
+ * For example <code>class X { }</code> will overwrite
+ * <code>class X { int ii; }</code> or
+ * <code>interface X { }</code>.
+ * A <code>MethodSnippet</code> will match another
+ * <code>MethodSnippet</code> if the names and parameter types
+ * match.
+ * For example <code>void m(int a) { }</code> will overwrite
+ * <code>int m(int a) { return a+a; }</code>.
+ * A <code>VarSnippet</code> will match another
+ * <code>VarSnippet</code> if the names match.
+ * For example <code>double z;</code> will overwrite
+ * <code>long z = 2L;</code>.
+ * Only a {@link jdk.jshell.PersistentSnippet} can have this
+ * <code>Status</code>.
+ * The snippet is not visible to other snippets
+ * ({@link Status#isDefined isDefined} <code> == false</code>)
+ * and thus cannot be referenced or executed.
+ * <p>
+ * The snippet will not update as dependents change
+ * ({@link Status#isActive isActive} <code> == false</code>), its
+ * <code>Status</code> will never change again.
+ */
+ OVERWRITTEN(false, false),
+
+ /**
+ * The snippet is inactive because it failed compilation on initial
+ * evaluation and it is not capable of becoming valid with further
+ * changes to the JShell state.
+ * The snippet is not visible to other snippets
+ * ({@link Status#isDefined isDefined} <code> == false</code>)
+ * and thus cannot be referenced or executed.
+ * <p>
+ * The snippet will not update as dependents change
+ * ({@link Status#isActive isActive} <code> == false</code>), its
+ * <code>Status</code> will never change again.
+ */
+ REJECTED(false, false),
+
+ /**
+ * The snippet is inactive because it does not yet exist.
+ * Used only in {@link SnippetEvent#previousStatus} for new
+ * snippets.
+ * {@link jdk.jshell.JShell#status(jdk.jshell.Snippet) JShell.status(Snippet)}
+ * will never return this <code>Status</code>.
+ * Vacuously, {@link Status#isDefined isDefined} and
+ * {@link Status#isActive isActive} are both defined <code>false</code>.
+ */
+ NONEXISTENT(false, false);
+
+ /**
+ * Is the Snippet active, that is, will the snippet
+ * be re-evaluated when a new
+ * {@link JShell#eval(java.lang.String) JShell.eval(String)} or
+ * {@link JShell#drop(jdk.jshell.PersistentSnippet)
+ * JShell.drop(PersistentSnippet)} that could change
+ * its status is invoked? This is more broad than
+ * {@link Status#isDefined} since a Snippet which is
+ * {@link Status#RECOVERABLE_NOT_DEFINED}
+ * will be updated.
+ */
+ public final boolean isActive;
+
+ /**
+ * Is the snippet currently part of the defined state of the JShell?
+ * Is it visible to compilation of other snippets?
+ */
+ public final boolean isDefined;
+
+ Status(boolean isActive, boolean isDefined) {
+ this.isActive = isActive;
+ this.isDefined = isDefined;
+ }
+ }
+
+ private final Key key;
+ private final String source;
+ private final Wrap guts;
+ final String unitName;
+ private final SubKind subkind;
+
+ private int seq;
+ private String className;
+ private String id;
+ private OuterWrap outer;
+ private Status status;
+ private List<String> unresolved;
+ private DiagList diagnostics;
+
+ Snippet(Key key, String userSource, Wrap guts, String unitName, SubKind subkind) {
+ this.key = key;
+ this.source = userSource;
+ this.guts = guts;
+ this.unitName = unitName;
+ this.subkind = subkind;
+ this.status = Status.NONEXISTENT;
+ setSequenceNumber(0);
+ }
+
+ /**** public access ****/
+
+ /**
+ * The unique identifier for the snippet. No two active snippets will have
+ * the same id().
+ * @return the snippet id string.
+ */
+ public String id() {
+ return id;
+ }
+
+ /**
+ * The {@link jdk.jshell.Snippet.Kind} for the snippet.
+ * Indicates the subclass of Snippet.
+ * @return the Kind of the snippet
+ * @see Snippet.Kind
+ */
+ public Kind kind() {
+ return subkind.kind();
+ }
+
+ /**
+ * Return the {@link SubKind} of snippet.
+ * The SubKind is useful for feedback to users.
+ * @return the SubKind corresponding to this snippet
+ */
+ public SubKind subKind() {
+ return subkind;
+ }
+
+ /**
+ * Return the source code of the snippet.
+ * @return the source code corresponding to this snippet
+ */
+ public String source() {
+ return source;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Snippet:");
+ if (key() != null) {
+ sb.append(key().toString());
+ }
+ sb.append('-');
+ sb.append(source);
+ return sb.toString();
+ }
+
+ //**** internal access ****
+
+ String name() {
+ return unitName;
+ }
+
+ Key key() {
+ return key;
+ }
+
+ List<String> unresolved() {
+ return Collections.unmodifiableList(unresolved);
+ }
+
+ DiagList diagnostics() {
+ return diagnostics;
+ }
+
+ /**
+ * @return the corralled guts
+ */
+ Wrap corralled() {
+ return null;
+ }
+
+ Collection<String> declareReferences() {
+ return null;
+ }
+
+ Collection<String> bodyReferences() {
+ return null;
+ }
+
+ String importLine(JShell state) {
+ return "";
+ }
+
+ void setId(String id) {
+ this.id = id;
+ }
+
+ final void setSequenceNumber(int seq) {
+ this.seq = seq;
+ this.className = REPL_CLASS_PREFIX + key().index() + asLetters(seq);
+ }
+
+ void setOuterWrap(OuterWrap outer) {
+ this.outer = outer;
+ }
+
+ void setCompilationStatus(Status status, List<String> unresolved, DiagList diagnostics) {
+ this.status = status;
+ this.unresolved = unresolved;
+ this.diagnostics = diagnostics;
+ }
+
+ void setDiagnostics(DiagList diagnostics) {
+ this.diagnostics = diagnostics;
+ }
+
+ void setFailed(DiagList diagnostics) {
+ this.seq = -1;
+ this.outer = null;
+ this.status = Status.REJECTED;
+ this.unresolved = Collections.emptyList();
+ this.diagnostics = diagnostics;
+ }
+
+ void setDropped() {
+ this.status = Status.DROPPED;
+ }
+
+ void setOverwritten() {
+ this.status = Status.OVERWRITTEN;
+ }
+
+ Status status() {
+ return status;
+ }
+
+ String className() {
+ return className;
+ }
+
+ /**
+ * Top-level wrap
+ * @return
+ */
+ OuterWrap outerWrap() {
+ return outer;
+ }
+
+ /**
+ * Basically, class version for this Key.
+ * @return int
+ */
+ int sequenceNumber() {
+ return seq;
+ }
+
+ Wrap guts() {
+ return guts;
+ }
+
+ boolean isExecutable() {
+ return subkind.isExecutable();
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/SnippetEvent.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,152 @@
+/*
+ * 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.Status;
+
+/**
+ * A description of a change to a Snippet. These are generated by direct changes
+ * to state with {@link JShell#eval(java.lang.String) JShell.eval(String)} or
+ * {@link JShell#drop(jdk.jshell.PersistentSnippet) JShell.drop(PersistentSnippet)},
+ * or indirectly by these same methods as
+ * dependencies change or Snippets are overwritten. For direct changes, the
+ * {@link SnippetEvent#causeSnippet()} is <code>null</code>.
+ * <p>
+ * <code>SnippetEvent</code> is immutable: an access to
+ * any of its methods will always return the same result.
+ * and thus is thread-safe.
+ * @author Robert Field
+ */
+public class SnippetEvent {
+
+ SnippetEvent(Snippet snippet, Status previousStatus, Status status,
+ boolean isSignatureChange, Snippet causeSnippet,
+ String value, Exception exception) {
+ this.snippet = snippet;
+ this.previousStatus = previousStatus;
+ this.status = status;
+ this.isSignatureChange = isSignatureChange;
+ this.causeSnippet = causeSnippet;
+ this.value = value;
+ this.exception = exception;
+ }
+
+ private final Snippet snippet;
+ private final Status previousStatus;
+ private final Status status;
+ private final boolean isSignatureChange;
+ private final Snippet causeSnippet;
+ private final String value;
+ private final Exception exception;
+
+ /**
+ * The Snippet which has changed
+ * @return the return the Snippet whose <code>Status</code> has changed.
+ */
+ public Snippet snippet() {
+ return snippet;
+ }
+
+ /**
+ * The status before the transition. If this event describes a Snippet
+ * creation return {@link Snippet.Status#NONEXISTENT NONEXISTENT}.
+ * @return the previousStatus
+ */
+ public Status previousStatus() {
+ return previousStatus;
+ }
+
+ /**
+ * The after status. Note: this may be the same as the previous status (not
+ * all changes cause a <code>Status</code> change.
+ * @return the status
+ */
+ public Status status() {
+ return status;
+ }
+
+ /**
+ * Has the signature changed? Coming in or out of definition
+ * (status.isDefined) is always a signature change. An overwritten Snippet
+ * {@link jdk.jshell.Snippet.Status#OVERWRITTEN (status == OVERWRITTEN)}
+ * is always <code>false</code> as responsibility for the
+ * definition has passed to the overwriting definition.
+ * @return <code>true</code> if the signature changed.
+ */
+ public boolean isSignatureChange() {
+ return isSignatureChange;
+ }
+
+ /**
+ * Either the snippet whose change caused this update or
+ * <code>null</code>. This returns <code>null</code> if this change is the
+ * creation of a new Snippet via
+ * {@link jdk.jshell.JShell#eval(java.lang.String) eval} or it is the
+ * explicit drop of a Snippet with
+ * {@link jdk.jshell.JShell#drop(jdk.jshell.PersistentSnippet) drop}.
+ *
+ * @return the Snippet which caused this change or <code>null</code> if
+ * directly caused by an API action.
+ */
+ public Snippet causeSnippet() {
+ return causeSnippet;
+ }
+
+ /**
+ * An instance of {@link jdk.jshell.UnresolvedReferenceException}, if an unresolved reference was
+ * encountered, or an instance of {@link jdk.jshell.EvalException} if an exception was thrown
+ * during execution, otherwise <code>null</code>.
+ * @return the exception or <code>null</code>.
+ */
+ public Exception exception() {
+ return exception;
+ }
+
+ /**
+ * The result value of successful run. The value is null if not executed
+ * or an exception was thrown.
+ * @return the value or <code>null</code>.
+ */
+ public String value() {
+ return value;
+ }
+
+ /**
+ * Return a string representation of the event
+ * @return a descriptive representation of the SnippetEvent
+ */
+ @Override
+ public String toString() {
+ return "SnippetEvent(snippet=" + snippet +
+ ",previousStatus=" + previousStatus +
+ ",status=" + status +
+ ",isSignatureChange=" + isSignatureChange +
+ ",causeSnippet" + causeSnippet +
+ (value == null? "" : "value=" + value) +
+ (exception == null? "" : "exception=" + exception) +
+ ")";
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/SnippetMaps.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,200 @@
+/*
+ * 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.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.stream.Stream;
+
+import static java.util.stream.Collectors.toList;
+import static jdk.internal.jshell.remote.RemoteCodes.prefixPattern;
+import static jdk.internal.jshell.debug.InternalDebugControl.DBG_DEP;
+
+/**
+ * Maintain relationships between the significant entities: Snippets,
+ * internal snippet index, Keys, etc.
+ * @author Robert Field
+ */
+final class SnippetMaps {
+
+ private String packageName;
+ private final List<Snippet> keyIndexToSnippet = new ArrayList<>();
+ private final Set<Snippet> snippets = new LinkedHashSet<>();
+ private final Map<String, Set<Integer>> dependencies = new HashMap<>();
+ private final JShell state;
+
+ SnippetMaps(JShell proc) {
+ this.state = proc;
+ }
+
+ void installSnippet(Snippet sn) {
+ if (sn != null && snippets.add(sn)) {
+ if (sn.key() != null) {
+ sn.setId((state.idGenerator != null)
+ ? state.idGenerator.apply(sn, sn.key().index())
+ : "" + sn.key().index());
+ setSnippet(sn.key().index(), sn);
+ }
+ }
+ }
+
+ private void setSnippet(int ki, Snippet snip) {
+ while (ki >= keyIndexToSnippet.size()) {
+ keyIndexToSnippet.add(null);
+ }
+ keyIndexToSnippet.set(ki, snip);
+ }
+
+ Snippet getSnippet(Key key) {
+ return getSnippet(key.index());
+ }
+
+ Snippet getSnippet(int ki) {
+ if (ki >= keyIndexToSnippet.size()) {
+ return null;
+ }
+ return keyIndexToSnippet.get(ki);
+ }
+
+ List<Snippet> snippetList() {
+ return new ArrayList<>(snippets);
+ }
+
+ void setPackageName(String n) {
+ packageName = n;
+ }
+
+ String packageName() {
+ return packageName;
+ }
+
+ String classFullName(Snippet sn) {
+ return packageName + "." + sn.className();
+ }
+
+ String packageAndImportsExcept(Set<Key> except, Collection<Snippet> plus) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("package ").append(packageName()).append(";\n");
+ for (Snippet si : keyIndexToSnippet) {
+ if (si != null && si.status().isDefined && (except == null || !except.contains(si.key()))) {
+ sb.append(si.importLine(state));
+ }
+ }
+ if (plus != null) {
+ plus.stream()
+ .forEach(psi -> sb.append(psi.importLine(state)));
+ }
+ return sb.toString();
+ }
+
+ List<Snippet> getDependents(Snippet snip) {
+ if (!snip.kind().isPersistent) {
+ return Collections.emptyList();
+ }
+ Set<Integer> depset;
+ if (snip.unitName.equals("*")) {
+ // star import
+ depset = new HashSet<>();
+ for (Set<Integer> as : dependencies.values()) {
+ depset.addAll(as);
+ }
+ } else {
+ depset = dependencies.get(snip.name());
+ }
+ if (depset == null) {
+ return Collections.emptyList();
+ }
+ List<Snippet> deps = new ArrayList<>();
+ for (Integer dss : depset) {
+ Snippet dep = getSnippet(dss);
+ if (dep != null) {
+ deps.add(dep);
+ state.debug(DBG_DEP, "Found dependency %s -> %s\n", snip.name(), dep.name());
+ }
+ }
+ return deps;
+ }
+
+ void mapDependencies(Snippet snip) {
+ addDependencies(snip.declareReferences(), snip);
+ addDependencies(snip.bodyReferences(), snip);
+ }
+
+ private void addDependencies(Collection<String> refs, Snippet snip) {
+ if (refs == null) return;
+ for (String ref : refs) {
+ dependencies.computeIfAbsent(ref, k -> new HashSet<>())
+ .add(snip.key().index());
+ state.debug(DBG_DEP, "Added dependency %s -> %s\n", ref, snip.name());
+ }
+ }
+
+ String fullClassNameAndPackageToClass(String full, String pkg) {
+ Matcher mat = prefixPattern.matcher(full);
+ if (mat.lookingAt()) {
+ return full.substring(mat.end());
+ }
+ state.debug(DBG_DEP, "SM %s %s\n", full, pkg);
+ List<String> klasses = importSnippets()
+ .filter(isi -> !isi.isStar)
+ .map(isi -> isi.fullname)
+ .collect(toList());
+ for (String k : klasses) {
+ if (k.equals(full)) {
+ return full.substring(full.lastIndexOf(".")+1, full.length());
+ }
+ }
+ List<String> pkgs = importSnippets()
+ .filter(isi -> isi.isStar)
+ .map(isi -> isi.fullname.substring(0, isi.fullname.lastIndexOf(".")))
+ .collect(toList());
+ pkgs.add(0, "java.lang");
+ for (String ipkg : pkgs) {
+ if (!ipkg.isEmpty() && ipkg.equals(pkg)) {
+ return full.substring(pkg.length() + 1);
+ }
+ }
+ return full;
+ }
+
+ /**
+ * Compute the set of imports to prepend to a snippet
+ * @return a stream of the import needed
+ */
+ private Stream<ImportSnippet> importSnippets() {
+ return state.keyMap.importKeys()
+ .map(key -> (ImportSnippet)getSnippet(key))
+ .filter(sn -> state.status(sn).isDefined);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysis.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,201 @@
+/*
+ * 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.List;
+
+/**
+ * Provides analysis utilities for source code input.
+ * Optional functionality that provides for a richer interactive experience.
+ * Includes completion analysis:
+ * Is the input a complete snippet of code?
+ * Do I need to prompt for more input?
+ * Would adding a semicolon make it complete?
+ * Is there more than one snippet?
+ * etc.
+ * Also includes completion suggestions, as might be used in tab-completion.
+ *
+ */
+public abstract class SourceCodeAnalysis {
+
+ /**
+ * Given an input string, find the first snippet of code (one statement,
+ * definition, import, or expression) and evaluate if it is complete.
+ * @param input the input source string
+ * @return a CompletionInfo instance with location and completeness info
+ */
+ public abstract CompletionInfo analyzeCompletion(String input);
+
+ /**
+ * Compute possible follow-ups for the given input.
+ * Uses information from the current <code>JShell</code> state, including
+ * type information, to filter the suggestions.
+ * @param input the user input, so far
+ * @param cursor the current position of the cursors in the given {@code input} text
+ * @param anchor outgoing parameter - when an option will be completed, the text between
+ * the anchor and cursor will be deleted and replaced with the given option
+ * @return list of candidate continuations of the given input.
+ */
+ public abstract List<Suggestion> completionSuggestions(String input, int cursor, int[] anchor);
+
+ /**
+ * Compute a description/help string for the given user's input.
+ * @param input the snippet the user wrote so far
+ * @param cursor the current position of the cursors in the given {@code input} text
+ * @return description/help string for the given user's input
+ */
+ public abstract String documentation(String input, int cursor);
+
+ /**
+ * Internal only constructor
+ */
+ SourceCodeAnalysis() {}
+
+ /**
+ * The result of <code>analyzeCompletion(String input)</code>.
+ * Describes the completeness and position of the first snippet in the given input.
+ */
+ public static class CompletionInfo {
+
+ public CompletionInfo(Completeness completeness, int unitEndPos, String source, String remaining) {
+ this.completeness = completeness;
+ this.unitEndPos = unitEndPos;
+ this.source = source;
+ this.remaining = remaining;
+ }
+
+ /**
+ * The analyzed completeness of the input.
+ */
+ public final Completeness completeness;
+
+ /**
+ * The end of the first unit of source.
+ */
+ public final int unitEndPos;
+
+ /**
+ * Source code for the first unit of code input. For example, first
+ * statement, or first method declaration. Trailing semicolons will
+ * be added, as needed
+ */
+ public final String source;
+
+ /**
+ * Input remaining after the source
+ */
+ public final String remaining;
+ }
+
+ /**
+ * Describes the completeness of the given input.
+ */
+ public enum Completeness {
+ /**
+ * The input is a complete source snippet (declaration or statement) as is.
+ */
+ COMPLETE(true),
+
+ /**
+ * With this addition of a semicolon the input is a complete source snippet.
+ * This will only be returned when the end of input is encountered.
+ */
+ COMPLETE_WITH_SEMI(true),
+
+ /**
+ * There must be further source beyond the given input in order for it
+ * to be complete. A semicolon would not complete it.
+ * This will only be returned when the end of input is encountered.
+ */
+ DEFINITELY_INCOMPLETE(false),
+
+ /**
+ * A statement with a trailing (non-terminated) empty statement.
+ * Though technically it would be a complete statement
+ * with the addition of a semicolon, it is rare
+ * that that assumption is the desired behavior.
+ * The input is considered incomplete. Comments and white-space are
+ * still considered empty.
+ */
+ CONSIDERED_INCOMPLETE(false),
+
+
+ /**
+ * An empty input.
+ * The input is considered incomplete. Comments and white-space are
+ * still considered empty.
+ */
+ EMPTY(false),
+
+ /**
+ * The completeness of the input could not be determined because it
+ * contains errors. Error detection is not a goal of completeness
+ * analysis, however errors interfered with determining its completeness.
+ * The input is considered complete because evaluating is the best
+ * mechanism to get error information.
+ */
+ UNKNOWN(true);
+
+ /**
+ * Is the first snippet of source complete. For example, "x=" is not
+ * complete, but "x=2" is complete, even though a subsequent line could
+ * make it "x=2+2". Already erroneous code is marked complete.
+ */
+ public final boolean isComplete;
+
+ Completeness(boolean isComplete) {
+ this.isComplete = isComplete;
+ }
+ }
+
+ /**
+ * A candidate for continuation of the given user's input.
+ */
+ public static class Suggestion {
+
+ /**
+ * Create a {@code Suggestion} instance.
+ * @param continuation a candidate continuation of the user's input
+ * @param isSmart is the candidate "smart"
+ */
+ public Suggestion(String continuation, boolean isSmart) {
+ this.continuation = continuation;
+ this.isSmart = isSmart;
+ }
+
+ /**
+ * The candidate continuation of the given user's input.
+ */
+ public final String continuation;
+
+ /**
+ * Is it an input continuation that matches the target type and is thus more
+ * likely to be the desired continuation. A smart continuation
+ * is preferred.
+ */
+ public final boolean isSmart;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,1000 @@
+/*
+ * 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 jdk.jshell.SourceCodeAnalysis.Completeness;
+import com.sun.source.tree.AssignmentTree;
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.ErroneousTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.ImportTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.Scope;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.util.SourcePositions;
+import com.sun.source.util.TreePath;
+import com.sun.source.util.TreePathScanner;
+import com.sun.tools.javac.api.JavacScope;
+import com.sun.tools.javac.code.Flags;
+import com.sun.tools.javac.code.Symbol.CompletionFailure;
+import com.sun.tools.javac.code.Symbol.VarSymbol;
+import com.sun.tools.javac.code.Symtab;
+import com.sun.tools.javac.code.Type;
+import com.sun.tools.javac.code.Type.ClassType;
+import com.sun.tools.javac.util.DefinedBy;
+import com.sun.tools.javac.util.DefinedBy.Api;
+import com.sun.tools.javac.util.Name;
+import com.sun.tools.javac.util.Names;
+import com.sun.tools.javac.util.Pair;
+import jdk.jshell.CompletenessAnalyzer.CaInfo;
+import jdk.jshell.TaskFactory.AnalyzeTask;
+import jdk.jshell.TaskFactory.ParseTask;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.Predicate;
+
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeMirror;
+
+import static jdk.internal.jshell.debug.InternalDebugControl.DBG_COMPA;
+
+import java.io.IOException;
+import java.util.Comparator;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import static java.util.stream.Collectors.collectingAndThen;
+import static java.util.stream.Collectors.joining;
+import static java.util.stream.Collectors.toCollection;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.QualifiedNameable;
+import javax.lang.model.element.VariableElement;
+import javax.lang.model.type.ArrayType;
+import javax.lang.model.type.ExecutableType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.util.ElementFilter;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import javax.tools.JavaFileManager.Location;
+import javax.tools.JavaFileObject;
+import javax.tools.StandardLocation;
+
+import static jdk.jshell.Util.REPL_DOESNOTMATTER_CLASS_NAME;
+
+/**
+ * The concrete implementation of SourceCodeAnalysis.
+ * @author Robert Field
+ */
+class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
+ private final JShell proc;
+ private final CompletenessAnalyzer ca;
+
+ SourceCodeAnalysisImpl(JShell proc) {
+ this.proc = proc;
+ this.ca = new CompletenessAnalyzer(proc);
+ }
+
+ @Override
+ public CompletionInfo analyzeCompletion(String srcInput) {
+ MaskCommentsAndModifiers mcm = new MaskCommentsAndModifiers(srcInput, false);
+ String cleared = mcm.cleared();
+ String trimmedInput = Util.trimEnd(cleared);
+ if (trimmedInput.isEmpty()) {
+ // Just comment or empty
+ return new CompletionInfo(Completeness.EMPTY, srcInput.length(), srcInput, "");
+ }
+ CaInfo info = ca.scan(trimmedInput);
+ Completeness status = info.status;
+ int unitEndPos = info.unitEndPos;
+ if (unitEndPos > srcInput.length()) {
+ unitEndPos = srcInput.length();
+ }
+ int nonCommentNonWhiteLength = trimmedInput.length();
+ String src = srcInput.substring(0, unitEndPos);
+ switch (status) {
+ case COMPLETE:
+ if (unitEndPos == nonCommentNonWhiteLength) {
+ // The unit is the whole non-coment/white input plus semicolon
+ String compileSource = src
+ + mcm.mask().substring(nonCommentNonWhiteLength);
+ proc.debug(DBG_COMPA, "Complete: %s\n", compileSource);
+ proc.debug(DBG_COMPA, " nothing remains.\n");
+ return new CompletionInfo(status, unitEndPos, compileSource, "");
+ } else {
+ String remain = srcInput.substring(unitEndPos);
+ proc.debug(DBG_COMPA, "Complete: %s\n", src);
+ proc.debug(DBG_COMPA, " remaining: %s\n", remain);
+ return new CompletionInfo(status, unitEndPos, src, remain);
+ }
+ case COMPLETE_WITH_SEMI:
+ // The unit is the whole non-coment/white input plus semicolon
+ String compileSource = src
+ + ";"
+ + mcm.mask().substring(nonCommentNonWhiteLength);
+ proc.debug(DBG_COMPA, "Complete with semi: %s\n", compileSource);
+ proc.debug(DBG_COMPA, " nothing remains.\n");
+ return new CompletionInfo(status, unitEndPos, compileSource, "");
+ case DEFINITELY_INCOMPLETE:
+ proc.debug(DBG_COMPA, "Incomplete: %s\n", srcInput);
+ return new CompletionInfo(status, unitEndPos, null, srcInput + '\n');
+ case CONSIDERED_INCOMPLETE:
+ proc.debug(DBG_COMPA, "Considered incomplete: %s\n", srcInput);
+ return new CompletionInfo(status, unitEndPos, null, srcInput + '\n');
+ case EMPTY:
+ proc.debug(DBG_COMPA, "Detected empty: %s\n", srcInput);
+ return new CompletionInfo(status, unitEndPos, srcInput, "");
+ case UNKNOWN:
+ proc.debug(DBG_COMPA, "Detected error: %s\n", srcInput);
+ return new CompletionInfo(status, unitEndPos, srcInput, "");
+ }
+ throw new InternalError();
+ }
+
+ private OuterWrap wrapInClass(Wrap guts) {
+ String imports = proc.maps.packageAndImportsExcept(null, null);
+ return OuterWrap.wrapInClass(proc.maps.packageName(), REPL_DOESNOTMATTER_CLASS_NAME, imports, "", guts);
+ }
+
+ private Tree.Kind guessKind(String code) {
+ ParseTask pt = proc.taskFactory.new ParseTask(code);
+ List<? extends Tree> units = pt.units();
+ if (units.isEmpty()) {
+ return Tree.Kind.BLOCK;
+ }
+ Tree unitTree = units.get(0);
+ proc.debug(DBG_COMPA, "Kind: %s -- %s\n", unitTree.getKind(), unitTree);
+ return unitTree.getKind();
+ }
+
+ //TODO: would be better handled through a lexer:
+ private final Pattern JAVA_IDENTIFIER = Pattern.compile("\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*");
+
+ @Override
+ public List<Suggestion> completionSuggestions(String code, int cursor, int[] anchor) {
+ code = code.substring(0, cursor);
+ Matcher m = JAVA_IDENTIFIER.matcher(code);
+ String identifier = "";
+ while (m.find()) {
+ if (m.end() == code.length()) {
+ cursor = m.start();
+ code = code.substring(0, cursor);
+ identifier = m.group();
+ }
+ }
+ code = code.substring(0, cursor);
+ if (code.trim().isEmpty()) { //TODO: comment handling
+ code += ";";
+ }
+ OuterWrap codeWrap;
+ switch (guessKind(code)) {
+ case IMPORT:
+ codeWrap = OuterWrap.wrapImport(null, Wrap.importWrap(code + "any.any"));
+ break;
+ case METHOD:
+ codeWrap = wrapInClass(Wrap.classMemberWrap(code));
+ break;
+ default:
+ codeWrap = wrapInClass(Wrap.methodWrap(code));
+ break;
+ }
+ String requiredPrefix = identifier;
+ return computeSuggestions(codeWrap, cursor, anchor).stream()
+ .filter(s -> s.continuation.startsWith(requiredPrefix) && !s.continuation.equals(REPL_DOESNOTMATTER_CLASS_NAME))
+ .sorted(Comparator.comparing(s -> s.continuation))
+ .collect(collectingAndThen(toList(), Collections::unmodifiableList));
+ }
+
+ private List<Suggestion> computeSuggestions(OuterWrap code, int cursor, int[] anchor) {
+ AnalyzeTask at = proc.taskFactory.new AnalyzeTask(code);
+ SourcePositions sp = at.trees().getSourcePositions();
+ CompilationUnitTree topLevel = at.cuTree();
+ List<Suggestion> result = new ArrayList<>();
+ TreePath tp = pathFor(topLevel, sp, code.snippetIndexToWrapIndex(cursor));
+ if (tp != null) {
+ Scope scope = at.trees().getScope(tp);
+ Predicate<Element> accessibility = createAccessibilityFilter(at, tp);
+ Predicate<Element> smartTypeFilter;
+ Predicate<Element> smartFilter;
+ Iterable<TypeMirror> targetTypes = findTargetType(at, tp);
+ if (targetTypes != null) {
+ smartTypeFilter = el -> {
+ TypeMirror resultOf = resultTypeOf(el);
+ return Util.stream(targetTypes)
+ .anyMatch(targetType -> at.getTypes().isAssignable(resultOf, targetType));
+ };
+
+ smartFilter = IS_CLASS.negate()
+ .and(IS_INTERFACE.negate())
+ .and(IS_PACKAGE.negate())
+ .and(smartTypeFilter);
+ } else {
+ smartFilter = TRUE;
+ smartTypeFilter = TRUE;
+ }
+ switch (tp.getLeaf().getKind()) {
+ case MEMBER_SELECT: {
+ MemberSelectTree mst = (MemberSelectTree)tp.getLeaf();
+ if (mst.getIdentifier().contentEquals("*"))
+ break;
+ TreePath exprPath = new TreePath(tp, mst.getExpression());
+ TypeMirror site = at.trees().getTypeMirror(exprPath);
+ boolean staticOnly = isStaticContext(at, exprPath);
+ ImportTree it = findImport(tp);
+ boolean isImport = it != null;
+
+ List<? extends Element> members = membersOf(at, site, staticOnly && !isImport);
+ Predicate<Element> filter = accessibility;
+ Function<Boolean, String> paren = DEFAULT_PAREN;
+
+ if (isNewClass(tp)) { // new xxx.|
+ Predicate<Element> constructorFilter = accessibility.and(IS_CONSTRUCTOR)
+ .and(el -> {
+ if (el.getEnclosingElement().getEnclosingElement().getKind() == ElementKind.CLASS) {
+ return el.getEnclosingElement().getModifiers().contains(Modifier.STATIC);
+ }
+ return true;
+ });
+ addElements(membersOf(at, members), constructorFilter, smartFilter, result);
+
+ filter = filter.and(IS_PACKAGE);
+ } else if (isThrowsClause(tp)) {
+ staticOnly = true;
+ filter = filter.and(IS_PACKAGE.or(IS_CLASS).or(IS_INTERFACE));
+ smartFilter = IS_PACKAGE.negate().and(smartTypeFilter);
+ } else if (isImport) {
+ paren = NO_PAREN;
+ if (!it.isStatic()) {
+ filter = filter.and(IS_PACKAGE.or(IS_CLASS).or(IS_INTERFACE));
+ }
+ } else {
+ filter = filter.and(IS_CONSTRUCTOR.negate());
+ }
+
+ filter = filter.and(staticOnly ? STATIC_ONLY : INSTANCE_ONLY);
+
+ addElements(members, filter, smartFilter, paren, result);
+ break;
+ }
+ case IDENTIFIER:
+ if (isNewClass(tp)) {
+ Function<Element, Iterable<? extends Element>> listEnclosed =
+ el -> el.getKind() == ElementKind.PACKAGE ? Collections.singletonList(el)
+ : el.getEnclosedElements();
+ Predicate<Element> filter = accessibility.and(IS_CONSTRUCTOR.or(IS_PACKAGE));
+ NewClassTree newClassTree = (NewClassTree)tp.getParentPath().getLeaf();
+ ExpressionTree enclosingExpression = newClassTree.getEnclosingExpression();
+ if (enclosingExpression != null) { // expr.new IDENT|
+ TypeMirror site = at.trees().getTypeMirror(new TreePath(tp, enclosingExpression));
+ filter = filter.and(el -> el.getEnclosingElement().getKind() == ElementKind.CLASS && !el.getEnclosingElement().getModifiers().contains(Modifier.STATIC));
+ addElements(membersOf(at, membersOf(at, site, false)), filter, smartFilter, result);
+ } else {
+ addScopeElements(at, scope, listEnclosed, filter, smartFilter, result);
+ }
+ break;
+ }
+ if (isThrowsClause(tp)) {
+ Predicate<Element> accept = accessibility.and(STATIC_ONLY)
+ .and(IS_PACKAGE.or(IS_CLASS).or(IS_INTERFACE));
+ addScopeElements(at, scope, IDENTITY, accept, IS_PACKAGE.negate().and(smartTypeFilter), result);
+ break;
+ }
+ ImportTree it = findImport(tp);
+ if (it != null) {
+ addElements(membersOf(at, at.getElements().getPackageElement("").asType(), false), it.isStatic() ? STATIC_ONLY.and(accessibility) : accessibility, smartFilter, result);
+ }
+ break;
+ case ERRONEOUS:
+ case EMPTY_STATEMENT: {
+ boolean staticOnly = ReplResolve.isStatic(((JavacScope)scope).getEnv());
+ Predicate<Element> accept = accessibility.and(staticOnly ? STATIC_ONLY : TRUE);
+ addScopeElements(at, scope, IDENTITY, accept, smartFilter, result);
+
+ Tree parent = tp.getParentPath().getLeaf();
+ switch (parent.getKind()) {
+ case VARIABLE:
+ accept = ((VariableTree)parent).getType() == tp.getLeaf() ?
+ IS_VOID.negate() :
+ TRUE;
+ break;
+ case PARAMETERIZED_TYPE: // TODO: JEP 218: Generics over Primitive Types
+ case TYPE_PARAMETER:
+ case CLASS:
+ case INTERFACE:
+ case ENUM:
+ accept = FALSE;
+ break;
+ default:
+ accept = TRUE;
+ break;
+ }
+ addElements(primitivesOrVoid(at), accept, smartFilter, result);
+ break;
+ }
+ }
+ }
+ anchor[0] = cursor;
+ return result;
+ }
+
+ private boolean isStaticContext(AnalyzeTask at, TreePath path) {
+ switch (path.getLeaf().getKind()) {
+ case ARRAY_TYPE:
+ case PRIMITIVE_TYPE:
+ return true;
+ default:
+ Element selectEl = at.trees().getElement(path);
+ return selectEl != null && (selectEl.getKind().isClass() || selectEl.getKind().isInterface() || selectEl.getKind() == ElementKind.TYPE_PARAMETER) && selectEl.asType().getKind() != TypeKind.ERROR;
+ }
+ }
+
+ private TreePath pathFor(CompilationUnitTree topLevel, SourcePositions sp, int pos) {
+ TreePath[] deepest = new TreePath[1];
+
+ new TreePathScanner<Void, Void>() {
+ @Override @DefinedBy(Api.COMPILER_TREE)
+ public Void scan(Tree tree, Void p) {
+ if (tree == null)
+ return null;
+
+ long start = sp.getStartPosition(topLevel, tree);
+ long end = sp.getEndPosition(topLevel, tree);
+
+ if (start <= pos && pos <= end) {
+ deepest[0] = new TreePath(getCurrentPath(), tree);
+ return super.scan(tree, p);
+ }
+
+ return null;
+ }
+ @Override @DefinedBy(Api.COMPILER_TREE)
+ public Void visitErroneous(ErroneousTree node, Void p) {
+ return scan(node.getErrorTrees(), null);
+ }
+ }.scan(topLevel, null);
+
+ return deepest[0];
+ }
+
+ private boolean isNewClass(TreePath tp) {
+ return tp.getParentPath() != null &&
+ tp.getParentPath().getLeaf().getKind() == Kind.NEW_CLASS &&
+ ((NewClassTree) tp.getParentPath().getLeaf()).getIdentifier() == tp.getLeaf();
+ }
+
+ private boolean isThrowsClause(TreePath tp) {
+ Tree parent = tp.getParentPath().getLeaf();
+ return parent.getKind() == Kind.METHOD &&
+ ((MethodTree)parent).getThrows().contains(tp.getLeaf());
+ }
+
+ private ImportTree findImport(TreePath tp) {
+ while (tp != null && tp.getLeaf().getKind() != Kind.IMPORT) {
+ tp = tp.getParentPath();
+ }
+ return tp != null ? (ImportTree)tp.getLeaf() : null;
+ }
+
+ private Predicate<Element> createAccessibilityFilter(AnalyzeTask at, TreePath tp) {
+ Scope scope = at.trees().getScope(tp);
+ return el -> {
+ switch (el.getKind()) {
+ case ANNOTATION_TYPE: case CLASS: case ENUM: case INTERFACE:
+ return at.trees().isAccessible(scope, (TypeElement) el);
+ case PACKAGE:
+ case EXCEPTION_PARAMETER: case PARAMETER: case LOCAL_VARIABLE: case RESOURCE_VARIABLE:
+ return true;
+ default:
+ TypeMirror type = el.getEnclosingElement().asType();
+ if (type.getKind() == TypeKind.DECLARED)
+ return at.trees().isAccessible(scope, el, (DeclaredType) type);
+ else
+ return true;
+ }
+ };
+ }
+
+ private final Predicate<Element> TRUE = el -> true;
+ private final Predicate<Element> FALSE = TRUE.negate();
+ private final Predicate<Element> IS_STATIC = el -> el.getModifiers().contains(Modifier.STATIC);
+ private final Predicate<Element> IS_CONSTRUCTOR = el -> el.getKind() == ElementKind.CONSTRUCTOR;
+ private final Predicate<Element> IS_METHOD = el -> el.getKind() == ElementKind.METHOD;
+ private final Predicate<Element> IS_PACKAGE = el -> el.getKind() == ElementKind.PACKAGE;
+ private final Predicate<Element> IS_CLASS = el -> el.getKind().isClass();
+ private final Predicate<Element> IS_INTERFACE = el -> el.getKind().isInterface();
+ private final Predicate<Element> IS_VOID = el -> el.asType().getKind() == TypeKind.VOID;
+ private final Predicate<Element> STATIC_ONLY = el -> {
+ ElementKind kind = el.getKind();
+ Element encl = el.getEnclosingElement();
+ ElementKind enclKind = encl != null ? encl.getKind() : ElementKind.OTHER;
+
+ return IS_STATIC.or(IS_PACKAGE).or(IS_CLASS).or(IS_INTERFACE).test(el) || IS_PACKAGE.test(encl) ||
+ (kind == ElementKind.TYPE_PARAMETER && !enclKind.isClass() && !enclKind.isInterface());
+ };
+ private final Predicate<Element> INSTANCE_ONLY = el -> {
+ Element encl = el.getEnclosingElement();
+
+ return IS_STATIC.or(IS_CLASS).or(IS_INTERFACE).negate().test(el) ||
+ IS_PACKAGE.test(encl);
+ };
+ private final Function<Element, Iterable<? extends Element>> IDENTITY = el -> Collections.singletonList(el);
+ private final Function<Boolean, String> DEFAULT_PAREN = hasParams -> hasParams ? "(" : "()";
+ private final Function<Boolean, String> NO_PAREN = hasParams -> "";
+
+ private void addElements(Iterable<? extends Element> elements, Predicate<Element> accept, Predicate<Element> smart, List<Suggestion> result) {
+ addElements(elements, accept, smart, DEFAULT_PAREN, result);
+ }
+ private void addElements(Iterable<? extends Element> elements, Predicate<Element> accept, Predicate<Element> smart, Function<Boolean, String> paren, List<Suggestion> result) {
+ Set<String> hasParams = Util.stream(elements)
+ .filter(accept)
+ .filter(IS_CONSTRUCTOR.or(IS_METHOD))
+ .filter(c -> !((ExecutableElement)c).getParameters().isEmpty())
+ .map(this::simpleName)
+ .collect(toSet());
+
+ for (Element c : elements) {
+ if (!accept.test(c))
+ continue;
+ String simpleName = simpleName(c);
+ if (c.getKind() == ElementKind.CONSTRUCTOR || c.getKind() == ElementKind.METHOD) {
+ simpleName += paren.apply(hasParams.contains(simpleName));
+ }
+ result.add(new Suggestion(simpleName, smart.test(c)));
+ }
+ }
+
+ private String simpleName(Element el) {
+ return el.getKind() == ElementKind.CONSTRUCTOR ? el.getEnclosingElement().getSimpleName().toString()
+ : el.getSimpleName().toString();
+ }
+
+ private List<? extends Element> membersOf(AnalyzeTask at, TypeMirror site, boolean shouldGenerateDotClassItem) {
+ if (site == null)
+ return Collections.emptyList();
+
+ switch (site.getKind()) {
+ case DECLARED: {
+ TypeElement element = (TypeElement) at.getTypes().asElement(site);
+ List<Element> result = new ArrayList<>();
+ result.addAll(at.getElements().getAllMembers(element));
+ if (shouldGenerateDotClassItem) {
+ result.add(createDotClassSymbol(at, site));
+ }
+ result.removeIf(el -> el.getKind() == ElementKind.STATIC_INIT);
+ return result;
+ }
+ case ERROR: {
+ //try current qualified name as a package:
+ TypeElement typeElement = (TypeElement) at.getTypes().asElement(site);
+ Element enclosingElement = typeElement.getEnclosingElement();
+ String parentPackageName = enclosingElement instanceof QualifiedNameable ?
+ ((QualifiedNameable)enclosingElement).getQualifiedName().toString() :
+ "";
+ Set<PackageElement> packages = listPackages(at, parentPackageName);
+ return packages.stream()
+ .filter(p -> p.getQualifiedName().equals(typeElement.getQualifiedName()))
+ .findAny()
+ .map(p -> membersOf(at, p.asType(), false))
+ .orElse(Collections.emptyList());
+ }
+ case PACKAGE: {
+ String packageName = site.toString()/*XXX*/;
+ List<Element> result = new ArrayList<>();
+ result.addAll(getEnclosedElements(at.getElements().getPackageElement(packageName)));
+ result.addAll(listPackages(at, packageName));
+ return result;
+ }
+ case BOOLEAN: case BYTE: case SHORT: case CHAR:
+ case INT: case FLOAT: case LONG: case DOUBLE:
+ case VOID: {
+ return shouldGenerateDotClassItem ?
+ Collections.singletonList(createDotClassSymbol(at, site)) :
+ Collections.emptyList();
+ }
+ case ARRAY: {
+ List<Element> result = new ArrayList<>();
+ result.add(createArrayLengthSymbol(at, site));
+ if (shouldGenerateDotClassItem)
+ result.add(createDotClassSymbol(at, site));
+ return result;
+ }
+ default:
+ return Collections.emptyList();
+ }
+ }
+
+ private List<? extends Element> membersOf(AnalyzeTask at, List<? extends Element> elements) {
+ return elements.stream()
+ .flatMap(e -> membersOf(at, e.asType(), true).stream())
+ .collect(toList());
+ }
+
+ private List<? extends Element> getEnclosedElements(PackageElement packageEl) {
+ if (packageEl == null) {
+ return Collections.emptyList();
+ }
+ //workaround for: JDK-8024687
+ while (true) {
+ try {
+ return packageEl.getEnclosedElements()
+ .stream()
+ .filter(el -> el.asType() != null)
+ .filter(el -> el.asType().getKind() != TypeKind.ERROR)
+ .collect(toList());
+ } catch (CompletionFailure cf) {
+ //ignore...
+ }
+ }
+ }
+
+ private List<? extends Element> primitivesOrVoid(AnalyzeTask at) {
+ Types types = at.getTypes();
+ return Stream.of(
+ TypeKind.BOOLEAN, TypeKind.BYTE, TypeKind.CHAR,
+ TypeKind.DOUBLE, TypeKind.FLOAT, TypeKind.INT,
+ TypeKind.LONG, TypeKind.SHORT, TypeKind.VOID)
+ .map(tk -> (Type)(tk == TypeKind.VOID ? types.getNoType(tk) : types.getPrimitiveType(tk)))
+ .map(Type::asElement)
+ .collect(toList());
+ }
+
+ private Set<PackageElement> listPackages(AnalyzeTask at, String enclosingPackage) {
+ Set<PackageElement> packs = new HashSet<>();
+ listPackages(at, StandardLocation.PLATFORM_CLASS_PATH, enclosingPackage, packs);
+ listPackages(at, StandardLocation.CLASS_PATH, enclosingPackage, packs);
+ listPackages(at, StandardLocation.SOURCE_PATH, enclosingPackage, packs);
+ return packs;
+ }
+
+ private void listPackages(AnalyzeTask at, Location loc, String currentPackage, Set<PackageElement> packs) {
+ try {
+ MemoryFileManager fm = proc.taskFactory.fileManager();
+ for (JavaFileObject file : fm.list(loc, currentPackage, fileKinds, true)) {
+ String binaryName = fm.inferBinaryName(loc, file);
+ if (!currentPackage.isEmpty() && !binaryName.startsWith(currentPackage + "."))
+ continue;
+ int nextDot = binaryName.indexOf('.', !currentPackage.isEmpty() ? currentPackage.length() + 1 : 0);
+ if (nextDot == (-1))
+ continue;
+ Elements elements = at.getElements();
+ PackageElement pack =
+ elements.getPackageElement(binaryName.substring(0, nextDot));
+ if (pack == null) {
+ //if no types in the package have ever been seen, the package will be unknown
+ //try to load a type, and then try to recognize the package again:
+ elements.getTypeElement(binaryName);
+ pack = elements.getPackageElement(binaryName.substring(0, nextDot));
+ }
+ if (pack != null)
+ packs.add(pack);
+ }
+ } catch (IOException ex) {
+ //TODO: should log?
+ }
+ }
+ //where:
+ private final Set<JavaFileObject.Kind> fileKinds = EnumSet.of(JavaFileObject.Kind.CLASS);
+
+ private Element createArrayLengthSymbol(AnalyzeTask at, TypeMirror site) {
+ Name length = Names.instance(at.getContext()).length;
+ Type intType = Symtab.instance(at.getContext()).intType;
+
+ return new VarSymbol(Flags.PUBLIC | Flags.FINAL, length, intType, ((Type) site).tsym);
+ }
+
+ private Element createDotClassSymbol(AnalyzeTask at, TypeMirror site) {
+ Name _class = Names.instance(at.getContext())._class;
+ Type classType = Symtab.instance(at.getContext()).classType;
+ Type erasedSite = (Type)at.getTypes().erasure(site);
+ classType = new ClassType(classType.getEnclosingType(), com.sun.tools.javac.util.List.of(erasedSite), classType.asElement());
+
+ return new VarSymbol(Flags.PUBLIC | Flags.STATIC | Flags.FINAL, _class, classType, erasedSite.tsym);
+ }
+
+ private Iterable<? extends Element> scopeContent(AnalyzeTask at, Scope scope, Function<Element, Iterable<? extends Element>> elementConvertor) {
+ Iterable<Scope> scopeIterable = () -> new Iterator<Scope>() {
+ private Scope currentScope = scope;
+ @Override
+ public boolean hasNext() {
+ return currentScope != null;
+ }
+ @Override
+ public Scope next() {
+ if (!hasNext())
+ throw new NoSuchElementException();
+ try {
+ return currentScope;
+ } finally {
+ currentScope = currentScope.getEnclosingScope();
+ }
+ }
+ };
+ @SuppressWarnings("unchecked")
+ List<Element> result = Util.stream(scopeIterable)
+ .flatMap(s -> Util.stream((Iterable<Element>)s.getLocalElements()))
+ .flatMap(el -> Util.stream((Iterable<Element>)elementConvertor.apply(el)))
+ .collect(toCollection(ArrayList :: new));
+ result.addAll(listPackages(at, ""));
+ return result;
+ }
+
+ @SuppressWarnings("fallthrough")
+ private Iterable<TypeMirror> findTargetType(AnalyzeTask at, TreePath forPath) {
+ if (forPath.getParentPath() == null)
+ return null;
+
+ Tree current = forPath.getLeaf();
+
+ switch (forPath.getParentPath().getLeaf().getKind()) {
+ case ASSIGNMENT: {
+ AssignmentTree tree = (AssignmentTree) forPath.getParentPath().getLeaf();
+ if (tree.getExpression() == current)
+ return Collections.singletonList(at.trees().getTypeMirror(new TreePath(forPath.getParentPath(), tree.getVariable())));
+ break;
+ }
+ case VARIABLE: {
+ VariableTree tree = (VariableTree) forPath.getParentPath().getLeaf();
+ if (tree.getInitializer()== current)
+ return Collections.singletonList(at.trees().getTypeMirror(forPath.getParentPath()));
+ break;
+ }
+ case ERRONEOUS:
+ return findTargetType(at, forPath.getParentPath());
+ case NEW_CLASS: {
+ NewClassTree nct = (NewClassTree) forPath.getParentPath().getLeaf();
+ List<TypeMirror> actuals = computeActualInvocationTypes(at, nct.getArguments(), forPath);
+
+ if (actuals != null) {
+ Iterable<Pair<ExecutableElement, ExecutableType>> candidateConstructors = newClassCandidates(at, forPath.getParentPath());
+
+ return computeSmartTypesForExecutableType(at, candidateConstructors, actuals);
+ } else {
+ return findTargetType(at, forPath.getParentPath());
+ }
+ }
+ case METHOD:
+ if (!isThrowsClause(forPath)) {
+ break;
+ }
+ // fall through
+ case THROW:
+ return Collections.singletonList(at.getElements().getTypeElement("java.lang.Throwable").asType());
+ case METHOD_INVOCATION: {
+ MethodInvocationTree mit = (MethodInvocationTree) forPath.getParentPath().getLeaf();
+ List<TypeMirror> actuals = computeActualInvocationTypes(at, mit.getArguments(), forPath);
+
+ if (actuals == null)
+ return null;
+
+ Iterable<Pair<ExecutableElement, ExecutableType>> candidateMethods = methodCandidates(at, forPath.getParentPath());
+
+ return computeSmartTypesForExecutableType(at, candidateMethods, actuals);
+ }
+ }
+
+ return null;
+ }
+
+ private List<TypeMirror> computeActualInvocationTypes(AnalyzeTask at, List<? extends ExpressionTree> arguments, TreePath currentArgument) {
+ if (currentArgument == null)
+ return null;
+
+ int paramIndex = arguments.indexOf(currentArgument.getLeaf());
+
+ if (paramIndex == (-1))
+ return null;
+
+ List<TypeMirror> actuals = new ArrayList<>();
+
+ for (ExpressionTree arg : arguments.subList(0, paramIndex)) {
+ actuals.add(at.trees().getTypeMirror(new TreePath(currentArgument.getParentPath(), arg)));
+ }
+
+ return actuals;
+ }
+
+ private List<Pair<ExecutableElement, ExecutableType>> filterExecutableTypesByArguments(AnalyzeTask at, Iterable<Pair<ExecutableElement, ExecutableType>> candidateMethods, List<TypeMirror> precedingActualTypes) {
+ List<Pair<ExecutableElement, ExecutableType>> candidate = new ArrayList<>();
+ int paramIndex = precedingActualTypes.size();
+
+ OUTER:
+ for (Pair<ExecutableElement, ExecutableType> method : candidateMethods) {
+ boolean varargInvocation = paramIndex >= method.snd.getParameterTypes().size();
+
+ for (int i = 0; i < paramIndex; i++) {
+ TypeMirror actual = precedingActualTypes.get(i);
+
+ if (this.parameterType(method.fst, method.snd, i, !varargInvocation)
+ .noneMatch(formal -> at.getTypes().isAssignable(actual, formal))) {
+ continue OUTER;
+ }
+ }
+ candidate.add(method);
+ }
+
+ return candidate;
+ }
+
+ private Stream<TypeMirror> parameterType(ExecutableElement method, ExecutableType methodType, int paramIndex, boolean allowVarArgsArray) {
+ int paramCount = methodType.getParameterTypes().size();
+ if (paramIndex >= paramCount && !method.isVarArgs())
+ return Stream.empty();
+ if (paramIndex < paramCount - 1 || !method.isVarArgs())
+ return Stream.of(methodType.getParameterTypes().get(paramIndex));
+ TypeMirror varargType = methodType.getParameterTypes().get(paramCount - 1);
+ TypeMirror elemenType = ((ArrayType) varargType).getComponentType();
+ if (paramIndex >= paramCount || !allowVarArgsArray)
+ return Stream.of(elemenType);
+ return Stream.of(varargType, elemenType);
+ }
+
+ private List<TypeMirror> computeSmartTypesForExecutableType(AnalyzeTask at, Iterable<Pair<ExecutableElement, ExecutableType>> candidateMethods, List<TypeMirror> precedingActualTypes) {
+ List<TypeMirror> candidate = new ArrayList<>();
+ int paramIndex = precedingActualTypes.size();
+
+ this.filterExecutableTypesByArguments(at, candidateMethods, precedingActualTypes)
+ .stream()
+ .flatMap(method -> parameterType(method.fst, method.snd, paramIndex, true))
+ .forEach(candidate::add);
+
+ return candidate;
+ }
+
+
+ private TypeMirror resultTypeOf(Element el) {
+ //TODO: should reflect the type of site!
+ switch (el.getKind()) {
+ case METHOD:
+ return ((ExecutableElement) el).getReturnType();
+ case CONSTRUCTOR:
+ case INSTANCE_INIT: case STATIC_INIT: //TODO: should be filtered out
+ return el.getEnclosingElement().asType();
+ default:
+ return el.asType();
+ }
+ }
+
+ private void addScopeElements(AnalyzeTask at, Scope scope, Function<Element, Iterable<? extends Element>> elementConvertor, Predicate<Element> filter, Predicate<Element> smartFilter, List<Suggestion> result) {
+ addElements(scopeContent(at, scope, elementConvertor), filter, smartFilter, result);
+ }
+
+ private Iterable<Pair<ExecutableElement, ExecutableType>> methodCandidates(AnalyzeTask at, TreePath invocation) {
+ MethodInvocationTree mit = (MethodInvocationTree) invocation.getLeaf();
+ ExpressionTree select = mit.getMethodSelect();
+ List<Pair<ExecutableElement, ExecutableType>> result = new ArrayList<>();
+ Predicate<Element> accessibility = createAccessibilityFilter(at, invocation);
+
+ switch (select.getKind()) {
+ case MEMBER_SELECT:
+ MemberSelectTree mst = (MemberSelectTree) select;
+ TreePath tp = new TreePath(new TreePath(invocation, select), mst.getExpression());
+ TypeMirror site = at.trees().getTypeMirror(tp);
+
+ if (site == null || site.getKind() != TypeKind.DECLARED)
+ break;
+
+ Element siteEl = at.getTypes().asElement(site);
+
+ if (siteEl == null)
+ break;
+
+ if (isStaticContext(at, tp)) {
+ accessibility = accessibility.and(STATIC_ONLY);
+ }
+
+ for (ExecutableElement ee : ElementFilter.methodsIn(membersOf(at, siteEl.asType(), false))) {
+ if (ee.getSimpleName().contentEquals(mst.getIdentifier())) {
+ if (accessibility.test(ee)) {
+ result.add(Pair.of(ee, (ExecutableType) at.getTypes().asMemberOf((DeclaredType) site, ee)));
+ }
+ }
+ }
+ break;
+ case IDENTIFIER:
+ IdentifierTree it = (IdentifierTree) select;
+ for (ExecutableElement ee : ElementFilter.methodsIn(scopeContent(at, at.trees().getScope(invocation), IDENTITY))) {
+ if (ee.getSimpleName().contentEquals(it.getName())) {
+ if (accessibility.test(ee)) {
+ result.add(Pair.of(ee, (ExecutableType) ee.asType())); //XXX: proper site
+ }
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return result;
+ }
+
+ private Iterable<Pair<ExecutableElement, ExecutableType>> newClassCandidates(AnalyzeTask at, TreePath newClassPath) {
+ NewClassTree nct = (NewClassTree) newClassPath.getLeaf();
+ Element type = at.trees().getElement(new TreePath(newClassPath.getParentPath(), nct.getIdentifier()));
+ TypeMirror targetType = at.trees().getTypeMirror(newClassPath);
+ if (targetType == null || targetType.getKind() != TypeKind.DECLARED) {
+ Iterable<TypeMirror> targetTypes = findTargetType(at, newClassPath);
+ if (targetTypes == null)
+ targetTypes = Collections.emptyList();
+ targetType =
+ StreamSupport.stream(targetTypes.spliterator(), false)
+ .filter(t -> at.getTypes().asElement(t) == type)
+ .findAny()
+ .orElse(at.getTypes().erasure(type.asType()));
+ }
+ List<Pair<ExecutableElement, ExecutableType>> candidateConstructors = new ArrayList<>();
+ Predicate<Element> accessibility = createAccessibilityFilter(at, newClassPath);
+
+ if (targetType != null &&
+ targetType.getKind() == TypeKind.DECLARED &&
+ type != null &&
+ (type.getKind().isClass() || type.getKind().isInterface())) {
+ for (ExecutableElement constr : ElementFilter.constructorsIn(type.getEnclosedElements())) {
+ if (accessibility.test(constr)) {
+ ExecutableType constrType =
+ (ExecutableType) at.getTypes().asMemberOf((DeclaredType) targetType, constr);
+ candidateConstructors.add(Pair.of(constr, constrType));
+ }
+ }
+ }
+
+ return candidateConstructors;
+ }
+
+ @Override
+ public String documentation(String code, int cursor) {
+ code = code.substring(0, cursor);
+ if (code.trim().isEmpty()) { //TODO: comment handling
+ code += ";";
+ }
+
+ if (guessKind(code) == Kind.IMPORT)
+ return null;
+
+ OuterWrap codeWrap = wrapInClass(Wrap.methodWrap(code));
+ AnalyzeTask at = proc.taskFactory.new AnalyzeTask(codeWrap);
+ SourcePositions sp = at.trees().getSourcePositions();
+ CompilationUnitTree topLevel = at.cuTree();
+ TreePath tp = pathFor(topLevel, sp, codeWrap.snippetIndexToWrapIndex(cursor));
+
+ if (tp == null)
+ return null;
+
+ TreePath prevPath = null;
+ while (tp != null && tp.getLeaf().getKind() != Kind.METHOD_INVOCATION && tp.getLeaf().getKind() != Kind.NEW_CLASS) {
+ prevPath = tp;
+ tp = tp.getParentPath();
+ }
+
+ if (tp == null)
+ return null;
+
+ Iterable<Pair<ExecutableElement, ExecutableType>> candidates;
+ List<? extends ExpressionTree> arguments;
+
+ if (tp.getLeaf().getKind() == Kind.METHOD_INVOCATION) {
+ MethodInvocationTree mit = (MethodInvocationTree) tp.getLeaf();
+ candidates = methodCandidates(at, tp);
+ arguments = mit.getArguments();
+ } else {
+ NewClassTree nct = (NewClassTree) tp.getLeaf();
+ candidates = newClassCandidates(at, tp);
+ arguments = nct.getArguments();
+ }
+
+ if (!isEmptyArgumentsContext(arguments)) {
+ List<TypeMirror> actuals = computeActualInvocationTypes(at, arguments, prevPath);
+ List<TypeMirror> fullActuals = actuals != null ? actuals : Collections.emptyList();
+
+ candidates =
+ this.filterExecutableTypesByArguments(at, candidates, fullActuals)
+ .stream()
+ .filter(method -> parameterType(method.fst, method.snd, fullActuals.size(), true).findAny().isPresent())
+ .collect(Collectors.toList());
+ }
+
+ return Util.stream(candidates)
+ .map(method -> Util.expunge(element2String(method.fst)))
+ .collect(joining("\n"));
+ }
+
+ private boolean isEmptyArgumentsContext(List<? extends ExpressionTree> arguments) {
+ if (arguments.size() == 1) {
+ Tree firstArgument = arguments.get(0);
+ return firstArgument.getKind() == Kind.ERRONEOUS;
+ }
+ return false;
+ }
+
+ private String element2String(Element el) {
+ switch (el.getKind()) {
+ case ANNOTATION_TYPE: case CLASS: case ENUM: case INTERFACE:
+ return ((TypeElement) el).getQualifiedName().toString();
+ case FIELD:
+ return element2String(el.getEnclosingElement()) + "." + el.getSimpleName() + ":" + el.asType();
+ case ENUM_CONSTANT:
+ return element2String(el.getEnclosingElement()) + "." + el.getSimpleName();
+ case EXCEPTION_PARAMETER: case LOCAL_VARIABLE: case PARAMETER: case RESOURCE_VARIABLE:
+ return el.getSimpleName() + ":" + el.asType();
+ case CONSTRUCTOR: case METHOD:
+ StringBuilder header = new StringBuilder();
+ header.append(element2String(el.getEnclosingElement()));
+ if (el.getKind() == ElementKind.METHOD) {
+ header.append(".");
+ header.append(el.getSimpleName());
+ }
+ header.append("(");
+ String sep = "";
+ ExecutableElement method = (ExecutableElement) el;
+ for (Iterator<? extends VariableElement> i = method.getParameters().iterator(); i.hasNext();) {
+ VariableElement p = i.next();
+ header.append(sep);
+ if (!i.hasNext() && method.isVarArgs()) {
+ header.append(unwrapArrayType(p.asType()));
+ header.append("...");
+
+ } else {
+ header.append(p.asType());
+ }
+ header.append(" ");
+ header.append(p.getSimpleName());
+ sep = ", ";
+ }
+ header.append(")");
+ return header.toString();
+ default:
+ return el.toString();
+ }
+ }
+ private TypeMirror unwrapArrayType(TypeMirror arrayType) {
+ if (arrayType.getKind() == TypeKind.ARRAY) {
+ return ((ArrayType)arrayType).getComponentType();
+ }
+ return arrayType;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/StatementSnippet.java Thu Oct 22 11:12:55 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.StatementKey;
+
+/**
+ * Snippet for a statement.
+ * The Kind is {@link jdk.jshell.Snippet.Kind#STATEMENT}.
+ * <p>
+ * <code>StatementSnippet</code> is immutable: an access to
+ * any of its methods will always return the same result.
+ * and thus is thread-safe.
+ * @jls 14.5: Statement.
+ */
+public class StatementSnippet extends Snippet {
+
+ StatementSnippet(StatementKey key, String userSource, Wrap guts) {
+ super(key, userSource, guts, null, SubKind.STATEMENT_SUBKIND);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/TaskFactory.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,465 @@
+/*
+ * 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 com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.util.Trees;
+import com.sun.tools.javac.api.JavacTaskImpl;
+import com.sun.tools.javac.api.JavacTool;
+import com.sun.tools.javac.util.Context;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import javax.tools.Diagnostic;
+import javax.tools.DiagnosticCollector;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileManager;
+import javax.tools.JavaFileObject;
+import javax.tools.ToolProvider;
+import static jdk.jshell.Util.*;
+import com.sun.source.tree.ImportTree;
+import com.sun.tools.javac.code.Types;
+import com.sun.tools.javac.util.JavacMessages;
+import jdk.jshell.MemoryFileManager.OutputMemoryJavaFileObject;
+import java.util.Collections;
+import java.util.Locale;
+import static javax.tools.StandardLocation.CLASS_OUTPUT;
+import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
+import java.io.File;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.lang.model.util.Elements;
+import javax.tools.FileObject;
+import jdk.jshell.MemoryFileManager.SourceMemoryJavaFileObject;
+import jdk.jshell.ClassTracker.ClassInfo;
+
+/**
+ * The primary interface to the compiler API. Parsing, analysis, and
+ * compilation to class files (in memory).
+ * @author Robert Field
+ */
+class TaskFactory {
+
+ private final JavaCompiler compiler;
+ private final MemoryFileManager fileManager;
+ private final JShell state;
+ private String classpath = System.getProperty("java.class.path");
+
+ TaskFactory(JShell state) {
+ this.state = state;
+ this.compiler = ToolProvider.getSystemJavaCompiler();
+ if (compiler == null) {
+ throw new UnsupportedOperationException("Compiler not available, must be run with full JDK 9.");
+ }
+ if (!System.getProperty("java.specification.version").equals("1.9")) {
+ throw new UnsupportedOperationException("Wrong compiler, must be run with full JDK 9.");
+ }
+ this.fileManager = new MemoryFileManager(
+ compiler.getStandardFileManager(null, null, null), state);
+ }
+
+ void addToClasspath(String path) {
+ classpath = classpath + File.pathSeparator + path;
+ List<String> args = new ArrayList<>();
+ args.add(classpath);
+ fileManager().handleOption("-classpath", args.iterator());
+ }
+
+ MemoryFileManager fileManager() {
+ return fileManager;
+ }
+
+ private interface SourceHandler<T> {
+
+ JavaFileObject sourceToFileObject(MemoryFileManager fm, T t);
+
+ Diag diag(Diagnostic<? extends JavaFileObject> d);
+ }
+
+ private class StringSourceHandler implements SourceHandler<String> {
+
+ @Override
+ public JavaFileObject sourceToFileObject(MemoryFileManager fm, String src) {
+ return fm.createSourceFileObject(src, "$NeverUsedName$", src);
+ }
+
+ @Override
+ public Diag diag(final Diagnostic<? extends JavaFileObject> d) {
+ return new Diag() {
+
+ @Override
+ public boolean isError() {
+ return d.getKind() == Diagnostic.Kind.ERROR;
+ }
+
+ @Override
+ public long getPosition() {
+ return d.getPosition();
+ }
+
+ @Override
+ public long getStartPosition() {
+ return d.getStartPosition();
+ }
+
+ @Override
+ public long getEndPosition() {
+ return d.getEndPosition();
+ }
+
+ @Override
+ public String getCode() {
+ return d.getCode();
+ }
+
+ @Override
+ public String getMessage(Locale locale) {
+ return expunge(d.getMessage(locale));
+ }
+
+ @Override
+ Unit unitOrNull() {
+ return null;
+ }
+ };
+ }
+ }
+
+ private class WrapSourceHandler implements SourceHandler<OuterWrap> {
+
+ final OuterWrap wrap;
+
+ WrapSourceHandler(OuterWrap wrap) {
+ this.wrap = wrap;
+ }
+
+ @Override
+ public JavaFileObject sourceToFileObject(MemoryFileManager fm, OuterWrap w) {
+ return fm.createSourceFileObject(w, w.classFullName(), w.wrapped());
+ }
+
+ @Override
+ public Diag diag(Diagnostic<? extends JavaFileObject> d) {
+ return wrap.wrapDiag(d);
+ }
+ }
+
+ private class UnitSourceHandler implements SourceHandler<Unit> {
+
+ @Override
+ public JavaFileObject sourceToFileObject(MemoryFileManager fm, Unit u) {
+ return fm.createSourceFileObject(u,
+ state.maps.classFullName(u.snippet()),
+ u.snippet().outerWrap().wrapped());
+ }
+
+ @Override
+ public Diag diag(Diagnostic<? extends JavaFileObject> d) {
+ SourceMemoryJavaFileObject smjfo = (SourceMemoryJavaFileObject) d.getSource();
+ Unit u = (Unit) smjfo.getOrigin();
+ return u.snippet().outerWrap().wrapDiag(d);
+ }
+ }
+
+ /**
+ * Parse a snippet of code (as a String) using the parser subclass. Return
+ * the parse tree (and errors).
+ */
+ class ParseTask extends BaseTask {
+
+ private final CompilationUnitTree cut;
+ private final List<? extends Tree> units;
+
+ ParseTask(final String source) {
+ super(Stream.of(source),
+ new StringSourceHandler(),
+ "-XDallowStringFolding=false", "-proc:none");
+ ReplParserFactory.instance(getContext());
+ Iterable<? extends CompilationUnitTree> asts = parse();
+ Iterator<? extends CompilationUnitTree> it = asts.iterator();
+ if (it.hasNext()) {
+ this.cut = it.next();
+ List<? extends ImportTree> imps = cut.getImports();
+ this.units = !imps.isEmpty() ? imps : cut.getTypeDecls();
+ } else {
+ this.cut = null;
+ this.units = Collections.emptyList();
+ }
+ }
+
+ private Iterable<? extends CompilationUnitTree> parse() {
+ try {
+ return task.parse();
+ } catch (Exception ex) {
+ throw new InternalError("Exception during parse - " + ex.getMessage(), ex);
+ }
+ }
+
+ List<? extends Tree> units() {
+ return units;
+ }
+
+ @Override
+ CompilationUnitTree cuTree() {
+ return cut;
+ }
+ }
+
+ /**
+ * Run the normal "analyze()" pass of the compiler over the wrapped snippet.
+ */
+ class AnalyzeTask extends BaseTask {
+
+ private final CompilationUnitTree cut;
+
+ AnalyzeTask(final OuterWrap wrap) {
+ this(Stream.of(wrap),
+ new WrapSourceHandler(wrap),
+ "-XDshouldStopPolicy=FLOW", "-proc:none");
+ }
+
+ AnalyzeTask(final Collection<Unit> units) {
+ this(units.stream(), new UnitSourceHandler(),
+ "-XDshouldStopPolicy=FLOW", "-Xlint:unchecked", "-proc:none");
+ }
+
+ <T>AnalyzeTask(final Stream<T> stream, SourceHandler<T> sourceHandler,
+ String... extraOptions) {
+ super(stream, sourceHandler, extraOptions);
+ Iterator<? extends CompilationUnitTree> cuts = analyze().iterator();
+ if (cuts.hasNext()) {
+ this.cut = cuts.next();
+ //proc.debug("AnalyzeTask element=%s cutp=%s cut=%s\n", e, cutp, cut);
+ } else {
+ this.cut = null;
+ //proc.debug("AnalyzeTask -- no elements -- %s\n", getDiagnostics());
+ }
+ }
+
+ private Iterable<? extends CompilationUnitTree> analyze() {
+ try {
+ Iterable<? extends CompilationUnitTree> cuts = task.parse();
+ task.analyze();
+ return cuts;
+ } catch (Exception ex) {
+ throw new InternalError("Exception during analyze - " + ex.getMessage(), ex);
+ }
+ }
+
+ @Override
+ CompilationUnitTree cuTree() {
+ return cut;
+ }
+
+ Elements getElements() {
+ return task.getElements();
+ }
+
+ javax.lang.model.util.Types getTypes() {
+ return task.getTypes();
+ }
+ }
+
+ /**
+ * Unit the wrapped snippet to class files.
+ */
+ class CompileTask extends BaseTask {
+
+ private final Map<Unit, List<OutputMemoryJavaFileObject>> classObjs = new HashMap<>();
+
+ CompileTask(Collection<Unit> units) {
+ super(units.stream(), new UnitSourceHandler(),
+ "-Xlint:unchecked", "-proc:none");
+ }
+
+ boolean compile() {
+ fileManager.registerClassFileCreationListener(this::listenForNewClassFile);
+ boolean result = task.call();
+ fileManager.registerClassFileCreationListener(null);
+ return result;
+ }
+
+
+ List<ClassInfo> classInfoList(Unit u) {
+ List<OutputMemoryJavaFileObject> l = classObjs.get(u);
+ if (l == null) return Collections.emptyList();
+ return l.stream()
+ .map(fo -> state.classTracker.classInfo(fo.getName(), fo.getBytes()))
+ .collect(Collectors.toList());
+ }
+
+ private void listenForNewClassFile(OutputMemoryJavaFileObject jfo, JavaFileManager.Location location,
+ String className, JavaFileObject.Kind kind, FileObject sibling) {
+ //debug("listenForNewClassFile %s loc=%s kind=%s\n", className, location, kind);
+ if (location == CLASS_OUTPUT) {
+ state.debug(DBG_GEN, "Compiler generating class %s\n", className);
+ Unit u = ((sibling instanceof SourceMemoryJavaFileObject)
+ && (((SourceMemoryJavaFileObject) sibling).getOrigin() instanceof Unit))
+ ? (Unit) ((SourceMemoryJavaFileObject) sibling).getOrigin()
+ : null;
+ classObjs.compute(u, (k, v) -> (v == null)? new ArrayList<>() : v)
+ .add(jfo);
+ }
+ }
+
+ @Override
+ CompilationUnitTree cuTree() {
+ throw new UnsupportedOperationException("Not supported.");
+ }
+ }
+
+ abstract class BaseTask {
+
+ final DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
+ final JavacTaskImpl task;
+ private DiagList diags = null;
+ private final SourceHandler<?> sourceHandler;
+ private final Context context = new Context();
+ private Types types;
+ private JavacMessages messages;
+ private Trees trees;
+
+ private <T>BaseTask(Stream<T> inputs,
+ //BiFunction<MemoryFileManager, T, JavaFileObject> sfoCreator,
+ SourceHandler<T> sh,
+ String... extraOptions) {
+ this.sourceHandler = sh;
+ List<String> options = Arrays.asList(extraOptions);
+ Iterable<? extends JavaFileObject> compilationUnits = inputs
+ .map(in -> sh.sourceToFileObject(fileManager, in))
+ .collect(Collectors.toList());
+ this.task = (JavacTaskImpl) ((JavacTool) compiler).getTask(null,
+ fileManager, diagnostics, options, null,
+ compilationUnits, context);
+ }
+
+ abstract CompilationUnitTree cuTree();
+
+ Diag diag(Diagnostic<? extends JavaFileObject> diag) {
+ return sourceHandler.diag(diag);
+ }
+
+ Context getContext() {
+ return context;
+ }
+
+ Types types() {
+ if (types == null) {
+ types = Types.instance(context);
+ }
+ return types;
+ }
+
+ JavacMessages messages() {
+ if (messages == null) {
+ messages = JavacMessages.instance(context);
+ }
+ return messages;
+ }
+
+ Trees trees() {
+ if (trees == null) {
+ trees = Trees.instance(task);
+ }
+ return trees;
+ }
+
+ // ------------------ diags functionality
+
+ DiagList getDiagnostics() {
+ if (diags == null) {
+ LinkedHashMap<String, Diag> diagMap = new LinkedHashMap<>();
+ for (Diagnostic<? extends JavaFileObject> in : diagnostics.getDiagnostics()) {
+ Diag d = diag(in);
+ String uniqueKey = d.getCode() + ":" + d.getPosition() + ":" + d.getMessage(null);
+ diagMap.put(uniqueKey, d);
+ }
+ diags = new DiagList(diagMap.values());
+ }
+ return diags;
+ }
+
+ boolean hasErrors() {
+ return getDiagnostics().hasErrors();
+ }
+
+ String shortErrorMessage() {
+ StringBuilder sb = new StringBuilder();
+ for (Diag diag : getDiagnostics()) {
+ for (String line : diag.getMessage(null).split("\\r?\\n")) {
+ if (!line.trim().startsWith("location:")) {
+ sb.append(line);
+ }
+ }
+ }
+ return sb.toString();
+ }
+
+ void debugPrintDiagnostics(String src) {
+ for (Diag diag : getDiagnostics()) {
+ state.debug(DBG_GEN, "ERROR --\n");
+ for (String line : diag.getMessage(null).split("\\r?\\n")) {
+ if (!line.trim().startsWith("location:")) {
+ state.debug(DBG_GEN, "%s\n", line);
+ }
+ }
+ int start = (int) diag.getStartPosition();
+ int end = (int) diag.getEndPosition();
+ if (src != null) {
+ String[] srcLines = src.split("\\r?\\n");
+ for (String line : srcLines) {
+ state.debug(DBG_GEN, "%s\n", line);
+ }
+
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < start; ++i) {
+ sb.append(' ');
+ }
+ sb.append('^');
+ if (end > start) {
+ for (int i = start + 1; i < end; ++i) {
+ sb.append('-');
+ }
+ sb.append('^');
+ }
+ state.debug(DBG_GEN, "%s\n", sb.toString());
+ }
+ state.debug(DBG_GEN, "printDiagnostics start-pos = %d ==> %d -- wrap = %s\n",
+ diag.getStartPosition(), start, this);
+ state.debug(DBG_GEN, "Code: %s\n", diag.getCode());
+ state.debug(DBG_GEN, "Pos: %d (%d - %d) -- %s\n", diag.getPosition(),
+ diag.getStartPosition(), diag.getEndPosition(), diag.getMessage(null));
+ }
+ }
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/TreeDependencyScanner.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,128 @@
+/*
+ * 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.source.tree.ClassTree;
+import com.sun.source.tree.IdentifierTree;
+import com.sun.source.tree.ImportTree;
+import com.sun.source.tree.MemberSelectTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.PackageTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.util.TreeScanner;
+import com.sun.tools.javac.util.DefinedBy;
+import com.sun.tools.javac.util.DefinedBy.Api;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import javax.lang.model.element.Name;
+
+/**
+ * Search a compiler API parse tree for dependencies.
+ */
+class TreeDependencyScanner extends TreeScanner<Void, Set<String>> {
+
+ private final Set<String> decl = new HashSet<>();
+ private final Set<String> body = new HashSet<>();
+
+ public void scan(Tree node) {
+ scan(node, decl);
+ }
+
+ public Collection<String> declareReferences() {
+ return decl;
+ }
+
+ public Collection<String> bodyReferences() {
+ return body;
+ }
+
+ private void add(Set<String> p, Name name) {
+ p.add(name.toString());
+ }
+
+ // -- Differentiate declaration references from body references ---
+
+ @Override @DefinedBy(Api.COMPILER_TREE)
+ public Void visitClass(ClassTree node, Set<String> p) {
+ scan(node.getModifiers(), p);
+ scan(node.getTypeParameters(), p);
+ scan(node.getExtendsClause(), p);
+ scan(node.getImplementsClause(), p);
+ scan(node.getMembers(), body);
+ return null;
+ }
+
+ @Override @DefinedBy(Api.COMPILER_TREE)
+ public Void visitMethod(MethodTree node, Set<String> p) {
+ scan(node.getModifiers(), p);
+ scan(node.getReturnType(), p);
+ scan(node.getTypeParameters(), p);
+ scan(node.getParameters(), p);
+ scan(node.getReceiverParameter(), p);
+ scan(node.getThrows(), p);
+ scan(node.getBody(), body);
+ scan(node.getDefaultValue(), body);
+ return null;
+ }
+
+ @Override @DefinedBy(Api.COMPILER_TREE)
+ public Void visitVariable(VariableTree node, Set<String> p) {
+ scan(node.getModifiers(), p);
+ scan(node.getType(), p);
+ scan(node.getNameExpression(), p);
+ scan(node.getInitializer(), body);
+ return null;
+ }
+
+ // --- Ignore these ---
+
+ @Override @DefinedBy(Api.COMPILER_TREE)
+ public Void visitPackage(PackageTree node, Set<String> p) {
+ return null;
+ }
+
+ @Override @DefinedBy(Api.COMPILER_TREE)
+ public Void visitImport(ImportTree node, Set<String> p) {
+ return null;
+ }
+
+
+ // -- Actual Symbol names ---
+
+ @Override @DefinedBy(Api.COMPILER_TREE)
+ public Void visitMemberSelect(MemberSelectTree node, Set<String> p) {
+ add(p, node.getIdentifier());
+ return super.visitMemberSelect(node, p);
+ }
+
+ @Override @DefinedBy(Api.COMPILER_TREE)
+ public Void visitIdentifier(IdentifierTree node, Set<String> p) {
+ add(p, node.getName());
+ return super.visitIdentifier(node, p);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/TreeDissector.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,283 @@
+/*
+ * 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 com.sun.source.tree.ClassTree;
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.ReturnTree;
+import com.sun.source.tree.StatementTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.VariableTree;
+import com.sun.source.util.SourcePositions;
+import com.sun.source.util.TreePath;
+import com.sun.source.util.Trees;
+import com.sun.tools.javac.code.Type;
+import com.sun.tools.javac.code.Type.MethodType;
+import com.sun.tools.javac.code.Types;
+import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
+import com.sun.tools.javac.util.JavacMessages;
+import com.sun.tools.javac.util.Name;
+import static jdk.jshell.Util.isDoIt;
+import jdk.jshell.Wrap.Range;
+import java.util.List;
+import java.util.Locale;
+import java.util.function.BinaryOperator;
+import javax.lang.model.type.TypeMirror;
+
+/**
+ * Utilities for analyzing compiler API parse trees.
+ * @author Robert Field
+ */
+
+class TreeDissector {
+
+ private static final String OBJECT_TYPE = "Object";
+
+ static class ExpressionInfo {
+
+ boolean isNonVoid;
+ String typeName;
+ ExpressionTree tree;
+ String signature;
+ }
+
+ private final TaskFactory.BaseTask bt;
+ private ClassTree firstClass;
+ private SourcePositions theSourcePositions = null;
+
+ TreeDissector(TaskFactory.BaseTask bt) {
+ this.bt = bt;
+ }
+
+
+ ClassTree firstClass() {
+ if (firstClass == null) {
+ firstClass = computeFirstClass();
+ }
+ return firstClass;
+ }
+
+ CompilationUnitTree cuTree() {
+ return bt.cuTree();
+ }
+
+ Types types() {
+ return bt.types();
+ }
+
+ Trees trees() {
+ return bt.trees();
+ }
+
+ SourcePositions getSourcePositions() {
+ if (theSourcePositions == null) {
+ theSourcePositions = trees().getSourcePositions();
+ }
+ return theSourcePositions;
+ }
+
+ int getStartPosition(Tree tree) {
+ return (int) getSourcePositions().getStartPosition(cuTree(), tree);
+ }
+
+ int getEndPosition(Tree tree) {
+ return (int) getSourcePositions().getEndPosition(cuTree(), tree);
+ }
+
+ Range treeToRange(Tree tree) {
+ return new Range(getStartPosition(tree), getEndPosition(tree));
+ }
+
+ Range treeListToRange(List<? extends Tree> treeList) {
+ int start = Integer.MAX_VALUE;
+ int end = -1;
+ for (Tree t : treeList) {
+ int tstart = getStartPosition(t);
+ int tend = getEndPosition(t);
+ if (tstart < start) {
+ start = tstart;
+ }
+ if (tend > end) {
+ end = tend;
+ }
+ }
+ if (start == Integer.MAX_VALUE) {
+ return null;
+ }
+ return new Range(start, end);
+ }
+
+ Tree firstClassMember() {
+ if (firstClass() != null) {
+ //TODO: missing classes
+ for (Tree mem : firstClass().getMembers()) {
+ if (mem.getKind() == Tree.Kind.VARIABLE) {
+ return mem;
+ }
+ if (mem.getKind() == Tree.Kind.METHOD) {
+ MethodTree mt = (MethodTree) mem;
+ if (!isDoIt(mt.getName()) && !mt.getName().toString().equals("<init>")) {
+ return mt;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ StatementTree firstStatement() {
+ if (firstClass() != null) {
+ for (Tree mem : firstClass().getMembers()) {
+ if (mem.getKind() == Tree.Kind.METHOD) {
+ MethodTree mt = (MethodTree) mem;
+ if (isDoIt(mt.getName())) {
+ List<? extends StatementTree> stmts = mt.getBody().getStatements();
+ if (!stmts.isEmpty()) {
+ return stmts.get(0);
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ VariableTree firstVariable() {
+ if (firstClass() != null) {
+ for (Tree mem : firstClass().getMembers()) {
+ if (mem.getKind() == Tree.Kind.VARIABLE) {
+ VariableTree vt = (VariableTree) mem;
+ return vt;
+ }
+ }
+ }
+ return null;
+ }
+
+ private ClassTree computeFirstClass() {
+ if (cuTree() == null) {
+ return null;
+ }
+ for (Tree decl : cuTree().getTypeDecls()) {
+ if (decl.getKind() == Tree.Kind.CLASS || decl.getKind() == Tree.Kind.INTERFACE) {
+ return (ClassTree) decl;
+ }
+ }
+ return null;
+ }
+
+ ExpressionInfo typeOfReturnStatement(JavacMessages messages, BinaryOperator<String> fullClassNameAndPackageToClass) {
+ ExpressionInfo ei = new ExpressionInfo();
+ Tree unitTree = firstStatement();
+ if (unitTree instanceof ReturnTree) {
+ ei.tree = ((ReturnTree) unitTree).getExpression();
+ if (ei.tree != null) {
+ TreePath viPath = trees().getPath(cuTree(), ei.tree);
+ if (viPath != null) {
+ TypeMirror tm = trees().getTypeMirror(viPath);
+ if (tm != null) {
+ Type type = (Type)tm;
+ TypePrinter tp = new TypePrinter(messages, fullClassNameAndPackageToClass, type);
+ ei.typeName = tp.visit(type, Locale.getDefault());
+ switch (tm.getKind()) {
+ case VOID:
+ case NONE:
+ case ERROR:
+ case OTHER:
+ break;
+ case NULL:
+ ei.isNonVoid = true;
+ ei.typeName = OBJECT_TYPE;
+ break;
+ default: {
+ ei.isNonVoid = true;
+ break;
+
+ }
+ }
+ }
+ }
+ }
+ }
+ return ei;
+ }
+
+ String typeOfMethod() {
+ Tree unitTree = firstClassMember();
+ if (unitTree instanceof JCMethodDecl) {
+ JCMethodDecl mtree = (JCMethodDecl) unitTree;
+ Type mt = types().erasure(mtree.type);
+ if (mt instanceof MethodType) {
+ return signature(types(), (MethodType) mt);
+ }
+ }
+ return null;
+ }
+
+ static String signature(Types types, MethodType mt) {
+ TDSignatureGenerator sg = new TDSignatureGenerator(types);
+ sg.assembleSig(mt);
+ return sg.toString();
+ }
+
+ /**
+ * Signature Generation
+ */
+ private static class TDSignatureGenerator extends Types.SignatureGenerator {
+
+ /**
+ * An output buffer for type signatures.
+ */
+ StringBuilder sb = new StringBuilder();
+
+ TDSignatureGenerator(Types types) {
+ super(types);
+ }
+
+ @Override
+ protected void append(char ch) {
+ sb.append(ch);
+ }
+
+ @Override
+ protected void append(byte[] ba) {
+ sb.append(new String(ba));
+ }
+
+ @Override
+ protected void append(Name name) {
+ sb.append(name);
+ }
+
+ @Override
+ public String toString() {
+ return sb.toString();
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/TypeDeclSnippet.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,54 @@
+/*
+ * 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.TypeDeclKey;
+
+/**
+ * Snippet for a type definition (a class, interface, enum, or annotation
+ * interface definition).
+ * The Kind is {@link jdk.jshell.Snippet.Kind#TYPE_DECL}.
+ * <p>
+ * <code>TypeDeclSnippet</code> is immutable: an access to
+ * any of its methods will always return the same result.
+ * and thus is thread-safe.
+ */
+public class TypeDeclSnippet extends DeclarationSnippet {
+
+ TypeDeclSnippet(TypeDeclKey key, String userSource, Wrap guts,
+ String unitName, SubKind subkind, Wrap corralled,
+ Collection<String> declareReferences,
+ Collection<String> bodyReferences) {
+ super(key, userSource, guts, unitName, subkind, corralled, declareReferences, bodyReferences);
+ }
+
+ /**** internal access ****/
+
+ @Override
+ TypeDeclKey key() {
+ return (TypeDeclKey) super.key();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/TypePrinter.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,148 @@
+/*
+ * 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 static com.sun.tools.javac.code.Flags.COMPOUND;
+import static com.sun.tools.javac.code.Kinds.Kind.PCK;
+import com.sun.tools.javac.code.Printer;
+import com.sun.tools.javac.code.Symbol;
+import com.sun.tools.javac.code.Symbol.ClassSymbol;
+import com.sun.tools.javac.code.Symbol.PackageSymbol;
+import com.sun.tools.javac.code.Type;
+import com.sun.tools.javac.code.Type.ClassType;
+import com.sun.tools.javac.util.JavacMessages;
+import java.util.Locale;
+import java.util.function.BinaryOperator;
+
+/**
+ * Print types in source form.
+ */
+class TypePrinter extends Printer {
+
+ private final JavacMessages messages;
+ private final BinaryOperator<String> fullClassNameAndPackageToClass;
+ private final Type typeToPrint;
+
+ TypePrinter(JavacMessages messages, BinaryOperator<String> fullClassNameAndPackageToClass, Type typeToPrint) {
+ this.messages = messages;
+ this.fullClassNameAndPackageToClass = fullClassNameAndPackageToClass;
+ this.typeToPrint = typeToPrint;
+ }
+
+ @Override
+ protected String localize(Locale locale, String key, Object... args) {
+ return messages.getLocalizedString(locale, key, args);
+ }
+
+ @Override
+ protected String capturedVarId(Type.CapturedType t, Locale locale) {
+ throw new InternalError("should never call this");
+ }
+
+ @Override
+ public String visitCapturedType(Type.CapturedType t, Locale locale) {
+ if (t == typeToPrint) {
+ return visit(t.getUpperBound(), locale);
+ } else {
+ return visit(t.wildcard, locale);
+ }
+ }
+
+ @Override
+ public String visitType(Type t, Locale locale) {
+ String s = (t.tsym == null || t.tsym.name == null)
+ ? "Object" // none
+ : t.tsym.name.toString();
+ return s;
+ }
+
+ /**
+ * Converts a class name into a (possibly localized) string. Anonymous
+ * inner classes get converted into a localized string.
+ *
+ * @param t the type of the class whose name is to be rendered
+ * @param longform if set, the class' fullname is displayed - if unset the
+ * short name is chosen (w/o package)
+ * @param locale the locale in which the string is to be rendered
+ * @return localized string representation
+ */
+ @Override
+ protected String className(ClassType t, boolean longform, Locale locale) {
+ Symbol sym = t.tsym;
+ if (sym.name.length() == 0 && (sym.flags() & COMPOUND) != 0) {
+ /***
+ StringBuilder s = new StringBuilder(visit(t.supertype_field, locale));
+ for (List<Type> is = t.interfaces_field; is.nonEmpty(); is = is.tail) {
+ s.append('&');
+ s.append(visit(is.head, locale));
+ }
+ return s.toString();
+ ***/
+ return "Object";
+ } else if (sym.name.length() == 0) {
+ // Anonymous
+ String s;
+ ClassType norm = (ClassType) t.tsym.type;
+ if (norm == null) {
+ s = "object";
+ } else if (norm.interfaces_field != null && norm.interfaces_field.nonEmpty()) {
+ s = visit(norm.interfaces_field.head, locale);
+ } else {
+ s = visit(norm.supertype_field, locale);
+ }
+ return s;
+ } else if (longform) {
+ String pkg = "";
+ for (Symbol psym = sym; psym != null; psym = psym.owner) {
+ if (psym.kind == PCK) {
+ pkg = psym.getQualifiedName().toString();
+ break;
+ }
+ }
+ return fullClassNameAndPackageToClass.apply(
+ sym.getQualifiedName().toString(),
+ pkg
+ );
+ } else {
+ return sym.name.toString();
+ }
+ }
+
+ @Override
+ public String visitClassSymbol(ClassSymbol sym, Locale locale) {
+ return sym.name.isEmpty()
+ ? sym.flatname.toString() // Anonymous
+ : sym.fullname.toString();
+ }
+
+ @Override
+ public String visitPackageSymbol(PackageSymbol s, Locale locale) {
+ return s.isUnnamed()
+ ? "" // Unnamed package
+ : s.fullname.toString();
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/Unit.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,493 @@
+/*
+ * 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.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Stream;
+import com.sun.jdi.ReferenceType;
+import jdk.jshell.Snippet.Kind;
+import jdk.jshell.Snippet.Status;
+import jdk.jshell.Snippet.SubKind;
+import jdk.jshell.TaskFactory.AnalyzeTask;
+import jdk.jshell.ClassTracker.ClassInfo;
+import jdk.jshell.TaskFactory.CompileTask;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toMap;
+import static java.util.stream.Collectors.toSet;
+import static jdk.internal.jshell.debug.InternalDebugControl.DBG_EVNT;
+import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
+import static jdk.jshell.Snippet.Status.OVERWRITTEN;
+import static jdk.jshell.Snippet.Status.RECOVERABLE_DEFINED;
+import static jdk.jshell.Snippet.Status.RECOVERABLE_NOT_DEFINED;
+import static jdk.jshell.Snippet.Status.REJECTED;
+import static jdk.jshell.Snippet.Status.VALID;
+import static jdk.jshell.Util.expunge;
+
+/**
+ * Tracks the compilation and load of a new or updated snippet.
+ * @author Robert Field
+ */
+final class Unit {
+
+ private final JShell state;
+ private final Snippet si;
+ private final Snippet siOld;
+ private final boolean isDependency;
+ private final boolean isNew;
+ private final Snippet causalSnippet;
+ private final DiagList generatedDiagnostics;
+
+ private int seq;
+ private int seqInitial;
+ private Wrap activeGuts;
+ private Status status;
+ private Status prevStatus;
+ private boolean signatureChanged;
+ private DiagList compilationDiagnostics;
+ private DiagList recompilationDiagnostics = null;
+ private List<String> unresolved;
+ private SnippetEvent replaceOldEvent;
+ private List<SnippetEvent> secondaryEvents;
+ private boolean isAttemptingCorral;
+ private List<ClassInfo> toRedefine;
+ private boolean dependenciesNeeded;
+
+ Unit(JShell state, Snippet si, Snippet causalSnippet,
+ DiagList generatedDiagnostics) {
+ this.state = state;
+ this.si = si;
+ this.isDependency = causalSnippet != null;
+ this.siOld = isDependency
+ ? si
+ : state.maps.getSnippet(si.key());
+ this.isNew = siOld == null;
+ this.causalSnippet = causalSnippet;
+ this.generatedDiagnostics = generatedDiagnostics;
+
+ this.seq = isNew? 0 : siOld.sequenceNumber();
+ this.seqInitial = seq;
+ this.prevStatus = (isNew || isDependency)
+ ? si.status()
+ : siOld.status();
+ si.setSequenceNumber(seq);
+ }
+
+ // Drop entry
+ Unit(JShell state, Snippet si) {
+ this.state = state;
+ this.si = si;
+ this.siOld = null;
+ this.isDependency = false;
+ this.isNew = false;
+ this.causalSnippet = null;
+ this.generatedDiagnostics = new DiagList();
+ this.prevStatus = si.status();
+ si.setDropped();
+ this.status = si.status();
+ }
+
+ @Override
+ public int hashCode() {
+ return si.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return (o instanceof Unit)
+ ? si.equals(((Unit) o).si)
+ : false;
+ }
+
+ Snippet snippet() {
+ return si;
+ }
+
+ boolean isDependency() {
+ return isDependency;
+ }
+
+ boolean isNew() {
+ return isNew;
+ }
+
+ boolean isRedundant() {
+ return !isNew && !isDependency() && !si.isExecutable() &&
+ prevStatus.isDefined &&
+ siOld.source().equals(si.source());
+ }
+
+ void initialize(Collection<Unit> working) {
+ isAttemptingCorral = false;
+ dependenciesNeeded = false;
+ toRedefine = null; // assure NPE if classToLoad not called
+ activeGuts = si.guts();
+ markOldDeclarationOverwritten();
+ setWrap(working, working);
+ }
+
+ void setWrap(Collection<Unit> except, Collection<Unit> plus) {
+ si.setOuterWrap(isImport()
+ ? OuterWrap.wrapImport(si.source(), activeGuts)
+ : state.eval.wrapInClass(si,
+ except.stream().map(u -> u.snippet().key()).collect(toSet()),
+ activeGuts,
+ plus.stream().map(u -> u.snippet())
+ .filter(sn -> sn != si)
+ .collect(toList())));
+ }
+
+ void setDiagnostics(AnalyzeTask ct) {
+ setDiagnostics(ct.getDiagnostics().ofUnit(this));
+ }
+
+ void setDiagnostics(DiagList diags) {
+ compilationDiagnostics = diags;
+ UnresolvedExtractor ue = new UnresolvedExtractor(diags);
+ unresolved = ue.unresolved();
+ state.debug(DBG_GEN, "++setCompilationInfo() %s\n%s\n-- diags: %s\n",
+ si, si.outerWrap().wrapped(), diags);
+ }
+
+ private boolean isRecoverable() {
+ // Unit failed, use corralling if it is defined on this Snippet,
+ // and either all the errors are resolution errors or this is a
+ // redeclare of an existing method
+ return compilationDiagnostics.hasErrors()
+ && si instanceof DeclarationSnippet
+ && (isDependency()
+ || (si.subKind() != SubKind.VAR_DECLARATION_WITH_INITIALIZER_SUBKIND
+ && compilationDiagnostics.hasResolutionErrorsAndNoOthers()));
+ }
+
+ /**
+ * If it meets the conditions for corralling, install the corralled wrap
+ * @return true is the corralled wrap was installed
+ */
+ boolean corralIfNeeded(Collection<Unit> working) {
+ if (isRecoverable()
+ && si.corralled() != null) {
+ activeGuts = si.corralled();
+ setWrap(working, working);
+ return isAttemptingCorral = true;
+ }
+ return isAttemptingCorral = false;
+ }
+
+ void setCorralledDiagnostics(AnalyzeTask cct) {
+ // set corralled diagnostics, but don't reset unresolved
+ recompilationDiagnostics = cct.getDiagnostics().ofUnit(this);
+ state.debug(DBG_GEN, "++recomp %s\n%s\n-- diags: %s\n",
+ si, si.outerWrap().wrapped(), recompilationDiagnostics);
+ }
+
+ boolean smashingErrorDiagnostics(CompileTask ct) {
+ if (isDefined()) {
+ // set corralled diagnostics, but don't reset unresolved
+ DiagList dl = ct.getDiagnostics().ofUnit(this);
+ if (dl.hasErrors()) {
+ setDiagnostics(dl);
+ status = RECOVERABLE_NOT_DEFINED;
+ // overwrite orginal bytes
+ state.debug(DBG_GEN, "++smashingErrorDiagnostics %s\n%s\n-- diags: %s\n",
+ si, si.outerWrap().wrapped(), dl);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void setStatus() {
+ if (!compilationDiagnostics.hasErrors()) {
+ status = VALID;
+ } else if (isRecoverable()) {
+ if (isAttemptingCorral && !recompilationDiagnostics.hasErrors()) {
+ status = RECOVERABLE_DEFINED;
+ } else {
+ status = RECOVERABLE_NOT_DEFINED;
+ }
+ } else {
+ status = REJECTED;
+ }
+ checkForOverwrite();
+
+ state.debug(DBG_GEN, "setStatus() %s - status: %s\n",
+ si, status);
+ }
+
+ /**
+ * Must be called for each unit
+ * @return
+ */
+ boolean isDefined() {
+ return status.isDefined;
+ }
+
+ /**
+ * Process the class information from the last compile.
+ * Requires loading of returned list.
+ * @return the list of classes to load
+ */
+ Stream<ClassInfo> classesToLoad(List<ClassInfo> cil) {
+ toRedefine = new ArrayList<>();
+ List<ClassInfo> toLoad = new ArrayList<>();
+ if (status.isDefined && !isImport()) {
+ cil.stream().forEach(ci -> {
+ if (!ci.isLoaded()) {
+ if (ci.getReferenceTypeOrNull() == null) {
+ toLoad.add(ci);
+ ci.setLoaded();
+ dependenciesNeeded = true;
+ } else {
+ toRedefine.add(ci);
+ }
+ }
+ });
+ }
+ return toLoad.stream();
+ }
+
+ /**
+ * Redefine classes needing redefine.
+ * classesToLoad() must be called first.
+ * @return true if all redefines succeeded (can be vacuously true)
+ */
+ boolean doRedefines() {
+ if (toRedefine.isEmpty()) {
+ return true;
+ }
+ Map<ReferenceType, byte[]> mp = toRedefine.stream()
+ .collect(toMap(ci -> ci.getReferenceTypeOrNull(), ci -> ci.getBytes()));
+ if (state.executionControl().commandRedefine(mp)) {
+ // success, mark as loaded
+ toRedefine.stream().forEach(ci -> ci.setLoaded());
+ return true;
+ } else {
+ // failed to redefine
+ return false;
+ }
+ }
+
+ void markForReplacement() {
+ // increment for replace class wrapper
+ si.setSequenceNumber(++seq);
+ }
+
+ private boolean isImport() {
+ return si.kind() == Kind.IMPORT;
+ }
+
+ private boolean sigChanged() {
+ return (status.isDefined != prevStatus.isDefined)
+ || (seq != seqInitial && status.isDefined)
+ || signatureChanged;
+ }
+
+ Stream<Unit> effectedDependents() {
+ return sigChanged() || dependenciesNeeded || status == RECOVERABLE_NOT_DEFINED
+ ? dependents()
+ : Stream.empty();
+ }
+
+ Stream<Unit> dependents() {
+ return state.maps.getDependents(si)
+ .stream()
+ .filter(xsi -> xsi != si && xsi.status().isActive)
+ .map(xsi -> new Unit(state, xsi, si, new DiagList()));
+ }
+
+ void finish() {
+ recordCompilation();
+ state.maps.installSnippet(si);
+ }
+
+ private void markOldDeclarationOverwritten() {
+ if (si != siOld && siOld != null && siOld.status().isActive) {
+ // Mark the old declaraion as replaced
+ replaceOldEvent = new SnippetEvent(siOld,
+ siOld.status(), OVERWRITTEN,
+ false, si, null, null);
+ siOld.setOverwritten();
+ }
+ }
+
+ private DiagList computeDiagnostics() {
+ DiagList diagnostics = new DiagList();
+ DiagList diags = compilationDiagnostics;
+ if (status == RECOVERABLE_DEFINED || status == RECOVERABLE_NOT_DEFINED) {
+ UnresolvedExtractor ue = new UnresolvedExtractor(diags);
+ diagnostics.addAll(ue.otherAll());
+ } else {
+ unresolved = Collections.emptyList();
+ diagnostics.addAll(diags);
+ }
+ diagnostics.addAll(generatedDiagnostics);
+ return diagnostics;
+ }
+
+ private void recordCompilation() {
+ state.maps.mapDependencies(si);
+ DiagList diags = computeDiagnostics();
+ si.setCompilationStatus(status, unresolved, diags);
+ state.debug(DBG_GEN, "recordCompilation: %s -- status %s, unresolved %s\n",
+ si, status, unresolved);
+ }
+
+ private void checkForOverwrite() {
+ secondaryEvents = new ArrayList<>();
+ if (replaceOldEvent != null) secondaryEvents.add(replaceOldEvent);
+
+ // Defined methods can overwrite methods of other (equivalent) snippets
+ if (si.kind() == Kind.METHOD && status.isDefined) {
+ String oqpt = ((MethodSnippet) si).qualifiedParameterTypes();
+ String nqpt = computeQualifiedParameterTypes(si);
+ if (!nqpt.equals(oqpt)) {
+ ((MethodSnippet) si).setQualifiedParamaterTypes(nqpt);
+ Status overwrittenStatus = overwriteMatchingMethod(si);
+ if (overwrittenStatus != null) {
+ prevStatus = overwrittenStatus;
+ signatureChanged = true;
+ }
+ }
+ }
+ }
+
+ // Check if there is a method whose user-declared parameter types are
+ // different (and thus has a different snippet) but whose compiled parameter
+ // types are the same. if so, consider it an overwrite replacement.
+ private Status overwriteMatchingMethod(Snippet si) {
+ String qpt = ((MethodSnippet) si).qualifiedParameterTypes();
+
+ // Look through all methods for a method of the same name, with the
+ // same computed qualified parameter types
+ Status overwrittenStatus = null;
+ for (MethodSnippet sn : state.methods()) {
+ if (sn != null && sn != si && sn.status().isActive && sn.name().equals(si.name())) {
+ if (qpt.equals(sn.qualifiedParameterTypes())) {
+ overwrittenStatus = sn.status();
+ SnippetEvent se = new SnippetEvent(
+ sn, overwrittenStatus, OVERWRITTEN,
+ false, si, null, null);
+ sn.setOverwritten();
+ secondaryEvents.add(se);
+ state.debug(DBG_EVNT,
+ "Overwrite event #%d -- key: %s before: %s status: %s sig: %b cause: %s\n",
+ secondaryEvents.size(), se.snippet(), se.previousStatus(),
+ se.status(), se.isSignatureChange(), se.causeSnippet());
+ }
+ }
+ }
+ return overwrittenStatus;
+ }
+
+ private String computeQualifiedParameterTypes(Snippet si) {
+ MethodSnippet msi = (MethodSnippet) si;
+ String qpt;
+ AnalyzeTask at = state.taskFactory.new AnalyzeTask(msi.outerWrap());
+ String rawSig = new TreeDissector(at).typeOfMethod();
+ String signature = expunge(rawSig);
+ int paren = signature.lastIndexOf(')');
+ if (paren < 0) {
+ // Uncompilable snippet, punt with user parameter types
+ qpt = msi.parameterTypes();
+ } else {
+ qpt = signature.substring(0, paren + 1);
+ }
+ return qpt;
+ }
+
+ SnippetEvent event(String value, Exception exception) {
+ boolean wasSignatureChanged = sigChanged();
+ state.debug(DBG_EVNT, "Snippet: %s id: %s before: %s status: %s sig: %b cause: %s\n",
+ si, si.id(), prevStatus, si.status(), wasSignatureChanged, causalSnippet);
+ return new SnippetEvent(si, prevStatus, si.status(),
+ wasSignatureChanged, causalSnippet, value, exception);
+ }
+
+ List<SnippetEvent> secondaryEvents() {
+ return secondaryEvents;
+ }
+
+ @Override
+ public String toString() {
+ return "Unit(" + si.name() + ")";
+ }
+
+ /**
+ * Separate out the unresolvedDependencies errors from both the other
+ * corralling errors and the overall errors.
+ */
+ private static class UnresolvedExtractor {
+
+ private static final String RESOLVE_ERROR_SYMBOL = "symbol:";
+ private static final String RESOLVE_ERROR_LOCATION = "location:";
+
+ //TODO extract from tree instead -- note: internationalization
+ private final Set<String> unresolved = new LinkedHashSet<>();
+ private final DiagList otherErrors = new DiagList();
+ private final DiagList otherAll = new DiagList();
+
+ UnresolvedExtractor(DiagList diags) {
+ for (Diag diag : diags) {
+ if (diag.isError()) {
+ if (diag.isResolutionError()) {
+ String m = diag.getMessage(null);
+ int symPos = m.indexOf(RESOLVE_ERROR_SYMBOL);
+ if (symPos >= 0) {
+ m = m.substring(symPos + RESOLVE_ERROR_SYMBOL.length());
+ int symLoc = m.indexOf(RESOLVE_ERROR_LOCATION);
+ if (symLoc >= 0) {
+ m = m.substring(0, symLoc);
+ }
+ m = m.trim();
+ unresolved.add(m);
+ continue;
+ }
+ }
+ otherErrors.add(diag);
+ }
+ otherAll.add(diag);
+ }
+ }
+
+ DiagList otherCorralledErrors() {
+ return otherErrors;
+ }
+
+ DiagList otherAll() {
+ return otherAll;
+ }
+
+ List<String> unresolved() {
+ return new ArrayList<>(unresolved);
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/UnresolvedReferenceException.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,60 @@
+/*
+ * 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;
+
+/**
+ * Exception reported on attempting to execute a
+ * {@link jdk.jshell.Snippet.Status#RECOVERABLE_DEFINED RECOVERABLE_DEFINED}
+ * method.
+ * <p>
+ * The stack can be queried by methods on <code>Exception</code>.
+ * 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 UnresolvedReferenceException extends Exception {
+
+ final MethodSnippet methodSnippet;
+
+ UnresolvedReferenceException(MethodSnippet methodSnippet, StackTraceElement[] stackElements) {
+ super("Attempt to invoke method with unresolved references");
+ this.methodSnippet = methodSnippet;
+ this.setStackTrace(stackElements);
+ }
+
+ /**
+ * Return the method Snippet which has the unresolved reference(s).
+ * @return the <code>MethodSnippet</code> of the
+ * {@link jdk.jshell.Snippet.Status#RECOVERABLE_DEFINED RECOVERABLE_DEFINED}
+ * method.
+ */
+ public MethodSnippet getMethodSnippet() {
+ return methodSnippet;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/Util.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,94 @@
+ /*
+ * 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.stream.Stream;
+import java.util.stream.StreamSupport;
+import javax.lang.model.element.Name;
+import static jdk.internal.jshell.remote.RemoteCodes.DOIT_METHOD_NAME;
+import static jdk.internal.jshell.remote.RemoteCodes.prefixPattern;
+
+/**
+ * Assorted shared utilities.
+ * @author Robert Field
+ */
+class Util {
+
+ static final String REPL_CLASS_PREFIX = "$REPL";
+ static final String REPL_DOESNOTMATTER_CLASS_NAME = REPL_CLASS_PREFIX+"00DOESNOTMATTER";
+
+ static boolean isDoIt(Name name) {
+ return isDoIt(name.toString());
+ }
+
+ static boolean isDoIt(String sname) {
+ return sname.equals(DOIT_METHOD_NAME);
+ }
+
+ static String expunge(String s) {
+ StringBuilder sb = new StringBuilder();
+ for (String comp : prefixPattern.split(s)) {
+ sb.append(comp);
+ }
+ return sb.toString();
+ }
+
+ static String asLetters(int i) {
+ if (i == 0) {
+ return "";
+ }
+
+ char buf[] = new char[33];
+ int charPos = 32;
+
+ i = -i;
+ while (i <= -26) {
+ buf[charPos--] = (char) ('A'-(i % 26));
+ i = i / 26;
+ }
+ buf[charPos] = (char) ('A'-i);
+
+ return new String(buf, charPos, (33 - charPos));
+ }
+
+
+ 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;
+ }
+ }
+
+ static <T> Stream<T> stream(Iterable<T> iterable) {
+ return StreamSupport.stream(iterable.spliterator(), false);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/VarSnippet.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,57 @@
+/*
+ * 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.VarKey;
+
+/**
+ * Snippet for a variable definition.
+ * The Kind is {@link jdk.jshell.Snippet.Kind#VAR}.
+ * <p>
+ * <code>VarSnippet</code> is immutable: an access to
+ * any of its methods will always return the same result.
+ * and thus is thread-safe.
+ * @jls 8.3: FieldDeclaration.
+ */
+public class VarSnippet extends DeclarationSnippet {
+
+ final String typeName;
+
+ VarSnippet(VarKey key, String userSource, Wrap guts,
+ String name, SubKind subkind, String typeName,
+ Collection<String> declareReferences) {
+ super(key, userSource, guts, name, subkind, null, declareReferences, null);
+ this.typeName = typeName;
+ }
+
+ /**
+ * A String representation of the type of the variable.
+ * @return the variable type as a String.
+ */
+ public String typeName() {
+ return typeName;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/Wrap.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,458 @@
+/*
+ * 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.List;
+import static jdk.internal.jshell.remote.RemoteCodes.DOIT_METHOD_NAME;
+
+/**
+ * Wrapping of source into Java methods, fields, etc. All but outer layer
+ * wrapping with imports and class.
+ *
+ * @author Robert Field
+ */
+abstract class Wrap implements GeneralWrap {
+
+ private static Wrap methodWrap(String prefix, String source, String suffix) {
+ Wrap wunit = new NoWrap(source);
+ return new DoitMethodWrap(new CompoundWrap(prefix, wunit, suffix));
+ }
+
+ public static Wrap methodWrap(String source) {
+ return methodWrap("", source, semi(source) + " return null;\n");
+ }
+
+ public static Wrap methodReturnWrap(String source) {
+ return methodWrap("return ", source, semi(source));
+ }
+
+ public static Wrap methodUnreachableSemiWrap(String source) {
+ return methodWrap("", source, semi(source));
+ }
+
+ public static Wrap methodUnreachableWrap(String source) {
+ return methodWrap("", source, "");
+ }
+
+ public static Wrap corralledMethod(String source, Range modRange, Range tpRange, Range typeRange, String name, Range paramRange, Range throwsRange, int id) {
+ List<Object> l = new ArrayList<>();
+ l.add(" public static\n ");
+ if (!modRange.isEmpty()) {
+ l.add(new RangeWrap(source, modRange));
+ l.add(" ");
+ }
+ if (tpRange != null) {
+ l.add("<");
+ l.add(new RangeWrap(source, tpRange));
+ l.add("> ");
+ }
+ l.add(new RangeWrap(source, typeRange));
+ l.add(" " + name + "(\n ");
+ if (paramRange != null) {
+ l.add(new RangeWrap(source, paramRange));
+ }
+ l.add(") ");
+ if (throwsRange != null) {
+ l.add("throws ");
+ l.add(new RangeWrap(source, throwsRange));
+ }
+ l.add(" {\n throw new jdk.internal.jshell.remote.RemoteResolutionException(" + id + ");\n}\n");
+ return new CompoundWrap(l.toArray());
+ }
+
+ /**
+ *
+ * @param in
+ * @param rname
+ * @param rinit Initializer or null
+ * @param rdecl Type name and name
+ * @return
+ */
+ public static Wrap varWrap(String source, Range rtype, String brackets, Range rname, Range rinit) {
+ RangeWrap wname = new RangeWrap(source, rname);
+ RangeWrap wtype = new RangeWrap(source, rtype);
+ Wrap wVarDecl = new VarDeclareWrap(wtype, brackets, wname);
+ Wrap wmeth;
+
+ if (rinit == null) {
+ wmeth = new CompoundWrap(new NoWrap(" "), " return null;\n");
+ } else {
+ RangeWrap winit = new RangeWrap(source, rinit);
+ // int x = y
+ // int x_ = y; return x = x_;
+ // decl + "_ = " + init ; + "return " + name + "=" + name + "_ ;"
+ wmeth = new CompoundWrap(
+ wtype, brackets + " ", wname, "_ =\n ", winit, semi(winit),
+ " return ", wname, " = ", wname, "_;\n"
+ );
+ }
+ Wrap wInitMeth = new DoitMethodWrap(wmeth);
+ return new CompoundWrap(wVarDecl, wInitMeth);
+ }
+
+ public static Wrap tempVarWrap(String source, String typename, String name) {
+ RangeWrap winit = new NoWrap(source);
+ // y
+ // return $1 = y;
+ // "return " + $1 + "=" + init ;
+ Wrap wmeth = new CompoundWrap("return " + name + " =\n ", winit, semi(winit));
+ Wrap wInitMeth = new DoitMethodWrap(wmeth);
+
+ String varDecl = " public static\n " + typename + " " + name + ";\n";
+ return new CompoundWrap(varDecl, wInitMeth);
+ }
+
+ public static Wrap importWrap(String source) {
+ return new NoWrap(source);
+ }
+
+ public static Wrap classMemberWrap(String source) {
+ Wrap w = new NoWrap(source);
+ return new CompoundWrap(" public static\n ", w);
+ }
+
+ private static int countLines(String s) {
+ return countLines(s, 0, s.length());
+ }
+
+ private static int countLines(String s, int from, int toEx) {
+ int cnt = 0;
+ int idx = from;
+ while ((idx = s.indexOf('\n', idx)) > 0) {
+ if (idx >= toEx) break;
+ ++cnt;
+ ++idx;
+ }
+ return cnt;
+ }
+
+ public static final class Range {
+ final int begin;
+ final int end;
+
+ Range(int begin, int end) {
+ this.begin = begin;
+ this.end = end;
+ }
+
+ Range(String s) {
+ this.begin = 0;
+ this.end = s.length();
+ }
+
+ String part(String s) {
+ return s.substring(begin, end);
+ }
+
+ int length() {
+ return end - begin;
+ }
+
+ boolean isEmpty() {
+ return end == begin;
+ }
+
+ void verify(String s) {
+ if (begin < 0 || end <= begin || end > s.length()) {
+ throw new InternalError("Bad Range: " + s + "[" + begin + "," + end + "]");
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "Range[" + begin + "," + end + "]";
+ }
+ }
+
+ public static class CompoundWrap extends Wrap {
+
+ final Object[] os;
+ final String wrapped;
+ final int snidxFirst;
+ final int snidxLast;
+ final int snlineFirst;
+ final int snlineLast;
+
+ CompoundWrap(Object... os) {
+ this.os = os;
+ int sniFirst = Integer.MAX_VALUE;
+ int sniLast = Integer.MIN_VALUE;
+ int snlnFirst = Integer.MAX_VALUE;
+ int snlnLast = Integer.MIN_VALUE;
+ StringBuilder sb = new StringBuilder();
+ for (Object o : os) {
+ if (o instanceof String) {
+ String s = (String) o;
+ sb.append(s);
+ } else if (o instanceof Wrap) {
+ Wrap w = (Wrap) o;
+ if (w.firstSnippetIndex() < sniFirst) {
+ sniFirst = w.firstSnippetIndex();
+ }
+ if (w.lastSnippetIndex() > sniLast) {
+ sniLast = w.lastSnippetIndex();
+ }
+ if (w.firstSnippetLine() < snlnFirst) {
+ snlnFirst = w.firstSnippetLine();
+ }
+ if (w.lastSnippetLine() > snlnLast) {
+ snlnLast = w.lastSnippetLine();
+ }
+ sb.append(w.wrapped());
+ } else {
+ throw new InternalError("Bad object in CommoundWrap: " + o);
+ }
+ }
+ this.wrapped = sb.toString();
+ this.snidxFirst = sniFirst;
+ this.snidxLast = sniLast;
+ this.snlineFirst = snlnFirst;
+ this.snlineLast = snlnLast;
+ }
+
+ @Override
+ public String wrapped() {
+ return wrapped;
+ }
+
+ @Override
+ public int snippetIndexToWrapIndex(int sni) {
+ int before = 0;
+ for (Object o : os) {
+ if (o instanceof String) {
+ String s = (String) o;
+ before += s.length();
+ } else if (o instanceof Wrap) {
+ Wrap w = (Wrap) o;
+ if (sni >= w.firstSnippetIndex() && sni <= w.lastSnippetIndex()) {
+ return w.snippetIndexToWrapIndex(sni) + before;
+ }
+ before += w.wrapped().length();
+ }
+ }
+ return 0;
+ }
+
+ @Override
+ public int wrapIndexToSnippetIndex(int wi) {
+ int before = 0;
+ for (Object o : os) {
+ if (o instanceof String) {
+ String s = (String) o;
+ before += s.length();
+ } else if (o instanceof Wrap) {
+ Wrap w = (Wrap) o;
+ int len = w.wrapped().length();
+ if ((wi - before) <= len) {
+ //System.err.printf("Defer to wrap %s - wi: %d. before; %d -- %s >>> %s\n",
+ // w, wi, before, w.debugPos(wi - before), w.wrapped());
+ return w.wrapIndexToSnippetIndex(wi - before);
+ }
+ before += len;
+ }
+ }
+ return lastSnippetIndex();
+ }
+
+ @Override
+ public int firstSnippetIndex() {
+ return snidxFirst;
+ }
+
+ @Override
+ public int lastSnippetIndex() {
+ return snidxLast;
+ }
+
+ @Override
+ public int snippetLineToWrapLine(int snline) {
+ int before = 0;
+ for (Object o : os) {
+ if (o instanceof String) {
+ String s = (String) o;
+ before += countLines(s);
+ } else if (o instanceof Wrap) {
+ Wrap w = (Wrap) o;
+ if (snline >= w.firstSnippetLine() && snline <= w.lastSnippetLine()) {
+ return w.snippetLineToWrapLine(snline) + before;
+ }
+ before += countLines(w.wrapped());
+ }
+ }
+ return 0;
+ }
+
+ @Override
+ public int wrapLineToSnippetLine(int wline) {
+ int before = 0;
+ for (Object o : os) {
+ if (o instanceof String) {
+ String s = (String) o;
+ before += countLines(s);
+ } else if (o instanceof Wrap) {
+ Wrap w = (Wrap) o;
+ int lns = countLines(w.wrapped());
+ if ((wline - before) < lns) {
+ return w.wrapLineToSnippetLine(wline - before);
+ }
+ before += lns;
+ }
+ }
+ return 0;
+ }
+
+ @Override
+ public int firstSnippetLine() {
+ return snlineFirst;
+ }
+
+ @Override
+ public int lastSnippetLine() {
+ return snlineLast;
+ }
+
+
+ }
+
+ private static class RangeWrap extends Wrap {
+
+ final Range range;
+ final String wrapped;
+ final int firstSnline;
+ final int lastSnline;
+
+ RangeWrap(String snippetSource, Range usedWithinSnippet) {
+ this.range = usedWithinSnippet;
+ this.wrapped = usedWithinSnippet.part(snippetSource);
+ usedWithinSnippet.verify(snippetSource);
+ this.firstSnline = countLines(snippetSource, 0, range.begin);
+ this.lastSnline = firstSnline + countLines(snippetSource, range.begin, range.end);
+ }
+
+ @Override
+ public String wrapped() {
+ return wrapped;
+ }
+
+ @Override
+ public int snippetIndexToWrapIndex(int sni) {
+ if (sni < range.begin) {
+ return 0;
+ }
+ if (sni > range.end) {
+ return range.length();
+ }
+ return sni - range.begin;
+ }
+
+ @Override
+ public int wrapIndexToSnippetIndex(int wi) {
+ if (wi < 0) {
+ return 0; // bad index
+ }
+ int max = range.length();
+ if (wi > max) {
+ wi = max;
+ }
+ return wi + range.begin;
+ }
+
+ @Override
+ public int firstSnippetIndex() {
+ return range.begin;
+ }
+
+ @Override
+ public int lastSnippetIndex() {
+ return range.end;
+ }
+
+ @Override
+ public int snippetLineToWrapLine(int snline) {
+ if (snline < firstSnline) {
+ return 0;
+ }
+ if (snline >= lastSnline) {
+ return lastSnline - firstSnline;
+ }
+ return snline - firstSnline;
+ }
+
+ @Override
+ public int wrapLineToSnippetLine(int wline) {
+ if (wline < 0) {
+ return 0; // bad index
+ }
+ int max = lastSnline - firstSnline;
+ if (wline > max) {
+ wline = max;
+ }
+ return wline + firstSnline;
+ }
+
+ @Override
+ public int firstSnippetLine() {
+ return firstSnline;
+ }
+
+ @Override
+ public int lastSnippetLine() {
+ return lastSnline;
+ }
+
+ }
+
+ private static class NoWrap extends RangeWrap {
+
+ NoWrap(String unit) {
+ super(unit, new Range(unit));
+ }
+ }
+
+ private static String semi(Wrap w) {
+ return semi(w.wrapped());
+ }
+
+ private static String semi(String s) {
+ return ((s.endsWith(";")) ? "\n" : ((s.endsWith(";\n")) ? "" : ";\n"));
+ }
+
+ private static class DoitMethodWrap extends CompoundWrap {
+
+ DoitMethodWrap(Wrap w) {
+ super(" public static Object " + DOIT_METHOD_NAME + "() throws Throwable {\n"
+ + " ", w,
+ " }\n");
+ }
+ }
+
+ private static class VarDeclareWrap extends CompoundWrap {
+
+ VarDeclareWrap(Wrap wtype, String brackets, Wrap wname) {
+ super(" public static ", wtype, brackets + " ", wname, semi(wname));
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.jshell/share/classes/jdk/jshell/package-info.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,146 @@
+/*
+ * 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.
+ */
+
+/**
+ * Provides interfaces for creating tools, such as a Read-Eval-Print Loop (REPL),
+ * which interactively evaluate "snippets" of Java programming language code.
+ * Where a "snippet" is a single expression, statement, or declaration.
+ * This functionality can be used to enhance tools such as IDEs or can be
+ * stand-alone.
+ * <p>
+ * {@link jdk.jshell.JShell} is the central class. An instance of
+ * <code>JShell</code> holds the evaluation state, which is both the current
+ * set of source snippets and the execution state they have produced.
+ * <p>
+ * Each source snippet is represented by an instance of a subclass of
+ * {@link jdk.jshell.Snippet}. For example, a statement is represented by an
+ * instance of {@link jdk.jshell.StatementSnippet}, and a method declaration is
+ * represented by an instance of {@link jdk.jshell.MethodSnippet}.
+ * Snippets are created when {@link jdk.jshell.JShell#eval(java.lang.String)}
+ * is invoked with an input which includes one or more snippets of code.
+ * <p>
+ * Any change to the compilation status of a snippet is reported with a
+ * {@link jdk.jshell.SnippetEvent}. There are three major kinds of
+ * changes to the status of a snippet: it can created with <code>eval</code>,
+ * it can be dropped from the active source state with
+ * {@link jdk.jshell.JShell#drop(jdk.jshell.PersistentSnippet)}, and it can have
+ * its status updated as a result of a status change in another snippet.
+ * For
+ * example: given <code>js</code>, an instance of <code>JShell</code>, executing
+ * <code>js.eval("int x = 5;")</code> will add the variable <code>x</code> to
+ * the source state and will generate an event describing the creation of a
+ * {@link jdk.jshell.VarSnippet} for <code>x</code>. Then executing
+ * <code>js.eval("int timesx(int val) { return val * x; }")</code> will add
+ * a method to the source state and will generate an event
+ * describing the creation of a {@link jdk.jshell.MethodSnippet} for
+ * <code>timesx</code>.
+ * Assume that <code>varx</code> holds the snippet created by the first
+ * call to <code>eval</code>, executing <code>js.drop(varx)</code> will
+ * generate two events: one for changing the status of the
+ * variable snippet to <code>DROPPED</code> and one for
+ * updating the method snippet (which now has an unresolved reference to
+ * <code>x</code>).
+ * <p>
+ * Of course, for any general application of the API, the input would not be
+ * fixed strings, but would come from the user. Below is a very simplified
+ * example of how the API might be used to implement a REPL.
+ * <pre>
+* {@code
+ * import java.io.ByteArrayInputStream;
+ * import java.io.Console;
+ * import java.util.List;
+ * import jdk.jshell.*;
+ * import jdk.jshell.Snippet.Status;
+ *
+ * class ExampleJShell {
+ * public static void main(String[] args) {
+ * Console console = System.console();
+ * try (JShell js = JShell.create()) {
+ * do {
+ * System.out.print("Enter some Java code: ");
+ * String input = console.readLine();
+ * if (input == null) {
+ * break;
+ * }
+ * List<SnippetEvent> events = js.eval(input);
+ * for (SnippetEvent e : events) {
+ * StringBuilder sb = new StringBuilder();
+ * if (e.causeSnippet == null) {
+ * // We have a snippet creation event
+ * switch (e.status) {
+ * case VALID:
+ * sb.append("Successful ");
+ * break;
+ * case RECOVERABLE_DEFINED:
+ * sb.append("With unresolved references ");
+ * break;
+ * case RECOVERABLE_NOT_DEFINED:
+ * sb.append("Possibly reparable, failed ");
+ * break;
+ * case REJECTED:
+ * sb.append("Failed ");
+ * break;
+ * }
+ * if (e.previousStatus == Status.NONEXISTENT) {
+ * sb.append("addition");
+ * } else {
+ * sb.append("modification");
+ * }
+ * sb.append(" of ");
+ * sb.append(e.snippet.source());
+ * System.out.println(sb);
+ * if (e.value != null) {
+ * System.out.printf("Value is: %s\n", e.value);
+ * }
+ * System.out.flush();
+ * }
+ * }
+ * } while (true);
+ * }
+ * System.out.println("\nGoodbye");
+ * }
+ * }
+ * }
+ * </pre>
+ * <p>
+ * To register for status change events use
+ * {@link jdk.jshell.JShell#onSnippetEvent(java.util.function.Consumer)}.
+ * These events are only generated by <code>eval</code> and <code>drop</code>,
+ * the return values of these methods are the list of events generated by that
+ * call. So, as in the example above, events can be used without registering
+ * to receive events.
+ * <p>
+ * If you experiment with this example, you will see that failing to terminate
+ * a statement or variable declaration with a semi-colon will simply fail.
+ * An unfinished entry (for example a desired multi-line method) will also just
+ * fail after one line. The utilities in {@link jdk.jshell.SourceCodeAnalysis}
+ * provide source boundary and completeness analysis to address cases like
+ * those. <code>SourceCodeAnalysis</code> also provides suggested completions
+ * of input, as might be used in tab-completion.
+ */
+
+
+package jdk.jshell;
+
--- a/langtools/test/com/sun/javadoc/testSimpleTag/TestSimpleTag.java Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/test/com/sun/javadoc/testSimpleTag/TestSimpleTag.java Thu Oct 22 11:12:55 2015 -0700
@@ -51,7 +51,8 @@
"-tag", "regular:a:Regular Tag:",
"-tag", "back-slash\\:tag\\\\:a:Back-Slash-Tag:",
testSrc("C.java"));
- checkExit(Exit.FAILED); // TODO: investigate why failed
+ // doclint fails because '\' is not allowed in tag name
+ checkExit(Exit.FAILED);
checkOutput("C.html", true,
"<span class=\"simpleTagLabel\">Todo:</span>",
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/AnalysisTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Test SourceCodeAnalysis
+ * @build KullaTesting TestingInputStream
+ * @run testng AnalysisTest
+ */
+
+import org.testng.annotations.Test;
+
+@Test
+public class AnalysisTest extends KullaTesting {
+
+ public void testSource() {
+ assertAnalyze("int x=3//test", "int x=3;//test", "", true);
+ assertAnalyze("int x=3 ;//test", "int x=3 ;//test", "", true);
+ assertAnalyze("int x=3 //;", "int x=3; //;", "", true);
+ assertAnalyze("void m5() {} /// hgjghj", "void m5() {} /// hgjghj", "", true);
+ assertAnalyze("int ff; int v // hi", "int ff;", " int v // hi", true);
+ }
+
+ public void testSourceSlashStar() {
+ assertAnalyze("/*zoo*/int x=3 /*test*/", "/*zoo*/int x=3; /*test*/", "", true);
+ assertAnalyze("/*zoo*/int x=3 ;/*test*/", "/*zoo*/int x=3 ;/*test*/", "", true);
+ assertAnalyze("int x=3 /*;*/", "int x=3; /*;*/", "", true);
+ assertAnalyze("void m5() {} /*hgjghj*/", "void m5() {} /*hgjghj*/", "", true);
+ assertAnalyze("int ff; int v /*hgjghj*/", "int ff;", " int v /*hgjghj*/", true);
+ }
+
+ public void testIncomplete() {
+ assertAnalyze("void m() { //erer", null, "void m() { //erer\n", false);
+ assertAnalyze("int m=//", null, "int m=//\n", false);
+ }
+
+ public void testExpression() {
+ assertAnalyze("45//test", "45//test", "", true);
+ assertAnalyze("45;//test", "45;//test", "", true);
+ assertAnalyze("45//;", "45//;", "", true);
+ assertAnalyze("/*zoo*/45/*test*/", "/*zoo*/45/*test*/", "", true);
+ assertAnalyze("/*zoo*/45;/*test*/", "/*zoo*/45;/*test*/", "", true);
+ assertAnalyze("45/*;*/", "45/*;*/", "", true);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/ClassMembersTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,355 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Test access to members of user defined class.
+ * @build KullaTesting TestingInputStream ExpectedDiagnostic
+ * @run testng/timeout=600 ClassMembersTest
+ */
+
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.tools.Diagnostic;
+
+import jdk.jshell.SourceCodeAnalysis;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class ClassMembersTest extends KullaTesting {
+
+ @Test(dataProvider = "memberTestCase")
+ public void memberTest(AccessModifier accessModifier, CodeChunk codeChunk, Static isStaticMember, Static isStaticReference) {
+ MemberTestCase testCase = new MemberTestCase(accessModifier, codeChunk, isStaticMember, isStaticReference);
+ assertEval(testCase.generateSource());
+ String expectedMessage = testCase.expectedMessage;
+ if (testCase.codeChunk != CodeChunk.CONSTRUCTOR || testCase.isAccessible()) {
+ assertEval("A a = new A();");
+ }
+ if (expectedMessage == null) {
+ assertEval(testCase.useCodeChunk());
+ } else {
+ assertDeclareFail(testCase.useCodeChunk(), expectedMessage);
+ }
+ }
+
+ private List<String> parseCode(String input) {
+ List<String> list = new ArrayList<>();
+ SourceCodeAnalysis codeAnalysis = getAnalysis();
+ String source = input;
+ while (!source.trim().isEmpty()) {
+ SourceCodeAnalysis.CompletionInfo info = codeAnalysis.analyzeCompletion(source);
+ list.add(info.source);
+ source = info.remaining;
+ }
+ return list;
+ }
+
+ @Test(dataProvider = "memberTestCase")
+ public void extendsMemberTest(AccessModifier accessModifier, CodeChunk codeChunk, Static isStaticMember, Static isStaticReference) {
+ MemberTestCase testCase = new ExtendsMemberTestCase(accessModifier, codeChunk, isStaticMember, isStaticReference);
+ String input = testCase.generateSource();
+ List<String> ss = parseCode(input);
+ assertEval(ss.get(0));
+ if (testCase.codeChunk != CodeChunk.CONSTRUCTOR || testCase.isAccessible()) {
+ assertEval(ss.get(1));
+ assertEval("B b = new B();");
+ }
+ String expectedMessage = testCase.expectedMessage;
+ if (expectedMessage == null) {
+ assertEval(testCase.useCodeChunk());
+ } else {
+ assertDeclareFail(testCase.useCodeChunk(), expectedMessage);
+ }
+ }
+
+ @Test
+ public void interfaceTest() {
+ String interfaceSource =
+ "interface A {\n" +
+ " default int defaultMethod() { return 1; }\n" +
+ " static int staticMethod() { return 2; }\n" +
+ " int method();\n" +
+ " class Inner1 {}\n" +
+ " static class Inner2 {}\n" +
+ "}";
+ assertEval(interfaceSource);
+ assertEval("A.staticMethod();", "2");
+ String classSource =
+ "class B implements A {\n" +
+ " public int method() { return 3; }\n" +
+ "}";
+ assertEval(classSource);
+ assertEval("B b = new B();");
+ assertEval("b.defaultMethod();", "1");
+ assertDeclareFail("B.staticMethod();",
+ new ExpectedDiagnostic("compiler.err.cant.resolve.location.args", 0, 14, 1, -1, -1, Diagnostic.Kind.ERROR));
+ assertEval("b.method();", "3");
+ assertEval("new A.Inner1();");
+ assertEval("new A.Inner2();");
+ assertEval("new B.Inner1();");
+ assertEval("new B.Inner2();");
+ }
+
+ @Test
+ public void enumTest() {
+ String enumSource =
+ "enum E {A(\"s\");\n" +
+ " private final String s;\n" +
+ " private E(String s) { this.s = s; }\n" +
+ " public String method() { return s; }\n" +
+ " private String privateMethod() { return s; }\n" +
+ " public static String staticMethod() { return staticPrivateMethod(); }\n" +
+ " private static String staticPrivateMethod() { return \"a\"; }\n" +
+ "}";
+ assertEval(enumSource);
+ assertEval("E a = E.A;", "A");
+ assertDeclareFail("a.s;",
+ new ExpectedDiagnostic("compiler.err.report.access", 0, 3, 1, -1, -1, Diagnostic.Kind.ERROR));
+ assertDeclareFail("new E(\"q\");",
+ new ExpectedDiagnostic("compiler.err.enum.cant.be.instantiated", 0, 10, 0, -1, -1, Diagnostic.Kind.ERROR));
+ assertEval("a.method();", "\"s\"");
+ assertDeclareFail("a.privateMethod();",
+ new ExpectedDiagnostic("compiler.err.report.access", 0, 15, 1, -1, -1, Diagnostic.Kind.ERROR));
+ assertEval("E.staticMethod();", "\"a\"");
+ assertDeclareFail("a.staticPrivateMethod();",
+ new ExpectedDiagnostic("compiler.err.report.access", 0, 21, 1, -1, -1, Diagnostic.Kind.ERROR));
+ assertDeclareFail("E.method();",
+ new ExpectedDiagnostic("compiler.err.non-static.cant.be.ref", 0, 8, 1, -1, -1, Diagnostic.Kind.ERROR));
+ }
+
+ @Test(enabled = false) // TODO 8080354
+ public void annotationTest() {
+ assertEval("import java.lang.annotation.*;");
+ for (RetentionPolicy policy : RetentionPolicy.values()) {
+ String annotationSource =
+ "@Retention(RetentionPolicy." + policy.toString() + ")\n" +
+ "@interface A {}";
+ assertEval(annotationSource);
+ String classSource =
+ "@A class C {\n" +
+ " @A C() {}\n" +
+ " @A void f() {}\n" +
+ " @A int f;\n" +
+ " @A class Inner {}\n" +
+ "}";
+ assertEval(classSource);
+ String isRuntimeVisible = policy == RetentionPolicy.RUNTIME ? "true" : "false";
+ assertEval("C.class.getAnnotationsByType(A.class).length > 0;", isRuntimeVisible);
+ assertEval("C.class.getDeclaredConstructor().getAnnotationsByType(A.class).length > 0;", isRuntimeVisible);
+ assertEval("C.class.getDeclaredMethod(\"f\").getAnnotationsByType(A.class).length > 0;", isRuntimeVisible);
+ assertEval("C.class.getDeclaredField(\"f\").getAnnotationsByType(A.class).length > 0;", isRuntimeVisible);
+ assertEval("C.Inner.class.getAnnotationsByType(A.class).length > 0;", isRuntimeVisible);
+ }
+ }
+
+ @DataProvider(name = "memberTestCase")
+ public Object[][] memberTestCaseGenerator() {
+ List<Object[]> list = new ArrayList<>();
+ for (AccessModifier accessModifier : AccessModifier.values()) {
+ for (Static isStaticMember : Static.values()) {
+ for (Static isStaticReference : Static.values()) {
+ for (CodeChunk codeChunk : CodeChunk.values()) {
+ if (codeChunk == CodeChunk.CONSTRUCTOR && isStaticMember == Static.STATIC) {
+ continue;
+ }
+ list.add(new Object[]{ accessModifier, codeChunk, isStaticMember, isStaticReference });
+ }
+ }
+ }
+ }
+ return list.toArray(new Object[list.size()][]);
+ }
+
+ public static class ExtendsMemberTestCase extends MemberTestCase {
+
+ public ExtendsMemberTestCase(AccessModifier accessModifier, CodeChunk codeChunk, Static isStaticMember, Static isStaticReference) {
+ super(accessModifier, codeChunk, isStaticMember, isStaticReference);
+ }
+
+ @Override
+ public String getSourceTemplate() {
+ return super.getSourceTemplate() + "\n"
+ + "class B extends A {}";
+ }
+
+ @Override
+ public String errorMessage() {
+ if (!isAccessible()) {
+ if (codeChunk == CodeChunk.METHOD) {
+ return "compiler.err.cant.resolve.location.args";
+ }
+ if (codeChunk == CodeChunk.CONSTRUCTOR) {
+ return "compiler.err.cant.resolve.location";
+ }
+ }
+ return super.errorMessage();
+ }
+
+ @Override
+ public String useCodeChunk() {
+ return useCodeChunk("B");
+ }
+ }
+
+ public static class MemberTestCase {
+ public final AccessModifier accessModifier;
+ public final CodeChunk codeChunk;
+ public final Static isStaticMember;
+ public final Static isStaticReference;
+ public final String expectedMessage;
+
+ public MemberTestCase(AccessModifier accessModifier, CodeChunk codeChunk, Static isStaticMember,
+ Static isStaticReference) {
+ this.accessModifier = accessModifier;
+ this.codeChunk = codeChunk;
+ this.isStaticMember = isStaticMember;
+ this.isStaticReference = isStaticReference;
+ this.expectedMessage = errorMessage();
+ }
+
+ public String getSourceTemplate() {
+ return "class A {\n" +
+ " #MEMBER#\n" +
+ "}";
+ }
+
+ public boolean isAccessible() {
+ return accessModifier != AccessModifier.PRIVATE;
+ }
+
+ public String errorMessage() {
+ if (!isAccessible()) {
+ return "compiler.err.report.access";
+ }
+ if (codeChunk == CodeChunk.INNER_INTERFACE) {
+ return "compiler.err.abstract.cant.be.instantiated";
+ }
+ if (isStaticMember == Static.STATIC) {
+ if (isStaticReference == Static.NO && codeChunk == CodeChunk.INNER_CLASS) {
+ return "compiler.err.qualified.new.of.static.class";
+ }
+ return null;
+ }
+ if (isStaticReference == Static.STATIC) {
+ if (codeChunk == CodeChunk.CONSTRUCTOR) {
+ return null;
+ }
+ if (codeChunk == CodeChunk.INNER_CLASS) {
+ return "compiler.err.encl.class.required";
+ }
+ return "compiler.err.non-static.cant.be.ref";
+ }
+ return null;
+ }
+
+ public String generateSource() {
+ return getSourceTemplate().replace("#MEMBER#", codeChunk.generateSource(accessModifier, isStaticMember));
+ }
+
+ protected String useCodeChunk(String className) {
+ String name = className.toLowerCase();
+ switch (codeChunk) {
+ case CONSTRUCTOR:
+ return String.format("new %s();", className);
+ case METHOD:
+ if (isStaticReference == Static.STATIC) {
+ return String.format("%s.method();", className);
+ } else {
+ return String.format("%s.method();", name);
+ }
+ case FIELD:
+ if (isStaticReference == Static.STATIC) {
+ return String.format("%s.field;", className);
+ } else {
+ return String.format("%s.field;", name);
+ }
+ case INNER_CLASS:
+ if (isStaticReference == Static.STATIC) {
+ return String.format("new %s.Inner();", className);
+ } else {
+ return String.format("%s.new Inner();", name);
+ }
+ case INNER_INTERFACE:
+ return String.format("new %s.Inner();", className);
+ default:
+ throw new AssertionError("Unknown code chunk: " + this);
+ }
+ }
+
+ public String useCodeChunk() {
+ return useCodeChunk("A");
+ }
+ }
+
+ public enum AccessModifier {
+ PUBLIC("public"),
+ PROTECTED("protected"),
+ PACKAGE_PRIVATE(""),
+ PRIVATE("private");
+
+ private final String modifier;
+
+ AccessModifier(String modifier) {
+ this.modifier = modifier;
+ }
+
+ public String getModifier() {
+ return modifier;
+ }
+ }
+
+ public enum Static {
+ STATIC("static"), NO("");
+
+ private final String modifier;
+
+ Static(String modifier) {
+ this.modifier = modifier;
+ }
+
+ public String getModifier() {
+ return modifier;
+ }
+ }
+
+ public enum CodeChunk {
+ CONSTRUCTOR("#MODIFIER# A() {}"),
+ METHOD("#MODIFIER# int method() { return 10; }"),
+ FIELD("#MODIFIER# int field = 10;"),
+ INNER_CLASS("#MODIFIER# class Inner {}"),
+ INNER_INTERFACE("#MODIFIER# interface Inner {}");
+
+ private final String code;
+
+ CodeChunk(String code) {
+ this.code = code;
+ }
+
+ public String generateSource(AccessModifier accessModifier, Static isStatic) {
+ return code.replace("#MODIFIER#", accessModifier.getModifier() + " " + isStatic.getModifier());
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/ClassPathTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,114 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Tests for EvalState#addToClasspath
+ * @library /tools/lib
+ * @build KullaTesting TestingInputStream ToolBox Compiler
+ * @run testng ClassPathTest
+ */
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.testng.annotations.Test;
+
+@Test
+public class ClassPathTest extends KullaTesting {
+
+ private final Compiler compiler = new Compiler();
+ private final Path outDir = Paths.get("class_path_test");
+
+ public void testDirectory() {
+ compiler.compile(outDir, "package pkg; public class TestDirectory { }");
+ assertDeclareFail("import pkg.TestDirectory;", "compiler.err.doesnt.exist");
+ assertDeclareFail("new pkg.TestDirectory();", "compiler.err.doesnt.exist");
+ addToClasspath(compiler.getPath(outDir));
+ assertEval("new pkg.TestDirectory();");
+ }
+
+ public void testJar() {
+ compiler.compile(outDir, "package pkg; public class TestJar { }");
+ String jarName = "test.jar";
+ compiler.jar(outDir, jarName, "pkg/TestJar.class");
+ assertDeclareFail("import pkg.TestJar;", "compiler.err.doesnt.exist");
+ assertDeclareFail("new pkg.TestJar();", "compiler.err.doesnt.exist");
+ addToClasspath(compiler.getPath(outDir).resolve(jarName));
+ assertEval("new pkg.TestJar();");
+ }
+
+ public void testAmbiguousDirectory() {
+ Path p1 = outDir.resolve("dir1");
+ compiler.compile(p1,
+ "package p; public class TestAmbiguous {\n" +
+ " public String toString() {\n" +
+ " return \"first\";" +
+ " }\n" +
+ "}");
+ addToClasspath(compiler.getPath(p1));
+ Path p2 = outDir.resolve("dir2");
+ compiler.compile(p2,
+ "package p; public class TestAmbiguous {\n" +
+ " public String toString() {\n" +
+ " return \"second\";" +
+ " }\n" +
+ "}");
+ addToClasspath(compiler.getPath(p2));
+ assertEval("new p.TestAmbiguous();", "first");
+ }
+
+ public void testAmbiguousJar() {
+ Path p1 = outDir.resolve("dir1");
+ compiler.compile(p1,
+ "package p; public class TestAmbiguous {\n" +
+ " public String toString() {\n" +
+ " return \"first\";" +
+ " }\n" +
+ "}");
+ String jarName = "test.jar";
+ compiler.jar(p1, jarName, "p/TestAmbiguous.class");
+ addToClasspath(compiler.getPath(p1.resolve(jarName)));
+ Path p2 = outDir.resolve("dir2");
+ compiler.compile(p2,
+ "package p; public class TestAmbiguous {\n" +
+ " public String toString() {\n" +
+ " return \"second\";" +
+ " }\n" +
+ "}");
+ addToClasspath(compiler.getPath(p2));
+ assertEval("new p.TestAmbiguous();", "first");
+ }
+
+ public void testEmptyClassPath() {
+ addToClasspath("");
+ assertEval("new java.util.ArrayList<String>();");
+ }
+
+ public void testUnknown() {
+ addToClasspath(compiler.getPath(outDir.resolve("UNKNOWN")));
+ assertDeclareFail("new Unknown();", "compiler.err.cant.resolve.location");
+ addToClasspath(compiler.getPath(outDir.resolve("UNKNOWN.jar")));
+ assertDeclareFail("new Unknown();", "compiler.err.cant.resolve.location");
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/ClassesTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,289 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Tests for EvaluationState.classes
+ * @build KullaTesting TestingInputStream ExpectedDiagnostic
+ * @run testng ClassesTest
+ */
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.tools.Diagnostic;
+
+import jdk.jshell.Snippet;
+import jdk.jshell.TypeDeclSnippet;
+import jdk.jshell.VarSnippet;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import jdk.jshell.Diag;
+import static jdk.jshell.Snippet.Status.VALID;
+import static jdk.jshell.Snippet.Status.RECOVERABLE_NOT_DEFINED;
+import static jdk.jshell.Snippet.Status.DROPPED;
+import static jdk.jshell.Snippet.Status.REJECTED;
+import static jdk.jshell.Snippet.SubKind.*;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+import static jdk.jshell.Snippet.Status.OVERWRITTEN;
+
+@Test
+public class ClassesTest extends KullaTesting {
+
+ public void noClasses() {
+ assertNumberOfActiveClasses(0);
+ }
+
+ public void testSignature1() {
+ TypeDeclSnippet c1 = classKey(assertEval("class A extends B {}", added(RECOVERABLE_NOT_DEFINED)));
+ assertTypeDeclSnippet(c1, "A", RECOVERABLE_NOT_DEFINED, CLASS_SUBKIND, 1, 0);
+ TypeDeclSnippet c2 = classKey(assertEval("@interface A { Class<B> f() default B.class; }",
+ ste(MAIN_SNIPPET, RECOVERABLE_NOT_DEFINED, RECOVERABLE_NOT_DEFINED, false, null),
+ ste(c1, RECOVERABLE_NOT_DEFINED, OVERWRITTEN, false, MAIN_SNIPPET)));
+ assertTypeDeclSnippet(c2, "A", RECOVERABLE_NOT_DEFINED, ANNOTATION_TYPE_SUBKIND, 1, 0);
+ TypeDeclSnippet c3 = classKey(assertEval("enum A {; private A(B b) {} }",
+ ste(MAIN_SNIPPET, RECOVERABLE_NOT_DEFINED, RECOVERABLE_NOT_DEFINED, false, null),
+ ste(c2, RECOVERABLE_NOT_DEFINED, OVERWRITTEN, false, MAIN_SNIPPET)));
+ assertTypeDeclSnippet(c3, "A", RECOVERABLE_NOT_DEFINED, ENUM_SUBKIND, 1, 0);
+ TypeDeclSnippet c4 = classKey(assertEval("interface A extends B {}",
+ ste(MAIN_SNIPPET, RECOVERABLE_NOT_DEFINED, RECOVERABLE_NOT_DEFINED, false, null),
+ ste(c3, RECOVERABLE_NOT_DEFINED, OVERWRITTEN, false, MAIN_SNIPPET)));
+ assertTypeDeclSnippet(c4, "A", RECOVERABLE_NOT_DEFINED, INTERFACE_SUBKIND, 1, 0);
+ TypeDeclSnippet c5 = classKey(assertEval("class A { void f(B b) {} }",
+ ste(MAIN_SNIPPET, RECOVERABLE_NOT_DEFINED, RECOVERABLE_NOT_DEFINED, false, null),
+ ste(c4, RECOVERABLE_NOT_DEFINED, OVERWRITTEN, false, MAIN_SNIPPET)));
+ assertTypeDeclSnippet(c5, "A", RECOVERABLE_NOT_DEFINED, CLASS_SUBKIND, 1, 0);
+ }
+
+ public void testSignature2() {
+ TypeDeclSnippet c1 = (TypeDeclSnippet) assertDeclareFail("class A { void f() { return g(); } }", "compiler.err.prob.found.req");
+ assertTypeDeclSnippet(c1, "A", REJECTED, CLASS_SUBKIND, 0, 2);
+ TypeDeclSnippet c2 = classKey(assertEval("class A { int f() { return g(); } }",
+ ste(c1, REJECTED, RECOVERABLE_NOT_DEFINED, false, null)));
+ assertTypeDeclSnippet(c2, "A", RECOVERABLE_NOT_DEFINED, CLASS_SUBKIND, 1, 0);
+ assertDrop(c2,
+ ste(c2, RECOVERABLE_NOT_DEFINED, DROPPED, false, null));
+ }
+
+ public void classDeclaration() {
+ assertEval("class A { }");
+ assertClasses(clazz(KullaTesting.ClassType.CLASS, "A"));
+ }
+
+
+ public void interfaceDeclaration() {
+ assertEval("interface A { }");
+ assertClasses(clazz(KullaTesting.ClassType.INTERFACE, "A"));
+ }
+
+ public void annotationDeclaration() {
+ assertEval("@interface A { }");
+ assertClasses(clazz(KullaTesting.ClassType.ANNOTATION, "A"));
+ }
+
+ public void enumDeclaration() {
+ assertEval("enum A { }");
+ assertClasses(clazz(KullaTesting.ClassType.ENUM, "A"));
+ }
+
+ public void classesDeclaration() {
+ assertEval("interface A { }");
+ assertEval("class B implements A { }");
+ assertEval("interface C extends A { }");
+ assertEval("enum D implements C { }");
+ assertEval("@interface E { }");
+ assertClasses(
+ clazz(KullaTesting.ClassType.INTERFACE, "A"),
+ clazz(KullaTesting.ClassType.CLASS, "B"),
+ clazz(KullaTesting.ClassType.INTERFACE, "C"),
+ clazz(KullaTesting.ClassType.ENUM, "D"),
+ clazz(KullaTesting.ClassType.ANNOTATION, "E"));
+ assertActiveKeys();
+ }
+
+ public void classesRedeclaration1() {
+ Snippet a = classKey(assertEval("class A { }"));
+ Snippet b = classKey(assertEval("interface B { }"));
+ assertClasses(clazz(KullaTesting.ClassType.CLASS, "A"), clazz(KullaTesting.ClassType.INTERFACE, "B"));
+ assertActiveKeys();
+
+ assertEval("interface A { }",
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(a, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertClasses(clazz(KullaTesting.ClassType.INTERFACE, "A"),
+ clazz(KullaTesting.ClassType.INTERFACE, "B"));
+ assertActiveKeys();
+
+ assertEval("interface B { } //again",
+ ste(MAIN_SNIPPET, VALID, VALID, false, null),
+ ste(b, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertClasses(clazz(KullaTesting.ClassType.INTERFACE, "A"),
+ clazz(KullaTesting.ClassType.INTERFACE, "B"));
+ assertActiveKeys();
+ }
+
+ public void classesRedeclaration2() {
+ assertEval("class A { }");
+ assertClasses(clazz(KullaTesting.ClassType.CLASS, "A"));
+ assertActiveKeys();
+
+ Snippet b = classKey(assertEval("class B extends A { }"));
+ assertClasses(clazz(KullaTesting.ClassType.CLASS, "A"),
+ clazz(KullaTesting.ClassType.CLASS, "B"));
+ assertActiveKeys();
+
+ Snippet c = classKey(assertEval("class C extends B { }"));
+ assertClasses(clazz(KullaTesting.ClassType.CLASS, "A"),
+ clazz(KullaTesting.ClassType.CLASS, "B"), clazz(KullaTesting.ClassType.CLASS, "C"));
+ assertActiveKeys();
+
+ assertEval("interface B { }",
+ DiagCheck.DIAG_OK,
+ DiagCheck.DIAG_ERROR,
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(b, VALID, OVERWRITTEN, false, MAIN_SNIPPET),
+ ste(c, VALID, RECOVERABLE_NOT_DEFINED, true, MAIN_SNIPPET));
+ assertClasses(clazz(KullaTesting.ClassType.CLASS, "A"),
+ clazz(KullaTesting.ClassType.INTERFACE, "B"), clazz(KullaTesting.ClassType.CLASS, "C"));
+ assertEval("new C();",
+ DiagCheck.DIAG_ERROR,
+ DiagCheck.DIAG_ERROR,
+ added(REJECTED));
+ assertActiveKeys();
+ }
+
+ public void classesCyclic1() {
+ Snippet b = classKey(assertEval("class B extends A { }",
+ added(RECOVERABLE_NOT_DEFINED)));
+ Snippet a = classKey(assertEval("class A extends B { }", DiagCheck.DIAG_IGNORE, DiagCheck.DIAG_IGNORE,
+ added(RECOVERABLE_NOT_DEFINED),
+ ste(b, RECOVERABLE_NOT_DEFINED, RECOVERABLE_NOT_DEFINED, false, MAIN_SNIPPET)));
+ /***
+ assertDeclareFail("class A extends B { }", "****",
+ added(REJECTED),
+ ste(b, RECOVERABLE_NOT_DEFINED, RECOVERABLE_NOT_DEFINED, false, MAIN_SNIPPET));
+ ***/
+ // It is random which one it shows up in, but cyclic error should be there
+ List<Diag> diagsA = getState().diagnostics(a);
+ List<Diag> diagsB = getState().diagnostics(b);
+ List<Diag> diags;
+ if (diagsA.isEmpty()) {
+ diags = diagsB;
+ } else {
+ diags = diagsA;
+ assertTrue(diagsB.isEmpty());
+ }
+ assertEquals(diags.size(), 1, "Expected one error");
+ assertEquals(diags.get(0).getCode(), "compiler.err.cyclic.inheritance", "Expected cyclic inheritance error");
+ assertActiveKeys();
+ }
+
+ public void classesCyclic2() {
+ Snippet d = classKey(assertEval("class D extends E { }", added(RECOVERABLE_NOT_DEFINED)));
+ assertEval("class E { D d; }",
+ added(VALID),
+ ste(d, RECOVERABLE_NOT_DEFINED, VALID, true, MAIN_SNIPPET));
+ assertActiveKeys();
+ }
+
+ public void classesCyclic3() {
+ Snippet outer = classKey(assertEval("class Outer { class Inner extends Foo { } }",
+ added(RECOVERABLE_NOT_DEFINED)));
+ Snippet foo = classKey(assertEval("class Foo { } ",
+ added(VALID),
+ ste(outer, RECOVERABLE_NOT_DEFINED, VALID, true, MAIN_SNIPPET)));
+ assertEval(" class Foo extends Outer { }",
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(foo, VALID, OVERWRITTEN, false, MAIN_SNIPPET),
+ ste(outer, VALID, VALID, true, MAIN_SNIPPET));
+ assertActiveKeys();
+ }
+
+ public void classesIgnoredModifiers() {
+ assertDeclareWarn1("public interface A { }",
+ new ExpectedDiagnostic("jdk.eval.warn.illegal.modifiers", 0, 6, 0, -1, -1, Diagnostic.Kind.WARNING));
+ assertDeclareWarn1("static class B implements A { }",
+ new ExpectedDiagnostic("jdk.eval.warn.illegal.modifiers", 0, 6, 0, -1, -1, Diagnostic.Kind.WARNING));
+ assertDeclareWarn1("final interface C extends A { }",
+ new ExpectedDiagnostic("jdk.eval.warn.illegal.modifiers", 0, 5, 0, -1, -1, Diagnostic.Kind.WARNING));
+ assertDeclareWarn1("protected enum D implements C { }",
+ new ExpectedDiagnostic("jdk.eval.warn.illegal.modifiers", 0, 9, 0, -1, -1, Diagnostic.Kind.WARNING));
+ assertActiveKeys();
+ }
+
+ public void ignoreModifierSpaceIssue() {
+ assertEval("interface I { void f(); } ");
+ // there should not be a space between 'I' and '{' to reproduce the failure
+ assertEval("class C implements I{ public void f() {}}");
+ assertClasses(clazz(KullaTesting.ClassType.CLASS, "C"), clazz(KullaTesting.ClassType.INTERFACE, "I"));
+ assertActiveKeys();
+ }
+
+ @DataProvider(name = "innerClasses")
+ public Object[][] innerClasses() {
+ List<Object[]> list = new ArrayList<>();
+ for (ClassType outerClassType : ClassType.values()) {
+ for (ClassType innerClassType : ClassType.values()) {
+ list.add(new Object[]{outerClassType, innerClassType});
+ }
+ }
+ return list.toArray(new Object[list.size()][]);
+ }
+
+ @Test(dataProvider = "innerClasses")
+ public void innerClasses(ClassType outerClassType, ClassType innerClassType) {
+ String source =
+ outerClassType + " A {" + (outerClassType == ClassType.ENUM ? ";" : "") +
+ innerClassType + " B { }" +
+ "}";
+ assertEval(source);
+ assertNumberOfActiveClasses(1);
+ assertActiveKeys();
+ }
+
+ public void testInnerClassesCrash() {
+ Snippet a = classKey(assertEval("class A { class B extends A {} }"));
+ Snippet a2 = classKey(assertEval("class A { interface I1 extends I2 {} interface I2 {} }",
+ ste(MAIN_SNIPPET, VALID, VALID, false, null),
+ ste(a, VALID, OVERWRITTEN, false, MAIN_SNIPPET)));
+ assertEval("class A { A a = new A() {}; }",
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(a2, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ }
+
+ public void testInnerClassesCrash1() {
+ assertEval("class A { class B extends A {} B getB() { return new B();} }");
+ assertEquals(varKey(assertEval("A a = new A();")).name(), "a");
+ VarSnippet variableKey = varKey(assertEval("a.getB();"));
+ assertEquals(variableKey.typeName(), "A.B");
+ }
+
+ public void testInnerClassesCrash2() {
+ assertEval("class A { interface I1 extends I2 {} interface I2 {} I1 x; }");
+ assertEquals(varKey(assertEval("A a = new A();")).name(), "a");
+ VarSnippet variableKey = varKey(assertEval("a.x;"));
+ assertEquals(variableKey.typeName(), "A.I1");
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/CommandCompletionTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,170 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Test Command Completion
+ * @library /tools/lib
+ * @build ReplToolTesting TestingInputStream Compiler ToolBox
+ * @run testng CommandCompletionTest
+ */
+
+import java.io.IOException;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.testng.annotations.Test;
+
+@Test
+public class CommandCompletionTest extends ReplToolTesting {
+
+ public void testCommand() {
+ assertCompletion("/f|", false, "/feedback ");
+ assertCompletion("/deb|", false);
+ assertCompletion("/feedback v|", false, "verbose");
+ assertCompletion("/c|", false, "/classes ", "/classpath ");
+ assertCompletion("/h|", false, "/help ", "/history ");
+ assertCompletion("/feedback |", false,
+ "?", "concise", "default", "normal", "off", "verbose");
+ }
+
+ public void testList() {
+ assertCompletion("/l|", false, "/list ");
+ assertCompletion("/list |", false, "all");
+ assertCompletion("/list q|", false);
+ }
+
+ public void testDrop() {
+ assertCompletion("/d|", false, "/drop ");
+
+ test(false, new String[] {"-nostartup"},
+ a -> assertClass(a, "class cTest {}", "class", "cTest"),
+ a -> assertMethod(a, "int mTest() { return 0; }", "()I", "mTest"),
+ a -> assertVariable(a, "int", "fTest"),
+ a -> assertCompletion(a, "/drop |", false, "1", "2", "3", "cTest", "fTest", "mTest"),
+ a -> assertCompletion(a, "/drop f|", false, "fTest")
+ );
+ }
+
+ public void testEdit() {
+ assertCompletion("/e|", false, "/edit ", "/exit ");
+ assertCompletion("/ed|", false, "/edit ");
+
+ test(false, new String[]{"-nostartup"},
+ a -> assertClass(a, "class cTest {}", "class", "cTest"),
+ a -> assertMethod(a, "int mTest() { return 0; }", "()I", "mTest"),
+ a -> assertVariable(a, "int", "fTest"),
+ a -> assertCompletion(a, "/edit |", false, "1", "2", "3", "cTest", "fTest", "mTest"),
+ a -> assertCompletion(a, "/edit f|", false, "fTest")
+ );
+ }
+
+ public void testOpen() throws IOException {
+ Compiler compiler = new Compiler();
+ assertCompletion("/o|", false, "/open ");
+ List<String> p1 = listFiles(Paths.get(""));
+ FileSystems.getDefault().getRootDirectories().forEach(s -> p1.add(s.toString()));
+ Collections.sort(p1);
+ assertCompletion("/open |", false, p1.toArray(new String[p1.size()]));
+ Path classDir = compiler.getClassDir();
+ List<String> p2 = listFiles(classDir);
+ assertCompletion("/open " + classDir + "/|", false, p2.toArray(new String[p2.size()]));
+ }
+
+ public void testSave() throws IOException {
+ Compiler compiler = new Compiler();
+ assertCompletion("/s|", false, "/save ", "/savestart ", "/seteditor ", "/setstart ");
+ List<String> p1 = listFiles(Paths.get(""));
+ Collections.addAll(p1, "all ", "history ");
+ FileSystems.getDefault().getRootDirectories().forEach(s -> p1.add(s.toString()));
+ Collections.sort(p1);
+ assertCompletion("/save |", false, p1.toArray(new String[p1.size()]));
+ Path classDir = compiler.getClassDir();
+ List<String> p2 = listFiles(classDir);
+ assertCompletion("/save " + classDir + "/|",
+ false, p2.toArray(new String[p2.size()]));
+ assertCompletion("/save all " + classDir + "/|",
+ false, p2.toArray(new String[p2.size()]));
+ }
+
+ public void testClassPath() throws IOException {
+ assertCompletion("/classp|", false, "/classpath ");
+ Compiler compiler = new Compiler();
+ Path outDir = compiler.getPath("testClasspathCompletion");
+ Files.createDirectories(outDir);
+ Files.createDirectories(outDir.resolve("dir"));
+ createIfNeeded(outDir.resolve("test.jar"));
+ createIfNeeded(outDir.resolve("test.zip"));
+ compiler.compile(outDir, "package pkg; public class A { public String toString() { return \"A\"; } }");
+ String jarName = "test.jar";
+ compiler.jar(outDir, jarName, "pkg/A.class");
+ compiler.getPath(outDir).resolve(jarName);
+ List<String> paths = listFiles(outDir, CLASSPATH_FILTER);
+ assertCompletion("/classpath " + outDir + "/|", false, paths.toArray(new String[paths.size()]));
+ }
+
+ public void testUserHome() throws IOException {
+ List<String> completions;
+ Path home = Paths.get(System.getProperty("user.home"));
+ try (Stream<Path> content = Files.list(home)) {
+ completions = content.filter(CLASSPATH_FILTER)
+ .map(file -> file.getFileName().toString() + (Files.isDirectory(file) ? "/" : ""))
+ .sorted()
+ .collect(Collectors.toList());
+ }
+ assertCompletion("/classpath ~/|", false, completions.toArray(new String[completions.size()]));
+ }
+
+ private void createIfNeeded(Path file) throws IOException {
+ if (!Files.exists(file))
+ Files.createFile(file);
+ }
+ private List<String> listFiles(Path path) throws IOException {
+ return listFiles(path, ACCEPT_ALL);
+ }
+
+ private List<String> listFiles(Path path, Predicate<? super Path> filter) throws IOException {
+ try (Stream<Path> stream = Files.list(path)) {
+ return stream.filter(filter)
+ .map(p -> p.getFileName().toString() + (Files.isDirectory(p) ? "/" : ""))
+ .sorted()
+ .collect(Collectors.toList());
+ }
+ }
+
+ private static final Predicate<? super Path> ACCEPT_ALL =
+ (file) -> !file.endsWith(".") && !file.endsWith("..");
+
+ private static final Predicate<? super Path> CLASSPATH_FILTER =
+ (file) -> ACCEPT_ALL.test(file) &&
+ (Files.isDirectory(file) ||
+ file.getFileName().toString().endsWith(".jar") ||
+ file.getFileName().toString().endsWith(".zip"));
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/Compiler.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+
+public class Compiler {
+
+ private final ToolBox tb = new ToolBox();
+
+ public Path getClassDir() {
+ String classes = ToolBox.testClasses;
+ if (classes == null) {
+ return Paths.get("build");
+ } else {
+ return Paths.get(classes);
+ }
+ }
+
+ public Path getPath(String path) {
+ return getPath(Paths.get(path));
+ }
+
+ public Path getPath(Path path) {
+ return getClassDir().resolve(path);
+ }
+
+ public void compile(String...sources) {
+ compile(Paths.get("."), sources);
+ }
+
+ public void compile(Path directory, String...sources) {
+ Path classDir = getClassDir();
+ tb.new JavacTask()
+ .options("-d", classDir.resolve(directory).toString())
+ .sources(sources)
+ .run();
+ }
+
+ public void jar(String jarName, String...files) {
+ jar(Paths.get("."), jarName, files);
+ }
+
+ public void jar(Path directory, String jarName, String...files) {
+ Manifest manifest = new Manifest();
+ manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
+ Path classDirPath = getClassDir();
+ Path baseDir = classDirPath.resolve(directory);
+ Path jarPath = baseDir.resolve(jarName);
+ tb.new JarTask(jarPath.toString())
+ .manifest(manifest)
+ .baseDir(baseDir.toString())
+ .files(files).run();
+ }
+
+ public void writeToFile(Path path, String...sources) {
+ try {
+ if (path.getParent() != null) {
+ Files.createDirectories(path.getParent());
+ }
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ try (BufferedWriter writer = Files.newBufferedWriter(path)) {
+ for (String source : sources) {
+ writer.append(source);
+ writer.append('\n');
+ }
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/CompletenessStressTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,265 @@
+/*
+ * 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.
+ */
+
+import java.io.File;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.lang.model.element.Modifier;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.ToolProvider;
+
+import com.sun.source.tree.BlockTree;
+import com.sun.source.tree.BreakTree;
+import com.sun.source.tree.CaseTree;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.ContinueTree;
+import com.sun.source.tree.DoWhileLoopTree;
+import com.sun.source.tree.ExpressionStatementTree;
+import com.sun.source.tree.ForLoopTree;
+import com.sun.source.tree.IfTree;
+import com.sun.source.tree.ImportTree;
+import com.sun.source.tree.LabeledStatementTree;
+import com.sun.source.tree.LineMap;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.tree.ReturnTree;
+import com.sun.source.tree.StatementTree;
+import com.sun.source.tree.SwitchTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.tree.WhileLoopTree;
+import com.sun.source.util.SourcePositions;
+import com.sun.source.util.Trees;
+import com.sun.tools.javac.api.JavacTaskImpl;
+
+import jdk.jshell.SourceCodeAnalysis;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import static java.lang.Integer.max;
+import static java.lang.Integer.min;
+import static jdk.jshell.SourceCodeAnalysis.Completeness.*;
+
+public class CompletenessStressTest extends KullaTesting {
+ public final static String JDK_ROOT_SRC_PROP = "jdk.root.src";
+ public final static String JDK_ROOT_SRC;
+
+ static {
+ JDK_ROOT_SRC = System.getProperty(JDK_ROOT_SRC_PROP);
+ }
+
+ public File getSourceFile(String fileName) {
+ for (File dir : getDirectoriesToTest()) {
+ File file = new File(dir, fileName);
+ if (file.exists()) {
+ return file;
+ }
+ }
+ throw new AssertionError("File not found: " + fileName);
+ }
+
+ public File[] getDirectoriesToTest() {
+ return new File[]{
+ new File(JDK_ROOT_SRC, "nashorn/src"),
+ new File(JDK_ROOT_SRC, "langtools/src"),
+ new File(JDK_ROOT_SRC, "jaxp/src"),
+ new File(JDK_ROOT_SRC, "jaxws/src"),
+ new File(JDK_ROOT_SRC, "jdk/src"),
+ new File(JDK_ROOT_SRC, "corba/src")
+ };
+ }
+
+ @DataProvider(name = "crawler")
+ public Object[][] dataProvider() throws IOException {
+ File[] srcDirs = getDirectoriesToTest();
+ List<String[]> list = new ArrayList<>();
+ for (File srcDir : srcDirs) {
+ String srcDirName = srcDir.getAbsolutePath();
+ // this is just to obtain pretty test names for testng tests
+ List<String[]> a = Files.walk(Paths.get(srcDirName))
+ .map(Path::toFile)
+ .map(File::getAbsolutePath)
+ .filter(n -> n.endsWith(".java"))
+ .map(n -> n.replace(srcDirName, ""))
+ .map(n -> new String[]{n})
+ .collect(Collectors.toList());
+ if (a.isEmpty()) {
+ throw new AssertionError("Java sources have not been found in directory: " + srcDirName);
+ }
+ list.addAll(a);
+ }
+ return list.toArray(new String[list.size()][]);
+ }
+
+ @Test(dataProvider = "crawler")
+ public void testFile(String fileName) throws IOException {
+ File file = getSourceFile(fileName);
+ final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+ final StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
+ boolean success = true;
+ StringWriter writer = new StringWriter();
+ writer.write("Testing : " + file.toString() + "\n");
+ String text = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
+ Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjects(file);
+ JavacTaskImpl task = (JavacTaskImpl) compiler.getTask(null, fileManager, null, null, null, compilationUnits);
+ Iterable<? extends CompilationUnitTree> asts = task.parse();
+ Trees trees = Trees.instance(task);
+ SourcePositions sp = trees.getSourcePositions();
+
+ for (CompilationUnitTree cut : asts) {
+ for (ImportTree imp : cut.getImports()) {
+ success &= testStatement(writer, sp, text, cut, imp);
+ }
+ for (Tree decl : cut.getTypeDecls()) {
+ success &= testStatement(writer, sp, text, cut, decl);
+ if (decl instanceof ClassTree) {
+ ClassTree ct = (ClassTree) decl;
+ for (Tree mem : ct.getMembers()) {
+ if (mem instanceof MethodTree) {
+ MethodTree mt = (MethodTree) mem;
+ BlockTree bt = mt.getBody();
+ // No abstract methods or constructors
+ if (bt != null && mt.getReturnType() != null) {
+ // The modifiers synchronized, abstract, and default are not allowed on
+ // top-level declarations and are errors.
+ Set<Modifier> modifier = mt.getModifiers().getFlags();
+ if (!modifier.contains(Modifier.ABSTRACT)
+ && !modifier.contains(Modifier.SYNCHRONIZED)
+ && !modifier.contains(Modifier.DEFAULT)) {
+ success &= testStatement(writer, sp, text, cut, mt);
+ }
+ testBlock(writer, sp, text, cut, bt);
+ }
+ }
+ }
+ }
+ }
+ }
+ fileManager.close();
+ if (!success) {
+ throw new AssertionError(writer.toString());
+ }
+ }
+
+ private boolean isLegal(StatementTree st) {
+ return !(st instanceof ReturnTree) &&
+ !(st instanceof ContinueTree) && !(st instanceof BreakTree);
+ }
+
+ private boolean testBranch(StringWriter writer, SourcePositions sp, String text, CompilationUnitTree cut, StatementTree statementTree) {
+ if (statementTree instanceof BlockTree) {
+ return testBlock(writer, sp, text, cut, (BlockTree) statementTree);
+ } else if (isLegal(statementTree)) {
+ return testStatement(writer, sp, text, cut, statementTree);
+ }
+ return true;
+ }
+
+ private boolean testBlock(StringWriter writer, SourcePositions sp, String text, CompilationUnitTree cut, BlockTree blockTree) {
+ boolean success = true;
+ for (StatementTree st : blockTree.getStatements()) {
+ if (isLegal(st)) {
+ success &= testStatement(writer, sp, text, cut, st);
+ }
+ if (st instanceof IfTree) {
+ IfTree ifTree = (IfTree) st;
+ success &= testBranch(writer, sp, text, cut, ifTree.getThenStatement());
+ success &= testBranch(writer, sp, text, cut, ifTree.getElseStatement());
+ } else if (st instanceof WhileLoopTree) {
+ WhileLoopTree whileLoopTree = (WhileLoopTree) st;
+ success &= testBranch(writer, sp, text, cut, whileLoopTree.getStatement());
+ } else if (st instanceof DoWhileLoopTree) {
+ DoWhileLoopTree doWhileLoopTree = (DoWhileLoopTree) st;
+ success &= testBranch(writer, sp, text, cut, doWhileLoopTree.getStatement());
+ } else if (st instanceof ForLoopTree) {
+ ForLoopTree forLoopTree = (ForLoopTree) st;
+ success &= testBranch(writer, sp, text, cut, forLoopTree.getStatement());
+ } else if (st instanceof LabeledStatementTree) {
+ LabeledStatementTree labelTree = (LabeledStatementTree) st;
+ success &= testBranch(writer, sp, text, cut, labelTree.getStatement());
+ } else if (st instanceof SwitchTree) {
+ SwitchTree switchTree = (SwitchTree) st;
+ for (CaseTree caseTree : switchTree.getCases()) {
+ for (StatementTree statementTree : caseTree.getStatements()) {
+ success &= testBranch(writer, sp, text, cut, statementTree);
+ }
+ }
+ }
+ }
+ return success;
+ }
+
+ private boolean testStatement(StringWriter writer, SourcePositions sp, String text, CompilationUnitTree cut, Tree statement) {
+ if (statement == null) {
+ return true;
+ }
+ int start = (int) sp.getStartPosition(cut, statement);
+ int end = (int) sp.getEndPosition(cut, statement);
+ char ch = text.charAt(end - 1);
+ SourceCodeAnalysis.Completeness expected = COMPLETE;
+ LineMap lineMap = cut.getLineMap();
+ int row = (int) lineMap.getLineNumber(start);
+ int column = (int) lineMap.getColumnNumber(start);
+ switch (ch) {
+ case ',':
+ case ';':
+ expected = (statement instanceof ExpressionStatementTree)
+ ? COMPLETE
+ : COMPLETE_WITH_SEMI;
+ --end;
+ break;
+ case '}':
+ break;
+ default:
+ writer.write(String.format("Unexpected end: row %d, column %d: '%c' -- %s\n",
+ row, column, ch, text.substring(start, end)));
+ return true;
+ }
+ String unit = text.substring(start, end);
+ SourceCodeAnalysis.CompletionInfo ci = getAnalysis().analyzeCompletion(unit);
+ if (ci.completeness != expected) {
+ if (expected == COMPLETE_WITH_SEMI && (ci.completeness == CONSIDERED_INCOMPLETE || ci.completeness == EMPTY)) {
+ writer.write(String.format("Empty statement: row %d, column %d: -- %s\n",
+ start, end, unit));
+ } else {
+ String oops = unit.substring(max(0, ci.unitEndPos - 10), ci.unitEndPos) + "|||" +
+ unit.substring(ci.unitEndPos, min(unit.length(), ci.unitEndPos + 10));
+ writer.write(String.format("Expected %s got %s: '%s' row %d, column %d: -- %s\n",
+ expected, ci.completeness, oops, row, column, unit));
+ return false;
+ }
+ }
+ return true;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/CompletenessTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,291 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Test SourceCodeAnalysis
+ * @build KullaTesting TestingInputStream
+ * @run testng CompletenessTest
+ */
+
+import java.util.Map;
+import java.util.HashMap;
+
+import org.testng.annotations.Test;
+import jdk.jshell.SourceCodeAnalysis.Completeness;
+
+import static jdk.jshell.SourceCodeAnalysis.Completeness.*;
+
+@Test
+public class CompletenessTest extends KullaTesting {
+
+ // Add complete units that end with semicolon to complete_with_semi (without
+ // the semicolon). Both cases will be tested.
+ static final String[] complete = new String[] {
+ "{ x= 4; }",
+ "int mm(int x) {kll}",
+ "if (t) { ddd; }",
+ "for (int i = 0; i < lines.length(); ++i) { foo }",
+ "while (ct == null) { switch (current.kind) { case EOF: { } } }",
+ "if (match.kind == BRACES && (prevCT.kind == ARROW || prevCT.kind == NEW_MIDDLE)) { new CT(UNMATCHED, current, \"Unmatched \" + unmatched); }",
+ "enum TK { EOF(TokenKind.EOF, 0), NEW_MIDDLE(XEXPR1|XTERM); }",
+ "List<T> f() { return null; }",
+ "List<?> f() { return null; }",
+ "List<? extends Object> f() { return null; }",
+ "Map<? extends Object, ? super Object> f() { return null; }",
+ "class C { int z; }",
+ "synchronized (r) { f(); }",
+ "try { } catch (Exception ex) { }",
+ "try { } catch (Exception ex) { } finally { }",
+ "try { } finally { }",
+ "try (java.util.zip.ZipFile zf = new java.util.zip.ZipFile(zipFileName)) { }",
+ "foo: while (true) { printf(\"Innn\"); break foo; }",
+ ";",
+ };
+
+ static final String[] expression = new String[] {
+ "test",
+ "x + y",
+ "x + y ++",
+ "p = 9",
+ "match(BRACKETS, TokenKind.LBRACKET)",
+ "new C()",
+ "new C() { public String toString() { return \"Hi\"; } }",
+ "new int[]",
+ "new int[] {1, 2,3}",
+ "new Foo() {}",
+ "i >= 0 && Character.isWhitespace(s.charAt(i))",
+ };
+
+ static final String[] complete_with_semi = new String[] {
+ "int mm",
+ "if (t) ddd",
+ "int p = 9",
+ "int p",
+ "Deque<Token> stack = new ArrayDeque<>()",
+ "final Deque<Token> stack = new ArrayDeque<>()",
+ "java.util.Scanner input = new java.util.Scanner(System.in)",
+ "java.util.Scanner input = new java.util.Scanner(System.in) { }",
+ "int j = -i",
+ "String[] a = { \"AAA\" }",
+ "assert true",
+ "int path[]",
+ "int path[][]",
+ "int path[][] = new int[22][]",
+ "int path[] = new int[22]",
+ "int path[] = new int[] {1, 2, 3}",
+ "int[] path",
+ "int path[] = new int[22]",
+ "int path[][] = new int[22][]",
+ "for (Object o : a) System.out.println(\"Yep\")",
+ "while (os == null) System.out.println(\"Yep\")",
+ "do f(); while (t)",
+ "if (os == null) System.out.println(\"Yep\")",
+ "if (t) if (!t) System.out.println(123)",
+ "for (int i = 0; i < 10; ++i) if (i < 5) System.out.println(i); else break",
+ "for (int i = 0; i < 10; ++i) if (i < 5) System.out.println(i); else continue",
+ "for (int i = 0; i < 10; ++i) if (i < 5) System.out.println(i); else return",
+ "throw ex",
+ "C c = new C()",
+ "java.util.zip.ZipFile zf = new java.util.zip.ZipFile(zipFileName)",
+ "BufferedReader br = new BufferedReader(new FileReader(path))",
+ "bar: g()",
+ "baz: while (true) if (t()) printf('-'); else break baz",
+ };
+
+ static final String[] considered_incomplete = new String[] {
+ "if (t)",
+ "if (t) { } else",
+ "if (t) if (!t)",
+ "if (match.kind == BRACES && (prevCT.kind == ARROW || prevCT.kind == NEW_MIDDLE))",
+ "for (int i = 0; i < 10; ++i)",
+ "while (os == null)",
+ };
+
+ static final String[] definitely_incomplete = new String[] {
+ "int mm(",
+ "int mm(int x",
+ "int mm(int x)",
+ "int mm(int x) {",
+ "int mm(int x) {kll",
+ "if",
+ "if (",
+ "if (t",
+ "if (t) {",
+ "if (t) { ddd",
+ "if (t) { ddd;",
+ "if (t) if (",
+ "if (stack.isEmpty()) {",
+ "if (match.kind == BRACES && (prevCT.kind == ARROW || prevCT.kind == NEW_MIDDLE)) {",
+ "if (match.kind == BRACES && (prevCT.kind == ARROW || prevCT.kind == NEW_MIDDLE)) { new CT(UNMATCHED, current, \"Unmatched \" + unmatched);",
+ "x +",
+ "int",
+ "for (int i = 0; i < lines.length(); ++i) {",
+ "new",
+ "new C(",
+ "new int[",
+ "new int[] {1, 2,3",
+ "new int[] {",
+ "while (ct == null) {",
+ "while (ct == null) { switch (current.kind) {",
+ "while (ct == null) { switch (current.kind) { case EOF: {",
+ "while (ct == null) { switch (current.kind) { case EOF: { } }",
+ "enum TK {",
+ "enum TK { EOF(TokenKind.EOF, 0),",
+ "enum TK { EOF(TokenKind.EOF, 0), NEW_MIDDLE(XEXPR1|XTERM)",
+ "enum TK { EOF(TokenKind.EOF, 0), NEW_MIDDLE(XEXPR1|XTERM); ",
+ };
+
+ static final String[] unknown = new String[] {
+ "new ;"
+ };
+
+ static final Map<Completeness, String[]> statusToCases = new HashMap<>();
+ static {
+ statusToCases.put(COMPLETE, complete);
+ statusToCases.put(COMPLETE_WITH_SEMI, complete_with_semi);
+ statusToCases.put(CONSIDERED_INCOMPLETE, considered_incomplete);
+ statusToCases.put(DEFINITELY_INCOMPLETE, definitely_incomplete);
+ }
+
+ private void assertStatus(String input, Completeness status, String source) {
+ String augSrc;
+ switch (status) {
+ case COMPLETE_WITH_SEMI:
+ augSrc = source + ";";
+ break;
+
+ case DEFINITELY_INCOMPLETE:
+ case CONSIDERED_INCOMPLETE:
+ augSrc = null;
+ break;
+
+ case EMPTY:
+ case COMPLETE:
+ case UNKNOWN:
+ augSrc = source;
+ break;
+
+ default:
+ throw new AssertionError();
+ }
+ assertAnalyze(input, status, augSrc);
+ }
+
+ private void assertStatus(String[] ins, Completeness status) {
+ for (String input : ins) {
+ assertStatus(input, status, input);
+ }
+ }
+
+ public void test_complete() {
+ assertStatus(complete, COMPLETE);
+ }
+
+ public void test_expression() {
+ assertStatus(expression, COMPLETE);
+ }
+
+ public void test_complete_with_semi() {
+ assertStatus(complete_with_semi, COMPLETE_WITH_SEMI);
+ }
+
+ public void test_considered_incomplete() {
+ assertStatus(considered_incomplete, CONSIDERED_INCOMPLETE);
+ }
+
+ public void test_definitely_incomplete() {
+ assertStatus(definitely_incomplete, DEFINITELY_INCOMPLETE);
+ }
+
+ public void test_unknown() {
+ assertStatus(definitely_incomplete, DEFINITELY_INCOMPLETE);
+ }
+
+ public void testCompleted_complete_with_semi() {
+ for (String in : complete_with_semi) {
+ String input = in + ";";
+ assertStatus(input, COMPLETE, input);
+ }
+ }
+
+ public void testCompleted_expression_with_semi() {
+ for (String in : expression) {
+ String input = in + ";";
+ assertStatus(input, COMPLETE, input);
+ }
+ }
+
+ public void testCompleted_considered_incomplete() {
+ for (String in : considered_incomplete) {
+ String input = in + ";";
+ assertStatus(input, COMPLETE, input);
+ }
+ }
+
+ private void assertSourceByStatus(String first) {
+ for (Map.Entry<Completeness, String[]> e : statusToCases.entrySet()) {
+ for (String in : e.getValue()) {
+ String input = first + in;
+ assertAnalyze(input, COMPLETE, first, in, true);
+ }
+ }
+ }
+
+ public void testCompleteSource_complete() {
+ for (String input : complete) {
+ assertSourceByStatus(input);
+ }
+ }
+
+ public void testCompleteSource_complete_with_semi() {
+ for (String in : complete_with_semi) {
+ String input = in + ";";
+ assertSourceByStatus(input);
+ }
+ }
+
+ public void testCompleteSource_expression() {
+ for (String in : expression) {
+ String input = in + ";";
+ assertSourceByStatus(input);
+ }
+ }
+
+ public void testCompleteSource_considered_incomplete() {
+ for (String in : considered_incomplete) {
+ String input = in + ";";
+ assertSourceByStatus(input);
+ }
+ }
+
+ public void testTrailingSlash() {
+ assertStatus("\"abc\\", UNKNOWN, "\"abc\\");
+ }
+
+ public void testMiscSource() {
+ assertStatus("if (t) if ", DEFINITELY_INCOMPLETE, "if (t) if"); //Bug
+ assertStatus("int m() {} dfd", COMPLETE, "int m() {}");
+ assertStatus("int p = ", DEFINITELY_INCOMPLETE, "int p ="); //Bug
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/CompletionSuggestionTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,524 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Test Completion
+ * @library /tools/lib
+ * @build KullaTesting TestingInputStream ToolBox Compiler
+ * @run testng CompletionSuggestionTest
+ */
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Set;
+import java.util.HashSet;
+
+import jdk.jshell.Snippet;
+import org.testng.annotations.Test;
+
+import static jdk.jshell.Snippet.Status.VALID;
+import static jdk.jshell.Snippet.Status.OVERWRITTEN;
+
+@Test
+public class CompletionSuggestionTest extends KullaTesting {
+
+ public void testMemberExpr() {
+ assertEval("class Test { static void test() { } }");
+ assertCompletion("Test.t|", "test()");
+ assertEval("Test ccTestInstance = new Test();");
+ assertCompletion("ccTestInstance.t|", "toString()");
+ assertCompletion(" ccTe|", "ccTestInstance");
+ assertCompletion("String value = ccTestInstance.to|", "toString()");
+ assertCompletion("java.util.Coll|", "Collection", "Collections");
+ assertCompletion("String.cla|", "class");
+ assertCompletion("boolean.cla|", "class");
+ assertCompletion("byte.cla|", "class");
+ assertCompletion("short.cla|", "class");
+ assertCompletion("char.cla|", "class");
+ assertCompletion("int.cla|", "class");
+ assertCompletion("float.cla|", "class");
+ assertCompletion("long.cla|", "class");
+ assertCompletion("double.cla|", "class");
+ assertCompletion("void.cla|", "class");
+ assertCompletion("Object[].|", "class");
+ assertCompletion("int[].|", "class");
+ assertEval("Object[] ao = null;");
+ assertCompletion("int i = ao.|", "length");
+ assertEval("int[] ai = null;");
+ assertCompletion("int i = ai.|", "length");
+ assertCompletionIncludesExcludes("\"\".|",
+ new HashSet<>(Collections.emptyList()),
+ new HashSet<>(Arrays.asList("String(")));
+ assertEval("double d = 0;");
+ assertEval("void m() {}");
+ assertCompletionIncludesExcludes("d.|",
+ new HashSet<>(Collections.emptyList()),
+ new HashSet<>(Arrays.asList("class")));
+ assertCompletionIncludesExcludes("m().|",
+ new HashSet<>(Collections.emptyList()),
+ new HashSet<>(Arrays.asList("class")));
+ assertEval("class C {class D {} static class E {} enum F {} interface H {} void method() {} int number;}");
+ assertCompletionIncludesExcludes("C.|",
+ new HashSet<>(Arrays.asList("D", "E", "F", "H", "class")),
+ new HashSet<>(Arrays.asList("method()", "number")));
+ assertCompletionIncludesExcludes("new C().|",
+ new HashSet<>(Arrays.asList("method()", "number")),
+ new HashSet<>(Arrays.asList("D", "E", "F", "H", "class")));
+ assertCompletionIncludesExcludes("new C() {}.|",
+ new HashSet<>(Arrays.asList("method()", "number")),
+ new HashSet<>(Arrays.asList("D", "E", "F", "H", "class")));
+ }
+
+ public void testStartOfExpression() {
+ assertEval("int ccTest = 0;");
+ assertCompletion("System.err.println(cc|", "ccTest");
+ assertCompletion("for (int i = cc|", "ccTest");
+ }
+
+ public void testParameter() {
+ assertCompletion("class C{void method(int num){num|", "num");
+ }
+
+ public void testPrimitive() {
+ Set<String> primitives = new HashSet<>(Arrays.asList("boolean", "char", "byte", "short", "int", "long", "float", "double"));
+ Set<String> onlyVoid = new HashSet<>(Collections.singletonList("void"));
+ Set<String> primitivesOrVoid = new HashSet<>(primitives);
+ primitivesOrVoid.addAll(onlyVoid);
+
+ assertCompletionIncludesExcludes("|",
+ primitivesOrVoid,
+ new HashSet<>(Collections.emptyList()));
+ assertCompletionIncludesExcludes("int num = |",
+ primitivesOrVoid,
+ new HashSet<>(Collections.emptyList()));
+ assertCompletionIncludesExcludes("num = |",
+ primitivesOrVoid,
+ new HashSet<>(Collections.emptyList()));
+ assertCompletionIncludesExcludes("class C{void m() {|",
+ primitivesOrVoid,
+ new HashSet<>(Collections.emptyList()));
+ assertCompletionIncludesExcludes("void method(|",
+ primitives,
+ onlyVoid);
+ assertCompletionIncludesExcludes("void method(int num, |",
+ primitives,
+ onlyVoid);
+ assertCompletion("new java.util.ArrayList<doub|");
+ assertCompletion("class A extends doubl|");
+ assertCompletion("class A implements doubl|");
+ assertCompletion("interface A extends doubl|");
+ assertCompletion("enum A implements doubl|");
+ assertCompletion("class A<T extends doubl|");
+ }
+
+ public void testEmpty() {
+ assertCompletionIncludesExcludes("|",
+ new HashSet<>(Arrays.asList("Object", "Void")),
+ new HashSet<>(Arrays.asList("$REPL00DOESNOTMATTER")));
+ assertCompletionIncludesExcludes("V|",
+ new HashSet<>(Collections.singletonList("Void")),
+ new HashSet<>(Collections.singletonList("Object")));
+ assertCompletionIncludesExcludes("{ |",
+ new HashSet<>(Arrays.asList("Object", "Void")),
+ new HashSet<>(Arrays.asList("$REPL00DOESNOTMATTER")));
+ }
+
+ public void testSmartCompletion() {
+ assertEval("int ccTest1 = 0;");
+ assertEval("int ccTest2 = 0;");
+ assertEval("String ccTest3 = null;");
+ assertEval("void method(int i, String str) { }");
+ assertEval("void method(String str, int i) { }");
+ assertEval("java.util.List<String> list = null;");
+ assertCompletion("int ccTest4 = |", true, "ccTest1", "ccTest2");
+ assertCompletion("ccTest2 = |", true, "ccTest1", "ccTest2");
+ assertCompletion("int ccTest4 = ccTe|", "ccTest1", "ccTest2", "ccTest3");
+ assertCompletion("int ccTest4 = ccTest3.len|", true, "length()");
+ assertCompletion("method(|", true, "ccTest1", "ccTest2", "ccTest3");
+ assertCompletion("method(0, |", true, "ccTest3");
+ assertCompletion("list.add(|", true, "ccTest1", "ccTest2", "ccTest3");
+ assertCompletion("list.add(0, |", true, "ccTest3");
+ assertCompletion("new String(|", true, "ccTest3");
+ assertCompletion("new String(new char[0], |", true, "ccTest1", "ccTest2");
+ assertCompletionIncludesExcludes("new jav|", new HashSet<>(Arrays.asList("java", "javax")), Collections.emptySet());
+ assertCompletion("Class<String> clazz = String.c|", true, "class");
+
+ Snippet klass = classKey(assertEval("class Klass {void method(int n) {} private void method(String str) {}}"));
+ assertCompletion("new Klass().method(|", true, "ccTest1", "ccTest2");
+ Snippet klass2 = classKey(assertEval("class Klass {static void method(int n) {} void method(String str) {}}",
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(klass, VALID, OVERWRITTEN, false, MAIN_SNIPPET)));
+ assertCompletion("Klass.method(|", true, "ccTest1", "ccTest2");
+ assertEval("class Klass {Klass(int n) {} private Klass(String str) {}}",
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(klass2, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertCompletion("new Klass(|", true, "ccTest1", "ccTest2");
+ }
+
+ public void testSmartCompletionInOverriddenMethodInvocation() {
+ assertEval("int ccTest1 = 0;");
+ assertEval("int ccTest2 = 0;");
+ assertEval("String ccTest3 = null;");
+ assertCompletion("\"\".wait(|", true, "ccTest1", "ccTest2");
+ assertEval("class Base {void method(int n) {}}");
+ assertEval("class Extend extends Base {}");
+ assertCompletion("new Extend().method(|", true, "ccTest1", "ccTest2");
+ }
+
+ public void testSmartCompletionForBoxedType() {
+ assertEval("int ccTest1 = 0;");
+ assertEval("Integer ccTest2 = 0;");
+ assertEval("Object ccTest3 = null;");
+ assertEval("int method1(int n) {return n;}");
+ assertEval("Integer method2(Integer n) {return n;}");
+ assertEval("Object method3(Object o) {return o;}");
+ assertCompletion("int ccTest4 = |", true, "ccTest1", "ccTest2", "method1(", "method2(");
+ assertCompletion("Integer ccTest4 = |", true, "ccTest1", "ccTest2", "method1(", "method2(");
+ assertCompletion("Object ccTest4 = |", true, "ccTest1", "ccTest2", "ccTest3", "method1(", "method2(", "method3(");
+ assertCompletion("method1(|", true, "ccTest1", "ccTest2", "method1(", "method2(");
+ assertCompletion("method2(|", true, "ccTest1", "ccTest2", "method1(", "method2(");
+ assertCompletion("method3(|", true, "ccTest1", "ccTest2", "ccTest3", "method1(", "method2(", "method3(");
+ }
+
+ public void testNewClass() {
+ assertCompletion("String str = new Strin|", "String(", "StringBuffer(", "StringBuilder(", "StringIndexOutOfBoundsException(");
+ assertCompletion("String str = new java.lang.Strin|", "String(", "StringBuffer(", "StringBuilder(", "StringIndexOutOfBoundsException(");
+ assertCompletion("String str = new |", true, "String(");
+ assertCompletion("String str = new java.lang.|", true, "String(");
+ assertCompletion("throw new Strin|", true, "StringIndexOutOfBoundsException(");
+
+ assertEval("class A{class B{} class C {C(int n) {}} static class D {} interface I {}}");
+ assertEval("A a;");
+ assertCompletion("new A().new |", "B()", "C(");
+ assertCompletion("a.new |", "B()", "C(");
+ assertCompletion("new A.|", "D()");
+
+ assertEval("enum E{; class A {}}");
+ assertEval("interface I{; class A {}}");
+ assertCompletion("new E.|", "A()");
+ assertCompletion("new I.|", "A()");
+ assertCompletion("new String(I.A|", "A");
+ }
+
+ public void testFullyQualified() {
+ assertCompletion("Optional<String> opt = java.u|", "util");
+ assertCompletionIncludesExcludes("Optional<Strings> opt = java.util.O|", new HashSet<>(Collections.singletonList("Optional")), Collections.emptySet());
+
+ assertEval("void method(java.util.Optional<String> opt) {}");
+ assertCompletion("method(java.u|", "util");
+
+ assertCompletion("Object.notElement.|");
+ assertCompletion("Object o = com.su|", "sun");
+ }
+
+ public void testCheckAccessibility() {
+ assertCompletion("java.util.regex.Pattern.co|", "compile(");
+ }
+
+ public void testCompletePackages() {
+ assertCompletion("java.u|", "util");
+ assertCompletionIncludesExcludes("jav|", new HashSet<>(Arrays.asList("java", "javax")), Collections.emptySet());
+ }
+
+ public void testImports() {
+ assertCompletion("import java.u|", "util");
+ assertCompletionIncludesExcludes("import jav|", new HashSet<>(Arrays.asList("java", "javax")), Collections.emptySet());
+ assertCompletion("import static java.u|", "util");
+ assertCompletionIncludesExcludes("import static jav|", new HashSet<>(Arrays.asList("java", "javax")), Collections.emptySet());
+ assertCompletion("import static java.lang.Boolean.g|", "getBoolean");
+ assertCompletion("import java.util.*|");
+ assertCompletionIncludesExcludes("import java.lang.String.|",
+ Collections.emptySet(),
+ new HashSet<>(Arrays.asList("CASE_INSENSITIVE_ORDER", "copyValueOf", "format", "join", "valueOf", "class", "length")));
+ assertCompletionIncludesExcludes("import static java.lang.String.|",
+ new HashSet<>(Arrays.asList("CASE_INSENSITIVE_ORDER", "copyValueOf", "format", "join", "valueOf")),
+ new HashSet<>(Arrays.asList("class", "length")));
+ assertCompletionIncludesExcludes("import java.util.Map.|",
+ new HashSet<>(Arrays.asList("Entry")),
+ new HashSet<>(Arrays.asList("class")));
+ }
+
+ public void testBrokenClassFile() throws Exception {
+ Compiler compiler = new Compiler();
+ Path testOutDir = Paths.get("CompletionTestBrokenClassFile");
+ String input = "package test.inner; public class Test {}";
+ compiler.compile(testOutDir, input);
+ addToClasspath(compiler.getPath(testOutDir).resolve("test"));
+ assertCompletion("import inner.|");
+ }
+
+ public void testDocumentation() {
+ assertDocumentation("System.getProperty(|",
+ "java.lang.System.getProperty(java.lang.String arg0)",
+ "java.lang.System.getProperty(java.lang.String arg0, java.lang.String arg1)");
+ assertEval("char[] chars = null;");
+ assertDocumentation("new String(chars, |",
+ "java.lang.String(char[] arg0, int arg1, int arg2)");
+ assertDocumentation("String.format(|",
+ "java.lang.String.format(java.lang.String arg0, java.lang.Object... arg1)",
+ "java.lang.String.format(java.util.Locale arg0, java.lang.String arg1, java.lang.Object... arg2)");
+ assertDocumentation("\"\".getBytes(\"\"|", "java.lang.String.getBytes(int arg0, int arg1, byte[] arg2, int arg3)",
+ "java.lang.String.getBytes(java.lang.String arg0)",
+ "java.lang.String.getBytes(java.nio.charset.Charset arg0)");
+ assertDocumentation("\"\".getBytes(\"\" |", "java.lang.String.getBytes(int arg0, int arg1, byte[] arg2, int arg3)",
+ "java.lang.String.getBytes(java.lang.String arg0)",
+ "java.lang.String.getBytes(java.nio.charset.Charset arg0)");
+ }
+
+ public void testMethodsWithNoArguments() {
+ assertDocumentation("System.out.println(|",
+ "java.io.PrintStream.println()",
+ "java.io.PrintStream.println(boolean arg0)",
+ "java.io.PrintStream.println(char arg0)",
+ "java.io.PrintStream.println(int arg0)",
+ "java.io.PrintStream.println(long arg0)",
+ "java.io.PrintStream.println(float arg0)",
+ "java.io.PrintStream.println(double arg0)",
+ "java.io.PrintStream.println(char[] arg0)",
+ "java.io.PrintStream.println(java.lang.String arg0)",
+ "java.io.PrintStream.println(java.lang.Object arg0)");
+ }
+
+ public void testErroneous() {
+ assertCompletion("Undefined.|");
+ }
+
+ public void testClinit() {
+ assertEval("enum E{;}");
+ assertEval("class C{static{}}");
+ assertCompletionIncludesExcludes("E.|", Collections.emptySet(), new HashSet<>(Collections.singletonList("<clinit>")));
+ assertCompletionIncludesExcludes("C.|", Collections.emptySet(), new HashSet<>(Collections.singletonList("<clinit>")));
+ }
+
+ public void testMethodHeaderContext() {
+ assertCompletion("private void f(Runn|", "Runnable");
+ assertCompletion("void f(Runn|", "Runnable");
+ assertCompletion("void f(Object o1, Runn|", "Runnable");
+ assertCompletion("void f(Object o1) throws Num|", true, "NumberFormatException");
+ assertCompletion("void f(Object o1) throws java.lang.Num|", true, "NumberFormatException");
+ assertEval("class HogeHoge {static class HogeHogeException extends Exception {}}");
+ assertCompletion("void f(Object o1) throws Hoge|", "HogeHoge");
+ assertCompletion("void f(Object o1) throws HogeHoge.|", true, "HogeHogeException");
+ }
+
+ public void testTypeVariables() {
+ assertCompletion("class A<TYPE> { public void test() { TY|", "TYPE");
+ assertCompletion("class A<TYPE> { public static void test() { TY|");
+ assertCompletion("class A<TYPE> { public <TYPE> void test() { TY|", "TYPE");
+ assertCompletion("class A<TYPE> { public static <TYPE> void test() { TY|", "TYPE");
+ }
+
+ public void testGeneric() {
+ assertEval("import java.util.concurrent.*;");
+ assertCompletion("java.util.List<Integ|", "Integer");
+ assertCompletion("class A<TYPE extends Call|", "Callable");
+ assertCompletion("class A<TYPE extends Callable<TY|", "TYPE");
+ assertCompletion("<TYPE> void f(TY|", "TYPE");
+ assertCompletion("class A<TYPE extends Callable<? sup|", "super");
+ assertCompletion("class A<TYPE extends Callable<? super TY|", "TYPE");
+ }
+
+ public void testFields() {
+ assertEval("interface Interface { int field = 0; }");
+ Snippet clazz = classKey(assertEval("class Clazz {" +
+ "static int staticField = 0;" +
+ "int field = 0;" +
+ " }"));
+ assertCompletion("Interface.fiel|", "field");
+ assertCompletion("Clazz.staticFiel|", "staticField");
+ assertCompletion("new Interface() {}.fiel|");
+ assertCompletion("new Clazz().staticFiel|");
+ assertCompletion("new Clazz().fiel|", "field");
+ assertCompletion("new Clazz() {}.fiel|", "field");
+ assertEval("class Clazz implements Interface {}",
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(clazz, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertCompletion("Clazz.fiel|", "field");
+ assertCompletion("new Clazz().fiel|");
+ assertCompletion("new Clazz() {}.fiel|");
+ }
+
+ public void testMethods() {
+ assertEval("interface Interface {" +
+ "default int defaultMethod() { return 0; }" +
+ "static int staticMethod() { return 0; }" +
+ "}");
+ Snippet clazz = classKey(assertEval("class Clazz {" +
+ "static int staticMethod() { return 0; }" +
+ "int method() { return 0; }" +
+ "}"));
+ assertCompletion("Interface.staticMeth|", "staticMethod()");
+ assertCompletion("Clazz.staticMeth|", "staticMethod()");
+ assertCompletion("new Interface() {}.defaultMe||", "defaultMethod()");
+ assertCompletion("new Clazz().staticMeth|");
+ assertCompletion("new Clazz().meth|", "method()");
+ assertEval("class Clazz implements Interface {}",
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(clazz, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertCompletion("Clazz.staticMeth|");
+ assertCompletion("new Clazz() {}.defaultM|", "defaultMethod()");
+ }
+
+ @Test(enabled = false) // TODO 8129422
+ public void testUncompletedDeclaration() {
+ assertCompletion("class Clazz { Claz|", "Clazz");
+ assertCompletion("class Clazz { class A extends Claz|", "Clazz");
+ assertCompletion("class Clazz { Clazz clazz; Object o = cla|", "clazz");
+ assertCompletion("class Clazz { static Clazz clazz; Object o = cla|", "clazz");
+ assertCompletion("class Clazz { Clazz clazz; static Object o = cla|", true);
+ assertCompletion("class Clazz { void method(Claz|", "Clazz");
+ assertCompletion("class A { int method() { return 0; } int a = meth|", "method");
+ assertCompletion("class A { int field = 0; int method() { return fiel|", "field");
+ assertCompletion("class A { static int method() { return 0; } int a = meth|", "method");
+ assertCompletion("class A { static int field = 0; int method() { return fiel|", "field");
+ assertCompletion("class A { int method() { return 0; } static int a = meth|", true);
+ assertCompletion("class A { int field = 0; static int method() { return fiel|", true);
+ }
+
+ @Test(enabled = false) // TODO 8129421
+ public void testClassDeclaration() {
+ assertEval("interface Interface {}");
+ assertCompletion("interface A extends Interf|", "Interface");
+ assertCompletion("class A implements Interf|", "Interface");
+ assertEval("class Clazz {}");
+ assertCompletion("class A extends Claz|", "Clazz");
+ assertCompletion("class A extends Clazz implements Interf|", "Interface");
+ assertEval("interface Interface1 {}");
+ assertCompletion("class A extends Clazz implements Interface, Interf|", "Interface", "Interface1");
+ assertCompletion("interface A implements Claz|");
+ assertCompletion("interface A implements Inter|");
+ assertCompletion("class A implements Claz|", true);
+ assertCompletion("class A extends Clazz implements Interface, Interf|", true, "Interface1");
+ }
+
+ public void testDocumentationOfUserDefinedMethods() {
+ assertEval("void f() {}");
+ assertDocumentation("f(|", "f()");
+ assertEval("void f(int a) {}");
+ assertDocumentation("f(|", "f()", "f(int arg0)");
+ assertEval("<T> void f(T... a) {}", DiagCheck.DIAG_WARNING, DiagCheck.DIAG_OK);
+ assertDocumentation("f(|", "f()", "f(int arg0)", "f(T... arg0)");
+ assertEval("class A {}");
+ assertEval("void f(A a) {}");
+ assertDocumentation("f(|", "f()", "f(int arg0)", "f(T... arg0)", "f(A arg0)");
+ }
+
+ public void testDocumentationOfUserDefinedConstructors() {
+ Snippet a = classKey(assertEval("class A {}"));
+ assertDocumentation("new A(|", "A()");
+ Snippet a2 = classKey(assertEval("class A { A() {} A(int a) {}}",
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(a, VALID, OVERWRITTEN, false, MAIN_SNIPPET)));
+ assertDocumentation("new A(|", "A()", "A(int arg0)");
+ assertEval("class A<T> { A(T a) {} A(int a) {}}",
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(a2, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertDocumentation("new A(|", "A(T arg0)", "A(int arg0)");
+ }
+
+ public void testDocumentationOfOverriddenMethods() {
+ assertDocumentation("\"\".wait(|",
+ "java.lang.Object.wait(long arg0)",
+ "java.lang.Object.wait(long arg0, int arg1)",
+ "java.lang.Object.wait()");
+ assertEval("class Base {void method() {}}");
+ Snippet e = classKey(assertEval("class Extend extends Base {}"));
+ assertDocumentation("new Extend().method(|", "Base.method()");
+ assertEval("class Extend extends Base {void method() {}}",
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(e, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertDocumentation("new Extend().method(|", "Extend.method()");
+ }
+
+ public void testDocumentationOfInvisibleMethods() {
+ assertDocumentation("Object.wait(|", "");
+ assertDocumentation("\"\".indexOfSupplementary(|", "");
+ Snippet a = classKey(assertEval("class A {void method() {}}"));
+ assertDocumentation("A.method(|", "");
+ assertEval("class A {private void method() {}}",
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(a, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertDocumentation("new A().method(|", "");
+ }
+
+ public void testDocumentationOfInvisibleConstructors() {
+ assertDocumentation("new Compiler(|", "");
+ assertEval("class A { private A() {} }");
+ assertDocumentation("new A(|", "");
+ }
+
+ public void testDocumentationWithBoxing() {
+ assertEval("int primitive = 0;");
+ assertEval("Integer boxed = 0;");
+ assertEval("Object object = null;");
+ assertEval("void method(int n, Object o) { }");
+ assertEval("void method(Object n, int o) { }");
+ assertDocumentation("method(primitive,|",
+ "method(int arg0, java.lang.Object arg1)",
+ "method(java.lang.Object arg0, int arg1)");
+ assertDocumentation("method(boxed,|",
+ "method(int arg0, java.lang.Object arg1)",
+ "method(java.lang.Object arg0, int arg1)");
+ assertDocumentation("method(object,|",
+ "method(java.lang.Object arg0, int arg1)");
+ }
+
+ public void testVarArgs() {
+ assertEval("int i = 0;");
+ assertEval("class Foo1 { static void m(int... i) { } } ");
+ assertCompletion("Foo1.m(|", true, "i");
+ assertCompletion("Foo1.m(i, |", true, "i");
+ assertCompletion("Foo1.m(i, i, |", true, "i");
+ assertEval("class Foo2 { static void m(String s, int... i) { } } ");
+ assertCompletion("Foo2.m(|", true);
+ assertCompletion("Foo2.m(i, |", true);
+ assertCompletion("Foo2.m(\"\", |", true, "i");
+ assertCompletion("Foo2.m(\"\", i, |", true, "i");
+ assertCompletion("Foo2.m(\"\", i, i, |", true, "i");
+ assertEval("class Foo3 { Foo3(String s, int... i) { } } ");
+ assertCompletion("new Foo3(|", true);
+ assertCompletion("new Foo3(i, |", true);
+ assertCompletion("new Foo3(\"\", |", true, "i");
+ assertCompletion("new Foo3(\"\", i, |", true, "i");
+ assertCompletion("new Foo3(\"\", i, i, |", true, "i");
+ assertEval("int[] ia = null;");
+ assertCompletion("Foo1.m(ia, |", true);
+ assertEval("class Foo4 { static void m(int... i) { } static void m(int[] ia, String str) { } } ");
+ assertEval("String str = null;");
+ assertCompletion("Foo4.m(ia, |", true, "str");
+ }
+
+ public void testConstructorAsMemberOf() {
+ assertEval("class Baz<X> { Baz(X x) { } } ");
+ assertEval("String str = null;");
+ assertEval("Integer i = null;");
+ assertCompletion("new Baz(|", true, "i", "str");
+ assertCompletion("new Baz<String>(|", true, "str");
+ assertCompletion("Baz<String> bz = new Baz<>(|", true, "str");
+ assertEval("class Foo { static void m(String str) {} static void m(Baz<String> baz) {} }");
+ assertCompletion("Foo.m(new Baz<>(|", true, "str");
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/CustomEditor.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,114 @@
+/*
+ * 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.
+ */
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collections;
+
+public class CustomEditor implements AutoCloseable {
+
+ public static final int SOURCE_CODE = 0;
+ public static final int GET_SOURCE_CODE = 1;
+ public static final int REMOVE_CODE = 2;
+
+ public static final int EXIT_CODE = -1;
+ public static final int ACCEPT_CODE = -2;
+ public static final int CANCEL_CODE = -3;
+
+ private final Socket socket;
+ private final Path path;
+ private final StringWriter writer;
+ private final String source;
+
+ public CustomEditor(int port, String fileName) throws IOException {
+ this.socket = new Socket((String) null, port);
+ this.path = Paths.get(fileName);
+ this.writer = new StringWriter();
+ this.source = new String(Files.readAllBytes(path), StandardCharsets.UTF_8);
+ }
+
+ public void loop() throws IOException {
+ DataInputStream input = new DataInputStream(new BufferedInputStream(socket.getInputStream()));
+ DataOutputStream output = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));
+
+ while (true) {
+ int cmd = input.readInt();
+ switch (cmd) {
+ case EXIT_CODE: {
+ Files.write(path, Collections.singletonList(writer.toString()));
+ return;
+ }
+ case GET_SOURCE_CODE: {
+ byte[] bytes = source.getBytes(StandardCharsets.UTF_8);
+ output.writeInt(bytes.length);
+ output.write(bytes);
+ output.flush();
+ break;
+ }
+ case REMOVE_CODE: {
+ // only for external editor
+ Files.delete(path);
+ break;
+ }
+ case CANCEL_CODE: {
+ return;
+ }
+ case ACCEPT_CODE: {
+ Files.write(path, Collections.singletonList(writer.toString()));
+ break;
+ }
+ case SOURCE_CODE: {
+ int length = input.readInt();
+ byte[] bytes = new byte[length];
+ input.readFully(bytes);
+ writer.write(new String(bytes, StandardCharsets.UTF_8));
+ break;
+ }
+ }
+ }
+ }
+
+ public static void main(String[] args) throws IOException {
+ if (args.length != 2) {
+ System.err.println("Usage: port file");
+ System.exit(1);
+ }
+ try (CustomEditor editor = new CustomEditor(Integer.parseInt(args[0]), args[1])) {
+ editor.loop();
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ socket.close();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/DropTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,216 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Test of JShell#drop().
+ * @build KullaTesting TestingInputStream
+ * @run testng DropTest
+ */
+
+import jdk.jshell.DeclarationSnippet;
+import jdk.jshell.PersistentSnippet;
+import org.testng.annotations.Test;
+
+import static jdk.jshell.Snippet.Status.*;
+
+@Test
+public class DropTest extends KullaTesting {
+
+ public void testDrop() {
+ PersistentSnippet var = varKey(assertEval("int x;"));
+ PersistentSnippet method = methodKey(assertEval("int mu() { return x * 4; }"));
+ PersistentSnippet clazz = classKey(assertEval("class C { String v() { return \"#\" + mu(); } }"));
+ assertDrop(var,
+ ste(var, VALID, DROPPED, true, null),
+ ste(method, VALID, RECOVERABLE_DEFINED, false, var));
+ //assertDrop(method,
+ // ste(method, RECOVERABLE_DEFINED, DROPPED, false, null),
+ // ste(clazz, RECOVERABLE_NOT_DEFINED, RECOVERABLE_NOT_DEFINED, false, method));
+ //assertDeclareFail("new C();", "compiler.err.cant.resolve.location");
+ assertVariables();
+ assertMethods();
+ assertClasses();
+ assertActiveKeys();
+
+ assertEval("int x = 10;", "10",
+ ste(var, DROPPED, VALID, true, null),
+ ste(method, RECOVERABLE_DEFINED, VALID, false, MAIN_SNIPPET));
+ PersistentSnippet c0 = varKey(assertEval("C c0 = new C();"));
+ assertEval("c0.v();", "\"#40\"");
+ assertEval("C c = new C();");
+ assertEval("c.v();", "\"#40\"");
+ assertEval("int mu() { return x * 3; }",
+ ste(MAIN_SNIPPET, VALID, VALID, false, null),
+ ste(method, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertEval("c.v();", "\"#30\"");
+ assertEval("class C { String v() { return \"@\" + mu(); } }",
+ ste(MAIN_SNIPPET, VALID, VALID, false, null),
+ ste(clazz, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertEval("c0.v();", "\"@30\"");
+ assertDrop(c0,
+ ste(c0, VALID, DROPPED, true, null));
+ assertEval("c = new C();");
+ assertEval("c.v();", "\"@30\"");
+
+ assertVariables();
+ assertMethods();
+ assertClasses();
+ assertActiveKeys();
+ }
+
+ @Test(enabled = false) // TODO 8081431
+ public void testDropImport() {
+ PersistentSnippet imp = importKey(assertEval("import java.util.*;"));
+ PersistentSnippet decl = varKey(
+ assertEval("List<Integer> list = Arrays.asList(1, 2, 3);", "[1, 2, 3]"));
+ assertEval("list;", "[1, 2, 3]");
+ assertDrop(imp,
+ DiagCheck.DIAG_OK,
+ DiagCheck.DIAG_ERROR,
+ ste(imp, VALID, DROPPED, true, null),
+ ste(decl, VALID, RECOVERABLE_NOT_DEFINED, true, imp));
+ assertDeclareFail("list;", "compiler.err.cant.resolve.location");
+ }
+
+ public void testDropVarToMethod() {
+ PersistentSnippet x = varKey(assertEval("int x;"));
+ DeclarationSnippet method = methodKey(assertEval("double mu() { return x * 4; }"));
+ assertEval("x == 0;", "true");
+ assertEval("mu() == 0.0;", "true");
+
+ assertDrop(x,
+ ste(x, VALID, DROPPED, true, null),
+ ste(method, VALID, RECOVERABLE_DEFINED, false, x));
+ assertUnresolvedDependencies1(method, RECOVERABLE_DEFINED, "variable x");
+ assertEvalUnresolvedException("mu();", "mu", 1, 0);
+
+ assertVariables();
+ assertMethods();
+ assertActiveKeys();
+ }
+
+ public void testDropMethodToMethod() {
+ PersistentSnippet a = methodKey(assertEval("double a() { return 2; }"));
+ DeclarationSnippet b = methodKey(assertEval("double b() { return a() * 10; }"));
+ assertEval("double c() { return b() * 3; }");
+ DeclarationSnippet d = methodKey(assertEval("double d() { return c() + 1000; }"));
+ assertEval("d();", "1060.0");
+ assertDrop(a,
+ ste(a, VALID, DROPPED, true, null),
+ ste(b, VALID, RECOVERABLE_DEFINED, false, a));
+ assertUnresolvedDependencies1(b, RECOVERABLE_DEFINED, "method a()");
+ assertUnresolvedDependencies(d, 0);
+ assertEvalUnresolvedException("d();", "b", 1, 0);
+ assertMethods();
+ assertActiveKeys();
+ }
+
+ public void testDropClassToMethod() {
+ PersistentSnippet c = classKey(assertEval("class C { int f() { return 7; } }"));
+ DeclarationSnippet m = methodKey(assertEval("int m() { return new C().f(); }"));
+ assertDrop(c,
+ ste(c, VALID, DROPPED, true, null),
+ ste(m, VALID, RECOVERABLE_DEFINED, false, c));
+ assertUnresolvedDependencies1(m, RECOVERABLE_DEFINED, "class C");
+ assertEvalUnresolvedException("m();", "m", 1, 0);
+ assertActiveKeys();
+ }
+
+ public void testDropVarToClass() {
+ PersistentSnippet x = varKey(assertEval("int x;"));
+ DeclarationSnippet a = classKey(assertEval("class A { double a = 4 * x; }"));
+ assertDrop(x,
+ DiagCheck.DIAG_OK,
+ DiagCheck.DIAG_ERROR,
+ ste(x, VALID, DROPPED, true, null),
+ ste(a, VALID, RECOVERABLE_NOT_DEFINED, true, x));
+ assertUnresolvedDependencies1(a, RECOVERABLE_NOT_DEFINED, "variable x");
+ assertDeclareFail("new A().a;", "compiler.err.cant.resolve.location");
+ assertVariables();
+ assertActiveKeys();
+ }
+
+ public void testDropMethodToClass() {
+ PersistentSnippet x = methodKey(assertEval("int x() { return 0; }"));
+ DeclarationSnippet a = classKey(assertEval("class A { double a = 4 * x(); }"));
+ assertDrop(x,
+ DiagCheck.DIAG_OK,
+ DiagCheck.DIAG_ERROR,
+ ste(x, VALID, DROPPED, true, null),
+ ste(a, VALID, RECOVERABLE_NOT_DEFINED, true, x));
+ assertUnresolvedDependencies1(a, RECOVERABLE_NOT_DEFINED, "method x()");
+ assertDeclareFail("new A().a;", "compiler.err.cant.resolve.location");
+ assertMethods();
+ assertActiveKeys();
+ }
+
+ public void testDropClassToClass() {
+ PersistentSnippet a = classKey(assertEval("class A {}"));
+ PersistentSnippet b = classKey(assertEval("class B extends A {}"));
+ PersistentSnippet c = classKey(assertEval("class C extends B {}"));
+ PersistentSnippet d = classKey(assertEval("class D extends C {}"));
+ assertDrop(a,
+ DiagCheck.DIAG_OK,
+ DiagCheck.DIAG_ERROR,
+ ste(a, VALID, DROPPED, true, null),
+ ste(b, VALID, RECOVERABLE_NOT_DEFINED, true, a),
+ ste(c, VALID, RECOVERABLE_NOT_DEFINED, true, b),
+ ste(d, VALID, RECOVERABLE_NOT_DEFINED, true, c));
+ assertUnresolvedDependencies1((DeclarationSnippet) b, RECOVERABLE_NOT_DEFINED, "class A");
+ assertDrop(c,
+ DiagCheck.DIAG_OK,
+ DiagCheck.DIAG_ERROR,
+ ste(c, RECOVERABLE_NOT_DEFINED, DROPPED, false, null),
+ ste(d, RECOVERABLE_NOT_DEFINED, RECOVERABLE_NOT_DEFINED, false, c));
+ assertEval("interface A {}", null, null,
+ DiagCheck.DIAG_OK, DiagCheck.DIAG_ERROR,
+ ste(a, DROPPED, VALID, true, null),
+ ste(b, RECOVERABLE_NOT_DEFINED, RECOVERABLE_NOT_DEFINED, false, MAIN_SNIPPET));
+ assertClasses();
+ assertActiveKeys();
+ }
+
+ public void testDropNoUpdate() {
+ String as1 = "class A {}";
+ String as2 = "class A extends java.util.ArrayList<Boolean> {}";
+ PersistentSnippet a = classKey(assertEval(as1, added(VALID)));
+ PersistentSnippet b = classKey(assertEval("class B extends A {}", added(VALID)));
+ PersistentSnippet ax = classKey(assertEval(as2,
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(a, VALID, OVERWRITTEN, false, MAIN_SNIPPET),
+ ste(b, VALID, VALID, true, MAIN_SNIPPET)));
+ ax = classKey(assertEval(as1,
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(ax, VALID, OVERWRITTEN, false, MAIN_SNIPPET),
+ ste(b, VALID, VALID, true, MAIN_SNIPPET)));
+ assertDrop(b,
+ ste(b, VALID, DROPPED, true, null));
+ ax = classKey(assertEval(as2,
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(ax, VALID, OVERWRITTEN, false, MAIN_SNIPPET)));
+ assertEval(as1,
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(ax, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/EditorPadTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,233 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Testing built-in editor.
+ * @ignore 8139872
+ * @build ReplToolTesting EditorTestBase
+ * @run testng EditorPadTest
+ */
+
+import java.awt.AWTException;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Frame;
+import java.awt.Point;
+import java.awt.Robot;
+import java.awt.event.InputEvent;
+import java.awt.event.WindowEvent;
+import java.lang.reflect.InvocationTargetException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+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.JViewport;
+import javax.swing.SwingUtilities;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class EditorPadTest extends EditorTestBase {
+
+ private static final int DELAY = 500;
+
+ private static Robot robot;
+ private static JFrame frame = null;
+ private static JTextArea area = null;
+ private static JButton cancel = null;
+ private static JButton accept = null;
+ private static JButton exit = null;
+
+ @BeforeClass
+ public static void setUp() {
+ try {
+ robot = new Robot();
+ robot.setAutoWaitForIdle(true);
+ robot.setAutoDelay(DELAY);
+ } catch (AWTException e) {
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
+ @AfterClass
+ public static void shutdown() {
+ executorShutdown();
+ }
+
+ @Override
+ public void writeSource(String s) {
+ SwingUtilities.invokeLater(() -> area.setText(s));
+ }
+
+ @Override
+ public String getSource() {
+ try {
+ String[] s = new String[1];
+ SwingUtilities.invokeAndWait(() -> s[0] = area.getText());
+ return s[0];
+ } catch (InvocationTargetException | InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void accept() {
+ clickOn(accept);
+ }
+
+ @Override
+ public void exit() {
+ clickOn(exit);
+ }
+
+ @Override
+ public void cancel() {
+ clickOn(cancel);
+ }
+
+ @Override
+ public void shutdownEditor() {
+ SwingUtilities.invokeLater(this::clearElements);
+ waitForIdle();
+ }
+
+ @Test
+ public void testShuttingDown() {
+ testEditor(
+ (a) -> assertEditOutput(a, "/e", "", this::shutdownEditor)
+ );
+ }
+
+ private void waitForIdle() {
+ robot.waitForIdle();
+ robot.delay(DELAY);
+ }
+
+ private Future<?> task;
+ @Override
+ public void assertEdit(boolean after, String cmd,
+ Consumer<String> checkInput, Consumer<String> checkOutput, Action action) {
+ if (!after) {
+ setCommandInput(cmd + "\n");
+ task = getExecutor().submit(() -> {
+ try {
+ waitForIdle();
+ SwingUtilities.invokeLater(this::seekElements);
+ waitForIdle();
+ checkInput.accept(getSource());
+ action.accept();
+ } catch (Throwable e) {
+ shutdownEditor();
+ if (e instanceof AssertionError) {
+ throw (AssertionError) e;
+ }
+ throw new RuntimeException(e);
+ }
+ });
+ } else {
+ try {
+ task.get();
+ waitForIdle();
+ checkOutput.accept(getCommandOutput());
+ } catch (ExecutionException e) {
+ if (e.getCause() instanceof AssertionError) {
+ throw (AssertionError) e.getCause();
+ }
+ throw new RuntimeException(e);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ } finally {
+ shutdownEditor();
+ }
+ }
+ }
+
+ private void seekElements() {
+ for (Frame f : Frame.getFrames()) {
+ if (f.getTitle().contains("Edit Pad")) {
+ frame = (JFrame) f;
+ // workaround
+ frame.setLocation(0, 0);
+ Container root = frame.getContentPane();
+ for (Component c : root.getComponents()) {
+ if (c instanceof JScrollPane) {
+ JScrollPane scrollPane = (JScrollPane) c;
+ for (Component comp : scrollPane.getComponents()) {
+ if (comp instanceof JViewport) {
+ JViewport view = (JViewport) comp;
+ area = (JTextArea) view.getComponent(0);
+ }
+ }
+ }
+ if (c instanceof JPanel) {
+ JPanel p = (JPanel) c;
+ for (Component comp : p.getComponents()) {
+ if (comp instanceof JButton) {
+ JButton b = (JButton) comp;
+ switch (b.getText()) {
+ case "Cancel":
+ cancel = b;
+ break;
+ case "Exit":
+ exit = b;
+ break;
+ case "Accept":
+ accept = b;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void clearElements() {
+ if (frame != null) {
+ frame.dispatchEvent(new WindowEvent(frame, WindowEvent.WINDOW_CLOSING));
+ frame = null;
+ }
+ area = null;
+ accept = null;
+ cancel = null;
+ exit = null;
+ }
+
+ private void clickOn(JButton button) {
+ waitForIdle();
+ Point p = button.getLocationOnScreen();
+ Dimension d = button.getSize();
+ robot.mouseMove(p.x + d.width / 2, p.y + d.height / 2);
+ robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
+ robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/EditorTestBase.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,273 @@
+/*
+ * 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.
+ */
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.function.Consumer;
+
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+public abstract class EditorTestBase extends ReplToolTesting {
+
+ private static ExecutorService executor;
+
+ public abstract void writeSource(String s);
+ public abstract String getSource();
+ public abstract void accept();
+ public abstract void exit();
+ public abstract void cancel();
+ public abstract void shutdownEditor();
+
+ public void testEditor(ReplTest... tests) {
+ testEditor(false, new String[]{"-nostartup"}, tests);
+ }
+
+ public void testEditor(boolean defaultStartup, String[] args, ReplTest... tests) {
+ test(defaultStartup, args, tests);
+ }
+
+ public abstract void assertEdit(boolean after, String cmd,
+ Consumer<String> checkInput, Consumer<String> checkOutput, Action action);
+
+ public void assertEditInput(boolean after, String cmd, Consumer<String> checkInput, Action action) {
+ assertEdit(after, cmd, checkInput, s -> {}, action);
+ }
+
+ public void assertEditOutput(boolean after, String cmd, Consumer<String> checkOutput, Action action) {
+ assertEdit(after, cmd, s -> {}, checkOutput, action);
+ }
+
+ public void assertEditInput(boolean after, String cmd, String input, Action action) {
+ assertEditInput(after, cmd, s -> assertEquals(s, input, "Input"), action);
+ }
+
+ public void assertEditOutput(boolean after, String cmd, String output, Action action) {
+ assertEditOutput(after, cmd, s -> assertEquals(s, output, "command"), action);
+ }
+
+ @Test
+ public void testEditNegative() {
+ for (String edit : new String[] {"/e", "/edit"}) {
+ test(new String[]{"-nostartup"},
+ a -> assertCommand(a, edit + " 1",
+ "| No definition or id named 1 found. See /classes /methods /vars or /list\n"),
+ a -> assertCommand(a, edit + " -1",
+ "| No definition or id named -1 found. See /classes /methods /vars or /list\n"),
+ a -> assertCommand(a, edit + " unknown",
+ "| No definition or id named unknown found. See /classes /methods /vars or /list\n")
+ );
+ }
+ }
+
+ @Test
+ public void testDoNothing() {
+ testEditor(
+ a -> assertVariable(a, "int", "a", "0", "0"),
+ a -> assertEditOutput(a, "/e 1", "", this::exit),
+ a -> assertCommandCheckOutput(a, "/v", assertVariables())
+ );
+ }
+
+ @Test
+ public void testEditVariable1() {
+ testEditor(
+ a -> assertVariable(a, "int", "a", "0", "0"),
+ a -> assertEditOutput(a, "/e 1", "| Modified variable a of type int with initial value 10\n", () -> {
+ writeSource("\n\n\nint a = 10;\n\n\n");
+ exit();
+ loadVariable(true, "int", "a", "10", "10");
+ }),
+ a -> assertEditOutput(a, "/e 1", "| Modified variable a of type int with initial value 15\n", () -> {
+ writeSource("int a = 15;");
+ exit();
+ loadVariable(true, "int", "a", "15", "15");
+ }),
+ a -> assertCommandCheckOutput(a, "/v", assertVariables())
+ );
+ }
+
+ @Test
+ public void testEditVariable2() {
+ testEditor(
+ a -> assertVariable(a, "int", "a", "0", "0"),
+ a -> assertEditOutput(a, "/e 1", "| Added variable b of type int with initial value 10\n", () -> {
+ writeSource("int b = 10;");
+ exit();
+ loadVariable(true, "int", "b", "10", "10");
+ }),
+ a -> assertEditOutput(a, "/e 1", "| Modified variable a of type int with initial value 15\n", () -> {
+ writeSource("int a = 15;");
+ exit();
+ loadVariable(true, "int", "a", "15", "15");
+ }),
+ a -> assertCommandCheckOutput(a, "/v", assertVariables())
+ );
+ }
+
+ @Test
+ public void testEditClass1() {
+ testEditor(
+ a -> assertClass(a, "class A {}", "class", "A"),
+ a -> assertEditOutput(a, "/e 1", "", () -> {
+ writeSource("\n\n\nclass A {}\n\n\n");
+ exit();
+ loadClass(true, "class A {}", "class", "A");
+ }),
+ a -> assertEditOutput(a, "/e 1",
+ "| Replaced enum A\n" +
+ "| Update overwrote class A\n", () -> {
+ writeSource("enum A {}");
+ exit();
+ loadClass(true, "enum A {}", "enum", "A");
+ }),
+ a -> assertCommandCheckOutput(a, "/c", assertClasses())
+ );
+ }
+
+ @Test
+ public void testEditClass2() {
+ testEditor(
+ a -> assertClass(a, "class A {}", "class", "A"),
+ a -> assertEditOutput(a, "/e 1", "| Added class B\n", () -> {
+ writeSource("class B { }");
+ exit();
+ loadClass(true, "class B {}", "class", "B");
+ }),
+ a -> assertEditOutput(a, "/e 1",
+ "| Replaced enum A\n" +
+ "| Update overwrote class A\n", () -> {
+ writeSource("enum A {}");
+ exit();
+ loadClass(true, "enum A {}", "enum", "A");
+ }),
+ a -> assertCommandCheckOutput(a, "/c", assertClasses())
+ );
+ }
+
+ @Test
+ public void testEditMethod1() {
+ testEditor(
+ a -> assertMethod(a, "void f() {}", "()void", "f"),
+ a -> assertEditOutput(a, "/e 1", "", () -> {
+ writeSource("\n\n\nvoid f() {}\n\n\n");
+ exit();
+ loadMethod(true, "void f() {}", "()void", "f");
+ }),
+ a -> assertEditOutput(a, "/e 1",
+ "| Replaced method f()\n" +
+ "| Update overwrote method f()\n", () -> {
+ writeSource("double f() { return 0; }");
+ exit();
+ loadMethod(true, "double f() { return 0; }", "()double", "f");
+ }),
+ a -> assertCommandCheckOutput(a, "/m", assertMethods())
+ );
+ }
+
+ @Test
+ public void testEditMethod2() {
+ testEditor(
+ a -> assertMethod(a, "void f() {}", "()void", "f"),
+ a -> assertEditOutput(a, "/e 1", "| Added method g()\n", () -> {
+ writeSource("void g() {}");
+ exit();
+ loadMethod(true, "void g() {}", "()void", "g");
+ }),
+ a -> assertEditOutput(a, "/e 1",
+ "| Replaced method f()\n" +
+ "| Update overwrote method f()\n", () -> {
+ writeSource("double f() { return 0; }");
+ exit();
+ loadMethod(true, "double f() { return 0; }", "()double", "f");
+ }),
+ a -> assertCommandCheckOutput(a, "/m", assertMethods())
+ );
+ }
+
+ @Test
+ public void testNoArguments() {
+ testEditor(
+ a -> assertVariable(a, "int", "a"),
+ a -> assertMethod(a, "void f() {}", "()void", "f"),
+ a -> assertClass(a, "class A {}", "class", "A"),
+ a -> assertEditInput(a, "/e", s -> {
+ String[] ss = s.split("\n");
+ assertEquals(ss.length, 3, "Expected 3 lines: " + s);
+ assertEquals(ss[0], "int a;");
+ assertEquals(ss[1], "void f() {}");
+ assertEquals(ss[2], "class A {}");
+ }, this::exit)
+ );
+ }
+
+ @Test
+ public void testStartup() {
+ testEditor(true, new String[0],
+ a -> assertEditInput(a, "/e", s -> assertTrue(s.isEmpty(), "Checking of startup: " + s), this::cancel),
+ a -> assertEditInput(a, "/e printf", assertStartsWith("void printf"), this::cancel));
+ }
+
+ @Test
+ public void testCancel() {
+ testEditor(
+ a -> assertVariable(a, "int", "a"),
+ a -> assertEditOutput(a, "/e a", "", () -> {
+ writeSource("int b = 10");
+ cancel();
+ })
+ );
+ }
+
+ @Test
+ public void testAccept() {
+ testEditor(
+ a -> assertVariable(a, "int", "a"),
+ a -> assertEditOutput(a, "/e a", "| Added variable b of type int with initial value 10\n", () -> {
+ writeSource("int b = 10");
+ accept();
+ exit();
+ })
+ );
+ }
+
+ public static ExecutorService getExecutor() {
+ if (executor == null) {
+ executor = Executors.newSingleThreadExecutor();
+ }
+ return executor;
+ }
+
+ public static void executorShutdown() {
+ if (executor != null) {
+ executor.shutdown();
+ executor = null;
+ }
+ }
+
+ interface Action {
+ void accept();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/EmptyTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ *
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary null test
+ * @build KullaTesting TestingInputStream
+ * @run testng EmptyTest
+ */
+
+import org.testng.annotations.Test;
+
+@Test
+public class EmptyTest extends KullaTesting {
+
+ public void testEmpty() {
+ assertEvalEmpty("");
+ }
+
+ public void testSpace() {
+ assertEvalEmpty(" ");
+ }
+
+ public void testSemicolon() {
+ assertEval(";", "");
+ }
+
+ public void testSlashStarComment() {
+ assertEvalEmpty("/*test*/");
+ }
+
+ public void testSlashStarCommentSemicolon() {
+ assertEval("/*test*/;", "");
+ }
+
+ public void testSlashComment() {
+ assertEvalEmpty("// test");
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/ErrorTranslationTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,162 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Tests for shell error translation
+ * @library /tools/lib
+ * @build KullaTesting TestingInputStream ExpectedDiagnostic ToolBox Compiler
+ * @run testng ErrorTranslationTest
+ */
+
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+import javax.tools.Diagnostic;
+
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+@Test
+public class ErrorTranslationTest extends ReplToolTesting {
+
+ @Test(enabled = false) // TODO 8080353
+ public void testErrors() {
+ test(
+ a -> assertDiagnostic(a, "abstract void f();", newExpectedDiagnostic(0, 8, 0, -1, -1, Diagnostic.Kind.ERROR)),
+ a -> assertDiagnostic(a, "native void f();", newExpectedDiagnostic(0, 6, 0, -1, -1, Diagnostic.Kind.ERROR)),
+ a -> assertDiagnostic(a, "static void f();", newExpectedDiagnostic(0, 16, 0, -1, -1, Diagnostic.Kind.ERROR)),
+ a -> assertDiagnostic(a, "synchronized void f();", newExpectedDiagnostic(0, 12, 0, -1, -1, Diagnostic.Kind.ERROR)),
+ a -> assertDiagnostic(a, "default void f();", newExpectedDiagnostic(0, 7, 0, -1, -1, Diagnostic.Kind.ERROR))
+ );
+ }
+
+ public void testWarnings() {
+ List<ReplTest> list = new ArrayList<>();
+ ExpectedDiagnostic[] diagnostics = new ExpectedDiagnostic[]{
+ newExpectedDiagnostic(0, 6, 0, -1, -1, Diagnostic.Kind.WARNING),
+ newExpectedDiagnostic(0, 9, 0, -1, -1, Diagnostic.Kind.WARNING),
+ newExpectedDiagnostic(0, 7, 0, -1, -1, Diagnostic.Kind.WARNING),
+ newExpectedDiagnostic(0, 6, 0, -1, -1, Diagnostic.Kind.WARNING),
+ newExpectedDiagnostic(0, 5, 0, -1, -1, Diagnostic.Kind.WARNING)};
+ String[] mods = {"public", "protected", "private", "static", "final"};
+ for (int i = 0; i < mods.length; ++i) {
+ for (String code : new String[] {"class A {}", "void f() {}", "int a;"}) {
+ final int finalI = i;
+ list.add(a -> assertDiagnostic(a, mods[finalI] + " " + code, diagnostics[finalI]));
+ }
+ }
+ test(list.toArray(new ReplTest[list.size()]));
+ }
+
+ @Test(enabled = false) // TODO 8132147
+ public void stressTest() {
+ Compiler compiler = new Compiler();
+ Path oome = compiler.getPath("OOME.repl");
+ Path soe = compiler.getPath("SOE.repl");
+ compiler.writeToFile(oome,
+ "List<byte[]> list = new ArrayList<>();\n",
+ "while (true) {\n" +
+ " list.add(new byte[1000000]);\n" +
+ "}");
+ compiler.writeToFile(soe,
+ "void f() { f(); }",
+ "f();");
+ List<ReplTest> tests = new ArrayList<>();
+ for (int i = 0; i < 25; ++i) {
+ tests.add(a -> assertCommandCheckOutput(a, "/o " + soe.toString(),
+ assertStartsWith("| java.lang.StackOverflowError thrown")));
+ tests.add(a -> assertCommandCheckOutput(a, "/o " + oome.toString(),
+ assertStartsWith("| java.lang.OutOfMemoryError thrown: Java heap space")));
+ }
+ test(tests.toArray(new ReplTest[tests.size()]));
+ }
+
+ private ExpectedDiagnostic newExpectedDiagnostic(long startPosition, long endPosition, long position,
+ long lineNumber, long columnNumber, Diagnostic.Kind kind) {
+ return new ExpectedDiagnostic("", startPosition, endPosition, position, lineNumber, columnNumber, kind);
+ }
+
+ private void assertDiagnostic(boolean after, String cmd, ExpectedDiagnostic expectedDiagnostic) {
+ assertCommandCheckOutput(after, cmd, assertDiagnostic(cmd, expectedDiagnostic));
+ }
+
+ private Consumer<String> assertDiagnostic(String expectedSource, ExpectedDiagnostic expectedDiagnostic) {
+ int start = (int) expectedDiagnostic.getStartPosition();
+ int end = (int) expectedDiagnostic.getEndPosition();
+ String expectedMarkingLine = createMarkingLine(start, end);
+ return s -> {
+ String[] lines = s.split("\n");
+ if (lines.length <= 3) {
+ throw new AssertionError("Not enough lines: " + s);
+ }
+ String kind = getKind(expectedDiagnostic.getKind());
+ assertEquals(lines[0], kind);
+ String source;
+ String markingLine;
+ switch (expectedDiagnostic.getKind()) {
+ case ERROR:
+ case WARNING:
+ source = lines[2];
+ markingLine = lines[3];
+ break;
+ default:
+ throw new AssertionError("Unsupported diagnostic kind: " + expectedDiagnostic.getKind());
+ }
+ assertTrue(source.endsWith(expectedSource), "Expected: " + expectedSource + ", found: " + source);
+ assertEquals(markingLine, expectedMarkingLine, "Input: " + expectedSource + ", marking line: ");
+ };
+ }
+
+ private String createMarkingLine(int start, int end) {
+ assertTrue(end >= start, String.format("End position %d is less than start position %d", end, start));
+ StringBuilder sb = new StringBuilder();
+ sb.append("| ");
+ for (int i = 0; i < start; ++i) {
+ sb.append(' ');
+ }
+ sb.append('^');
+ for (int i = 1; i < end - start - 1; ++i) {
+ sb.append('-');
+ }
+ if (start < end) {
+ sb.append('^');
+ }
+ return sb.toString();
+ }
+
+ public String getKind(Diagnostic.Kind kind) {
+ switch (kind) {
+ case WARNING:
+ return "| Warning:";
+ case ERROR:
+ return "| Error:";
+ default:
+ throw new AssertionError("Unsupported kind: " + kind);
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/ExceptionsTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,244 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Tests for exceptions
+ * @build KullaTesting TestingInputStream
+ * @run testng ExceptionsTest
+ */
+
+import jdk.jshell.SnippetEvent;
+import jdk.jshell.EvalException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import jdk.jshell.Snippet;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.*;
+
+@Test
+public class ExceptionsTest extends KullaTesting {
+
+ public void throwUncheckedException() {
+ String message = "error_message";
+ SnippetEvent cr = assertEvalException("throw new RuntimeException(\"" + message + "\");");
+ assertExceptionMatch(cr,
+ new ExceptionInfo(RuntimeException.class, message,
+ newStackTraceElement("", "", cr.snippet(), 1)));
+ }
+
+ public void throwCheckedException() {
+ String message = "error_message";
+ SnippetEvent cr = assertEvalException("throw new Exception(\"" + message + "\");");
+ assertExceptionMatch(cr,
+ new ExceptionInfo(Exception.class, message,
+ newStackTraceElement("", "", cr.snippet(), 1)));
+ }
+
+ public void throwFromStaticMethodOfClass() {
+ String message = "error_message";
+ Snippet s1 = methodKey(assertEval("void f() { throw new RuntimeException(\"" + message + "\"); }"));
+ Snippet s2 = classKey(assertEval("class A { static void g() { f(); } }"));
+ SnippetEvent cr3 = assertEvalException("A.g();");
+ assertExceptionMatch(cr3,
+ new ExceptionInfo(RuntimeException.class, message,
+ newStackTraceElement("", "f", s1, 1),
+ newStackTraceElement("A", "g", s2, 1),
+ newStackTraceElement("", "", cr3.snippet(), 1)));
+ }
+
+ public void throwFromStaticMethodOfInterface() {
+ String message = "error_message";
+ Snippet s1 = methodKey(assertEval("void f() { throw new RuntimeException(\"" + message + "\"); }"));
+ Snippet s2 = classKey(assertEval("interface A { static void g() { f(); } }"));
+ SnippetEvent cr3 = assertEvalException("A.g();");
+ assertExceptionMatch(cr3,
+ new ExceptionInfo(RuntimeException.class, message,
+ newStackTraceElement("", "f", s1, 1),
+ newStackTraceElement("A", "g", s2, 1),
+ newStackTraceElement("", "", cr3.snippet(), 1)));
+ }
+
+ public void throwFromConstructor() {
+ String message = "error_message";
+ Snippet s1 = methodKey(assertEval("void f() { throw new RuntimeException(\"" + message + "\"); }"));
+ Snippet s2 = classKey(assertEval("class A { A() { f(); } }"));
+ SnippetEvent cr3 = assertEvalException("new A();");
+ assertExceptionMatch(cr3,
+ new ExceptionInfo(RuntimeException.class, message,
+ newStackTraceElement("", "f", s1, 1),
+ newStackTraceElement("A", "<init>", s2, 1),
+ newStackTraceElement("", "", cr3.snippet(), 1)));
+ }
+
+ public void throwFromDefaultMethodOfInterface() {
+ String message = "error_message";
+ Snippet s1 = methodKey(assertEval("void f() { throw new RuntimeException(\"" + message + "\"); }"));
+ Snippet s2 = classKey(assertEval("interface A { default void g() { f(); } }"));
+ SnippetEvent cr3 = assertEvalException("new A() { }.g();");
+ assertExceptionMatch(cr3,
+ new ExceptionInfo(RuntimeException.class, message,
+ newStackTraceElement("", "f", s1, 1),
+ newStackTraceElement("A", "g", s2, 1),
+ newStackTraceElement("", "", cr3.snippet(), 1)));
+ }
+
+ public void throwFromLambda() {
+ String message = "lambda";
+ Snippet s1 = varKey(assertEval(
+ "Runnable run = () -> {\n" +
+ " throw new RuntimeException(\"" + message + "\");\n" +
+ "};"
+ ));
+ SnippetEvent cr2 = assertEvalException("run.run();");
+ assertExceptionMatch(cr2,
+ new ExceptionInfo(RuntimeException.class, message,
+ newStackTraceElement("", "lambda$", s1, 2),
+ newStackTraceElement("", "", cr2.snippet(), 1)));
+ }
+
+ public void throwFromAnonymousClass() {
+ String message = "anonymous";
+ Snippet s1 = varKey(assertEval(
+ "Runnable run = new Runnable() {\n" +
+ " public void run() {\n"+
+ " throw new RuntimeException(\"" + message + "\");\n" +
+ " }\n" +
+ "};"
+ ));
+ SnippetEvent cr2 = assertEvalException("run.run();");
+ assertExceptionMatch(cr2,
+ new ExceptionInfo(RuntimeException.class, message,
+ newStackTraceElement("1", "run", s1, 3),
+ newStackTraceElement("", "", cr2.snippet(), 1)));
+ }
+
+ public void throwFromLocalClass() {
+ String message = "local";
+ Snippet s1 = methodKey(assertEval(
+ "void f() {\n" +
+ " class A {\n" +
+ " void f() {\n"+
+ " throw new RuntimeException(\"" + message + "\");\n" +
+ " }\n" +
+ " }\n" +
+ " new A().f();\n" +
+ "}"
+ ));
+ SnippetEvent cr2 = assertEvalException("f();");
+ assertExceptionMatch(cr2,
+ new ExceptionInfo(RuntimeException.class, message,
+ newStackTraceElement("1A", "f", s1, 4),
+ newStackTraceElement("", "f", s1, 7),
+ newStackTraceElement("", "", cr2.snippet(), 1)));
+ }
+
+ @Test(enabled = false) // TODO 8129427
+ public void outOfMemory() {
+ assertEval("import java.util.*;");
+ assertEval("List<byte[]> list = new ArrayList<>();");
+ assertExecuteException("while (true) { list.add(new byte[10000]); }", OutOfMemoryError.class);
+ }
+
+ public void stackOverflow() {
+ assertEval("void f() { f(); }");
+ assertExecuteException("f();", StackOverflowError.class);
+ }
+
+ private StackTraceElement newStackTraceElement(String className, String methodName, Snippet key, int lineNumber) {
+ return new StackTraceElement(className, methodName, "#" + key.id(), lineNumber);
+ }
+
+ private static class ExceptionInfo {
+ public final Class<? extends Throwable> exception;
+ public final String message;
+ public final StackTraceElement[] stackTraceElements;
+
+ public ExceptionInfo(Class<? extends Throwable> exception, String message, StackTraceElement...stackTraceElements) {
+ this.exception = exception;
+ this.message = message;
+ this.stackTraceElements = stackTraceElements.length == 0 ? null : stackTraceElements;
+ }
+ }
+
+ private void assertExecuteException(String input, Class<? extends Throwable> exception) {
+ assertExceptionMatch(assertEvalException(input), new ExceptionInfo(exception, null));
+ }
+
+ private void assertExceptionMatch(SnippetEvent cr, ExceptionInfo exceptionInfo) {
+ assertNotNull(cr.exception(), "Expected exception was not thrown: " + exceptionInfo.exception);
+ if (cr.exception() instanceof EvalException) {
+ EvalException ex = (EvalException) cr.exception();
+ String actualException = ex.getExceptionClassName();
+ String expectedException = exceptionInfo.exception.getCanonicalName();
+ String stackTrace = getStackTrace(ex);
+ String source = cr.snippet().source();
+ assertEquals(actualException, expectedException,
+ String.format("Given \"%s\" expected exception: %s, got: %s%nStack trace:%n%s",
+ source, expectedException, actualException, stackTrace));
+ if (exceptionInfo.message != null) {
+ assertEquals(ex.getMessage(), exceptionInfo.message,
+ String.format("Given \"%s\" expected message: %s, got: %s",
+ source, exceptionInfo.message, ex.getMessage()));
+ }
+ if (exceptionInfo.stackTraceElements != null) {
+ assertStackTrace(ex.getStackTrace(), exceptionInfo.stackTraceElements,
+ String.format("Given \"%s\"%nStack trace:%n%s%n",
+ source, stackTrace));
+ }
+ } else {
+ fail("Unexpected execution exceptionInfo: " + cr.exception());
+ }
+ }
+
+ private void assertStackTrace(StackTraceElement[] actual, StackTraceElement[] expected, String message) {
+ if (actual != expected) {
+ if (actual == null || expected == null) {
+ fail(message);
+ } else {
+ assertEquals(actual.length, expected.length, message + " : arrays do not have the same size");
+ for (int i = 0; i < actual.length; ++i) {
+ StackTraceElement actualElement = actual[i];
+ StackTraceElement expectedElement = expected[i];
+ assertEquals(actualElement.getClassName(), expectedElement.getClassName(), message + " : class names");
+ String expectedMethodName = expectedElement.getMethodName();
+ if (expectedMethodName.startsWith("lambda$")) {
+ assertTrue(actualElement.getMethodName().startsWith("lambda$"), message + " : method names");
+ } else {
+ assertEquals(actualElement.getMethodName(), expectedElement.getMethodName(), message + " : method names");
+ }
+ assertEquals(actualElement.getFileName(), expectedElement.getFileName(), message + " : file names");
+ assertEquals(actualElement.getLineNumber(), expectedElement.getLineNumber(), message + " : line numbers");
+ }
+ }
+ }
+ }
+
+ private String getStackTrace(EvalException ex) {
+ StringWriter st = new StringWriter();
+ ex.printStackTrace(new PrintWriter(st));
+ return st.toString();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/ExpectedDiagnostic.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,94 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import javax.tools.Diagnostic;
+
+import jdk.jshell.Diag;
+import static org.testng.Assert.assertEquals;
+
+public class ExpectedDiagnostic {
+
+ private final String code;
+ private final long startPosition;
+ private final long endPosition;
+ private final long position;
+ private final long lineNumber;
+ private final long columnNumber;
+ private final Diagnostic.Kind kind;
+
+ public ExpectedDiagnostic(
+ String code, long startPosition, long endPosition,
+ long position, long lineNumber, long columnNumber,
+ Diagnostic.Kind kind) {
+ this.code = code;
+ this.startPosition = startPosition;
+ this.endPosition = endPosition;
+ this.position = position;
+ this.lineNumber = lineNumber;
+ this.columnNumber = columnNumber;
+ this.kind = kind;
+ }
+
+ public long getStartPosition() {
+ return startPosition;
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public long getEndPosition() {
+ return endPosition;
+ }
+
+ public long getPosition() {
+ return position;
+ }
+
+ public long getLineNumber() {
+ return lineNumber;
+ }
+
+ public long getColumnNumber() {
+ return columnNumber;
+ }
+
+ public Diagnostic.Kind getKind() {
+ return kind;
+ }
+
+ public void assertDiagnostic(Diag diagnostic) {
+ String code = diagnostic.getCode();
+ assertEquals(code, this.code, "Expected error: " + this.code + ", got: " + code);
+ assertEquals(diagnostic.isError(), kind == Diagnostic.Kind.ERROR);
+ if (startPosition != -1) {
+ assertEquals(diagnostic.getStartPosition(), startPosition, "Start position");
+ }
+ if (endPosition != -1) {
+ assertEquals(diagnostic.getEndPosition(), endPosition, "End position");
+ }
+ if (position != -1) {
+ assertEquals(diagnostic.getPosition(), position, "Position");
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/ExternalEditorTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,225 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Testing external editor.
+ * @bug 8080843
+ * @ignore 8080843
+ * @build ReplToolTesting CustomEditor EditorTestBase
+ * @run testng ExternalEditorTest
+ */
+
+import java.io.BufferedWriter;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketTimeoutException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.function.Consumer;
+
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.fail;
+
+public class ExternalEditorTest extends EditorTestBase {
+
+ private static Path executionScript;
+ private static ServerSocket listener;
+
+ private DataInputStream inputStream;
+ private DataOutputStream outputStream;
+
+ @Override
+ public void writeSource(String s) {
+ try {
+ outputStream.writeInt(CustomEditor.SOURCE_CODE);
+ byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
+ outputStream.writeInt(bytes.length);
+ outputStream.write(bytes);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ @Override
+ public String getSource() {
+ try {
+ outputStream.writeInt(CustomEditor.GET_SOURCE_CODE);
+ int length = inputStream.readInt();
+ byte[] bytes = new byte[length];
+ inputStream.readFully(bytes);
+ return new String(bytes, StandardCharsets.UTF_8);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ private void sendCode(int code) {
+ try {
+ outputStream.writeInt(code);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ @Override
+ public void accept() {
+ sendCode(CustomEditor.ACCEPT_CODE);
+ }
+
+ @Override
+ public void exit() {
+ sendCode(CustomEditor.EXIT_CODE);
+ inputStream = null;
+ outputStream = null;
+ }
+
+ @Override
+ public void cancel() {
+ sendCode(CustomEditor.CANCEL_CODE);
+ }
+
+ @Override
+ public void testEditor(boolean defaultStartup, String[] args, ReplTest... tests) {
+ ReplTest[] t = new ReplTest[tests.length + 1];
+ t[0] = a -> assertCommandCheckOutput(a, "/seteditor " + executionScript,
+ assertStartsWith("| Editor set to: " + executionScript));
+ System.arraycopy(tests, 0, t, 1, tests.length);
+ super.testEditor(defaultStartup, args, t);
+ }
+
+ private static boolean isWindows() {
+ return System.getProperty("os.name").startsWith("Windows");
+ }
+
+ @BeforeClass
+ public static void setUp() throws IOException {
+ listener = new ServerSocket(0);
+ listener.setSoTimeout(30000);
+ int localPort = listener.getLocalPort();
+
+ executionScript = Paths.get(isWindows() ? "editor.bat" : "editor.sh").toAbsolutePath();
+ Path java = Paths.get(System.getProperty("java.home")).resolve("bin").resolve("java");
+ try (BufferedWriter writer = Files.newBufferedWriter(executionScript)) {
+ if(!isWindows()) {
+ writer.append(java.toString()).append(" ")
+ .append(" -cp ").append(System.getProperty("java.class.path"))
+ .append(" CustomEditor ").append(Integer.toString(localPort)).append(" $@");
+ executionScript.toFile().setExecutable(true);
+ } else {
+ writer.append(java.toString()).append(" ")
+ .append(" -cp ").append(System.getProperty("java.class.path"))
+ .append(" CustomEditor ").append(Integer.toString(localPort)).append(" %*");
+ }
+ }
+ }
+
+ private Future<?> task;
+ @Override
+ public void assertEdit(boolean after, String cmd,
+ Consumer<String> checkInput, Consumer<String> checkOutput, Action action) {
+ if (!after) {
+ setCommandInput(cmd + "\n");
+ task = getExecutor().submit(() -> {
+ try (Socket socket = listener.accept()) {
+ inputStream = new DataInputStream(socket.getInputStream());
+ outputStream = new DataOutputStream(socket.getOutputStream());
+ checkInput.accept(getSource());
+ action.accept();
+ } catch (SocketTimeoutException e) {
+ fail("Socket timeout exception.\n Output: " + getCommandOutput() +
+ "\n, error: " + getCommandErrorOutput());
+ } catch (Throwable e) {
+ shutdownEditor();
+ if (e instanceof AssertionError) {
+ throw (AssertionError) e;
+ }
+ throw new RuntimeException(e);
+ }
+ });
+ } else {
+ try {
+ task.get();
+ checkOutput.accept(getCommandOutput());
+ } catch (ExecutionException e) {
+ if (e.getCause() instanceof AssertionError) {
+ throw (AssertionError) e.getCause();
+ }
+ throw new RuntimeException(e);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ @Override
+ public void shutdownEditor() {
+ if (outputStream != null) {
+ exit();
+ }
+ }
+
+ @Test
+ public void setUnknownEditor() {
+ test(
+ a -> assertCommand(a, "/seteditor", "| /seteditor requires a path argument\n"),
+ a -> assertCommand(a, "/seteditor UNKNOWN", "| Editor set to: UNKNOWN\n"),
+ a -> assertCommand(a, "int a;", null),
+ a -> assertCommand(a, "/e 1",
+ "| Edit Error: process IO failure: Cannot run program \"UNKNOWN\": error=2, No such file or directory\n")
+ );
+ }
+
+ @Test(enabled = false)
+ public void testRemoveTempFile() {
+ test(new String[]{"-nostartup"},
+ a -> assertCommandCheckOutput(a, "/seteditor " + executionScript,
+ assertStartsWith("| Editor set to: " + executionScript)),
+ a -> assertVariable(a, "int", "a", "0", "0"),
+ a -> assertEditOutput(a, "/e 1", assertStartsWith("| Edit Error: Failure read edit file:"), () -> {
+ sendCode(CustomEditor.REMOVE_CODE);
+ exit();
+ }),
+ a -> assertCommandCheckOutput(a, "/v", assertVariables())
+ );
+ }
+
+ @AfterClass
+ public static void shutdown() throws IOException {
+ executorShutdown();
+ if (listener != null) {
+ listener.close();
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/HistoryTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,245 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Test Completion
+ * @build HistoryTest
+ * @run testng HistoryTest
+ */
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.prefs.AbstractPreferences;
+import java.util.prefs.BackingStoreException;
+import jdk.internal.jline.console.history.MemoryHistory;
+
+import jdk.jshell.JShell;
+import jdk.jshell.SourceCodeAnalysis;
+import jdk.jshell.SourceCodeAnalysis.CompletionInfo;
+import org.testng.annotations.Test;
+import jdk.internal.jshell.tool.EditingHistory;
+
+import static org.testng.Assert.*;
+
+@Test
+public class HistoryTest {
+
+ public void testHistory() {
+ JShell eval = JShell.builder()
+ .in(new ByteArrayInputStream(new byte[0]))
+ .out(new PrintStream(new ByteArrayOutputStream()))
+ .err(new PrintStream(new ByteArrayOutputStream()))
+ .build();
+ SourceCodeAnalysis analysis = eval.sourceCodeAnalysis();
+ MemoryPreferences prefs = new MemoryPreferences(null, "");
+ EditingHistory history = new EditingHistory(prefs) {
+ @Override protected CompletionInfo analyzeCompletion(String input) {
+ return analysis.analyzeCompletion(input);
+ }
+ };
+ history.add("void test() {");
+ history.add(" System.err.println(1);");
+ history.add("}");
+ history.add("/exit");
+
+ previousAndAssert(history, "/exit");
+
+ history.previous(); history.previous(); history.previous();
+
+ history.add("void test() { /*changed*/");
+
+ previousAndAssert(history, "}");
+ previousAndAssert(history, " System.err.println(1);");
+ previousAndAssert(history, "void test() {");
+
+ assertFalse(history.previous());
+
+ nextAndAssert(history, " System.err.println(1);");
+ nextAndAssert(history, "}");
+ nextAndAssert(history, "");
+
+ history.add(" System.err.println(2);");
+ history.add("} /*changed*/");
+
+ assertEquals(history.size(), 7);
+
+ history.save();
+
+ history = new EditingHistory(prefs) {
+ @Override protected CompletionInfo analyzeCompletion(String input) {
+ return analysis.analyzeCompletion(input);
+ }
+ };
+
+ previousSnippetAndAssert(history, "void test() { /*changed*/");
+ previousSnippetAndAssert(history, "/exit");
+ previousSnippetAndAssert(history, "void test() {");
+
+ assertFalse(history.previousSnippet());
+
+ nextSnippetAndAssert(history, "/exit");
+ nextSnippetAndAssert(history, "void test() { /*changed*/");
+ nextSnippetAndAssert(history, "");
+
+ assertFalse(history.nextSnippet());
+
+ history.add("{");
+ history.add("}");
+
+ history.save();
+
+ history = new EditingHistory(prefs) {
+ @Override protected CompletionInfo analyzeCompletion(String input) {
+ return analysis.analyzeCompletion(input);
+ }
+ };
+
+ previousSnippetAndAssert(history, "{");
+ previousSnippetAndAssert(history, "void test() { /*changed*/");
+ previousSnippetAndAssert(history, "/exit");
+ previousSnippetAndAssert(history, "void test() {");
+
+ while (history.next());
+
+ history.add("/*current1*/");
+ history.add("/*current2*/");
+ history.add("/*current3*/");
+
+ assertEquals(history.currentSessionEntries(), Arrays.asList("/*current1*/", "/*current2*/", "/*current3*/"));
+
+ history.remove(0);
+
+ assertEquals(history.currentSessionEntries(), Arrays.asList("/*current1*/", "/*current2*/", "/*current3*/"));
+
+ while (history.size() > 2)
+ history.remove(0);
+
+ assertEquals(history.currentSessionEntries(), Arrays.asList("/*current2*/", "/*current3*/"));
+
+ for (int i = 0; i < MemoryHistory.DEFAULT_MAX_SIZE * 2; i++) {
+ history.add("/exit");
+ }
+
+ history.add("void test() { /*after full*/");
+ history.add(" System.err.println(1);");
+ history.add("}");
+
+ previousSnippetAndAssert(history, "void test() { /*after full*/");
+ }
+
+ public void testSaveOneHistory() {
+ JShell eval = JShell.builder()
+ .in(new ByteArrayInputStream(new byte[0]))
+ .out(new PrintStream(new ByteArrayOutputStream()))
+ .err(new PrintStream(new ByteArrayOutputStream()))
+ .build();
+ SourceCodeAnalysis analysis = eval.sourceCodeAnalysis();
+ MemoryPreferences prefs = new MemoryPreferences(null, "");
+ EditingHistory history = new EditingHistory(prefs) {
+ @Override protected CompletionInfo analyzeCompletion(String input) {
+ return analysis.analyzeCompletion(input);
+ }
+ };
+
+ history.add("first");
+ history.save();
+ }
+
+ private void previousAndAssert(EditingHistory history, String expected) {
+ assertTrue(history.previous());
+ assertEquals(history.current().toString(), expected);
+ }
+
+ private void nextAndAssert(EditingHistory history, String expected) {
+ assertTrue(history.next());
+ assertEquals(history.current().toString(), expected);
+ }
+
+ private void previousSnippetAndAssert(EditingHistory history, String expected) {
+ assertTrue(history.previousSnippet());
+ assertEquals(history.current().toString(), expected);
+ }
+
+ private void nextSnippetAndAssert(EditingHistory history, String expected) {
+ assertTrue(history.nextSnippet());
+ assertEquals(history.current().toString(), expected);
+ }
+
+ private static final class MemoryPreferences extends AbstractPreferences {
+
+ private final Map<String, String> key2Value = new HashMap<>();
+ private final Map<String, MemoryPreferences> key2SubNode = new HashMap<>();
+
+ public MemoryPreferences(AbstractPreferences parent, String name) {
+ super(parent, name);
+ }
+
+ @Override
+ protected void putSpi(String key, String value) {
+ key2Value.put(key, value);
+ }
+
+ @Override
+ protected String getSpi(String key) {
+ return key2Value.get(key);
+ }
+
+ @Override
+ protected void removeSpi(String key) {
+ key2Value.remove(key);
+ }
+
+ @Override
+ protected void removeNodeSpi() throws BackingStoreException {
+ ((MemoryPreferences) parent()).key2SubNode.remove(name());
+ }
+
+ @Override
+ protected String[] keysSpi() throws BackingStoreException {
+ return key2Value.keySet().toArray(new String[key2Value.size()]);
+ }
+
+ @Override
+ protected String[] childrenNamesSpi() throws BackingStoreException {
+ return key2SubNode.keySet().toArray(new String[key2SubNode.size()]);
+ }
+
+ @Override
+ protected AbstractPreferences childSpi(String name) {
+ return key2SubNode.computeIfAbsent(name, n -> new MemoryPreferences(this, n));
+ }
+
+ @Override
+ protected void syncSpi() throws BackingStoreException {}
+
+ @Override
+ protected void flushSpi() throws BackingStoreException {}
+
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/IOTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Test input/output
+ * @build KullaTesting TestingInputStream
+ * @run testng IOTest
+ */
+
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+
+@Test
+public class IOTest extends KullaTesting {
+
+ String LINE_SEPARATOR = System.getProperty("line.separator");
+
+ public void testOutput() {
+ assertEval("System.out.println(\"Test\");");
+ assertEquals(getOutput(), "Test" + LINE_SEPARATOR);
+ }
+
+ public void testErrorOutput() {
+ assertEval("System.err.println(\"Oops\");");
+ assertEquals(getErrorOutput(), "Oops" + LINE_SEPARATOR);
+ }
+
+ public void testInput() {
+ setInput("x");
+ assertEval("(char)System.in.read();", "'x'");
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/IdGeneratorTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,128 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Test custom id generators
+ * @build KullaTesting TestingInputStream
+ * @run testng IdGeneratorTest
+ */
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.List;
+import java.util.function.Supplier;
+
+import jdk.jshell.EvalException;
+import jdk.jshell.JShell;
+import jdk.jshell.PersistentSnippet;
+import jdk.jshell.SnippetEvent;
+import jdk.jshell.UnresolvedReferenceException;
+import jdk.jshell.VarSnippet;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+@Test
+public class IdGeneratorTest {
+
+ public JShell.Builder getBuilder() {
+ TestingInputStream inStream = new TestingInputStream();
+ ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+ ByteArrayOutputStream errStream = new ByteArrayOutputStream();
+ return JShell.builder()
+ .in(inStream)
+ .out(new PrintStream(outStream))
+ .err(new PrintStream(errStream));
+ }
+
+ public void testTempNameGenerator() {
+ JShell.Builder builder = getBuilder().tempVariableNameGenerator(new Supplier<String>() {
+ int count = 0;
+
+ @Override
+ public String get() {
+ return "temp" + ++count;
+ }
+ });
+ try (JShell jShell = builder.build()) {
+ for (int i = 0; i < 3; ++i) {
+ VarSnippet v = (VarSnippet) jShell.eval("2 + " + (i + 1)).get(0).snippet();
+ assertEquals("temp" + (i + 1), v.name(), "Custom id: ");
+ }
+ }
+ }
+
+ public void testResetTempNameGenerator() {
+ JShell.Builder builder = getBuilder().tempVariableNameGenerator(() -> {
+ throw new AssertionError("Should not be called");
+ });
+ try (JShell jShell = builder.tempVariableNameGenerator(null).build()) {
+ jShell.eval("2 + 2");
+ }
+ }
+
+ public void testIdGenerator() {
+ JShell.Builder builder = getBuilder().idGenerator(((snippet, id) -> "custom" + id));
+ try (JShell jShell = builder.build()) {
+ List<SnippetEvent> eval = jShell.eval("int a, b;");
+ checkIds(eval);
+ checkIds(jShell.drop((PersistentSnippet) eval.get(0).snippet()));
+ }
+ }
+
+ private void checkIds(List<SnippetEvent> events) {
+ for (SnippetEvent event : events) {
+ assertTrue(event.snippet().id().startsWith("custom"), "Not started with \"custom\": "
+ + event.snippet().id());
+ }
+ }
+
+ @Test(enabled = false) // TODO 8133507
+ public void testIdInException() {
+ JShell.Builder builder = getBuilder().idGenerator(((snippet, id) -> "custom" + id));
+ try (JShell jShell = builder.build()) {
+ EvalException evalException = (EvalException) jShell.eval("throw new Error();").get(0).exception();
+ for (StackTraceElement ste : evalException.getStackTrace()) {
+ assertTrue(ste.getFileName().startsWith("custom"), "Not started with \"custom\": "
+ + ste.getFileName());
+ }
+ jShell.eval("void f() { g(); }");
+ UnresolvedReferenceException unresolvedException = (UnresolvedReferenceException) jShell.eval("f();").get(0).exception();
+ for (StackTraceElement ste : unresolvedException.getStackTrace()) {
+ assertTrue(ste.getFileName().startsWith("custom"), "Not started with \"custom\": "
+ + ste.getFileName());
+ }
+ }
+ }
+
+ public void testResetIdGenerator() {
+ JShell.Builder builder = getBuilder().idGenerator((sn, id) -> {
+ throw new AssertionError("Should not be called");
+ });
+ try (JShell jShell = builder.idGenerator(null).build()) {
+ jShell.eval("2 + 2");
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/IgnoreTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,114 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Test the ignoring of comments and certain modifiers
+ * @build KullaTesting TestingInputStream
+ * @run testng IgnoreTest
+ */
+
+import org.testng.annotations.Test;
+
+import jdk.jshell.MethodSnippet;
+import jdk.jshell.TypeDeclSnippet;
+import jdk.jshell.VarSnippet;
+import static jdk.jshell.Snippet.Status.VALID;
+import static jdk.jshell.Snippet.SubKind.*;
+
+@Test
+public class IgnoreTest extends KullaTesting {
+
+ public void testComment() {
+ assertVarKeyMatch("//t1\n int//t2\n x//t3\n =//t4\n 12//t5\n ;//t6\n",
+ true, "x", VAR_DECLARATION_WITH_INITIALIZER_SUBKIND, "int", added(VALID));
+ assertVarKeyMatch("//t1\n int//t2\n y//t3\n =//t4\n 12//t5\n ;//t6",
+ true, "y", VAR_DECLARATION_WITH_INITIALIZER_SUBKIND, "int", added(VALID));
+ assertDeclarationKeyMatch(" //t0\n" +
+ " int//t0\n" +
+ " f//t0\n" +
+ " (//t0\n" +
+ " int x//t1\n" +
+ " ) {//t2\n" +
+ " return x+//t3\n" +
+ " x//t4\n" +
+ " ;//t5\n" +
+ " }//t6",
+ false, "f", METHOD_SUBKIND, added(VALID));
+ }
+
+ public void testVarModifier() {
+ VarSnippet x1 = (VarSnippet) assertDeclareWarn1("public int x1;", "jdk.eval.warn.illegal.modifiers");
+ assertVariableDeclSnippet(x1, "x1", "int", VALID, VAR_DECLARATION_SUBKIND, 0, 1);
+ VarSnippet x2 = (VarSnippet) assertDeclareWarn1("protected int x2;", "jdk.eval.warn.illegal.modifiers");
+ assertVariableDeclSnippet(x2, "x2", "int", VALID, VAR_DECLARATION_SUBKIND, 0, 1);
+ VarSnippet x3 = (VarSnippet) assertDeclareWarn1("private int x3;", "jdk.eval.warn.illegal.modifiers");
+ assertVariableDeclSnippet(x3, "x3", "int", VALID, VAR_DECLARATION_SUBKIND, 0, 1);
+ VarSnippet x4 = (VarSnippet) assertDeclareWarn1("static int x4;", "jdk.eval.warn.illegal.modifiers");
+ assertVariableDeclSnippet(x4, "x4", "int", VALID, VAR_DECLARATION_SUBKIND, 0, 1);
+ VarSnippet x5 = (VarSnippet) assertDeclareWarn1("final int x5;", "jdk.eval.warn.illegal.modifiers");
+ assertVariableDeclSnippet(x5, "x5", "int", VALID, VAR_DECLARATION_SUBKIND, 0, 1);
+ }
+
+ public void testMethodModifier() {
+ MethodSnippet m1 = (MethodSnippet) assertDeclareWarn1("public void m1() {}", "jdk.eval.warn.illegal.modifiers");
+ assertMethodDeclSnippet(m1, "m1", "()void", VALID, 0, 1);
+ MethodSnippet m2 = (MethodSnippet) assertDeclareWarn1("protected void m2() {}", "jdk.eval.warn.illegal.modifiers");
+ assertMethodDeclSnippet(m2, "m2", "()void", VALID, 0, 1);
+ MethodSnippet m3 = (MethodSnippet) assertDeclareWarn1("private void m3() {}", "jdk.eval.warn.illegal.modifiers");
+ assertMethodDeclSnippet(m3, "m3", "()void", VALID, 0, 1);
+ MethodSnippet m4 = (MethodSnippet) assertDeclareWarn1("static void m4() {}", "jdk.eval.warn.illegal.modifiers");
+ assertMethodDeclSnippet(m4, "m4", "()void", VALID, 0, 1);
+ MethodSnippet m5 = (MethodSnippet) assertDeclareWarn1("final void m5() {}", "jdk.eval.warn.illegal.modifiers");
+ assertMethodDeclSnippet(m5, "m5", "()void", VALID, 0, 1);
+ }
+
+ public void testClassModifier() {
+ TypeDeclSnippet c1 = (TypeDeclSnippet) assertDeclareWarn1("public class C1 {}", "jdk.eval.warn.illegal.modifiers");
+ assertTypeDeclSnippet(c1, "C1", VALID, CLASS_SUBKIND, 0, 1);
+ TypeDeclSnippet c2 = (TypeDeclSnippet) assertDeclareWarn1("protected class C2 {}", "jdk.eval.warn.illegal.modifiers");
+ assertTypeDeclSnippet(c2, "C2", VALID, CLASS_SUBKIND, 0, 1);
+ TypeDeclSnippet c3 = (TypeDeclSnippet) assertDeclareWarn1("private class C3 {}", "jdk.eval.warn.illegal.modifiers");
+ assertTypeDeclSnippet(c3, "C3", VALID, CLASS_SUBKIND, 0, 1);
+ TypeDeclSnippet c4 = (TypeDeclSnippet) assertDeclareWarn1("static class C4 {}", "jdk.eval.warn.illegal.modifiers");
+ assertTypeDeclSnippet(c4, "C4", VALID, CLASS_SUBKIND, 0, 1);
+ TypeDeclSnippet c5 = (TypeDeclSnippet) assertDeclareWarn1("final class C5 {}", "jdk.eval.warn.illegal.modifiers");
+ assertTypeDeclSnippet(c5, "C5", VALID, CLASS_SUBKIND, 0, 1);
+ }
+
+ public void testInsideModifier() {
+ assertEval("import static java.lang.reflect.Modifier.*;");
+ assertEval("class C {"
+ + "public int z;"
+ + "final int f = 3;"
+ + "protected int a;"
+ + "private void m() {}"
+ + "static void b() {}"
+ + "}");
+ assertEval("C.class.getDeclaredField(\"z\").getModifiers() == PUBLIC;", "true");
+ assertEval("C.class.getDeclaredField(\"f\").getModifiers() == FINAL;", "true");
+ assertEval("C.class.getDeclaredField(\"a\").getModifiers() == PROTECTED;", "true");
+ assertEval("C.class.getDeclaredMethod(\"m\").getModifiers() == PRIVATE;", "true");
+ assertEval("C.class.getDeclaredMethod(\"b\").getModifiers() == STATIC;", "true");
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/IllegalArgumentExceptionTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Testing IllegalArgumentException.
+ * @build KullaTesting TestingInputStream IllegalArgumentExceptionTest
+ * @run testng IllegalArgumentExceptionTest
+ */
+
+import java.util.function.Consumer;
+
+import jdk.jshell.DeclarationSnippet;
+import jdk.jshell.PersistentSnippet;
+import jdk.jshell.Snippet;
+import jdk.jshell.VarSnippet;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.fail;
+import static jdk.jshell.Snippet.Status.VALID;
+
+@Test
+public class IllegalArgumentExceptionTest extends KullaTesting {
+
+ private void testIllegalArgumentException(Consumer<Snippet> action) {
+ Snippet key = varKey(assertEval("int value;", added(VALID)));
+ tearDown();
+ setUp();
+ assertEval("double value;");
+ try {
+ action.accept(key);
+ fail("Exception expected.");
+ } catch (IllegalArgumentException e) {
+ // Expected
+ }
+ }
+
+ public void testVarValue() {
+ testIllegalArgumentException((key) -> getState().varValue((VarSnippet) key));
+ }
+
+ public void testStatus() {
+ testIllegalArgumentException((key) -> getState().status(key));
+ }
+
+ public void testDrop() {
+ testIllegalArgumentException((key) -> getState().drop((PersistentSnippet) key));
+ }
+
+ public void testUnresolved() {
+ testIllegalArgumentException((key) -> getState().unresolvedDependencies((DeclarationSnippet) key));
+ }
+
+ public void testDiagnostics() {
+ testIllegalArgumentException((key) -> getState().diagnostics(key));
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/ImportTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,162 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Test imports
+ * @library /tools/lib
+ * @build KullaTesting TestingInputStream ToolBox ExpectedDiagnostic
+ * @run testng ImportTest
+ */
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import javax.tools.Diagnostic;
+
+import jdk.jshell.Snippet;
+import org.testng.annotations.Test;
+
+import static jdk.jshell.Snippet.Status.VALID;
+import static jdk.jshell.Snippet.Status.OVERWRITTEN;
+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;
+
+@Test
+public class ImportTest extends KullaTesting {
+
+ public void testImport() {
+ assertImportKeyMatch("import java.util.List;", "List", SINGLE_TYPE_IMPORT_SUBKIND, added(VALID));
+ assertImportKeyMatch("import java.util.ArrayList;", "ArrayList", SINGLE_TYPE_IMPORT_SUBKIND, added(VALID));
+ assertEval("List<Integer> list = new ArrayList<>();");
+ assertEval("list.add(45);");
+ assertEval("list.size();", "1");
+ }
+
+ public void testImportOnDemand() {
+ assertImportKeyMatch("import java.util.*;", "java.util.*", TYPE_IMPORT_ON_DEMAND_SUBKIND, added(VALID));
+ assertEval("List<Integer> list = new ArrayList<>();");
+ assertEval("list.add(45);");
+ assertEval("list.size();", "1");
+ }
+
+ public void testImportStatic() {
+ assertImportKeyMatch("import static java.lang.Math.PI;", "PI", SINGLE_STATIC_IMPORT_SUBKIND, added(VALID));
+ assertEval("new Double(PI).toString().substring(0, 16).equals(\"3.14159265358979\");", "true");
+ }
+
+ public void testImportStaticOnDemand() {
+ assertImportKeyMatch("import static java.lang.Math.*;", "java.lang.Math.*", STATIC_IMPORT_ON_DEMAND_SUBKIND, added(VALID));
+ assertEval("abs(cos(PI / 2)) < 0.00001;", "true");
+ }
+
+ @Test(enabled = false) // TODO 8129418
+ public void testUnknownPackage() {
+ assertDeclareFail("import unknown.qqq;",
+ new ExpectedDiagnostic("compiler.err.doesnt.exist", 7, 18, 14, -1, -1, Diagnostic.Kind.ERROR));
+ assertDeclareFail("import unknown.*;",
+ new ExpectedDiagnostic("compiler.err.doesnt.exist", 7, 15, 7, -1, -1, Diagnostic.Kind.ERROR));
+ }
+
+ public void testBogusImportIgnoredInFuture() {
+ assertDeclareFail("import unknown.qqq;", "compiler.err.doesnt.exist");
+ assertDeclareFail("import unknown.*;", "compiler.err.doesnt.exist");
+ assertEval("2 + 2;");
+ }
+
+ public void testBadImport() {
+ assertDeclareFail("import static java.lang.reflect.Modifier;",
+ new ExpectedDiagnostic("compiler.err.cant.resolve.location", 14, 31, 23, -1, -1, Diagnostic.Kind.ERROR));
+ }
+
+ public void testBadSyntaxImport() {
+ assertDeclareFail("import not found.*;",
+ new ExpectedDiagnostic("compiler.err.expected", 10, 10, 10, -1, -1, Diagnostic.Kind.ERROR));
+ }
+
+ public void testImportRedefinition() {
+ Compiler compiler = new Compiler();
+ Path path = Paths.get("testImport");
+ compiler.compile(path, "package util; public class ArrayList { public String toString() { return \"MyList\"; } }");
+ compiler.compile(path, "package util; public class A { public static class ArrayList {\n" +
+ "public String toString() { return \"MyInnerList\"; } } }");
+ addToClasspath(compiler.getPath(path));
+
+ assertImportKeyMatch("import util.*;", "util.*", TYPE_IMPORT_ON_DEMAND_SUBKIND, added(VALID));
+ assertEval("new ArrayList();", "MyList", added(VALID));
+
+ Snippet import0 = assertImportKeyMatch("import java.util.ArrayList;", "ArrayList", SINGLE_TYPE_IMPORT_SUBKIND, added(VALID));
+ assertEval("new ArrayList();", "[]");
+
+ Snippet import1 = assertImportKeyMatch("import util.ArrayList;", "ArrayList", SINGLE_TYPE_IMPORT_SUBKIND,
+ ste(MAIN_SNIPPET, VALID, VALID, false, null),
+ ste(import0, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertEval("new ArrayList();", "MyList");
+
+ Snippet import2 = assertImportKeyMatch("import java.util.ArrayList;", "ArrayList", SINGLE_TYPE_IMPORT_SUBKIND,
+ ste(MAIN_SNIPPET, VALID, VALID, false, null),
+ ste(import1, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertEval("new ArrayList();", "[]");
+
+ Snippet import3 = assertImportKeyMatch("import util.A.ArrayList;", "ArrayList", SINGLE_TYPE_IMPORT_SUBKIND,
+ ste(MAIN_SNIPPET, VALID, VALID, false, null),
+ ste(import2, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertEval("new ArrayList();", "MyInnerList");
+
+ Snippet import4 = assertImportKeyMatch("import java.util.ArrayList;", "ArrayList", SINGLE_TYPE_IMPORT_SUBKIND,
+ ste(MAIN_SNIPPET, VALID, VALID, false, null),
+ ste(import3, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertEval("new ArrayList();", "[]");
+
+ Snippet import5 = assertImportKeyMatch("import static util.A.ArrayList;", "ArrayList", SINGLE_STATIC_IMPORT_SUBKIND,
+ ste(MAIN_SNIPPET, VALID, VALID, false, null),
+ ste(import4, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertEval("new ArrayList();", "MyInnerList");
+ }
+
+ public void testImportMemberRedefinition() {
+ Compiler compiler = new Compiler();
+ Path path = Paths.get("testImport");
+ compiler.compile(path, "package util; public class A {" +
+ "public static String field = \"A\";" +
+ "public static String method() { return \"A\"; } }");
+ compiler.compile(path, "package util; public class B {" +
+ "public static String field = \"B\";" +
+ "public static String method() { return \"B\"; } }");
+ addToClasspath(compiler.getPath(path));
+
+ assertImportKeyMatch("import static util.B.*;", "util.B.*", STATIC_IMPORT_ON_DEMAND_SUBKIND, added(VALID));
+ assertEval("field;", "\"B\"");
+ assertEval("method();", "\"B\"");
+
+ assertImportKeyMatch("import static util.A.method;", "method", SINGLE_STATIC_IMPORT_SUBKIND, added(VALID));
+ assertEval("field;", "\"B\"");
+ assertEval("method();", "\"A\"");
+
+ assertImportKeyMatch("import static util.A.field;", "field", SINGLE_STATIC_IMPORT_SUBKIND, added(VALID));
+ assertEval("field;", "\"A\"");
+ assertEval("method();", "\"A\"");
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/JShellStateClosedTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,114 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Testing IllegalStateException.
+ * @build KullaTesting TestingInputStream JShellStateClosedTest
+ * @run testng JShellStateClosedTest
+ */
+
+import java.util.function.Consumer;
+
+import jdk.jshell.DeclarationSnippet;
+import jdk.jshell.PersistentSnippet;
+import jdk.jshell.Snippet;
+import jdk.jshell.VarSnippet;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.fail;
+
+@Test
+public class JShellStateClosedTest extends KullaTesting {
+
+ private void testStateClosedException(Runnable action) {
+ getState().close();
+ try {
+ action.run();
+ fail("Exception expected");
+ } catch (IllegalStateException e) {
+ // Expected
+ }
+ }
+
+ public void testClasses() {
+ testStateClosedException(() -> getState().types());
+ }
+
+ public void testVariables() {
+ testStateClosedException(() -> getState().variables());
+ }
+
+ public void testMethods() {
+ testStateClosedException(() -> getState().methods());
+ }
+
+ public void testKeys() {
+ testStateClosedException(() -> getState().snippets());
+ }
+
+ public void testEval() {
+ testStateClosedException(() -> getState().eval("int a;"));
+ }
+
+ private void testStateClosedException(Consumer<Snippet> action) {
+ Snippet k = varKey(assertEval("int a;"));
+ getState().close();
+ try {
+ action.accept(k);
+ fail("IllegalStateException expected since closed");
+ } catch (IllegalStateException e) {
+ // Expected
+ }
+ }
+
+ private void testStateClosedWithoutException(Consumer<Snippet> action) {
+ Snippet k = varKey(assertEval("int a;"));
+ getState().close();
+ try {
+ action.accept(k);
+ } catch (IllegalStateException e) {
+ fail("Expected no IllegalStateException even though closed");
+ }
+ }
+
+ public void testStatus() {
+ testStateClosedWithoutException((key) -> getState().status(key));
+ }
+
+ public void testVarValue() {
+ testStateClosedException((key) -> getState().varValue((VarSnippet) key));
+ }
+
+ public void testDrop() {
+ testStateClosedException((key) -> getState().drop((PersistentSnippet) key));
+ }
+
+ public void testUnresolved() {
+ testStateClosedWithoutException((key) -> getState().unresolvedDependencies((DeclarationSnippet) key));
+ }
+
+ public void testDiagnostics() {
+ testStateClosedWithoutException((key) -> getState().diagnostics(key));
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/KullaCompletenessStressTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Test SourceCodeAnalysis
+ * @build KullaTesting TestingInputStream KullaCompletenessStressTest CompletenessStressTest
+ * @run testng KullaCompletenessStressTest
+ */
+
+import java.io.File;
+
+import org.testng.annotations.Test;
+
+@Test
+public class KullaCompletenessStressTest extends CompletenessStressTest {
+ @Override
+ public File[] getDirectoriesToTest() {
+ return new File[]{ getKullaSourceDirectory() };
+ }
+
+ public File getKullaSourceDirectory() {
+ String src = System.getProperty("test.src");
+ File file;
+ if (src == null) {
+ file = new File("../src/jdk.jshell/share/classes");
+ } else {
+ file = new File(src, "../../../src/jdk.jshell/share/classes");
+ }
+ return file;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/KullaTesting.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,1144 @@
+/*
+ * 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.io.StringWriter;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.tools.Diagnostic;
+
+import jdk.jshell.EvalException;
+import jdk.jshell.JShell;
+import jdk.jshell.JShell.Subscription;
+import jdk.jshell.Snippet;
+import jdk.jshell.DeclarationSnippet;
+import jdk.jshell.ExpressionSnippet;
+import jdk.jshell.ImportSnippet;
+import jdk.jshell.Snippet.Kind;
+import jdk.jshell.MethodSnippet;
+import jdk.jshell.PersistentSnippet;
+import jdk.jshell.Snippet.Status;
+import jdk.jshell.Snippet.SubKind;
+import jdk.jshell.TypeDeclSnippet;
+import jdk.jshell.VarSnippet;
+import jdk.jshell.SnippetEvent;
+import jdk.jshell.SourceCodeAnalysis;
+import jdk.jshell.SourceCodeAnalysis.CompletionInfo;
+import jdk.jshell.SourceCodeAnalysis.Completeness;
+import jdk.jshell.SourceCodeAnalysis.Suggestion;
+import jdk.jshell.UnresolvedReferenceException;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+
+import jdk.jshell.Diag;
+import static jdk.jshell.Snippet.Status.*;
+import static org.testng.Assert.*;
+import static jdk.jshell.Snippet.SubKind.METHOD_SUBKIND;
+
+public class KullaTesting {
+
+ public static final String IGNORE_VALUE = "<ignore-value>";
+ public static final Class<? extends Throwable> IGNORE_EXCEPTION = (new Throwable() {}).getClass();
+ public static final Snippet MAIN_SNIPPET;
+
+ private SourceCodeAnalysis analysis = null;
+ private JShell state = null;
+ private TestingInputStream inStream = null;
+ private ByteArrayOutputStream outStream = null;
+ private ByteArrayOutputStream errStream = null;
+
+ private Map<String, Snippet> idToSnippet = new LinkedHashMap<>();
+ private Set<Snippet> allSnippets = new LinkedHashSet<>();
+ private List<String> classpath;
+
+ static {
+ JShell js = JShell.create();
+ MAIN_SNIPPET = js.eval("MAIN_SNIPPET").get(0).snippet();
+ js.close();
+ assertTrue(MAIN_SNIPPET != null, "Bad MAIN_SNIPPET set-up -- must not be null");
+ }
+
+ public enum DiagCheck {
+ DIAG_OK,
+ DIAG_WARNING,
+ DIAG_ERROR,
+ DIAG_IGNORE
+ }
+
+ public void setInput(String s) {
+ inStream.setInput(s);
+ }
+
+ public String getOutput() {
+ String s = outStream.toString();
+ outStream.reset();
+ return s;
+ }
+
+ public String getErrorOutput() {
+ String s = errStream.toString();
+ errStream.reset();
+ return s;
+ }
+
+ /**
+ * @return the analysis
+ */
+ public SourceCodeAnalysis getAnalysis() {
+ if (analysis == null) {
+ analysis = state.sourceCodeAnalysis();
+ }
+ return analysis;
+ }
+
+ /**
+ * @return the state
+ */
+ public JShell getState() {
+ return state;
+ }
+
+ public List<Snippet> getActiveKeys() {
+ return allSnippets.stream()
+ .filter(k -> getState().status(k).isActive)
+ .collect(Collectors.toList());
+ }
+
+ public void addToClasspath(String path) {
+ classpath.add(path);
+ getState().addToClasspath(path);
+ }
+
+ public void addToClasspath(Path path) {
+ addToClasspath(path.toString());
+ }
+
+ @BeforeMethod
+ public void setUp() {
+ inStream = new TestingInputStream();
+ outStream = new ByteArrayOutputStream();
+ errStream = new ByteArrayOutputStream();
+ state = JShell.builder()
+ .in(inStream)
+ .out(new PrintStream(outStream))
+ .err(new PrintStream(errStream))
+ .build();
+ allSnippets = new LinkedHashSet<>();
+ idToSnippet = new LinkedHashMap<>();
+ classpath = new ArrayList<>();
+ }
+
+ @AfterMethod
+ public void tearDown() {
+ if (state != null) state.close();
+ state = null;
+ analysis = null;
+ allSnippets = null;
+ idToSnippet = null;
+ classpath = null;
+ }
+
+ public List<String> assertUnresolvedDependencies(DeclarationSnippet key, int unresolvedSize) {
+ List<String> unresolved = getState().unresolvedDependencies(key);
+ assertEquals(unresolved.size(), unresolvedSize, "Input: " + key.source() + ", checking unresolved: ");
+ return unresolved;
+ }
+
+ public DeclarationSnippet assertUnresolvedDependencies1(DeclarationSnippet key, Status status, String name) {
+ List<String> unresolved = assertUnresolvedDependencies(key, 1);
+ String input = key.source();
+ assertEquals(unresolved.size(), 1, "Given input: " + input + ", checking unresolved");
+ assertEquals(unresolved.get(0), name, "Given input: " + input + ", checking unresolved: ");
+ assertEquals(getState().status(key), status, "Given input: " + input + ", checking status: ");
+ return key;
+ }
+
+ public MethodSnippet assertEvalUnresolvedException(String input, String name, int unresolvedSize, int diagnosticsSize) {
+ List<SnippetEvent> events = assertEval(input, null, UnresolvedReferenceException.class, DiagCheck.DIAG_OK, DiagCheck.DIAG_OK, null);
+ SnippetEvent ste = events.get(0);
+ MethodSnippet methodKey = ((UnresolvedReferenceException) ste.exception()).getMethodSnippet();
+ assertEquals(methodKey.name(), name, "Given input: " + input + ", checking name");
+ assertEquals(getState().unresolvedDependencies(methodKey).size(), unresolvedSize, "Given input: " + input + ", checking unresolved");
+ assertEquals(getState().diagnostics(methodKey).size(), diagnosticsSize, "Given input: " + input + ", checking diagnostics");
+ return methodKey;
+ }
+
+ public Snippet assertKeyMatch(String input, boolean isExecutable, SubKind expectedSubKind, STEInfo mainInfo, STEInfo... updates) {
+ Snippet key = key(assertEval(input, IGNORE_VALUE, mainInfo, updates));
+ String source = key.source();
+ assertEquals(source, input, "Key \"" + input + "\" source mismatch, got: " + source + ", expected: " + input);
+ SubKind subkind = key.subKind();
+ assertEquals(subkind, expectedSubKind, "Key \"" + input + "\" subkind mismatch, got: "
+ + subkind + ", expected: " + expectedSubKind);
+ assertEquals(subkind.isExecutable(), isExecutable, "Key \"" + input + "\", expected isExecutable: "
+ + isExecutable + ", got: " + subkind.isExecutable());
+ Snippet.Kind expectedKind = getKind(key);
+ assertEquals(key.kind(), expectedKind, "Checking kind: ");
+ assertEquals(expectedSubKind.kind(), expectedKind, "Checking kind: ");
+ return key;
+ }
+
+ private Kind getKind(Snippet key) {
+ SubKind expectedSubKind = key.subKind();
+ Kind expectedKind;
+ switch (expectedSubKind) {
+ case SINGLE_TYPE_IMPORT_SUBKIND:
+ case SINGLE_STATIC_IMPORT_SUBKIND:
+ case TYPE_IMPORT_ON_DEMAND_SUBKIND:
+ case STATIC_IMPORT_ON_DEMAND_SUBKIND:
+ expectedKind = Kind.IMPORT;
+ break;
+ case CLASS_SUBKIND:
+ case INTERFACE_SUBKIND:
+ case ENUM_SUBKIND:
+ case ANNOTATION_TYPE_SUBKIND:
+ expectedKind = Kind.TYPE_DECL;
+ break;
+ case METHOD_SUBKIND:
+ expectedKind = Kind.METHOD;
+ break;
+ case VAR_DECLARATION_SUBKIND:
+ case TEMP_VAR_EXPRESSION_SUBKIND:
+ case VAR_DECLARATION_WITH_INITIALIZER_SUBKIND:
+ expectedKind = Kind.VAR;
+ break;
+ case VAR_VALUE_SUBKIND:
+ case ASSIGNMENT_SUBKIND:
+ expectedKind = Kind.EXPRESSION;
+ break;
+ case STATEMENT_SUBKIND:
+ expectedKind = Kind.STATEMENT;
+ break;
+ case UNKNOWN_SUBKIND:
+ expectedKind = Kind.ERRONEOUS;
+ break;
+ default:
+ throw new AssertionError("Unsupported key: " + key.getClass().getCanonicalName());
+ }
+ return expectedKind;
+ }
+
+ public ImportSnippet assertImportKeyMatch(String input, String name, SubKind subkind, STEInfo mainInfo, STEInfo... updates) {
+ Snippet key = assertKeyMatch(input, false, subkind, mainInfo, updates);
+
+ assertTrue(key instanceof ImportSnippet, "Expected an ImportKey, got: " + key.getClass().getName());
+ ImportSnippet importKey = (ImportSnippet) key;
+ assertEquals(importKey.name(), name, "Input \"" + input +
+ "\" name mismatch, got: " + importKey.name() + ", expected: " + name);
+ assertEquals(importKey.kind(), Kind.IMPORT, "Checking kind: ");
+ return importKey;
+ }
+
+ public DeclarationSnippet assertDeclarationKeyMatch(String input, boolean isExecutable, String name, SubKind subkind, STEInfo mainInfo, STEInfo... updates) {
+ Snippet key = assertKeyMatch(input, isExecutable, subkind, mainInfo, updates);
+
+ assertTrue(key instanceof DeclarationSnippet, "Expected a DeclarationKey, got: " + key.getClass().getName());
+ DeclarationSnippet declKey = (DeclarationSnippet) key;
+ assertEquals(declKey.name(), name, "Input \"" + input +
+ "\" name mismatch, got: " + declKey.name() + ", expected: " + name);
+ return declKey;
+ }
+
+ public VarSnippet assertVarKeyMatch(String input, boolean isExecutable, String name, SubKind kind, String typeName, STEInfo mainInfo, STEInfo... updates) {
+ Snippet sn = assertDeclarationKeyMatch(input, isExecutable, name, kind, mainInfo, updates);
+ assertTrue(sn instanceof VarSnippet, "Expected a VarKey, got: " + sn.getClass().getName());
+ VarSnippet variableKey = (VarSnippet) sn;
+ String signature = variableKey.typeName();
+ assertEquals(signature, typeName, "Key \"" + input +
+ "\" typeName mismatch, got: " + signature + ", expected: " + typeName);
+ assertEquals(variableKey.kind(), Kind.VAR, "Checking kind: ");
+ return variableKey;
+ }
+
+ public void assertExpressionKeyMatch(String input, String name, SubKind kind, String typeName) {
+ Snippet key = assertKeyMatch(input, true, kind, added(VALID));
+ assertTrue(key instanceof ExpressionSnippet, "Expected a ExpressionKey, got: " + key.getClass().getName());
+ ExpressionSnippet exprKey = (ExpressionSnippet) key;
+ assertEquals(exprKey.name(), name, "Input \"" + input +
+ "\" name mismatch, got: " + exprKey.name() + ", expected: " + name);
+ assertEquals(exprKey.typeName(), typeName, "Key \"" + input +
+ "\" typeName mismatch, got: " + exprKey.typeName() + ", expected: " + typeName);
+ assertEquals(exprKey.kind(), Kind.EXPRESSION, "Checking kind: ");
+ }
+
+ // For expressions throwing an EvalException
+ public SnippetEvent assertEvalException(String input) {
+ List<SnippetEvent> events = assertEval(input, null, EvalException.class,
+ DiagCheck.DIAG_OK, DiagCheck.DIAG_OK, null);
+ return events.get(0);
+ }
+
+
+ public List<SnippetEvent> assertEvalFail(String input) {
+ return assertEval(input, null, null,
+ DiagCheck.DIAG_ERROR, DiagCheck.DIAG_IGNORE, added(REJECTED));
+ }
+
+ public List<SnippetEvent> assertEval(String input) {
+ return assertEval(input, IGNORE_VALUE, null, DiagCheck.DIAG_OK, DiagCheck.DIAG_OK, added(VALID));
+ }
+
+ public List<SnippetEvent> assertEval(String input, String value) {
+ return assertEval(input, value, null, DiagCheck.DIAG_OK, DiagCheck.DIAG_OK, added(VALID));
+ }
+
+ public List<SnippetEvent> assertEval(String input, STEInfo mainInfo, STEInfo... updates) {
+ return assertEval(input, IGNORE_VALUE, null, DiagCheck.DIAG_OK, DiagCheck.DIAG_OK, mainInfo, updates);
+ }
+
+ public List<SnippetEvent> assertEval(String input, String value,
+ STEInfo mainInfo, STEInfo... updates) {
+ return assertEval(input, value, null, DiagCheck.DIAG_OK, DiagCheck.DIAG_OK, mainInfo, updates);
+ }
+
+ public List<SnippetEvent> assertEval(String input, DiagCheck diagMain, DiagCheck diagUpdates) {
+ return assertEval(input, IGNORE_VALUE, null, diagMain, diagUpdates, added(VALID));
+ }
+
+ public List<SnippetEvent> assertEval(String input, DiagCheck diagMain, DiagCheck diagUpdates,
+ STEInfo mainInfo, STEInfo... updates) {
+ return assertEval(input, IGNORE_VALUE, null, diagMain, diagUpdates, mainInfo, updates);
+ }
+
+ public List<SnippetEvent> assertEval(String input,
+ String value, Class<? extends Throwable> exceptionClass,
+ DiagCheck diagMain, DiagCheck diagUpdates,
+ STEInfo mainInfo, STEInfo... updates) {
+ return assertEval(input, diagMain, diagUpdates, new EventChain(mainInfo, value, exceptionClass, updates));
+ }
+
+ // Use this directly or usually indirectly for all non-empty calls to eval()
+ public List<SnippetEvent> assertEval(String input,
+ DiagCheck diagMain, DiagCheck diagUpdates, EventChain... eventChains) {
+ return checkEvents(() -> getState().eval(input), "eval(" + input + ")", diagMain, diagUpdates, eventChains);
+ }
+
+ private Map<Snippet, Snippet> closure(List<SnippetEvent> events) {
+ Map<Snippet, Snippet> transitions = new HashMap<>();
+ for (SnippetEvent event : events) {
+ transitions.put(event.snippet(), event.causeSnippet());
+ }
+ Map<Snippet, Snippet> causeSnippets = new HashMap<>();
+ for (Map.Entry<Snippet, Snippet> entry : transitions.entrySet()) {
+ Snippet snippet = entry.getKey();
+ Snippet cause = getInitialCause(transitions, entry.getValue());
+ causeSnippets.put(snippet, cause);
+ }
+ return causeSnippets;
+ }
+
+ private Snippet getInitialCause(Map<Snippet, Snippet> transitions, Snippet snippet) {
+ Snippet result;
+ while ((result = transitions.get(snippet)) != null) {
+ snippet = result;
+ }
+ return snippet;
+ }
+
+ private Map<Snippet, List<SnippetEvent>> groupByCauseSnippet(List<SnippetEvent> events) {
+ Map<Snippet, List<SnippetEvent>> map = new TreeMap<>((a, b) -> a.id().compareTo(b.id()));
+ for (SnippetEvent event : events) {
+ if (event == null) {
+ throw new InternalError("null event found in " + events);
+ }
+ if (event.snippet() == null) {
+ throw new InternalError("null event Snippet found in " + events);
+ }
+ if (event.snippet().id() == null) {
+ throw new InternalError("null event Snippet id() found in " + events);
+ }
+ }
+ for (SnippetEvent event : events) {
+ if (event.causeSnippet() == null) {
+ map.computeIfAbsent(event.snippet(), ($) -> new ArrayList<>()).add(event);
+ }
+ }
+ Map<Snippet, Snippet> causeSnippets = closure(events);
+ for (SnippetEvent event : events) {
+ Snippet causeSnippet = causeSnippets.get(event.snippet());
+ if (causeSnippet != null) {
+ map.get(causeSnippet).add(event);
+ }
+ }
+ for (Map.Entry<Snippet, List<SnippetEvent>> entry : map.entrySet()) {
+ Collections.sort(entry.getValue(),
+ (a, b) -> a.causeSnippet() == null
+ ? -1 : b.causeSnippet() == null
+ ? 1 : a.snippet().id().compareTo(b.snippet().id()));
+ }
+ return map;
+ }
+
+ private List<STEInfo> getInfos(EventChain... eventChains) {
+ List<STEInfo> list = new ArrayList<>();
+ for (EventChain i : eventChains) {
+ list.add(i.mainInfo);
+ Collections.addAll(list, i.updates);
+ }
+ return list;
+ }
+
+ private List<SnippetEvent> checkEvents(Supplier<List<SnippetEvent>> toTest,
+ String descriptor,
+ DiagCheck diagMain, DiagCheck diagUpdates,
+ EventChain... eventChains) {
+ List<SnippetEvent> dispatched = new ArrayList<>();
+ Subscription token = getState().onSnippetEvent(kse -> {
+ if (dispatched.size() > 0 && dispatched.get(dispatched.size() - 1) == null) {
+ throw new RuntimeException("dispatch event after done");
+ }
+ dispatched.add(kse);
+ });
+ List<SnippetEvent> events = toTest.get();
+ getState().unsubscribe(token);
+ assertEquals(dispatched.size(), events.size(), "dispatched event size not the same as event size");
+ for (int i = events.size() - 1; i >= 0; --i) {
+ assertEquals(dispatched.get(i), events.get(i), "Event element " + i + " does not match");
+ }
+ dispatched.add(null); // mark end of dispatchs
+
+ for (SnippetEvent evt : events) {
+ assertTrue(evt.snippet() != null, "key must never be null, but it was for: " + descriptor);
+ assertTrue(evt.previousStatus() != null, "previousStatus must never be null, but it was for: " + descriptor);
+ assertTrue(evt.status() != null, "status must never be null, but it was for: " + descriptor);
+ assertTrue(evt.status() != NONEXISTENT, "status must not be NONEXISTENT: " + descriptor);
+ if (evt.previousStatus() != NONEXISTENT) {
+ Snippet old = idToSnippet.get(evt.snippet().id());
+ if (old != null) {
+ switch (evt.status()) {
+ case DROPPED:
+ assertEquals(old, evt.snippet(),
+ "Drop: Old snippet must be what is dropped -- input: " + descriptor);
+ break;
+ case OVERWRITTEN:
+ assertEquals(old, evt.snippet(),
+ "Overwrite: Old snippet (" + old
+ + ") must be what is overwritten -- input: "
+ + descriptor + " -- " + evt);
+ break;
+ default:
+ if (evt.causeSnippet() == null) {
+ // New source
+ assertNotEquals(old, evt.snippet(),
+ "New source: Old snippet must be different from the replacing -- input: "
+ + descriptor);
+ } else {
+ // An update (key Overwrite??)
+ assertEquals(old, evt.snippet(),
+ "Update: Old snippet must be equal to the replacing -- input: "
+ + descriptor);
+ }
+ break;
+ }
+ }
+ }
+ }
+ for (SnippetEvent evt : events) {
+ if (evt.causeSnippet() == null && evt.status() != DROPPED) {
+ allSnippets.add(evt.snippet());
+ idToSnippet.put(evt.snippet().id(), evt.snippet());
+ }
+ }
+ assertTrue(events.size() >= 1, "Expected at least one event, got none.");
+ List<STEInfo> all = getInfos(eventChains);
+ if (events.size() != all.size()) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("Got events --\n");
+ for (SnippetEvent evt : events) {
+ sb.append(" key: ").append(evt.snippet());
+ sb.append(" before: ").append(evt.previousStatus());
+ sb.append(" status: ").append(evt.status());
+ sb.append(" isSignatureChange: ").append(evt.isSignatureChange());
+ sb.append(" cause: ");
+ if (evt.causeSnippet() == null) {
+ sb.append("direct");
+ } else {
+ sb.append(evt.causeSnippet());
+ }
+ sb.append("\n");
+ }
+ sb.append("Expected ").append(all.size());
+ sb.append(" events, got: ").append(events.size());
+ fail(sb.toString());
+ }
+
+ int impactId = 0;
+ Map<Snippet, List<SnippetEvent>> groupedEvents = groupByCauseSnippet(events);
+ assertEquals(groupedEvents.size(), eventChains.length, "Number of main events");
+ for (Map.Entry<Snippet, List<SnippetEvent>> entry : groupedEvents.entrySet()) {
+ EventChain eventChain = eventChains[impactId++];
+ SnippetEvent main = entry.getValue().get(0);
+ Snippet mainKey = main.snippet();
+ if (eventChain.mainInfo != null) {
+ eventChain.mainInfo.assertMatch(entry.getValue().get(0), mainKey);
+ if (eventChain.updates.length > 0) {
+ if (eventChain.updates.length == 1) {
+ eventChain.updates[0].assertMatch(entry.getValue().get(1), mainKey);
+ } else {
+ Arrays.sort(eventChain.updates, (a, b) -> ((a.snippet() == MAIN_SNIPPET)
+ ? mainKey
+ : a.snippet()).id().compareTo(b.snippet().id()));
+ List<SnippetEvent> updateEvents = new ArrayList<>(entry.getValue().subList(1, entry.getValue().size()));
+ int idx = 0;
+ for (SnippetEvent ste : updateEvents) {
+ eventChain.updates[idx++].assertMatch(ste, mainKey);
+ }
+ }
+ }
+ }
+ if (((Object) eventChain.value) != IGNORE_VALUE) {
+ assertEquals(main.value(), eventChain.value, "Expected execution value of: " + eventChain.value +
+ ", but got: " + main.value());
+ }
+ if (eventChain.exceptionClass != IGNORE_EXCEPTION) {
+ if (main.exception() == null) {
+ assertEquals(eventChain.exceptionClass, null, "Expected an exception of class "
+ + eventChain.exceptionClass + " got no exception");
+ } else if (eventChain.exceptionClass == null) {
+ fail("Expected no exception but got " + main.exception().toString());
+ } else {
+ assertTrue(eventChain.exceptionClass.isInstance(main.exception()),
+ "Expected an exception of class " + eventChain.exceptionClass +
+ " got: " + main.exception().toString());
+ }
+ }
+ List<Diag> diagnostics = getState().diagnostics(mainKey);
+ switch (diagMain) {
+ case DIAG_OK:
+ assertEquals(diagnostics.size(), 0, "Expected no diagnostics, got: " + diagnosticsToString(diagnostics));
+ break;
+ case DIAG_WARNING:
+ assertFalse(hasFatalError(diagnostics), "Expected no errors, got: " + diagnosticsToString(diagnostics));
+ break;
+ case DIAG_ERROR:
+ assertTrue(hasFatalError(diagnostics), "Expected errors, got: " + diagnosticsToString(diagnostics));
+ break;
+ }
+ if (eventChain.mainInfo != null) {
+ for (STEInfo ste : eventChain.updates) {
+ diagnostics = getState().diagnostics(ste.snippet());
+ switch (diagUpdates) {
+ case DIAG_OK:
+ assertEquals(diagnostics.size(), 0, "Expected no diagnostics, got: " + diagnosticsToString(diagnostics));
+ break;
+ case DIAG_WARNING:
+ assertFalse(hasFatalError(diagnostics), "Expected no errors, got: " + diagnosticsToString(diagnostics));
+ break;
+ }
+ }
+ }
+ }
+ return events;
+ }
+
+ // Use this for all EMPTY calls to eval()
+ public void assertEvalEmpty(String input) {
+ List<SnippetEvent> events = getState().eval(input);
+ assertEquals(events.size(), 0, "Expected no events, got: " + events.size());
+ }
+
+ public VarSnippet varKey(List<SnippetEvent> events) {
+ Snippet key = key(events);
+ assertTrue(key instanceof VarSnippet, "Expected a VariableKey, got: " + key);
+ return (VarSnippet) key;
+ }
+
+ public MethodSnippet methodKey(List<SnippetEvent> events) {
+ Snippet key = key(events);
+ assertTrue(key instanceof MethodSnippet, "Expected a MethodKey, got: " + key);
+ return (MethodSnippet) key;
+ }
+
+ public TypeDeclSnippet classKey(List<SnippetEvent> events) {
+ Snippet key = key(events);
+ assertTrue(key instanceof TypeDeclSnippet, "Expected a ClassKey, got: " + key);
+ return (TypeDeclSnippet) key;
+ }
+
+ public ImportSnippet importKey(List<SnippetEvent> events) {
+ Snippet key = key(events);
+ assertTrue(key instanceof ImportSnippet, "Expected a ImportKey, got: " + key);
+ return (ImportSnippet) key;
+ }
+
+ private Snippet key(List<SnippetEvent> events) {
+ assertTrue(events.size() >= 1, "Expected at least one event, got none.");
+ return events.get(0).snippet();
+ }
+
+ public void assertVarValue(Snippet key, String expected) {
+ String value = state.varValue((VarSnippet) key);
+ assertEquals(value, expected, "Expected var value of: " + expected + ", but got: " + value);
+ }
+
+ public Snippet assertDeclareFail(String input, String expectedErrorCode) {
+ return assertDeclareFail(input, expectedErrorCode, added(REJECTED));
+ }
+
+ public Snippet assertDeclareFail(String input, String expectedErrorCode,
+ STEInfo mainInfo, STEInfo... updates) {
+ return assertDeclareFail(input,
+ new ExpectedDiagnostic(expectedErrorCode, -1, -1, -1, -1, -1, Diagnostic.Kind.ERROR),
+ mainInfo, updates);
+ }
+
+ public Snippet assertDeclareFail(String input, ExpectedDiagnostic expectedDiagnostic) {
+ return assertDeclareFail(input, expectedDiagnostic, added(REJECTED));
+ }
+
+ public Snippet assertDeclareFail(String input, ExpectedDiagnostic expectedDiagnostic,
+ STEInfo mainInfo, STEInfo... updates) {
+ List<SnippetEvent> events = assertEval(input, null, null,
+ DiagCheck.DIAG_ERROR, DiagCheck.DIAG_IGNORE, mainInfo, updates);
+ SnippetEvent e = events.get(0);
+ Snippet key = e.snippet();
+ assertEquals(getState().status(key), REJECTED);
+ List<Diag> diagnostics = getState().diagnostics(e.snippet());
+ assertTrue(diagnostics.size() > 0, "Expected diagnostics, got none");
+ assertDiagnostic(input, diagnostics.get(0), expectedDiagnostic);
+ assertTrue(key != null, "key must never be null, but it was for: " + input);
+ return key;
+ }
+
+ public Snippet assertDeclareWarn1(String input, String expectedErrorCode) {
+ return assertDeclareWarn1(input, new ExpectedDiagnostic(expectedErrorCode, -1, -1, -1, -1, -1, Diagnostic.Kind.WARNING));
+ }
+
+ public Snippet assertDeclareWarn1(String input, ExpectedDiagnostic expectedDiagnostic) {
+ return assertDeclareWarn1(input, expectedDiagnostic, added(VALID));
+ }
+
+ public Snippet assertDeclareWarn1(String input, ExpectedDiagnostic expectedDiagnostic, STEInfo mainInfo, STEInfo... updates) {
+ List<SnippetEvent> events = assertEval(input, IGNORE_VALUE, null,
+ DiagCheck.DIAG_WARNING, DiagCheck.DIAG_IGNORE, mainInfo, updates);
+ SnippetEvent e = events.get(0);
+ List<Diag> diagnostics = getState().diagnostics(e.snippet());
+ assertDiagnostic(input, diagnostics.get(0), expectedDiagnostic);
+ return e.snippet();
+ }
+
+ private void assertDiagnostic(String input, Diag diagnostic, ExpectedDiagnostic expectedDiagnostic) {
+ if (expectedDiagnostic != null) expectedDiagnostic.assertDiagnostic(diagnostic);
+ // assertEquals(diagnostic.getSource(), input, "Diagnostic source");
+ }
+
+ public void assertTypeDeclSnippet(TypeDeclSnippet type, String expectedName,
+ Status expectedStatus, SubKind expectedSubKind,
+ int unressz, int othersz) {
+ assertDeclarationSnippet(type, expectedName, expectedStatus,
+ expectedSubKind, unressz, othersz);
+ }
+
+ public void assertMethodDeclSnippet(MethodSnippet method,
+ String expectedName, String expectedSignature,
+ Status expectedStatus, int unressz, int othersz) {
+ assertDeclarationSnippet(method, expectedName, expectedStatus,
+ METHOD_SUBKIND, unressz, othersz);
+ String signature = method.signature();
+ assertEquals(signature, expectedSignature,
+ "Expected " + method.source() + " to have the name: " +
+ expectedSignature + ", got: " + signature);
+ }
+
+ public void assertVariableDeclSnippet(VarSnippet var,
+ String expectedName, String expectedTypeName,
+ Status expectedStatus, SubKind expectedSubKind,
+ int unressz, int othersz) {
+ assertDeclarationSnippet(var, expectedName, expectedStatus,
+ expectedSubKind, unressz, othersz);
+ String signature = var.typeName();
+ assertEquals(signature, expectedTypeName,
+ "Expected " + var.source() + " to have the name: " +
+ expectedTypeName + ", got: " + signature);
+ }
+
+ public void assertDeclarationSnippet(DeclarationSnippet declarationKey,
+ String expectedName,
+ Status expectedStatus, SubKind expectedSubKind,
+ int unressz, int othersz) {
+ assertKey(declarationKey, expectedStatus, expectedSubKind);
+ String source = declarationKey.source();
+ assertEquals(declarationKey.name(), expectedName,
+ "Expected " + source + " to have the name: " + expectedName + ", got: " + declarationKey.name());
+ List<String> unresolved = getState().unresolvedDependencies(declarationKey);
+ assertEquals(unresolved.size(), unressz, "Expected " + source + " to have " + unressz
+ + " unresolved symbols, got: " + unresolved.size());
+ List<Diag> otherCorralledErrors = getState().diagnostics(declarationKey);
+ assertEquals(otherCorralledErrors.size(), othersz, "Expected " + source + " to have " + othersz
+ + " other errors, got: " + otherCorralledErrors.size());
+ }
+
+ public void assertKey(Snippet key, Status expectedStatus, SubKind expectedSubKind) {
+ String source = key.source();
+ SubKind actualSubKind = key.subKind();
+ assertEquals(actualSubKind, expectedSubKind,
+ "Expected " + source + " to have the subkind: " + expectedSubKind + ", got: " + actualSubKind);
+ Status status = getState().status(key);
+ assertEquals(status, expectedStatus, "Expected " + source + " to be "
+ + expectedStatus + ", but it is " + status);
+ Snippet.Kind expectedKind = getKind(key);
+ assertEquals(key.kind(), expectedKind, "Checking kind: ");
+ assertEquals(expectedSubKind.kind(), expectedKind, "Checking kind: ");
+ }
+
+ public void assertDrop(PersistentSnippet key, STEInfo mainInfo, STEInfo... updates) {
+ assertDrop(key, DiagCheck.DIAG_OK, DiagCheck.DIAG_OK, mainInfo, updates);
+ }
+
+ public void assertDrop(PersistentSnippet key, DiagCheck diagMain, DiagCheck diagUpdates, STEInfo mainInfo, STEInfo... updates) {
+ assertDrop(key, diagMain, diagUpdates, new EventChain(mainInfo, null, null, updates));
+ }
+
+ public void assertDrop(PersistentSnippet key, DiagCheck diagMain, DiagCheck diagUpdates, EventChain... eventChains) {
+ checkEvents(() -> getState().drop(key), "drop(" + key + ")", diagMain, diagUpdates, eventChains);
+ }
+
+ public void assertAnalyze(String input, String source, String remaining, boolean isComplete) {
+ assertAnalyze(input, null, source, remaining, isComplete);
+ }
+
+ public void assertAnalyze(String input, Completeness status, String source) {
+ assertAnalyze(input, status, source, null, null);
+ }
+
+ public void assertAnalyze(String input, Completeness status, String source, String remaining, Boolean isComplete) {
+ CompletionInfo ci = getAnalysis().analyzeCompletion(input);
+ if (status != null) assertEquals(ci.completeness, status, "Input : " + input + ", status: ");
+ if (source != null) assertEquals(ci.source, source, "Input : " + input + ", source: ");
+ if (remaining != null) assertEquals(ci.remaining, remaining, "Input : " + input + ", remaining: ");
+ if (isComplete != null) {
+ boolean isExpectedComplete = isComplete;
+ assertEquals(ci.completeness.isComplete, isExpectedComplete, "Input : " + input + ", isComplete: ");
+ }
+ }
+
+ public void assertNumberOfActiveVariables(int cnt) {
+ Collection<VarSnippet> variables = getState().variables();
+ assertEquals(variables.size(), cnt, "Variables : " + variables);
+ }
+
+ public void assertNumberOfActiveMethods(int cnt) {
+ Collection<MethodSnippet> methods = getState().methods();
+ assertEquals(methods.size(), cnt, "Methods : " + methods);
+ }
+
+ public void assertNumberOfActiveClasses(int cnt) {
+ Collection<TypeDeclSnippet> classes = getState().types();
+ assertEquals(classes.size(), cnt, "Classes : " + classes);
+ }
+
+ public void assertMembers(Collection<? extends Snippet> members, Set<MemberInfo> expected) {
+ assertEquals(members.size(), expected.size(), "Expected : " + expected + ", actual : " + members);
+ assertEquals(members.stream()
+ .map(this::getMemberInfo)
+ .collect(Collectors.toSet()),
+ expected);
+ }
+
+ public void assertKeys(MemberInfo... expected) {
+ int index = 0;
+ List<Snippet> snippets = getState().snippets();
+ assertEquals(allSnippets.size(), snippets.size());
+ for (Snippet sn : snippets) {
+ if (sn.kind().isPersistent && getState().status(sn).isActive) {
+ MemberInfo actual = getMemberInfo(sn);
+ MemberInfo exp = expected[index];
+ assertEquals(actual, exp, String.format("Difference in #%d. Expected: %s, actual: %s",
+ index, exp, actual));
+ ++index;
+ }
+ }
+ }
+
+ public void assertActiveKeys() {
+ Collection<Snippet> expected = getActiveKeys();
+ assertActiveKeys(expected.toArray(new Snippet[expected.size()]));
+ }
+
+ public void assertActiveKeys(Snippet... expected) {
+ int index = 0;
+ for (Snippet key : getState().snippets()) {
+ if (state.status(key).isActive) {
+ assertEquals(expected[index], key, String.format("Difference in #%d. Expected: %s, actual: %s", index, key, expected[index]));
+ ++index;
+ }
+ }
+ }
+
+ private List<Snippet> filterDeclaredKeys(Predicate<Snippet> p) {
+ return getActiveKeys().stream()
+ .filter(p)
+ .collect(Collectors.toList());
+ }
+
+ public void assertVariables() {
+ assertEquals(getState().variables(), filterDeclaredKeys((key) -> key instanceof VarSnippet), "Variables");
+ }
+
+ public void assertMethods() {
+ assertEquals(getState().methods(), filterDeclaredKeys((key) -> key instanceof MethodSnippet), "Methods");
+ }
+
+ public void assertClasses() {
+ assertEquals(getState().types(), filterDeclaredKeys((key) -> key instanceof TypeDeclSnippet), "Classes");
+ }
+
+ public void assertVariables(MemberInfo...expected) {
+ assertMembers(getState().variables(), Stream.of(expected).collect(Collectors.toSet()));
+ }
+
+ public void assertMethods(MemberInfo...expected) {
+ assertMembers(getState().methods(), Stream.of(expected).collect(Collectors.toSet()));
+ for (MethodSnippet methodKey : getState().methods()) {
+ MemberInfo expectedInfo = null;
+ for (MemberInfo info : expected) {
+ if (info.name.equals(methodKey.name()) && info.type.equals(methodKey.signature())) {
+ expectedInfo = getMemberInfo(methodKey);
+ }
+ }
+ assertNotNull(expectedInfo, "Not found method: " + methodKey.name());
+ int lastIndexOf = expectedInfo.type.lastIndexOf(')');
+ assertEquals(methodKey.parameterTypes(), expectedInfo.type.substring(1, lastIndexOf), "Parameter types");
+ }
+ }
+
+ public void assertClasses(MemberInfo...expected) {
+ assertMembers(getState().types(), Stream.of(expected).collect(Collectors.toSet()));
+ }
+
+ public void assertCompletion(String code, String... expected) {
+ assertCompletion(code, null, expected);
+ }
+
+ public void assertCompletion(String code, Boolean isSmart, String... expected) {
+ List<String> completions = computeCompletions(code, isSmart);
+ assertEquals(completions, Arrays.asList(expected), "Input: " + code + ", " + completions.toString());
+ }
+
+ public void assertCompletionIncludesExcludes(String code, Set<String> expected, Set<String> notExpected) {
+ assertCompletionIncludesExcludes(code, null, expected, notExpected);
+ }
+
+ public void assertCompletionIncludesExcludes(String code, Boolean isSmart, Set<String> expected, Set<String> notExpected) {
+ List<String> completions = computeCompletions(code, isSmart);
+ assertTrue(completions.containsAll(expected), String.valueOf(completions));
+ assertTrue(Collections.disjoint(completions, notExpected), String.valueOf(completions));
+ }
+
+ private List<String> computeCompletions(String code, Boolean isSmart) {
+ int cursor = code.indexOf('|');
+ code = code.replace("|", "");
+ assertTrue(cursor > -1, "'|' expected, but not found in: " + code);
+ List<Suggestion> completions =
+ getAnalysis().completionSuggestions(code, cursor, new int[1]); //XXX: ignoring anchor for now
+ return completions.stream()
+ .filter(s -> isSmart == null || isSmart == s.isSmart)
+ .map(s -> s.continuation)
+ .distinct()
+ .collect(Collectors.toList());
+ }
+
+ public void assertDocumentation(String code, String... expected) {
+ int cursor = code.indexOf('|');
+ code = code.replace("|", "");
+ assertTrue(cursor > -1, "'|' expected, but not found in: " + code);
+ String documentation = getAnalysis().documentation(code, cursor);
+ Set<String> docSet = Stream.of(documentation.split("\r?\n")).collect(Collectors.toSet());
+ Set<String> expectedSet = Stream.of(expected).collect(Collectors.toSet());
+ assertEquals(docSet, expectedSet, "Input: " + code);
+ }
+
+ public enum ClassType {
+ CLASS("CLASS_SUBKIND") {
+ @Override
+ public String toString() {
+ return "class";
+ }
+ },
+ ENUM("ENUM_SUBKIND") {
+ @Override
+ public String toString() {
+ return "enum";
+ }
+ },
+ INTERFACE("INTERFACE_SUBKIND") {
+ @Override
+ public String toString() {
+ return "interface";
+ }
+ },
+ ANNOTATION("ANNOTATION_TYPE_SUBKIND") {
+ @Override
+ public String toString() {
+ return "@interface";
+ }
+ };
+
+ private final String classType;
+
+ ClassType(String classType) {
+ this.classType = classType;
+ }
+
+ public String getClassType() {
+ return classType;
+ }
+
+ @Override
+ public abstract String toString();
+ }
+
+ public static MemberInfo variable(String type, String name) {
+ return new MemberInfo(type, name);
+ }
+
+ public static MemberInfo method(String signature, String name) {
+ return new MemberInfo(signature, name);
+ }
+
+ public static MemberInfo clazz(ClassType classType, String className) {
+ return new MemberInfo(classType.getClassType(), className);
+ }
+
+ public static class MemberInfo {
+ public final String type;
+ public final String name;
+
+ public MemberInfo(String type, String name) {
+ this.type = type;
+ this.name = name;
+ }
+
+ @Override
+ public int hashCode() {
+ return type.hashCode() + 3 * name.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof MemberInfo) {
+ MemberInfo other = (MemberInfo) o;
+ return type.equals(other.type) && name.equals(other.name);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s %s", type, name);
+ }
+ }
+
+ public MemberInfo getMemberInfo(Snippet key) {
+ SubKind subkind = key.subKind();
+ switch (subkind) {
+ case CLASS_SUBKIND:
+ case INTERFACE_SUBKIND:
+ case ENUM_SUBKIND:
+ case ANNOTATION_TYPE_SUBKIND:
+ return new MemberInfo(subkind.name(), ((DeclarationSnippet) key).name());
+ case METHOD_SUBKIND:
+ MethodSnippet method = (MethodSnippet) key;
+ return new MemberInfo(method.signature(), method.name());
+ case VAR_DECLARATION_SUBKIND:
+ case VAR_DECLARATION_WITH_INITIALIZER_SUBKIND:
+ case TEMP_VAR_EXPRESSION_SUBKIND:
+ VarSnippet var = (VarSnippet) key;
+ return new MemberInfo(var.typeName(), var.name());
+ default:
+ throw new AssertionError("Unknown snippet : " + key.kind() + " in expression " + key.toString());
+ }
+ }
+
+ public String diagnosticsToString(List<Diag> diagnostics) {
+ StringWriter writer = new StringWriter();
+ for (Diag diag : diagnostics) {
+ writer.write("Error --\n");
+ for (String line : diag.getMessage(null).split("\\r?\\n")) {
+ writer.write(String.format("%s\n", line));
+ }
+ }
+ return writer.toString().replace("\n", System.lineSeparator());
+ }
+
+ public boolean hasFatalError(List<Diag> diagnostics) {
+ for (Diag diag : diagnostics) {
+ if (diag.isError()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static EventChain chain(STEInfo mainInfo, STEInfo... updates) {
+ return chain(mainInfo, IGNORE_VALUE, null, updates);
+ }
+
+ public static EventChain chain(STEInfo mainInfo, String value, Class<? extends Throwable> exceptionClass, STEInfo... updates) {
+ return new EventChain(mainInfo, value, exceptionClass, updates);
+ }
+
+ public static STEInfo ste(Snippet key, Status previousStatus, Status status,
+ Boolean isSignatureChange, Snippet causeKey) {
+ return new STEInfo(key, previousStatus, status, isSignatureChange, causeKey);
+ }
+
+ public static STEInfo added(Status status) {
+ return new STEInfo(MAIN_SNIPPET, NONEXISTENT, status, status.isDefined, null);
+ }
+
+ public static class EventChain {
+ public final STEInfo mainInfo;
+ public final STEInfo[] updates;
+ public final String value;
+ public final Class<? extends Throwable> exceptionClass;
+
+ public EventChain(STEInfo mainInfo, String value, Class<? extends Throwable> exceptionClass, STEInfo... updates) {
+ this.mainInfo = mainInfo;
+ this.updates = updates;
+ this.value = value;
+ this.exceptionClass = exceptionClass;
+ }
+ }
+
+ public static class STEInfo {
+
+ STEInfo(Snippet snippet, Status previousStatus, Status status,
+ Boolean isSignatureChange, Snippet causeSnippet) {
+ this.snippet = snippet;
+ this.previousStatus = previousStatus;
+ this.status = status;
+ this.checkIsSignatureChange = isSignatureChange != null;
+ this.isSignatureChange = checkIsSignatureChange ? isSignatureChange : false;
+ this.causeSnippet = causeSnippet;
+ assertTrue(snippet != null, "Bad test set-up. The match snippet must not be null");
+ }
+
+ final Snippet snippet;
+ final Status previousStatus;
+ final Status status;
+ final boolean isSignatureChange;
+ final Snippet causeSnippet;
+
+ final boolean checkIsSignatureChange;
+ public Snippet snippet() {
+ return snippet;
+ }
+ public Status previousStatus() {
+ return previousStatus;
+ }
+ public Status status() {
+ return status;
+ }
+ public boolean isSignatureChange() {
+ if (!checkIsSignatureChange) {
+ throw new IllegalStateException("isSignatureChange value is undefined");
+ }
+ return isSignatureChange;
+ }
+ public Snippet causeSnippet() {
+ return causeSnippet;
+ }
+ public String value() {
+ return null;
+ }
+ public Exception exception() {
+ return null;
+ }
+
+ public void assertMatch(SnippetEvent ste, Snippet mainSnippet) {
+ assertKeyMatch(ste, ste.snippet(), snippet(), mainSnippet);
+ assertStatusMatch(ste, ste.previousStatus(), previousStatus());
+ assertStatusMatch(ste, ste.status(), status());
+ if (checkIsSignatureChange) {
+ assertEquals(ste.isSignatureChange(), isSignatureChange(),
+ "Expected " +
+ (isSignatureChange()? "" : "no ") +
+ "signature-change, got: " +
+ (ste.isSignatureChange()? "" : "no ") +
+ "signature-change" +
+ "\n expected-event: " + this + "\n got-event: " + toString(ste));
+ }
+ assertKeyMatch(ste, ste.causeSnippet(), causeSnippet(), mainSnippet);
+ }
+
+ private void assertKeyMatch(SnippetEvent ste, Snippet sn, Snippet expected, Snippet mainSnippet) {
+ Snippet testKey = expected;
+ if (testKey != null) {
+ if (expected == MAIN_SNIPPET) {
+ assertNotNull(mainSnippet, "MAIN_SNIPPET used, test must pass value to assertMatch");
+ testKey = mainSnippet;
+ }
+ if (ste.causeSnippet() == null && ste.status() != DROPPED && expected != MAIN_SNIPPET) {
+ // Source change, always new snippet -- only match id()
+ assertTrue(sn != testKey,
+ "Main-event: Expected new snippet to be != : " + testKey
+ + "\n got-event: " + toString(ste));
+ assertEquals(sn.id(), testKey.id(), "Expected IDs to match: " + testKey + ", got: " + sn
+ + "\n expected-event: " + this + "\n got-event: " + toString(ste));
+ } else {
+ assertEquals(sn, testKey, "Expected key to be: " + testKey + ", got: " + sn
+ + "\n expected-event: " + this + "\n got-event: " + toString(ste));
+ }
+ }
+ }
+
+ private void assertStatusMatch(SnippetEvent ste, Status status, Status expected) {
+ if (expected != null) {
+ assertEquals(status, expected, "Expected status to be: " + expected + ", got: " + status +
+ "\n expected-event: " + this + "\n got-event: " + toString(ste));
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "STEInfo key: " +
+ (snippet()==MAIN_SNIPPET? "MAIN_SNIPPET" : (snippet()==null? "ignore" : snippet().id())) +
+ " before: " + previousStatus() +
+ " status: " + status() + " sig: " + isSignatureChange() +
+ " cause: " + (causeSnippet()==null? "null" : causeSnippet().id());
+ }
+
+ private String toString(SnippetEvent ste) {
+ return "key: " + (ste.snippet()==MAIN_SNIPPET? "MAIN_SNIPPET" : ste.snippet().id()) + " before: " + ste.previousStatus()
+ + " status: " + ste.status() + " sig: " + ste.isSignatureChange()
+ + " cause: " + ste.causeSnippet();
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/MethodsTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,297 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Tests for EvaluationState.methods
+ * @build KullaTesting TestingInputStream ExpectedDiagnostic
+ * @run testng MethodsTest
+ */
+
+import javax.tools.Diagnostic;
+
+import jdk.jshell.Snippet;
+import jdk.jshell.MethodSnippet;
+import jdk.jshell.Snippet.Status;
+import org.testng.annotations.Test;
+
+import static jdk.jshell.Snippet.Status.*;
+import static jdk.jshell.Snippet.SubKind.*;
+
+@Test
+public class MethodsTest extends KullaTesting {
+
+ public void noMethods() {
+ assertNumberOfActiveMethods(0);
+ }
+
+ public void testSignature1() {
+ MethodSnippet m1 = methodKey(assertEval("void f() { g(); }", added(RECOVERABLE_DEFINED)));
+ assertMethodDeclSnippet(m1, "f", "()void", RECOVERABLE_DEFINED, 1, 0);
+ MethodSnippet m2 = methodKey(assertEval("void g() { }",
+ added(VALID),
+ ste(m1, RECOVERABLE_DEFINED, VALID, false, null)));
+ assertMethodDeclSnippet(m2, "g", "()void", VALID, 0, 0);
+ }
+
+ public void testSignature2() {
+ MethodSnippet m1 = (MethodSnippet) assertDeclareFail("void f() { return g(); }", "compiler.err.prob.found.req");
+ assertMethodDeclSnippet(m1, "f", "()void", REJECTED, 0, 2);
+ MethodSnippet m2 = methodKey(assertEval("int f() { return g(); }",
+ ste(m1, REJECTED, RECOVERABLE_DEFINED, true, null)));
+ assertMethodDeclSnippet(m1, "f", "()void", REJECTED, 0, 2);
+ assertMethodDeclSnippet(m2, "f", "()int", RECOVERABLE_DEFINED, 1, 0);
+ }
+
+ @Test(enabled = false) // TODO 8081690
+ public void testSignature3() {
+ MethodSnippet m1 = methodKey(assertEval("void f(Bar b) { }", added(RECOVERABLE_NOT_DEFINED)));
+ assertMethodDeclSnippet(m1, "f", "(Bar)void", RECOVERABLE_NOT_DEFINED, 1, 0);
+ MethodSnippet m2 = methodKey(assertEval("void f(A.Bar b) { }", added(RECOVERABLE_NOT_DEFINED)));
+ assertMethodDeclSnippet(m1, "f", "(Bar)void", RECOVERABLE_NOT_DEFINED, 1, 0);
+ assertMethodDeclSnippet(m2, "f", "(A.Bar)void", RECOVERABLE_NOT_DEFINED, 1, 0);
+ assertDrop(m1, ste(m1, RECOVERABLE_NOT_DEFINED, DROPPED, false, null));
+ assertMethodDeclSnippet(m1, "f", "(Bar)void", DROPPED, 1, 0);
+ }
+
+ public void methods() {
+ assertEval("int x() { return 10; }");
+ assertEval("String y() { return null; }");
+ assertEval("long z() { return 0; }");
+ assertMethods(method("()int", "x"), method("()String", "y"), method("()long", "z"));
+ assertActiveKeys();
+ }
+
+ public void methodOverload() {
+ assertEval("int m() { return 1; }");
+ assertEval("int m(int x) { return 2; }");
+ assertEval("int m(String s) { return 3; }");
+ assertEval("int m(int x, int y) { return 4; }");
+ assertEval("int m(int x, String z) { return 5; }");
+ assertEval("int m(int x, String z, long g) { return 6; }");
+ assertMethods(
+ method("()int", "m"),
+ method("(int)int", "m"),
+ method("(String)int", "m"),
+ method("(int,int)int", "m"),
+ method("(int,String)int", "m"),
+ method("(int,String,long)int", "m")
+ );
+ assertEval("m();", "1");
+ assertEval("m(3);", "2");
+ assertEval("m(\"hi\");", "3");
+ assertEval("m(7, 8);", "4");
+ assertEval("m(7, \"eight\");", "5");
+ assertEval("m(7, \"eight\", 9L);", "6");
+ assertActiveKeys();
+ }
+
+ public void methodsRedeclaration1() {
+ Snippet x = methodKey(assertEval("int x() { return 10; }"));
+ Snippet y = methodKey(assertEval("String y() { return \"\"; }"));
+ assertMethods(method("()int", "x"), method("()String", "y"));
+ assertActiveKeys();
+
+ assertEval("long x() { return 0; }",
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(x, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertMethods(method("()long", "x"), method("()String", "y"));
+ assertActiveKeys();
+
+ assertEval("String y() { return null; }",
+ ste(MAIN_SNIPPET, VALID, VALID, false, null),
+ ste(y, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertMethods(method("()long", "x"), method("()String", "y"));
+ assertActiveKeys();
+ }
+
+ public void methodsRedeclaration2() {
+ assertEval("int a() { return 1; }");
+ assertMethods(method("()int", "a"));
+ assertActiveKeys();
+
+ Snippet b = methodKey(assertEval("Integer b() { return a(); }"));
+ assertMethods(method("()int", "a"), method("()Integer", "b"));
+ assertActiveKeys();
+
+ Snippet c = methodKey(assertEval("double c() { return b(); }"));
+ assertMethods(method("()int", "a"), method("()Integer", "b"), method("()double", "c"));
+ assertActiveKeys();
+
+ assertEval("double b() { return 3.14159; }",
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(b, VALID, OVERWRITTEN, false, MAIN_SNIPPET),
+ ste(c, VALID, VALID, false, MAIN_SNIPPET));
+ assertMethods(method("()int", "a"), method("()double", "b"), method("()double", "c"));
+ assertEval("c();", "3.14159");
+ assertActiveKeys();
+ }
+
+ public void methodsRedeclaration3() {
+ Snippet x = methodKey(assertEval("int x(Object...a) { return 10; }"));
+ assertMethods(method("(Object...)int", "x"));
+ assertActiveKeys();
+
+ assertEval("int x(Object[]a) { return 10; }",
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(x, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertMethods(method("(Object[])int", "x"));
+ assertActiveKeys();
+ }
+
+
+ public void methodsRedeclaration4() {
+ Snippet a = methodKey(assertEval("int foo(int a) { return a; }"));
+ assertEval("int x = foo(10);");
+ assertActiveKeys();
+ assertMethods(method("(int)int", "foo"));
+ assertEval("int foo(int a) { return a * a; }",
+ ste(MAIN_SNIPPET, VALID, VALID, false, null),
+ ste(a, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertActiveKeys();
+ }
+
+ public void methodsErrors() {
+ assertDeclareFail("String f();",
+ new ExpectedDiagnostic("compiler.err.missing.meth.body.or.decl.abstract", 0, 11, 7, -1, -1, Diagnostic.Kind.ERROR));
+ assertNumberOfActiveMethods(0);
+ assertActiveKeys();
+
+ assertDeclareFail("abstract String f();",
+ new ExpectedDiagnostic("jdk.eval.error.illegal.modifiers", 0, 8, 0, -1, -1, Diagnostic.Kind.ERROR));
+ assertNumberOfActiveMethods(0);
+ assertActiveKeys();
+
+ assertDeclareFail("native String f();",
+ new ExpectedDiagnostic("jdk.eval.error.illegal.modifiers", 0, 6, 0, -1, -1, Diagnostic.Kind.ERROR));
+ assertNumberOfActiveMethods(0);
+ assertActiveKeys();
+
+ assertDeclareFail("synchronized String f() {return null;}",
+ new ExpectedDiagnostic("jdk.eval.error.illegal.modifiers", 0, 12, 0, -1, -1, Diagnostic.Kind.ERROR));
+ assertNumberOfActiveMethods(0);
+ assertActiveKeys();
+
+ assertDeclareFail("int f() {}", "compiler.err.missing.ret.stmt",
+ ste(MAIN_SNIPPET, REJECTED, REJECTED, false, null));
+ assertNumberOfActiveMethods(0);
+ assertActiveKeys();
+
+ assertEval("String x() { return \"\"; };");
+ assertMethods(method("()String", "x"));
+ assertActiveKeys();
+ }
+
+ public void methodsWarn() {
+ Snippet f = assertDeclareWarn1("public String f() {return null;}",
+ new ExpectedDiagnostic("jdk.eval.warn.illegal.modifiers", 0, 6, 0, -1, -1, Diagnostic.Kind.WARNING),
+ added(VALID));
+ assertNumberOfActiveMethods(1);
+ assertActiveKeys();
+
+ f = assertDeclareWarn1("protected String f() {return null;}",
+ new ExpectedDiagnostic("jdk.eval.warn.illegal.modifiers", 0, 9, 0, -1, -1, Diagnostic.Kind.WARNING),
+ ste(MAIN_SNIPPET, VALID, VALID, false, null),
+ ste(f, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertNumberOfActiveMethods(1);
+ assertActiveKeys();
+
+ f = assertDeclareWarn1("private String f() {return null;}",
+ new ExpectedDiagnostic("jdk.eval.warn.illegal.modifiers", 0, 7, 0, -1, -1, Diagnostic.Kind.WARNING),
+ ste(MAIN_SNIPPET, VALID, VALID, false, null),
+ ste(f, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertNumberOfActiveMethods(1);
+ assertActiveKeys();
+
+ f = assertDeclareWarn1("static String f() {return null;}",
+ new ExpectedDiagnostic("jdk.eval.warn.illegal.modifiers", 0, 6, 0, -1, -1, Diagnostic.Kind.WARNING),
+ ste(MAIN_SNIPPET, VALID, VALID, false, null),
+ ste(f, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertNumberOfActiveMethods(1);
+ assertActiveKeys();
+
+ assertDeclareWarn1("final String f() {return null;}",
+ new ExpectedDiagnostic("jdk.eval.warn.illegal.modifiers", 0, 5, 0, -1, -1, Diagnostic.Kind.WARNING),
+ ste(MAIN_SNIPPET, VALID, VALID, false, null),
+ ste(f, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertNumberOfActiveMethods(1);
+ assertActiveKeys();
+ }
+
+ public void methodSignatureUnresolved() {
+ MethodSnippet key = (MethodSnippet) methodKey(assertEval("und m() { return new und(); }", added(RECOVERABLE_NOT_DEFINED)));
+ assertMethodDeclSnippet(key, "m", "()und", RECOVERABLE_NOT_DEFINED, 1, 0);
+ assertUnresolvedDependencies1(key, Status.RECOVERABLE_NOT_DEFINED, "class und");
+ assertEval("class und {}",
+ added(VALID),
+ ste(key, RECOVERABLE_NOT_DEFINED, VALID, true, null));
+ assertMethodDeclSnippet(key, "m", "()und", Status.VALID, 0, 0);
+ assertNumberOfActiveMethods(1);
+ assertActiveKeys();
+ }
+
+ @Test(enabled = false) // TODO 8081689
+ public void classMethodsAreNotVisible() {
+ assertEval(
+ "class A {" +
+ "int foo() {" +
+ "int x = 10;" +
+ "int y = 2 * x;" +
+ "return x * y;" +
+ "}" +
+ "}");
+ assertNumberOfActiveMethods(0);
+ assertEval("int x = 10;", "10");
+ assertEval("int foo() {" +
+ "int y = 2 * x;" +
+ "return x * y;" +
+ "}");
+ assertMethods(method("()int", "foo"));
+ assertEval("foo();", "200");
+ assertActiveKeys();
+ }
+
+ public void lambdas() {
+ assertEval("class Inner1 implements Runnable {" +
+ "public Runnable lambda1 = () -> {};" +
+ "public void function() {}" +
+ "public void run() {}" +
+ "}");
+
+ assertEval("class Inner2 {" +
+ "private Runnable lambda1 = () -> {};" +
+ "private static void staticFunction() {}" +
+ "}");
+
+ // the following method references and lambda functions
+ // generate synthetic methods
+ assertEval("Runnable run = () -> {};");
+ assertEval("Inner1 inner = new Inner1();");
+ assertEval("Runnable l1 = inner::function;");
+ assertEval("Runnable l2 = Inner1::new;");
+ assertEval("inner.lambda1 = inner::function;");
+ assertEval("java.util.stream.IntStream.of(2).mapToObj(int[]::new);");
+ assertNumberOfActiveMethods(0);
+ assertActiveKeys();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/ModifiersTest.java Thu Oct 22 11:12:55 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.
+ *
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Tests for modifiers
+ * @build KullaTesting TestingInputStream ExpectedDiagnostic
+ * @run testng ModifiersTest
+ */
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.tools.Diagnostic;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+@Test
+public class ModifiersTest extends KullaTesting {
+
+ @DataProvider(name = "ignoredModifiers")
+ public Object[][] getTestCases() {
+ List<Object[]> testCases = new ArrayList<>();
+ String[] ignoredModifiers = new String[] {
+ "public", "protected", "private", "static", "final"
+ };
+ for (String ignoredModifier : ignoredModifiers) {
+ for (ClassType classType : ClassType.values()) {
+ testCases.add(new Object[] { ignoredModifier, classType });
+ }
+ }
+ return testCases.toArray(new Object[testCases.size()][]);
+ }
+
+ @Test(dataProvider = "ignoredModifiers")
+ public void ignoredModifiers(String modifier, ClassType classType) {
+ assertDeclareWarn1(
+ String.format("%s %s A {}", modifier, classType), "jdk.eval.warn.illegal.modifiers");
+ assertNumberOfActiveClasses(1);
+ assertClasses(clazz(classType, "A"));
+ assertActiveKeys();
+ }
+
+ public void accessToStaticFieldsOfClass() {
+ assertEval("class A {" +
+ "int x = 14;" +
+ "static int y = 18;" +
+ " }");
+ assertDeclareFail("A.x;",
+ new ExpectedDiagnostic("compiler.err.non-static.cant.be.ref", 0, 3, 1, -1, -1, Diagnostic.Kind.ERROR));
+ assertEval("A.y;", "18");
+ assertEval("new A().x;", "14");
+ assertEval("A.y = 88;", "88");
+ assertActiveKeys();
+ }
+
+ public void accessToStaticMethodsOfClass() {
+ assertEval("class A {" +
+ "void x() {}" +
+ "static void y() {}" +
+ " }");
+ assertDeclareFail("A.x();",
+ new ExpectedDiagnostic("compiler.err.non-static.cant.be.ref", 0, 3, 1, -1, -1, Diagnostic.Kind.ERROR));
+ assertEval("A.y();");
+ assertActiveKeys();
+ }
+
+ public void accessToStaticFieldsOfInterface() {
+ assertEval("interface A {" +
+ "int x = 14;" +
+ "static int y = 18;" +
+ " }");
+ assertEval("A.x;", "14");
+ assertEval("A.y;", "18");
+
+ assertDeclareFail("A.x = 18;",
+ new ExpectedDiagnostic("compiler.err.cant.assign.val.to.final.var", 0, 3, 1, -1, -1, Diagnostic.Kind.ERROR));
+ assertDeclareFail("A.y = 88;",
+ new ExpectedDiagnostic("compiler.err.cant.assign.val.to.final.var", 0, 3, 1, -1, -1, Diagnostic.Kind.ERROR));
+ assertActiveKeys();
+ }
+
+ public void accessToStaticMethodsOfInterface() {
+ assertEval("interface A { static void x() {} }");
+ assertEval("A.x();");
+ assertActiveKeys();
+ }
+
+ public void finalMethod() {
+ assertEval("class A { final void f() {} }");
+ assertDeclareFail("class B extends A { void f() {} }",
+ new ExpectedDiagnostic("compiler.err.override.meth", 20, 31, 25, -1, -1, Diagnostic.Kind.ERROR));
+ assertActiveKeys();
+ }
+
+ //TODO: is this the right semantics?
+ public void finalConstructor() {
+ assertDeclareFail("class A { final A() {} }",
+ new ExpectedDiagnostic("compiler.err.mod.not.allowed.here", 10, 22, 16, -1, -1, Diagnostic.Kind.ERROR));
+ assertActiveKeys();
+ }
+
+ //TODO: is this the right semantics?
+ public void finalDefaultMethod() {
+ assertDeclareFail("interface A { final default void a() {} }",
+ new ExpectedDiagnostic("compiler.err.mod.not.allowed.here", 14, 39, 33, -1, -1, Diagnostic.Kind.ERROR));
+ assertActiveKeys();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/NullTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ *
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary null test
+ * @build KullaTesting TestingInputStream
+ * @run testng NullTest
+ */
+
+import org.testng.annotations.Test;
+
+@Test
+public class NullTest extends KullaTesting {
+
+ public void testNull() {
+ assertEval("null;", "null");
+ assertEval("(Object)null;", "null");
+ assertEval("(String)null;", "null");
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/RejectedFailedTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Tests for hard errors, like syntax errors
+ * @build KullaTesting
+ * @run testng RejectedFailedTest
+ */
+
+import java.util.List;
+
+import jdk.jshell.Snippet.SubKind;
+import org.testng.annotations.Test;
+
+import jdk.jshell.Diag;
+import jdk.jshell.Snippet;
+import jdk.jshell.Snippet.Kind;
+import jdk.jshell.Snippet.Status;
+
+import jdk.jshell.SnippetEvent;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+@Test
+public class RejectedFailedTest extends KullaTesting {
+
+ private String bad(String input, Kind kind, String prevId) {
+ List<SnippetEvent> events = assertEvalFail(input);
+ assertEquals(events.size(), 1, "Expected one event, got: " + events.size());
+ SnippetEvent e = events.get(0);
+ List<Diag> diagnostics = getState().diagnostics(e.snippet());
+ assertTrue(diagnostics.size() > 0, "Expected diagnostics, got none");
+ assertEquals(e.exception(), null, "Expected exception to be null.");
+ assertEquals(e.value(), null, "Expected value to be null.");
+
+ Snippet key = e.snippet();
+ assertTrue(key != null, "key must be non-null, but was null.");
+ assertEquals(key.kind(), kind, "Expected kind: " + kind + ", got: " + key.kind());
+ SubKind expectedSubKind = kind == Kind.ERRONEOUS ? SubKind.UNKNOWN_SUBKIND : SubKind.METHOD_SUBKIND;
+ assertEquals(key.subKind(), expectedSubKind, "SubKind: ");
+ assertTrue(key.id().compareTo(prevId) > 0, "Current id: " + key.id() + ", previous: " + prevId);
+ assertEquals(getState().diagnostics(key), diagnostics, "Expected retrieved diagnostics to match, but didn't.");
+ assertEquals(key.source(), input, "Expected retrieved source: " +
+ key.source() + " to match input: " + input);
+ assertEquals(getState().status(key), Status.REJECTED, "Expected status of REJECTED, got: " + getState().status(key));
+ return key.id();
+ }
+
+ private void checkByKind(String[] inputs, Kind kind) {
+ String prevId = "";
+ for (String in : inputs) {
+ prevId = bad(in, kind, prevId);
+ }
+ }
+
+ public void testErroneous() {
+ String[] inputsErroneous = {
+ "%&^%&",
+ " a b c",
+ ")",
+ "class interface A",
+ };
+ checkByKind(inputsErroneous, Kind.ERRONEOUS);
+ }
+
+ public void testBadMethod() {
+ String[] inputsMethod = {
+ "transient int m() { return x; }",
+ "int h() { }",
+ "void l() { return 4; }",
+ "int vv(void x) { return 2; }",
+ };
+ checkByKind(inputsMethod, Kind.METHOD);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/ReplToolTesting.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,653 @@
+/*
+ * 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.
+ */
+
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import jdk.internal.jshell.tool.JShellTool;
+import jdk.jshell.SourceCodeAnalysis.Suggestion;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+public class ReplToolTesting {
+
+ private final static String DEFAULT_STARTUP_MESSAGE = "| Welcome to";
+
+ private WaitingTestingInputStream cmdin = null;
+ private ByteArrayOutputStream cmdout = null;
+ private ByteArrayOutputStream cmderr = null;
+ private PromptedCommandOutputStream console = null;
+ private TestingInputStream userin = null;
+ private ByteArrayOutputStream userout = null;
+ private ByteArrayOutputStream usererr = null;
+
+ private List<MemberInfo> keys;
+ private Map<String, VariableInfo> variables;
+ private Map<String, MethodInfo> methods;
+ private Map<String, ClassInfo> classes;
+ private boolean isDefaultStartUp = true;
+
+ public JShellTool repl = null;
+
+ public interface ReplTest {
+ void run(boolean after);
+ }
+
+ public void setCommandInput(String s) {
+ cmdin.setInput(s);
+ }
+
+ public final static Pattern idPattern = Pattern.compile("^\\s+(\\d+)");
+ public Consumer<String> assertList() {
+ return s -> {
+ List<String> lines = Stream.of(s.split("\n"))
+ .filter(l -> !l.isEmpty())
+ .collect(Collectors.toList());
+ int previousId = Integer.MIN_VALUE;
+ assertEquals(lines.size(), keys.size(), "Number of keys");
+ for (int i = 0; i < lines.size(); ++i) {
+ String line = lines.get(i);
+ Matcher matcher = idPattern.matcher(line);
+ assertTrue(matcher.find(), "Snippet id not found: " + line);
+ String src = keys.get(i).getSource();
+ assertTrue(line.endsWith(src), "Line '" + line + "' does not end with: " + src);
+ int id = Integer.parseInt(matcher.group(1));
+ assertTrue(previousId < id,
+ String.format("The previous id is not less than the next one: previous: %d, next: %d",
+ previousId, id));
+ previousId = id;
+ }
+ };
+ }
+
+ private final static Pattern extractPattern = Pattern.compile("^\\| *(.*)$");
+ private Consumer<String> assertMembers(String message, Map<String, ? extends MemberInfo> set) {
+ return s -> {
+ List<String> lines = Stream.of(s.split("\n"))
+ .filter(l -> !l.isEmpty())
+ .collect(Collectors.toList());
+ assertEquals(lines.size(), set.size(), message + " : expected: " + set.keySet() + "\ngot:\n" + lines);
+ for (String line : lines) {
+ Matcher matcher = extractPattern.matcher(line);
+ assertTrue(matcher.find(), line);
+ String src = matcher.group(1);
+ MemberInfo info = set.get(src);
+ assertNotNull(info, "Not found snippet with signature: " + src + ", line: "
+ + line + ", keys: " + set.keySet() + "\n");
+ }
+ };
+ }
+
+ public Consumer<String> assertVariables() {
+ return assertMembers("Variables", variables);
+ }
+
+ public Consumer<String> assertMethods() {
+ return assertMembers("Methods", methods);
+ }
+
+ public Consumer<String> assertClasses() {
+ return assertMembers("Classes", classes);
+ }
+
+ public String getCommandOutput() {
+ String s = cmdout.toString();
+ cmdout.reset();
+ return s;
+ }
+
+ public String getCommandErrorOutput() {
+ String s = cmderr.toString();
+ cmderr.reset();
+ return s;
+ }
+
+ public void setUserInput(String s) {
+ userin.setInput(s);
+ }
+
+ public String getUserOutput() {
+ String s = userout.toString();
+ userout.reset();
+ return s;
+ }
+
+ public String getUserErrorOutput() {
+ String s = usererr.toString();
+ usererr.reset();
+ return s;
+ }
+
+ public void test(ReplTest... tests) {
+ test(new String[0], tests);
+ }
+
+ public void test(String[] args, ReplTest... tests) {
+ test(true, args, tests);
+ }
+
+ public void test(boolean isDefaultStartUp, String[] args, ReplTest... tests) {
+ test(isDefaultStartUp, args, DEFAULT_STARTUP_MESSAGE, tests);
+ }
+
+ public void test(boolean isDefaultStartUp, String[] args, String startUpMessage, ReplTest... tests) {
+ this.isDefaultStartUp = isDefaultStartUp;
+ initSnippets();
+ ReplTest[] wtests = new ReplTest[tests.length + 3];
+ wtests[0] = a -> assertCommandCheckOutput(a, "<start>",
+ s -> assertTrue(s.startsWith(startUpMessage), "Expected start-up message '" + startUpMessage + "' Got: " + s));
+ wtests[1] = a -> assertCommand(a, "/debug 0", null);
+ System.arraycopy(tests, 0, wtests, 2, tests.length);
+ wtests[tests.length + 2] = a -> assertCommand(a, "/exit", null);
+ testRaw(args, wtests);
+ }
+
+ private void initSnippets() {
+ keys = new ArrayList<>();
+ variables = new HashMap<>();
+ methods = new HashMap<>();
+ classes = new HashMap<>();
+ if (isDefaultStartUp) {
+ methods.put("printf (String,Object...)void",
+ new MethodInfo("", "(String,Object...)void", "printf"));
+ }
+ }
+
+ public void testRaw(String[] args, ReplTest... tests) {
+ cmdin = new WaitingTestingInputStream();
+ cmdout = new ByteArrayOutputStream();
+ cmderr = new ByteArrayOutputStream();
+ console = new PromptedCommandOutputStream(tests);
+ userin = new TestingInputStream();
+ userout = new ByteArrayOutputStream();
+ usererr = new ByteArrayOutputStream();
+ repl = new JShellTool(
+ cmdin,
+ new PrintStream(cmdout),
+ new PrintStream(cmderr),
+ new PrintStream(console),
+ userin,
+ new PrintStream(userout),
+ new PrintStream(usererr));
+ repl.testPrompt = true;
+ try {
+ repl.start(args);
+ } catch (Exception ex) {
+ fail("Repl tool died with exception", ex);
+ }
+ // perform internal consistency checks on state, if desired
+ String cos = getCommandOutput();
+ String ceos = getCommandErrorOutput();
+ String uos = getUserOutput();
+ String ueos = getUserErrorOutput();
+ assertTrue((cos.isEmpty() || cos.startsWith("| Goodbye")),
+ "Expected a goodbye, but got: " + cos);
+ assertTrue(ceos.isEmpty(), "Expected empty error output, got: " + ceos);
+ assertTrue(uos.isEmpty(), "Expected empty output, got: " + uos);
+ assertTrue(ueos.isEmpty(), "Expected empty error output, got: " + ueos);
+ }
+
+ public void assertReset(boolean after, String cmd) {
+ assertCommand(after, cmd, "| Resetting state.\n");
+ initSnippets();
+ }
+
+ public void evaluateExpression(boolean after, String type, String expr, String value) {
+ String output = String.format("\\| *Expression values is: %s\n|" +
+ " *.*temporary variable (\\$\\d+) of type %s", value, type);
+ Pattern outputPattern = Pattern.compile(output);
+ assertCommandCheckOutput(after, expr, s -> {
+ Matcher matcher = outputPattern.matcher(s);
+ assertTrue(matcher.find(), "Output: '" + s + "' does not fit pattern: '" + output + "'");
+ String name = matcher.group(1);
+ VariableInfo tempVar = new TempVariableInfo(expr, type, name, value);
+ variables.put(tempVar.toString(), tempVar);
+ addKey(after, tempVar);
+ });
+ }
+
+ public void loadVariable(boolean after, String type, String name) {
+ loadVariable(after, type, name, null, null);
+ }
+
+ public void loadVariable(boolean after, String type, String name, String expr, String value) {
+ String src = expr == null
+ ? String.format("%s %s", type, name)
+ : String.format("%s %s = %s", type, name, expr);
+ VariableInfo var = expr == null
+ ? new VariableInfo(src, type, name)
+ : new VariableInfo(src, type, name, value);
+ addKey(after, var, variables);
+ addKey(after, var);
+ }
+
+ public void assertVariable(boolean after, String type, String name) {
+ assertVariable(after, type, name, null, null);
+ }
+
+ public void assertVariable(boolean after, String type, String name, String expr, String value) {
+ String src = expr == null
+ ? String.format("%s %s", type, name)
+ : String.format("%s %s = %s", type, name, expr);
+ VariableInfo var = expr == null
+ ? new VariableInfo(src, type, name)
+ : new VariableInfo(src, type, name, value);
+ assertCommandCheckOutput(after, src, var.checkOutput());
+ addKey(after, var, variables);
+ addKey(after, var);
+ }
+
+ public void loadMethod(boolean after, String src, String signature, String name) {
+ MethodInfo method = new MethodInfo(src, signature, name);
+ addKey(after, method, methods);
+ addKey(after, method);
+ }
+
+ public void assertMethod(boolean after, String src, String signature, String name) {
+ MethodInfo method = new MethodInfo(src, signature, name);
+ assertCommandCheckOutput(after, src, method.checkOutput());
+ addKey(after, method, methods);
+ addKey(after, method);
+ }
+
+ public void loadClass(boolean after, String src, String type, String name) {
+ ClassInfo clazz = new ClassInfo(src, type, name);
+ addKey(after, clazz, classes);
+ addKey(after, clazz);
+ }
+
+ public void assertClass(boolean after, String src, String type, String name) {
+ ClassInfo clazz = new ClassInfo(src, type, name);
+ assertCommandCheckOutput(after, src, clazz.checkOutput());
+ addKey(after, clazz, classes);
+ addKey(after, clazz);
+ }
+
+ private <T extends MemberInfo> void addKey(boolean after, T memberInfo, Map<String, T> map) {
+ if (after) {
+ map.entrySet().removeIf(e -> e.getValue().equals(memberInfo));
+ map.put(memberInfo.toString(), memberInfo);
+ }
+ }
+
+ private <T extends MemberInfo> void addKey(boolean after, T memberInfo) {
+ if (after) {
+ for (int i = 0; i < keys.size(); ++i) {
+ MemberInfo m = keys.get(i);
+ if (m.equals(memberInfo)) {
+ keys.set(i, memberInfo);
+ return;
+ }
+ }
+ keys.add(memberInfo);
+ }
+ }
+
+ private void dropKey(boolean after, String cmd, String name, Map<String, ? extends MemberInfo> map) {
+ assertCommand(after, cmd, "");
+ if (after) {
+ map.remove(name);
+ for (int i = 0; i < keys.size(); ++i) {
+ MemberInfo m = keys.get(i);
+ if (m.toString().equals(name)) {
+ keys.remove(i);
+ return;
+ }
+ }
+ throw new AssertionError("Key not found: " + name + ", keys: " + keys);
+ }
+ }
+
+ public void dropVariable(boolean after, String cmd, String name) {
+ dropKey(after, cmd, name, variables);
+ }
+
+ public void dropMethod(boolean after, String cmd, String name) {
+ dropKey(after, cmd, name, methods);
+ }
+
+ public void dropClass(boolean after, String cmd, String name) {
+ dropKey(after, cmd, name, classes);
+ }
+
+ public void assertCommand(boolean after, String cmd, String out) {
+ assertCommand(after, cmd, out, "", null, "", "");
+ }
+
+ public void assertCommandCheckOutput(boolean after, String cmd, Consumer<String> check) {
+ if (!after) {
+ assertCommand(false, cmd, null);
+ } else {
+ String got = getCommandOutput();
+ check.accept(got);
+ assertCommand(true, cmd, null);
+ }
+ }
+
+ public void assertCommand(boolean after, String cmd, String out, String err,
+ String userinput, String print, String usererr) {
+ if (!after) {
+ if (userinput != null) {
+ setUserInput(userinput);
+ }
+ setCommandInput(cmd + "\n");
+ } else {
+ assertOutput(getCommandOutput(), out, "command");
+ assertOutput(getCommandErrorOutput(), err, "command error");
+ assertOutput(getUserOutput(), print, "user");
+ assertOutput(getUserErrorOutput(), usererr, "user error");
+ }
+ }
+
+ public void assertCompletion(boolean after, String code, boolean isSmart, String... expected) {
+ if (!after) {
+ setCommandInput("\n");
+ } else {
+ assertCompletion(code, isSmart, expected);
+ }
+ }
+
+ public void assertCompletion(String code, boolean isSmart, String... expected) {
+ List<String> completions = computeCompletions(code, isSmart);
+ assertEquals(completions, Arrays.asList(expected), "Command: " + code + ", output: " +
+ completions.toString());
+ }
+
+ private List<String> computeCompletions(String code, boolean isSmart) {
+ JShellTool repl = this.repl != null ? this.repl
+ : new JShellTool(null, null, null, null, null, null, null);
+ int cursor = code.indexOf('|');
+ code = code.replace("|", "");
+ assertTrue(cursor > -1, "'|' not found: " + code);
+ List<Suggestion> completions =
+ repl.commandCompletionSuggestions(code, cursor, new int[1]); //XXX: ignoring anchor for now
+ return completions.stream()
+ .filter(s -> isSmart == s.isSmart)
+ .map(s -> s.continuation)
+ .distinct()
+ .collect(Collectors.toList());
+ }
+
+ public Consumer<String> assertStartsWith(String prefix) {
+ return (output) -> assertTrue(output.startsWith(prefix), "Output: \'" + output + "' does not start with: " + prefix);
+ }
+
+ public void assertOutput(String got, String expected, String kind) {
+ if (expected != null) {
+ assertEquals(got, expected, "Kind: " + kind + ".\n");
+ }
+ }
+
+ public static abstract class MemberInfo {
+ public final String source;
+ public final String type;
+ public final String name;
+
+ public MemberInfo(String source, String type, String name) {
+ this.source = source;
+ this.type = type;
+ this.name = name;
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
+
+ public abstract Consumer<String> checkOutput();
+
+ public String getSource() {
+ return source;
+ }
+ }
+
+ public static class VariableInfo extends MemberInfo {
+
+ public final String value;
+ public final String initialValue;
+
+ public VariableInfo(String src, String type, String name) {
+ super(src, type, name);
+ this.initialValue = null;
+ switch (type) {
+ case "byte":
+ case "short":
+ case "int":
+ case "long":
+ value = "0";
+ break;
+ case "boolean":
+ value = "false";
+ break;
+ case "char":
+ value = "''";
+ break;
+ case "float":
+ case "double":
+ value = "0.0";
+ break;
+ default:
+ value = "null";
+ }
+ }
+
+ public VariableInfo(String src, String type, String name, String value) {
+ super(src, type, name);
+ this.value = value;
+ this.initialValue = value;
+ }
+
+ @Override
+ public Consumer<String> checkOutput() {
+ String pattern = String.format("\\| *\\w+ variable %s of type %s", name, type);
+ if (initialValue != null) {
+ pattern += " with initial value " + initialValue;
+ }
+ Predicate<String> checkOutput = Pattern.compile(pattern).asPredicate();
+ final String finalPattern = pattern;
+ return output -> assertTrue(checkOutput.test(output),
+ "Output: " + output + " does not fit pattern: " + finalPattern);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof VariableInfo) {
+ VariableInfo v = (VariableInfo) o;
+ return name.equals(v.name);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s %s = %s", type, name, value);
+ }
+
+ @Override
+ public String getSource() {
+ String src = super.getSource();
+ return src.endsWith(";") ? src : src + ";";
+ }
+ }
+
+ public static class TempVariableInfo extends VariableInfo {
+
+ public TempVariableInfo(String src, String type, String name, String value) {
+ super(src, type, name, value);
+ }
+
+ @Override
+ public String getSource() {
+ return source;
+ }
+ }
+
+ public static class MethodInfo extends MemberInfo {
+
+ public final String signature;
+
+ public MethodInfo(String source, String signature, String name) {
+ super(source, signature.substring(0, signature.lastIndexOf(')') + 1), name);
+ this.signature = signature;
+ }
+
+ @Override
+ public Consumer<String> checkOutput() {
+ String expectedOutput = String.format("\\| *\\w+ method %s", name);
+ Predicate<String> checkOutput = Pattern.compile(expectedOutput).asPredicate();
+ return s -> assertTrue(checkOutput.test(s), "Expected: '" + expectedOutput + "', actual: " + s);
+ }
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof MemberInfo) {
+ MemberInfo m = (MemberInfo) o;
+ return name.equals(m.name) && type.equals(m.type);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s %s", name, signature);
+ }
+ }
+
+ public static class ClassInfo extends MemberInfo {
+
+ public ClassInfo(String source, String type, String name) {
+ super(source, type, name);
+ }
+
+ @Override
+ public Consumer<String> checkOutput() {
+ String fullType = type.equals("@interface")? "annotation interface" : type;
+ String expectedOutput = String.format("\\| *\\w+ %s %s", fullType, name);
+ Predicate<String> checkOutput = Pattern.compile(expectedOutput).asPredicate();
+ return s -> assertTrue(checkOutput.test(s), "Expected: '" + expectedOutput + "', actual: " + s);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof ClassInfo) {
+ ClassInfo c = (ClassInfo) o;
+ return name.equals(c.name);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s %s", type, name);
+ }
+ }
+
+ class WaitingTestingInputStream extends TestingInputStream {
+
+ @Override
+ synchronized void setInput(String s) {
+ super.setInput(s);
+ notify();
+ }
+
+ synchronized void waitForInput() {
+ boolean interrupted = false;
+ try {
+ while (available() == 0) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ interrupted = true;
+ // fall through and retry
+ }
+ }
+ } finally {
+ if (interrupted) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+ @Override
+ public int read() {
+ waitForInput();
+ return super.read();
+ }
+
+ @Override
+ public int read(byte b[], int off, int len) {
+ waitForInput();
+ return super.read(b, off, len);
+ }
+ }
+
+ class PromptedCommandOutputStream extends OutputStream {
+ private final ReplTest[] tests;
+ private int index = 0;
+ PromptedCommandOutputStream(ReplTest[] tests) {
+ this.tests = tests;
+ }
+
+ @Override
+ public synchronized void write(int b) {
+ if (b == 5 || b == 6) {
+ if (index < (tests.length - 1)) {
+ tests[index].run(true);
+ tests[index + 1].run(false);
+ } else {
+ fail("Did not exit Repl tool after test");
+ }
+ ++index;
+ } // For now, anything else is thrown away
+ }
+
+ @Override
+ public synchronized void write(byte b[], int off, int len) {
+ if ((off < 0) || (off > b.length) || (len < 0)
+ || ((off + len) - b.length > 0)) {
+ throw new IndexOutOfBoundsException();
+ }
+ for (int i = 0; i < len; ++i) {
+ write(b[off + i]);
+ }
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/ReplaceTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,803 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Test of Snippet redefinition and replacement.
+ * @build KullaTesting TestingInputStream
+ * @run testng ReplaceTest
+ */
+
+import java.util.Collection;
+
+import jdk.jshell.Snippet;
+import jdk.jshell.MethodSnippet;
+import jdk.jshell.PersistentSnippet;
+import jdk.jshell.TypeDeclSnippet;
+import jdk.jshell.VarSnippet;
+import jdk.jshell.DeclarationSnippet;
+import org.testng.annotations.Test;
+
+import static jdk.jshell.Snippet.Status.*;
+import static jdk.jshell.Snippet.SubKind.*;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+@Test
+public class ReplaceTest extends KullaTesting {
+
+ public void testRedefine() {
+ Snippet vx = varKey(assertEval("int x;"));
+ Snippet mu = methodKey(assertEval("int mu() { return x * 4; }"));
+ Snippet c = classKey(assertEval("class C { String v() { return \"#\" + mu(); } }"));
+ assertEval("C c0 = new C();");
+ assertEval("c0.v();", "\"#0\"");
+ assertEval("int x = 10;", "10",
+ ste(MAIN_SNIPPET, VALID, VALID, false, null),
+ ste(vx, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertEval("c0.v();", "\"#40\"");
+ assertEval("C c = new C();");
+ assertEval("c.v();", "\"#40\"");
+ assertEval("int mu() { return x * 3; }",
+ ste(MAIN_SNIPPET, VALID, VALID, false, null),
+ ste(mu, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertEval("c.v();", "\"#30\"");
+ assertEval("class C { String v() { return \"@\" + mu(); } }",
+ ste(MAIN_SNIPPET, VALID, VALID, false, null),
+ ste(c, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertEval("c0.v();", "\"@30\"");
+ assertEval("c = new C();");
+ assertEval("c.v();", "\"@30\"");
+ assertActiveKeys();
+ }
+
+ public void testReplaceClassToVar() {
+ Snippet oldA = classKey(assertEval("class A { public String toString() { return \"old\"; } }"));
+ Snippet v = varKey(assertEval("A a = new A();", "old"));
+ assertEval("a;", "old");
+ Snippet midA = classKey(assertEval("class A { public String toString() { return \"middle\"; } }",
+ ste(MAIN_SNIPPET, VALID, VALID, false, null),
+ ste(oldA, VALID, OVERWRITTEN, false, MAIN_SNIPPET)));
+ assertEval("a;", "middle");
+ assertEval("class A { int x; public String toString() { return \"new\"; } }",
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(midA, VALID, OVERWRITTEN, false, MAIN_SNIPPET),
+ ste(v, VALID, VALID, true, MAIN_SNIPPET));
+ assertEval("a;", "null");
+ assertActiveKeys();
+ }
+
+ public void testReplaceVarToMethod() {
+ Snippet x = varKey(assertEval("int x;"));
+ Snippet musn = methodKey(assertEval("double mu() { return x * 4; }"));
+ assertEval("x == 0;", "true");
+ assertEval("mu() == 0.0;", "true");
+ assertEval("double x = 2.5;",
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(x, VALID, OVERWRITTEN, false, MAIN_SNIPPET),
+ ste(musn, VALID, VALID, false, MAIN_SNIPPET));
+ Collection<MethodSnippet> meths = getState().methods();
+ assertEquals(meths.size(), 1);
+ assertTrue(musn == meths.iterator().next(), "Identity must not change");
+ assertEval("x == 2.5;", "true");
+ assertEval("mu() == 10.0;", "true"); // Auto redefine
+ assertActiveKeys();
+ }
+
+ public void testReplaceMethodToMethod() {
+ Snippet a = methodKey(assertEval("double a() { return 2; }"));
+ Snippet b = methodKey(assertEval("double b() { return a() * 10; }"));
+ assertEval("double c() { return b() * 3; }");
+ assertEval("double d() { return c() + 1000; }");
+ assertEval("d();", "1060.0");
+ assertEval("int a() { return 5; }",
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(a, VALID, OVERWRITTEN, false, MAIN_SNIPPET),
+ ste(b, VALID, VALID, false, MAIN_SNIPPET));
+ assertEval("d();", "1150.0");
+ assertActiveKeys();
+ }
+
+ public void testReplaceClassToMethod() {
+ Snippet c = classKey(assertEval("class C { int f() { return 7; } }"));
+ Snippet m = methodKey(assertEval("int m() { return new C().f(); }"));
+ assertEval("m();", "7");
+ assertEval("class C { int x = 99; int f() { return x; } }",
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(c, VALID, OVERWRITTEN, false, MAIN_SNIPPET),
+ ste(m, VALID, VALID, false, MAIN_SNIPPET));
+ assertEval("m();", "99");
+ assertActiveKeys();
+ }
+
+ public void testReplaceVarToClass() {
+ Snippet x = varKey(assertEval("int x;"));
+ Snippet c = classKey(assertEval("class A { double a = 4 * x; }"));
+ assertEval("x == 0;", "true");
+ assertEval("new A().a == 0.0;", "true");
+ assertEval("double x = 2.5;",
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(x, VALID, OVERWRITTEN, false, MAIN_SNIPPET),
+ ste(c, VALID, VALID, false, MAIN_SNIPPET));
+ Collection<TypeDeclSnippet> classes = getState().types();
+ assertEquals(classes.size(), 1);
+ assertTrue(c == classes.iterator().next(), "Identity must not change");
+ assertEval("x == 2.5;", "true");
+ assertEval("new A().a == 10.0;", "true");
+ assertActiveKeys();
+ }
+
+ public void testReplaceMethodToClass() {
+ Snippet x = methodKey(assertEval("int x() { return 0; }"));
+ Snippet c = classKey(assertEval("class A { double a = 4 * x(); }"));
+ assertEval("x() == 0;", "true");
+ assertEval("new A().a == 0.0;", "true");
+ assertEval("double x() { return 2.5; }",
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(x, VALID, OVERWRITTEN, false, MAIN_SNIPPET),
+ ste(c, VALID, VALID, false, MAIN_SNIPPET));
+ assertEval("x();", "2.5");
+ Collection<TypeDeclSnippet> classes = getState().types();
+ assertEquals(classes.size(), 1);
+ assertTrue(c == classes.iterator().next(), "Identity must not change");
+ assertEval("x() == 2.5;", "true");
+ assertEval("new A().a == 10.0;", "true");
+ assertActiveKeys();
+ }
+
+ public void testReplaceClassToClass() {
+ TypeDeclSnippet a = classKey(assertEval("class A {}"));
+ assertTypeDeclSnippet(a, "A", VALID, CLASS_SUBKIND, 0, 0);
+ TypeDeclSnippet b = classKey(assertEval("class B extends A {}"));
+ TypeDeclSnippet c = classKey(assertEval("class C extends B {}"));
+ TypeDeclSnippet d = classKey(assertEval("class D extends C {}"));
+ assertEval("class A { int x; public String toString() { return \"NEW\"; } }",
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(a, VALID, OVERWRITTEN, false, MAIN_SNIPPET),
+ ste(b, VALID, VALID, true, MAIN_SNIPPET),
+ ste(c, VALID, VALID, true, b),
+ ste(d, VALID, VALID, true, c));
+ assertTypeDeclSnippet(b, "B", VALID, CLASS_SUBKIND, 0, 0);
+ assertTypeDeclSnippet(c, "C", VALID, CLASS_SUBKIND, 0, 0);
+ assertTypeDeclSnippet(d, "D", VALID, CLASS_SUBKIND, 0, 0);
+ assertEval("new D();", "NEW");
+ assertActiveKeys();
+ }
+
+ public void testOverwriteReplaceMethod() {
+ MethodSnippet k1 = methodKey(assertEval("String m(Integer i) { return i.toString(); }"));
+ MethodSnippet k2 = methodKey(assertEval("String m(java.lang.Integer i) { return \"java.lang.\" + i.toString(); }",
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(k1, VALID, OVERWRITTEN, false, MAIN_SNIPPET)));
+ assertMethodDeclSnippet(k1, "m", "(Integer)String", OVERWRITTEN, 0, 0);
+ assertEval("m(6);", "\"java.lang.6\"");
+ assertEval("String m(Integer i) { return i.toString(); }",
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(k2, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertMethodDeclSnippet(k2, "m", "(java.lang.Integer)String", OVERWRITTEN, 0, 0);
+ assertEval("m(6);", "\"6\"");
+ assertActiveKeys();
+ }
+
+ public void testOverwriteMethodForwardReferenceClass() {
+ Snippet k1 = methodKey(assertEval("int q(Boo b) { return b.x; }",
+ added(RECOVERABLE_NOT_DEFINED)));
+ assertUnresolvedDependencies1((MethodSnippet) k1, RECOVERABLE_NOT_DEFINED, "class Boo");
+ assertEval("class Boo { int x = 55; }",
+ added(VALID),
+ ste(k1, RECOVERABLE_NOT_DEFINED, VALID, true, null));
+ assertMethodDeclSnippet((MethodSnippet) k1, "q", "(Boo)int", VALID, 0, 0);
+ assertEval("q(new Boo());", "55");
+ assertActiveKeys();
+ }
+
+ public void testOverwriteMethodForwardReferenceClassImport() {
+ MethodSnippet k1 = methodKey(assertEval("int ff(List lis) { return lis.size(); }",
+ added(RECOVERABLE_NOT_DEFINED)));
+ assertUnresolvedDependencies1(k1, RECOVERABLE_NOT_DEFINED, "class List");
+ assertEval("import java.util.*;",
+ added(VALID),
+ ste(k1, RECOVERABLE_NOT_DEFINED, VALID, true, null));
+ assertMethodDeclSnippet(k1, "ff", "(List)int", VALID, 0, 0);
+ assertEval("ff(new ArrayList());", "0");
+ assertActiveKeys();
+ }
+
+ public void testForwardVarToMethod() {
+ DeclarationSnippet t = methodKey(assertEval("int t() { return x; }", added(RECOVERABLE_DEFINED)));
+ assertUnresolvedDependencies1(t, RECOVERABLE_DEFINED, "variable x");
+ assertEvalUnresolvedException("t();", "t", 1, 0);
+ Snippet x = varKey(assertEval("int x = 33;", "33",
+ added(VALID),
+ ste(t, RECOVERABLE_DEFINED, VALID, false, null)));
+ assertEval("t();", "33");
+ assertEval("double x = 0.88;",
+ "0.88", null,
+ DiagCheck.DIAG_OK,
+ DiagCheck.DIAG_ERROR,
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(x, VALID, OVERWRITTEN, false, MAIN_SNIPPET),
+ ste(t, VALID, RECOVERABLE_DEFINED, false, MAIN_SNIPPET));
+ assertEvalUnresolvedException("t();", "t", 0, 1);
+ assertActiveKeys();
+ }
+
+ public void testForwardMethodToMethod() {
+ Snippet t = methodKey(assertEval("int t() { return f(); }", added(RECOVERABLE_DEFINED)));
+ Snippet f = methodKey(assertEval("int f() { return g(); }",
+ added(RECOVERABLE_DEFINED),
+ ste(t, RECOVERABLE_DEFINED, VALID, false, null)));
+ assertUnresolvedDependencies1((DeclarationSnippet) f, RECOVERABLE_DEFINED, "method g()");
+ assertEvalUnresolvedException("t();", "f", 1, 0);
+ Snippet g = methodKey(assertEval("int g() { return 55; }",
+ added(VALID),
+ ste(f, RECOVERABLE_DEFINED, VALID, false, null)));
+ assertEval("t();", "55");
+ assertEval("double g() { return 3.14159; }",
+ DiagCheck.DIAG_OK,
+ DiagCheck.DIAG_ERROR,
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(g, VALID, OVERWRITTEN, false, MAIN_SNIPPET),
+ ste(f, VALID, RECOVERABLE_DEFINED, false, MAIN_SNIPPET));
+ MethodSnippet exsn = assertEvalUnresolvedException("t();", "f", 0, 1);
+ assertTrue(exsn == f, "Identity must not change");
+ assertActiveKeys();
+ }
+
+ public void testForwardClassToMethod() {
+ DeclarationSnippet t = methodKey(assertEval("int t() { return new A().f(); }", added(RECOVERABLE_DEFINED)));
+ assertUnresolvedDependencies1(t, RECOVERABLE_DEFINED, "class A");
+ assertEvalUnresolvedException("t();", "t", 1, 0);
+ Snippet a = classKey(assertEval(
+ "class A {\n" +
+ " int f() { return 10; }\n" +
+ "}",
+ added(VALID),
+ ste(t, RECOVERABLE_DEFINED, VALID, false, null)));
+ assertEval("t();", "10");
+ assertEval(
+ "class A {\n" +
+ " double f() { return 88.0; }\n" +
+ "}",
+ DiagCheck.DIAG_OK,
+ DiagCheck.DIAG_ERROR,
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(a, VALID, OVERWRITTEN, false, MAIN_SNIPPET),
+ ste(t, VALID, RECOVERABLE_DEFINED, false, MAIN_SNIPPET));
+ assertEvalUnresolvedException("t();", "t", 0, 1);
+ assertActiveKeys();
+ }
+
+ public void testForwardVarToClass() {
+ DeclarationSnippet a = classKey(assertEval("class A { int f() { return g; } }", added(RECOVERABLE_NOT_DEFINED)));
+ assertUnresolvedDependencies1(a, RECOVERABLE_NOT_DEFINED, "variable g");
+ Snippet g = varKey(assertEval("int g = 10;", "10",
+ added(VALID),
+ ste(a, RECOVERABLE_NOT_DEFINED, VALID, true, null)));
+ assertEval("new A().f();", "10");
+ assertEval("double g = 10;", "10.0", null,
+ DiagCheck.DIAG_OK,
+ DiagCheck.DIAG_ERROR,
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(g, VALID, OVERWRITTEN, false, MAIN_SNIPPET),
+ ste(a, VALID, RECOVERABLE_NOT_DEFINED, true, MAIN_SNIPPET));
+ assertUnresolvedDependencies(a, 0);
+ assertActiveKeys();
+ }
+
+
+ public void testForwardMethodToClass() {
+ DeclarationSnippet a = classKey(assertEval("class A { int f() { return g(); } }", added(RECOVERABLE_NOT_DEFINED)));
+ assertUnresolvedDependencies1(a, RECOVERABLE_NOT_DEFINED, "method g()");
+ Snippet g = methodKey(assertEval("int g() { return 10; }",
+ added(VALID),
+ ste(a, RECOVERABLE_NOT_DEFINED, VALID, true, null)));
+ assertEval("new A().f();", "10");
+ assertEval("double g() { return 10; }",
+ DiagCheck.DIAG_OK,
+ DiagCheck.DIAG_ERROR,
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(g, VALID, OVERWRITTEN, false, MAIN_SNIPPET),
+ ste(a, VALID, RECOVERABLE_NOT_DEFINED, true, MAIN_SNIPPET));
+ assertUnresolvedDependencies(a, 0);
+ assertActiveKeys();
+ }
+
+ public void testForwardClassToClass1() {
+ Snippet a = classKey(assertEval("class A { B b = new B(); }", added(RECOVERABLE_NOT_DEFINED)));
+ assertDeclareFail("new A().b;", "compiler.err.cant.resolve.location");
+
+ Snippet b = classKey(assertEval("class B { public String toString() { return \"B\"; } }",
+ added(VALID),
+ ste(a, RECOVERABLE_NOT_DEFINED, VALID, true, null)));
+ assertEval("new A().b;", "B");
+ assertEval("interface B { }",
+ DiagCheck.DIAG_OK,
+ DiagCheck.DIAG_ERROR,
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(b, VALID, OVERWRITTEN, false, MAIN_SNIPPET),
+ ste(a, VALID, RECOVERABLE_NOT_DEFINED, true, MAIN_SNIPPET));
+ assertDeclareFail("new A().b;", "compiler.err.cant.resolve.location");
+ assertActiveKeys();
+ }
+
+ public void testForwardClassToClass2() {
+ Snippet a = classKey(assertEval("class A extends B { }", added(RECOVERABLE_NOT_DEFINED)));
+ assertDeclareFail("new A();", "compiler.err.cant.resolve.location");
+
+ Snippet b = classKey(assertEval("class B { public String toString() { return \"B\"; } }",
+ added(VALID),
+ ste(a, RECOVERABLE_NOT_DEFINED, VALID, true, null)));
+ assertEval("new A();", "B");
+ assertEval("interface B { }",
+ DiagCheck.DIAG_OK,
+ DiagCheck.DIAG_ERROR,
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(b, VALID, OVERWRITTEN, false, MAIN_SNIPPET),
+ ste(a, VALID, RECOVERABLE_NOT_DEFINED, true, MAIN_SNIPPET));
+ assertDeclareFail("new A();", "compiler.err.cant.resolve.location");
+ assertActiveKeys();
+ }
+
+ public void testForwardClassToClass3() {
+ Snippet a = classKey(assertEval("interface A extends B { static int f() { return 10; } }", added(RECOVERABLE_NOT_DEFINED)));
+ assertDeclareFail("A.f();", "compiler.err.cant.resolve.location");
+
+ Snippet b = classKey(assertEval("interface B { }",
+ added(VALID),
+ ste(a, RECOVERABLE_NOT_DEFINED, VALID, true, null)));
+ assertEval("A.f();", "10");
+ assertEval("class B { }",
+ DiagCheck.DIAG_OK,
+ DiagCheck.DIAG_ERROR,
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(b, VALID, OVERWRITTEN, false, MAIN_SNIPPET),
+ ste(a, VALID, RECOVERABLE_NOT_DEFINED, true, MAIN_SNIPPET));
+ assertDeclareFail("A.f();", "compiler.err.cant.resolve.location");
+ assertActiveKeys();
+ }
+
+ public void testImportDeclare() {
+ Snippet singleImport = importKey(assertEval("import java.util.List;", added(VALID)));
+ Snippet importOnDemand = importKey(assertEval("import java.util.*;", added(VALID)));
+ Snippet singleStaticImport = importKey(assertEval("import static java.lang.Math.abs;", added(VALID)));
+ Snippet staticImportOnDemand = importKey(assertEval("import static java.lang.Math.*;", added(VALID)));
+ assertEval("import java.util.List; //again",
+ ste(MAIN_SNIPPET, VALID, VALID, false, null),
+ ste(singleImport, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertEval("import java.util.*; //again",
+ ste(MAIN_SNIPPET, VALID, VALID, false, null),
+ ste(importOnDemand, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertEval("import static java.lang.Math.abs; //again",
+ ste(MAIN_SNIPPET, VALID, VALID, false, null),
+ ste(singleStaticImport, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertEval("import static java.lang.Math.*; //again",
+ ste(MAIN_SNIPPET, VALID, VALID, false, null),
+ ste(staticImportOnDemand, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertActiveKeys();
+ }
+
+ public void testForwardVariable() {
+ assertEval("int f() { return x; }", added(RECOVERABLE_DEFINED));
+ assertEvalUnresolvedException("f();", "f", 1, 0);
+ assertActiveKeys();
+ }
+
+ public void testLocalClassInUnresolved() {
+ Snippet f = methodKey(assertEval("void f() { class A {} g(); }", added(RECOVERABLE_DEFINED)));
+ assertEval("void g() {}",
+ added(VALID),
+ ste(f, RECOVERABLE_DEFINED, VALID, false, null));
+ assertEval("f();", "");
+ }
+
+ @Test(enabled = false) // TODO 8129420
+ public void testLocalClassEvolve() {
+ Snippet j = methodKey(assertEval("Object j() { return null; }", added(VALID)));
+ assertEval("Object j() { class B {}; return null; }",
+ ste(MAIN_SNIPPET, VALID, VALID, false, null));
+ assertEval("Object j() { class B {}; return new B(); }",
+ ste(MAIN_SNIPPET, VALID, VALID, false, null));
+ assertEval("j().getClass().getSimpleName();", "\"B\"");
+ assertEval("Object j() { class B { int p; public String toString() { return \"Yep\";} }; return new B(); }",
+ ste(MAIN_SNIPPET, VALID, VALID, false, null));
+ assertEval("j().getClass().getSimpleName();", "\"B\"");
+ assertEval("j();", "Yep");
+ }
+
+ public void testForwardSingleImportMethodToMethod() {
+ DeclarationSnippet string = methodKey(assertEval("String string() { return format(\"string\"); }",
+ added(RECOVERABLE_DEFINED)));
+ assertUnresolvedDependencies1(string, RECOVERABLE_DEFINED, "method format(java.lang.String)");
+ assertEvalUnresolvedException("string();", "string", 1, 0);
+ assertEval("import static java.lang.String.format;",
+ added(VALID),
+ ste(string, RECOVERABLE_DEFINED, VALID, false, null));
+ assertEval("string();", "\"string\"");
+
+ assertEval("double format(String s) { return 0; }",
+ DiagCheck.DIAG_OK,
+ DiagCheck.DIAG_ERROR,
+ added(VALID),
+ ste(string, VALID, RECOVERABLE_DEFINED, false, null));
+ assertEvalUnresolvedException("string();", "string", 0, 1);
+ assertActiveKeys();
+ }
+
+ public void testForwardImportMethodOnDemandToMethod() {
+ DeclarationSnippet string = methodKey(assertEval("String string() { return format(\"string\"); }",
+ added(RECOVERABLE_DEFINED)));
+ assertUnresolvedDependencies1(string, RECOVERABLE_DEFINED, "method format(java.lang.String)");
+ assertEvalUnresolvedException("string();", "string", 1, 0);
+ assertEval("import static java.lang.String.*;",
+ added(VALID),
+ ste(string, RECOVERABLE_DEFINED, VALID, false, null));
+ assertEval("string();", "\"string\"");
+
+ assertEval("double format(String s) { return 0; }",
+ DiagCheck.DIAG_OK,
+ DiagCheck.DIAG_ERROR,
+ added(VALID),
+ ste(string, VALID, RECOVERABLE_DEFINED, false, null));
+ assertEvalUnresolvedException("string();", "string", 0, 1);
+ assertActiveKeys();
+ }
+
+ public void testForwardSingleImportFieldToMethod() {
+ DeclarationSnippet pi = methodKey(assertEval("double pi() { return PI; }",
+ added(RECOVERABLE_DEFINED)));
+ assertUnresolvedDependencies1(pi, RECOVERABLE_DEFINED, "variable PI");
+ assertEvalUnresolvedException("pi();", "pi", 1, 0);
+ assertEval("import static java.lang.Math.PI;",
+ added(VALID),
+ ste(pi, RECOVERABLE_DEFINED, VALID, false, null));
+ assertEval("Math.abs(pi() - 3.1415) < 0.001;", "true");
+
+ assertEval("String PI;",
+ DiagCheck.DIAG_OK,
+ DiagCheck.DIAG_ERROR,
+ added(VALID),
+ ste(pi, VALID, RECOVERABLE_DEFINED, false, null));
+ assertEvalUnresolvedException("pi();", "pi", 0, 1);
+ assertActiveKeys();
+ }
+
+ public void testForwardImportFieldOnDemandToMethod() {
+ DeclarationSnippet pi = methodKey(assertEval("double pi() { return PI; }",
+ added(RECOVERABLE_DEFINED)));
+ assertUnresolvedDependencies1(pi, RECOVERABLE_DEFINED, "variable PI");
+ assertEvalUnresolvedException("pi();", "pi", 1, 0);
+ assertEval("import static java.lang.Math.*;",
+ added(VALID),
+ ste(pi, RECOVERABLE_DEFINED, VALID, false, MAIN_SNIPPET));
+ assertEval("Math.abs(pi() - 3.1415) < 0.001;", "true");
+
+ assertEval("String PI;",
+ DiagCheck.DIAG_OK,
+ DiagCheck.DIAG_ERROR,
+ added(VALID),
+ ste(pi, VALID, RECOVERABLE_DEFINED, false, MAIN_SNIPPET));
+ assertEvalUnresolvedException("pi();", "pi", 0, 1);
+ assertActiveKeys();
+ }
+
+ public void testForwardSingleImportMethodToClass1() {
+ PersistentSnippet a = classKey(assertEval("class A { String s = format(\"%d\", 10); }",
+ added(RECOVERABLE_NOT_DEFINED)));
+ assertDeclareFail("new A();", "compiler.err.cant.resolve.location");
+ assertEval("import static java.lang.String.format;",
+ added(VALID),
+ ste(a, RECOVERABLE_NOT_DEFINED, VALID, true, null));
+ assertEval("new A().s;", "\"10\"");
+ PersistentSnippet format = methodKey(assertEval("void format(String s, int d) { }",
+ DiagCheck.DIAG_OK,
+ DiagCheck.DIAG_ERROR,
+ added(VALID),
+ ste(a, VALID, RECOVERABLE_NOT_DEFINED, true, MAIN_SNIPPET)));
+ assertDeclareFail("new A().s;", "compiler.err.cant.resolve.location");
+ assertActiveKeys();
+ assertDrop(format,
+ ste(format, VALID, DROPPED, true, null),
+ ste(a, RECOVERABLE_NOT_DEFINED, VALID, true, format));
+ }
+
+ public void testForwardSingleImportMethodToClass2() {
+ PersistentSnippet a = classKey(assertEval("class A { String s() { return format(\"%d\", 10); } }",
+ added(RECOVERABLE_NOT_DEFINED)));
+ assertDeclareFail("new A();", "compiler.err.cant.resolve.location");
+ assertEval("import static java.lang.String.format;",
+ added(VALID),
+ ste(a, RECOVERABLE_NOT_DEFINED, VALID, true, null));
+ assertEval("new A().s();", "\"10\"");
+ PersistentSnippet format = methodKey(assertEval("void format(String s, int d) { }",
+ DiagCheck.DIAG_OK,
+ DiagCheck.DIAG_ERROR,
+ added(VALID),
+ ste(a, VALID, RECOVERABLE_NOT_DEFINED, true, null)));
+ assertDeclareFail("new A().s();", "compiler.err.cant.resolve.location");
+ assertActiveKeys();
+ assertDrop(format,
+ ste(format, VALID, DROPPED, true, null),
+ ste(a, RECOVERABLE_NOT_DEFINED, VALID, true, format));
+ }
+
+ public void testForwardSingleImportClassToClass1() {
+ PersistentSnippet a = classKey(assertEval("class A { static List<Integer> list; }",
+ added(RECOVERABLE_NOT_DEFINED)));
+ assertDeclareFail("new A();", "compiler.err.cant.resolve.location");
+ assertEval("import java.util.List;",
+ added(VALID),
+ ste(a, RECOVERABLE_NOT_DEFINED, VALID, true, null));
+ assertEval("import java.util.Arrays;", added(VALID));
+ assertEval("A.list = Arrays.asList(1, 2, 3);", "[1, 2, 3]");
+
+ PersistentSnippet list = classKey(assertEval("class List {}",
+ DiagCheck.DIAG_OK,
+ DiagCheck.DIAG_ERROR,
+ added(VALID),
+ ste(a, VALID, RECOVERABLE_NOT_DEFINED, true, null)));
+ assertDeclareFail("A.list = Arrays.asList(1, 2, 3);", "compiler.err.already.defined.static.single.import");
+ assertActiveKeys();
+ assertDrop(list,
+ ste(list, VALID, DROPPED, true, null),
+ ste(a, RECOVERABLE_NOT_DEFINED, VALID, true, list));
+ }
+
+ public void testForwardSingleImportClassToClass2() {
+ PersistentSnippet clsA = classKey(assertEval("class A extends ArrayList<Integer> { }",
+ added(RECOVERABLE_NOT_DEFINED)));
+ assertDeclareFail("new A();", "compiler.err.cant.resolve.location");
+ assertEval("import java.util.ArrayList;",
+ added(VALID),
+ ste(clsA, RECOVERABLE_NOT_DEFINED, VALID, true, MAIN_SNIPPET));
+ Snippet vara = varKey(assertEval("A a = new A();", "[]"));
+
+ PersistentSnippet arraylist = classKey(assertEval("class ArrayList {}",
+ DiagCheck.DIAG_OK,
+ DiagCheck.DIAG_ERROR,
+ added(VALID),
+ ste(clsA, VALID, RECOVERABLE_NOT_DEFINED, true, MAIN_SNIPPET),
+ ste(vara, VALID, RECOVERABLE_NOT_DEFINED, true, clsA)));
+ assertDeclareFail("A a = new A();", "compiler.err.cant.resolve.location",
+ ste(MAIN_SNIPPET, RECOVERABLE_NOT_DEFINED, REJECTED, false, null),
+ ste(vara, RECOVERABLE_NOT_DEFINED, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertActiveKeys();
+ assertDrop(arraylist,
+ ste(arraylist, VALID, DROPPED, true, null),
+ ste(clsA, RECOVERABLE_NOT_DEFINED, VALID, true, arraylist));
+ }
+
+ public void testForwardImportOnDemandMethodToClass1() {
+ PersistentSnippet a = classKey(assertEval("class A { String s = format(\"%d\", 10); }",
+ added(RECOVERABLE_NOT_DEFINED)));
+ assertDeclareFail("new A();", "compiler.err.cant.resolve.location");
+ assertEval("import static java.lang.String.*;",
+ added(VALID),
+ ste(a, RECOVERABLE_NOT_DEFINED, VALID, true, null));
+ assertEval("new A().s;", "\"10\"");
+ PersistentSnippet format = methodKey(assertEval("void format(String s, int d) { }",
+ DiagCheck.DIAG_OK,
+ DiagCheck.DIAG_ERROR,
+ added(VALID),
+ ste(a, VALID, RECOVERABLE_NOT_DEFINED, true, null)));
+ assertDeclareFail("new A().s;", "compiler.err.cant.resolve.location");
+ assertActiveKeys();
+ assertDrop(format,
+ ste(format, VALID, DROPPED, true, null),
+ ste(a, RECOVERABLE_NOT_DEFINED, VALID, true, format));
+ }
+
+ public void testForwardImportOnDemandMethodToClass2() {
+ PersistentSnippet a = classKey(assertEval("class A { String s() { return format(\"%d\", 10); } }",
+ added(RECOVERABLE_NOT_DEFINED)));
+ assertDeclareFail("new A();", "compiler.err.cant.resolve.location");
+ assertEval("import static java.lang.String.*;",
+ added(VALID),
+ ste(a, RECOVERABLE_NOT_DEFINED, VALID, true, null));
+ assertEval("new A().s();", "\"10\"");
+ PersistentSnippet format = methodKey(assertEval("void format(String s, int d) { }",
+ DiagCheck.DIAG_OK,
+ DiagCheck.DIAG_ERROR,
+ added(VALID),
+ ste(a, VALID, RECOVERABLE_NOT_DEFINED, true, null)));
+ assertDeclareFail("new A().s();", "compiler.err.cant.resolve.location");
+ assertActiveKeys();
+ assertDrop(format,
+ ste(format, VALID, DROPPED, true, null),
+ ste(a, RECOVERABLE_NOT_DEFINED, VALID, true, format));
+ }
+
+ public void testForwardImportOnDemandClassToClass1() {
+ PersistentSnippet a = classKey(assertEval("class A { static List<Integer> list; }",
+ added(RECOVERABLE_NOT_DEFINED)));
+ assertDeclareFail("new A();", "compiler.err.cant.resolve.location");
+ assertEval("import java.util.*;",
+ added(VALID),
+ ste(a, RECOVERABLE_NOT_DEFINED, VALID, true, null));
+ assertEval("A.list = Arrays.asList(1, 2, 3);", "[1, 2, 3]");
+
+ PersistentSnippet list = classKey(assertEval("class List {}",
+ DiagCheck.DIAG_OK,
+ DiagCheck.DIAG_ERROR,
+ added(VALID),
+ ste(a, VALID, RECOVERABLE_NOT_DEFINED, true, null)));
+ assertDeclareFail("A.list = Arrays.asList(1, 2, 3);", "compiler.err.cant.resolve.location");
+ assertActiveKeys();
+ assertDrop(list,
+ ste(list, VALID, DROPPED, true, null),
+ ste(a, RECOVERABLE_NOT_DEFINED, VALID, true, list));
+ }
+
+ public void testForwardImportOnDemandClassToClass2() {
+ PersistentSnippet clsA = classKey(assertEval("class A extends ArrayList<Integer> { }",
+ added(RECOVERABLE_NOT_DEFINED)));
+ assertDeclareFail("new A();", "compiler.err.cant.resolve.location");
+ assertEval("import java.util.*;",
+ added(VALID),
+ ste(clsA, RECOVERABLE_NOT_DEFINED, VALID, true, MAIN_SNIPPET));
+ Snippet vara = varKey(assertEval("A a = new A();", "[]"));
+
+ PersistentSnippet arraylist = classKey(assertEval("class ArrayList {}",
+ DiagCheck.DIAG_OK,
+ DiagCheck.DIAG_ERROR,
+ added(VALID),
+ ste(clsA, VALID, RECOVERABLE_NOT_DEFINED, true, MAIN_SNIPPET),
+ ste(vara, VALID, RECOVERABLE_NOT_DEFINED, true, clsA)));
+ assertDeclareFail("new A();", "compiler.err.cant.resolve.location");
+ assertActiveKeys();
+ assertDrop(arraylist,
+ ste(arraylist, VALID, DROPPED, true, null),
+ ste(clsA, RECOVERABLE_NOT_DEFINED, VALID, true, arraylist),
+ ste(vara, RECOVERABLE_NOT_DEFINED, VALID, true, clsA));
+ }
+
+ public void testForwardSingleImportFieldToClass1() {
+ PersistentSnippet a = classKey(assertEval("class A { static double pi() { return PI; } }",
+ added(RECOVERABLE_NOT_DEFINED)));
+ assertDeclareFail("new A();", "compiler.err.cant.resolve.location");
+ assertEval("import static java.lang.Math.PI;",
+ added(VALID),
+ ste(a, RECOVERABLE_NOT_DEFINED, VALID, true, null));
+ assertEval("Math.abs(A.pi() - 3.1415) < 0.001;", "true");
+
+ PersistentSnippet list = varKey(assertEval("String PI;",
+ DiagCheck.DIAG_OK,
+ DiagCheck.DIAG_ERROR,
+ added(VALID),
+ ste(a, VALID, RECOVERABLE_NOT_DEFINED, true, null)));
+ assertDeclareFail("new A();", "compiler.err.cant.resolve.location");
+ assertActiveKeys();
+ assertDrop(list,
+ ste(list, VALID, DROPPED, true, null),
+ ste(a, RECOVERABLE_NOT_DEFINED, VALID, true, list));
+ }
+
+ public void testForwardSingleImportFieldToClass2() {
+ PersistentSnippet a = classKey(assertEval("class A { static double pi = PI; }",
+ added(RECOVERABLE_NOT_DEFINED)));
+ assertDeclareFail("new A();", "compiler.err.cant.resolve.location");
+ assertEval("import static java.lang.Math.PI;",
+ added(VALID),
+ ste(a, RECOVERABLE_NOT_DEFINED, VALID, true, null));
+ assertEval("Math.abs(A.pi - 3.1415) < 0.001;", "true");
+
+ PersistentSnippet list = varKey(assertEval("String PI;",
+ DiagCheck.DIAG_OK,
+ DiagCheck.DIAG_ERROR,
+ added(VALID),
+ ste(a, VALID, RECOVERABLE_NOT_DEFINED, true, null)));
+ assertDeclareFail("new A();", "compiler.err.cant.resolve.location");
+ assertActiveKeys();
+ assertDrop(list,
+ ste(list, VALID, DROPPED, true, null),
+ ste(a, RECOVERABLE_NOT_DEFINED, VALID, true, list));
+ }
+
+ public void testForwardImportOnDemandFieldToClass1() {
+ PersistentSnippet a = classKey(assertEval("class A { static double pi() { return PI; } }",
+ added(RECOVERABLE_NOT_DEFINED)));
+ assertDeclareFail("new A();", "compiler.err.cant.resolve.location");
+ assertEval("import static java.lang.Math.*;",
+ added(VALID),
+ ste(a, RECOVERABLE_NOT_DEFINED, VALID, true, null));
+ assertEval("Math.abs(A.pi() - 3.1415) < 0.001;", "true");
+
+ PersistentSnippet list = varKey(assertEval("String PI;",
+ DiagCheck.DIAG_OK,
+ DiagCheck.DIAG_ERROR,
+ added(VALID),
+ ste(a, VALID, RECOVERABLE_NOT_DEFINED, true, null)));
+ assertDeclareFail("new A();", "compiler.err.cant.resolve.location");
+ assertActiveKeys();
+ assertDrop(list,
+ ste(list, VALID, DROPPED, true, null),
+ ste(a, RECOVERABLE_NOT_DEFINED, VALID, true, list));
+ }
+
+ public void testForwardImportOnDemandFieldToClass2() {
+ PersistentSnippet a = classKey(assertEval("class A { static double pi = PI; }",
+ added(RECOVERABLE_NOT_DEFINED)));
+ assertDeclareFail("new A();", "compiler.err.cant.resolve.location");
+ assertEval("import static java.lang.Math.*;",
+ added(VALID),
+ ste(a, RECOVERABLE_NOT_DEFINED, VALID, true, null));
+ assertEval("Math.abs(A.pi - 3.1415) < 0.001;", "true");
+
+ PersistentSnippet list = varKey(assertEval("String PI;",
+ DiagCheck.DIAG_OK,
+ DiagCheck.DIAG_ERROR,
+ added(VALID),
+ ste(a, VALID, RECOVERABLE_NOT_DEFINED, true, null)));
+ assertDeclareFail("new A();", "compiler.err.cant.resolve.location");
+ assertActiveKeys();
+ assertDrop(list,
+ ste(list, VALID, DROPPED, true, null),
+ ste(a, RECOVERABLE_NOT_DEFINED, VALID, true, list));
+ }
+
+ public void testReplaceCausesMethodReferenceError() {
+ Snippet l = classKey(assertEval("interface Logger { public void log(String message); }", added(VALID)));
+ Snippet v = varKey(assertEval("Logger l = System.out::println;", added(VALID)));
+ assertEval("interface Logger { public boolean accept(String message); }",
+ DiagCheck.DIAG_OK,
+ DiagCheck.DIAG_ERROR,
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(l, VALID, OVERWRITTEN, false, MAIN_SNIPPET),
+ ste(v, VALID, RECOVERABLE_NOT_DEFINED, true, MAIN_SNIPPET));
+ }
+
+ public void testReplaceCausesClassCompilationError() {
+ Snippet l = classKey(assertEval("interface L { }", added(VALID)));
+ Snippet c = classKey(assertEval("class C implements L { }", added(VALID)));
+ assertEval("interface L { void m(); }",
+ DiagCheck.DIAG_OK,
+ DiagCheck.DIAG_ERROR,
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(l, VALID, OVERWRITTEN, false, MAIN_SNIPPET),
+ ste(c, VALID, RECOVERABLE_NOT_DEFINED, true, MAIN_SNIPPET));
+ }
+
+ public void testOverwriteNoUpdate() {
+ String xsi = "int x = 5;";
+ String xsd = "double x = 3.14159;";
+ VarSnippet xi = varKey(assertEval(xsi, added(VALID)));
+ String ms1 = "double m(Integer i) { return i + x; }";
+ String ms2 = "double m(java.lang.Integer i) { return i + x; }";
+ MethodSnippet k1 = methodKey(assertEval(ms1, added(VALID)));
+ VarSnippet xd = varKey(assertEval(xsd,
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(xi, VALID, OVERWRITTEN, false, MAIN_SNIPPET),
+ ste(k1, VALID, VALID, false, MAIN_SNIPPET)));
+ MethodSnippet k2 = methodKey(assertEval(ms2,
+ ste(MAIN_SNIPPET, VALID, VALID, true, null), //TODO: technically, should be false
+ ste(k1, VALID, OVERWRITTEN, false, MAIN_SNIPPET)));
+ VarSnippet xi2 = varKey(assertEval(xsi,
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(xd, VALID, OVERWRITTEN, false, MAIN_SNIPPET),
+ ste(k2, VALID, VALID, false, MAIN_SNIPPET)));
+ varKey(assertEval(xsd,
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(xi2, VALID, OVERWRITTEN, false, MAIN_SNIPPET),
+ ste(k2, VALID, VALID, false, MAIN_SNIPPET)));
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/ShutdownTest.java Thu Oct 22 11:12:55 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.
+ *
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Shutdown tests
+ * @build KullaTesting TestingInputStream
+ * @run testng ShutdownTest
+ */
+
+import java.util.function.Consumer;
+
+import jdk.jshell.JShell;
+import jdk.jshell.JShell.Subscription;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+
+@Test
+public class ShutdownTest extends KullaTesting {
+
+ int shutdownCount;
+
+ void shutdownCounter(JShell state) {
+ ++shutdownCount;
+ }
+
+ @Test(enabled = false) //TODO 8139873
+ public void testExit() {
+ shutdownCount = 0;
+ getState().onShutdown(this::shutdownCounter);
+ assertEval("System.exit(1);");
+ assertEquals(shutdownCount, 1);
+ }
+
+ public void testCloseCallback() {
+ shutdownCount = 0;
+ getState().onShutdown(this::shutdownCounter);
+ getState().close();
+ assertEquals(shutdownCount, 1);
+ }
+
+ public void testCloseUnsubscribe() {
+ shutdownCount = 0;
+ Subscription token = getState().onShutdown(this::shutdownCounter);
+ getState().unsubscribe(token);
+ getState().close();
+ assertEquals(shutdownCount, 0);
+ }
+
+ public void testTwoShutdownListeners() {
+ ShutdownListener listener1 = new ShutdownListener();
+ ShutdownListener listener2 = new ShutdownListener();
+ Subscription subscription1 = getState().onShutdown(listener1);
+ Subscription subscription2 = getState().onShutdown(listener2);
+ getState().unsubscribe(subscription1);
+ getState().close();
+
+ assertEquals(listener1.getEvents(), 0, "Checking got events");
+ assertEquals(listener2.getEvents(), 1, "Checking got events");
+
+ getState().close();
+
+ assertEquals(listener1.getEvents(), 0, "Checking got events");
+ assertEquals(listener2.getEvents(), 1, "Checking got events");
+
+ getState().unsubscribe(subscription2);
+ }
+
+ @Test(expectedExceptions = IllegalStateException.class)
+ public void testCloseException() {
+ getState().close();
+ getState().eval("45");
+ }
+
+ @Test(expectedExceptions = IllegalStateException.class,
+ enabled = false) //TODO 8139873
+ public void testShutdownException() {
+ assertEval("System.exit(0);");
+ getState().eval("45");
+ }
+
+ @Test(expectedExceptions = NullPointerException.class)
+ public void testNullCallback() {
+ getState().onShutdown(null);
+ }
+
+ @Test(expectedExceptions = IllegalStateException.class)
+ public void testSubscriptionAfterClose() {
+ getState().close();
+ getState().onShutdown(e -> {});
+ }
+
+ @Test(expectedExceptions = IllegalStateException.class,
+ enabled = false) //TODO 8139873
+ public void testSubscriptionAfterShutdown() {
+ assertEval("System.exit(0);");
+ getState().onShutdown(e -> {});
+ }
+
+ private static class ShutdownListener implements Consumer<JShell> {
+ private int count;
+
+ @Override
+ public void accept(JShell shell) {
+ ++count;
+ }
+
+ public int getEvents() {
+ return count;
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/SimpleRegressionTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,101 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary simple regression test
+ * @build KullaTesting TestingInputStream
+ * @run testng SimpleRegressionTest
+ */
+
+
+import java.util.List;
+
+import javax.tools.Diagnostic;
+
+import jdk.jshell.Snippet;
+import jdk.jshell.VarSnippet;
+import jdk.jshell.SnippetEvent;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static jdk.jshell.Snippet.SubKind.TEMP_VAR_EXPRESSION_SUBKIND;
+import static jdk.jshell.Snippet.Status.VALID;
+
+@Test
+public class SimpleRegressionTest extends KullaTesting {
+
+ public void testSnippetMemberAssignment() {
+ assertEval("class C { int y; }");
+ assertEval("C c = new C();");
+ assertVarKeyMatch("c.y = 4;", true, "$1", TEMP_VAR_EXPRESSION_SUBKIND, "int", added(VALID));
+ }
+
+ public void testUserTakesTempVarName() {
+ assertEval("int $2 = 4;");
+ assertEval("String $1;");
+ assertVarKeyMatch("1234;", true, "$3", TEMP_VAR_EXPRESSION_SUBKIND, "int", added(VALID));
+ }
+
+ public void testCompileThrow() {
+ assertEvalException("throw new Exception();");
+ }
+
+ public void testMultiSnippetDependencies() {
+ List<SnippetEvent> events = assertEval("int a = 3, b = a+a, c = b *100;",
+ DiagCheck.DIAG_OK, DiagCheck.DIAG_OK,
+ chain(added(VALID)),
+ chain(added(VALID)),
+ chain(added(VALID)));
+ assertEquals(events.get(0).value(), "3");
+ assertEquals(events.get(1).value(), "6");
+ assertEquals(events.get(2).value(), "600");
+ assertEval("c;", "600");
+ }
+
+ public void testNotStmtCannotResolve() {
+ assertDeclareFail("dfasder;", new ExpectedDiagnostic("compiler.err.cant.resolve.location", 0, 7, 0, -1, -1, Diagnostic.Kind.ERROR));
+ }
+
+ public void testNotStmtIncomparable() {
+ assertDeclareFail("true == 5.0;", new ExpectedDiagnostic("compiler.err.incomparable.types", 0, 11, 5, -1, -1, Diagnostic.Kind.ERROR));
+ }
+
+ public void testStringAdd() {
+ assertEval("String s = \"a\" + \"b\";", "\"ab\"");
+ }
+
+ public void testExprSanity() {
+ assertEval("int x = 3;", "3");
+ assertEval("int y = 4;", "4");
+ assertEval("x + y;", "7");
+ assertActiveKeys();
+ }
+
+ public void testGenericMethodCrash() {
+ assertDeclareWarn1("<T> void f(T...a) {}", (ExpectedDiagnostic) null);
+ Snippet sn = methodKey(assertEval("<R> R n(R x) { return x; }", added(VALID)));
+ VarSnippet sne = varKey(assertEval("n(5)", added(VALID)));
+ assertEquals(sne.typeName(), "Integer");
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/SnippetStatusListenerTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,118 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Subscribe tests
+ * @build KullaTesting TestingInputStream
+ * @run testng SnippetStatusListenerTest
+ */
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
+
+import jdk.jshell.DeclarationSnippet;
+import jdk.jshell.JShell.Subscription;
+import jdk.jshell.SnippetEvent;
+import jdk.jshell.TypeDeclSnippet;
+import org.testng.annotations.Test;
+
+import static jdk.jshell.Snippet.Status.*;
+import static org.testng.Assert.assertEquals;
+
+@Test
+public class SnippetStatusListenerTest extends KullaTesting {
+
+ public void testTwoSnippetEventListeners() {
+ SnippetListener listener1 = new SnippetListener();
+ SnippetListener listener2 = new SnippetListener();
+
+ Subscription subscription1 = getState().onSnippetEvent(listener1);
+ getState().onSnippetEvent(listener2);
+
+ TypeDeclSnippet a = classKey(assertEval("class A {}"));
+ assertEval("interface A {}",
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(a, VALID, OVERWRITTEN, false, null));
+ DeclarationSnippet f = (DeclarationSnippet) assertDeclareFail("double f() { }", "compiler.err.missing.ret.stmt");
+ assertEvalException("throw new RuntimeException();");
+ assertEval("int a = 0;");
+
+ List<SnippetEvent> events1 = Collections.unmodifiableList(listener1.getEvents());
+ assertEquals(events1, listener2.getEvents(), "Checking got events");
+ getState().unsubscribe(subscription1);
+
+ assertDrop(f, DiagCheck.DIAG_IGNORE, DiagCheck.DIAG_IGNORE, ste(f, REJECTED, DROPPED, false, null));
+ assertEval("void f() { }", ste(MAIN_SNIPPET, DROPPED, VALID, true, null));
+ assertEvalException("throw new RuntimeException();");
+ assertEquals(listener1.getEvents(), events1, "Checking that unsubscribed listener does not get events");
+
+ List<SnippetEvent> events2 = new ArrayList<>(listener2.getEvents());
+ events2.removeAll(events1);
+
+ assertEquals(events2.size(), 3, "The second listener got events");
+ }
+
+ @Test(expectedExceptions = NullPointerException.class)
+ public void testNullCallback() {
+ getState().onSnippetEvent(null);
+ }
+
+ @Test(expectedExceptions = IllegalStateException.class)
+ public void testSubscriptionAfterClose() {
+ getState().close();
+ getState().onSnippetEvent(e -> {});
+ }
+
+ @Test(expectedExceptions = IllegalStateException.class,
+ enabled = false) //TODO 8139873
+ public void testSubscriptionAfterShutdown() {
+ assertEval("System.exit(0);");
+ getState().onSnippetEvent(e -> {});
+ }
+
+ public void testSubscriptionToAnotherState() {
+ SnippetListener listener = new SnippetListener();
+ Subscription subscription = getState().onSnippetEvent(listener);
+ tearDown();
+ setUp();
+ assertEval("int x;");
+ assertEquals(Collections.emptyList(), listener.getEvents(), "No events");
+ getState().unsubscribe(subscription);
+ }
+
+ private static class SnippetListener implements Consumer<SnippetEvent> {
+ private final List<SnippetEvent> events = new ArrayList<>();
+
+ @Override
+ public void accept(SnippetEvent event) {
+ events.add(event);
+ }
+
+ public List<SnippetEvent> getEvents() {
+ return events;
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/SnippetTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,151 @@
+/*
+ * 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.
+ *
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary test accessors of Snippet
+ * @build KullaTesting TestingInputStream
+ * @run testng SnippetTest
+ */
+
+import jdk.jshell.Snippet;
+import jdk.jshell.DeclarationSnippet;
+import org.testng.annotations.Test;
+
+import static jdk.jshell.Snippet.Status.VALID;
+import static jdk.jshell.Snippet.Status.RECOVERABLE_DEFINED;
+import static jdk.jshell.Snippet.Status.OVERWRITTEN;
+import static jdk.jshell.Snippet.SubKind.*;
+
+@Test
+public class SnippetTest extends KullaTesting {
+
+ public void testImportKey() {
+ assertImportKeyMatch("import java.util.List;", "List", SINGLE_TYPE_IMPORT_SUBKIND, added(VALID));
+ assertImportKeyMatch("import java.util.*;", "java.util.*", TYPE_IMPORT_ON_DEMAND_SUBKIND, added(VALID));
+ assertImportKeyMatch("import static java.lang.String.*;", "java.lang.String.*", STATIC_IMPORT_ON_DEMAND_SUBKIND, added(VALID));
+ }
+
+ public void testClassKey() {
+ assertDeclarationKeyMatch("class X {}", false, "X", CLASS_SUBKIND, added(VALID));
+ }
+
+ public void testInterfaceKey() {
+ assertDeclarationKeyMatch("interface I {}", false, "I", INTERFACE_SUBKIND, added(VALID));
+ }
+
+ public void testEnumKey() {
+ assertDeclarationKeyMatch("enum E {}", false, "E", ENUM_SUBKIND, added(VALID));
+ }
+
+ public void testAnnotationKey() {
+ assertDeclarationKeyMatch("@interface A {}", false, "A", ANNOTATION_TYPE_SUBKIND, added(VALID));
+ }
+
+ public void testMethodKey() {
+ assertDeclarationKeyMatch("void m() {}", false, "m", METHOD_SUBKIND, added(VALID));
+ }
+
+ public void testVarDeclarationKey() {
+ assertVarKeyMatch("int a;", false, "a", VAR_DECLARATION_SUBKIND, "int", added(VALID));
+ }
+
+ public void testVarDeclarationWithInitializerKey() {
+ assertVarKeyMatch("double b = 9.0;", true, "b", VAR_DECLARATION_WITH_INITIALIZER_SUBKIND, "double", added(VALID));
+ }
+
+ public void testTempVarExpressionKey() {
+ assertVarKeyMatch("47;", true, "$1", TEMP_VAR_EXPRESSION_SUBKIND, "int", added(VALID));
+ }
+
+ public void testVarValueKey() {
+ assertEval("double x = 4;", "4.0");
+ assertExpressionKeyMatch("x;", "x", VAR_VALUE_SUBKIND, "double");
+ }
+
+ public void testAssignmentKey() {
+ assertEval("int y;");
+ assertExpressionKeyMatch("y = 4;", "y", ASSIGNMENT_SUBKIND, "int");
+ }
+
+ public void testStatementKey() {
+ assertKeyMatch("if (true) {}", true, STATEMENT_SUBKIND, added(VALID));
+ assertKeyMatch("while (true) { break; }", true, STATEMENT_SUBKIND, added(VALID));
+ assertKeyMatch("do { } while (false);", true, STATEMENT_SUBKIND, added(VALID));
+ assertKeyMatch("for (;;) { break; }", true, STATEMENT_SUBKIND, added(VALID));
+ }
+
+ public void noKeys() {
+ assertActiveKeys(new DeclarationSnippet[0]);
+ }
+
+ public void testKeyId1() {
+ Snippet a = classKey(assertEval("class A { }"));
+ assertEval("void f() { }");
+ assertEval("int f;");
+ assertEval("interface A { }",
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(a, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertKeys(method("()void", "f"), variable("int", "f"), clazz(KullaTesting.ClassType.INTERFACE, "A"));
+ assertActiveKeys();
+ }
+
+ @Test(enabled = false) // TODO 8081689
+ public void testKeyId2() {
+ Snippet g = methodKey(assertEval("void g() { f(); }", added(RECOVERABLE_DEFINED)));
+ Snippet f = methodKey(assertEval("void f() { }",
+ added(VALID),
+ ste(g, RECOVERABLE_DEFINED, VALID, false, null)));
+ assertEval("int f;");
+ assertEval("interface A { }");
+ assertKeys(method("()void", "g"), method("()void", "f"), variable("int", "f"),
+ clazz(KullaTesting.ClassType.INTERFACE, "A"));
+ assertActiveKeys();
+ assertEval("double f() { return 0.0; }",
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(f, VALID, OVERWRITTEN, false, MAIN_SNIPPET),
+ ste(g, VALID, VALID, false, MAIN_SNIPPET));
+ assertKeys(method("()void", "g"), method("()double", "f"), variable("int", "f"),
+ clazz(KullaTesting.ClassType.INTERFACE, "A"));
+ assertActiveKeys();
+ }
+
+ public void testKeyId3() {
+ Snippet g = methodKey(assertEval("void g() { f(); }", added(RECOVERABLE_DEFINED)));
+ Snippet f = methodKey(assertEval("void f() { }",
+ added(VALID),
+ ste(g, RECOVERABLE_DEFINED, VALID, false, null)));
+ assertDeclareFail("qqqq;", "compiler.err.cant.resolve.location");
+ assertEval("interface A { }");
+ assertKeys(method("()void", "g"), method("()void", "f"),
+ clazz(KullaTesting.ClassType.INTERFACE, "A"));
+ assertActiveKeys();
+ assertEval("double f() { return 0.0; }",
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(f, VALID, OVERWRITTEN, false, MAIN_SNIPPET),
+ ste(g, VALID, VALID, false, MAIN_SNIPPET));
+ assertKeys(method("()void", "g"), clazz(KullaTesting.ClassType.INTERFACE, "A"),
+ method("()double", "f"));
+ assertActiveKeys();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/StartOptionTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,138 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Testing start-up options.
+ * @library /tools/lib
+ * @build Compiler ToolBox
+ * @run testng StartOptionTest
+ */
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.util.function.Consumer;
+
+import jdk.internal.jshell.tool.JShellTool;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+@Test
+public class StartOptionTest {
+
+ private ByteArrayOutputStream out;
+ private ByteArrayOutputStream err;
+
+ private JShellTool getShellTool() {
+ return new JShellTool(null, new PrintStream(out), new PrintStream(err), null, null, null, null);
+ }
+
+ private String getOutput() {
+ byte[] bytes = out.toByteArray();
+ out.reset();
+ return new String(bytes, StandardCharsets.UTF_8);
+ }
+
+ private String getError() {
+ byte[] bytes = err.toByteArray();
+ err.reset();
+ return new String(bytes, StandardCharsets.UTF_8);
+ }
+
+ private void start(Consumer<String> checkOutput, Consumer<String> checkError, String... args) throws Exception {
+ JShellTool tool = getShellTool();
+ tool.start(args);
+ if (checkOutput != null) {
+ checkOutput.accept(getOutput());
+ } else {
+ assertEquals("", getOutput(), "Output: ");
+ }
+ if (checkError != null) {
+ checkError.accept(getError());
+ } else {
+ assertEquals("", getError(), "Error: ");
+ }
+ }
+
+ private void start(String expectedOutput, String expectedError, String... args) throws Exception {
+ start(s -> assertEquals(s, expectedOutput, "Output: "), s -> assertEquals(s, expectedError, "Error: "), args);
+ }
+
+ @BeforeMethod
+ public void setUp() {
+ out = new ByteArrayOutputStream();
+ err = new ByteArrayOutputStream();
+ }
+
+ @Test
+ public void testUsage() throws Exception {
+ start(s -> {
+ assertTrue(s.split("\n").length >= 7, s);
+ assertTrue(s.startsWith("Usage: jshell <options>"), s);
+ }, null, "-help");
+ }
+
+ @Test
+ public void testUnknown() throws Exception {
+ start(s -> {
+ assertTrue(s.split("\n").length >= 7, s);
+ assertTrue(s.startsWith("Usage: jshell <options>"), s);
+ }, s -> assertEquals(s, "Unknown option: -unknown\n"), "-unknown");
+ }
+
+ @Test(enabled = false) // TODO 8080883
+ public void testStartup() throws Exception {
+ Compiler compiler = new Compiler();
+ Path p = compiler.getPath("file.txt");
+ compiler.writeToFile(p);
+ start("", "Argument to -startup missing.\n", "-startup");
+ start("", "Conflicting -startup or -nostartup option.\n", "-startup", p.toString(), "-startup", p.toString());
+ start("", "Conflicting -startup or -nostartup option.\n", "-nostartup", "-startup", p.toString());
+ start("", "Conflicting -startup option.\n", "-startup", p.toString(), "-nostartup");
+ }
+
+ @Test
+ public void testClasspath() throws Exception {
+ for (String cp : new String[] {"-cp", "-classpath"}) {
+ start("", "Conflicting -classpath option.\n", cp, ".", "-classpath", ".");
+ start("", "Argument to -classpath missing.\n", cp);
+ }
+ }
+
+ @Test
+ public void testVersion() throws Exception {
+ start(s -> assertTrue(s.startsWith("jshell")), null, "-version");
+ }
+
+ @AfterMethod
+ public void tearDown() {
+ out = null;
+ err = null;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/StopExecutionTest.java Thu Oct 22 11:12:55 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.
+ *
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Test JShell#stop
+ * @build KullaTesting TestingInputStream
+ * @run testng StopExecutionTest
+ */
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Random;
+
+import jdk.internal.jshell.tool.StopDetectingInputStream;
+import jdk.internal.jshell.tool.StopDetectingInputStream.State;
+import jdk.jshell.JShell;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.fail;
+
+@Test
+public class StopExecutionTest extends KullaTesting {
+
+ private final Object lock = new Object();
+ private boolean isStopped;
+
+ @Test(enabled = false) // TODO 8129546
+ public void testStopLoop() throws InterruptedException {
+ scheduleStop("while (true) ;");
+ }
+
+ @Test(enabled = false) // TODO 8129546
+ public void testStopASleep() throws InterruptedException {
+ scheduleStop("while (true) { try { Thread.sleep(100); } catch (InterruptedException ex) { } }");
+ }
+
+ @Test(enabled = false) // TODO 8129546
+ public void testScriptCatchesStop() throws Exception {
+ scheduleStop("for (int i = 0; i < 30; i++) { try { Thread.sleep(100); } catch (Throwable ex) { } }");
+ }
+
+ private void scheduleStop(String src) throws InterruptedException {
+ JShell state = getState();
+ isStopped = false;
+ StringWriter writer = new StringWriter();
+ PrintWriter out = new PrintWriter(writer);
+ Thread t = new Thread(() -> {
+ int i = 1;
+ int n = 30;
+ synchronized (lock) {
+ do {
+ state.stop();
+ if (!isStopped) {
+ out.println("Not stopped. Try again: " + i);
+ try {
+ lock.wait(1000);
+ } catch (InterruptedException ignored) {
+ }
+ }
+ } while (i++ < n && !isStopped);
+ if (!isStopped) {
+ System.err.println(writer.toString());
+ fail("Evaluation was not stopped: '" + src + "'");
+ }
+ }
+ });
+ t.start();
+ assertEval(src);
+ synchronized (lock) {
+ out.println("Evaluation was stopped successfully: '" + src + "'");
+ isStopped = true;
+ lock.notify();
+ }
+ // wait until the background thread finishes to prevent from calling 'stop' on closed state.
+ t.join();
+ }
+
+ public void testStopDetectingInputRandom() throws IOException {
+ long seed = System.nanoTime();
+ Random r = new Random(seed);
+
+ for (int m = 0; m < 10; m++) {
+ StopDetectingInputStream buffer = new StopDetectingInputStream(null, null);
+
+ buffer.setState(State.BUFFER);
+
+ for (int i = 0; i < 1000; i++) {
+ int chunkSize = r.nextInt(StopDetectingInputStream.INITIAL_SIZE * 3);
+
+ doChunk(buffer, chunkSize);
+ }
+ }
+ }
+
+ private void doChunk(StopDetectingInputStream buffer, int chunkSize) throws IOException {
+ for (int c = 0; c < chunkSize; c++) {
+ buffer.write(c);
+ }
+
+ for (int c = 0; c < chunkSize; c++) {
+ int read = buffer.read();
+
+ assertEquals(read, c);
+ }
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/TestingInputStream.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+import java.io.ByteArrayInputStream;
+
+class TestingInputStream extends ByteArrayInputStream {
+
+ TestingInputStream() {
+ super(new byte[0]);
+ }
+
+ void setInput(String s) {
+ this.buf = s.getBytes();
+ this.pos = 0;
+ this.count = buf.length;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/ToolBasicTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,846 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Tests for Basic tests for REPL tool
+ * @ignore 8139873
+ * @library /tools/lib
+ * @build KullaTesting TestingInputStream ToolBox Compiler
+ * @run testng ToolBasicTest
+ */
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Scanner;
+import java.util.function.Consumer;
+import java.util.prefs.BackingStoreException;
+import java.util.prefs.Preferences;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+@Test
+public class ToolBasicTest extends ReplToolTesting {
+
+ public void defineVar() {
+ test(
+ (a) -> assertCommand(a, "int x = 72", "| Added variable x of type int with initial value 72\n"),
+ (a) -> assertCommand(a, "x", "| Variable x of type int has value 72\n"),
+ (a) -> assertCommand(a, "/vars", "| int x = 72\n")
+ );
+ }
+
+ public void defineUnresolvedVar() {
+ test(
+ (a) -> assertCommand(a, "undefined x",
+ "| Added variable x, however, it cannot be referenced until class undefined is declared\n"),
+ (a) -> assertCommand(a, "/vars", "| undefined x = (not-active)\n")
+ );
+ }
+
+ public void testUnresolved() {
+ test(
+ (a) -> assertCommand(a, "int f() { return g() + x + new A().a; }",
+ "| Added method f(), however, it cannot be invoked until method g(), variable x, and class A are declared\n"),
+ (a) -> assertCommand(a, "f()",
+ "| Attempted to call f which cannot be invoked until method g(), variable x, and class A are declared\n"),
+ (a) -> assertCommand(a, "int g() { return x; }",
+ "| Added method g(), however, it cannot be invoked until variable x is declared\n"),
+ (a) -> assertCommand(a, "g()", "| Attempted to call g which cannot be invoked until variable x is declared\n")
+ );
+ }
+
+ public void elideStartUpFromList() {
+ test(
+ (a) -> assertCommandCheckOutput(a, "123", (s) ->
+ assertTrue(s.contains("type int"), s)),
+ (a) -> assertCommandCheckOutput(a, "/list", (s) -> {
+ int cnt;
+ try (Scanner scanner = new Scanner(s)) {
+ cnt = 0;
+ while (scanner.hasNextLine()) {
+ String line = scanner.nextLine();
+ if (!line.trim().isEmpty()) {
+ ++cnt;
+ }
+ }
+ }
+ assertEquals(cnt, 1, "Expected only one listed line");
+ })
+ );
+ }
+
+ public void elideStartUpFromSave() throws IOException {
+ Compiler compiler = new Compiler();
+ Path path = compiler.getPath("myfile");
+ test(
+ (a) -> assertCommandCheckOutput(a, "123",
+ (s) -> assertTrue(s.contains("type int"), s)),
+ (a) -> assertCommand(a, "/save " + path.toString(), "")
+ );
+ try (Stream<String> lines = Files.lines(path)) {
+ assertEquals(lines.count(), 1, "Expected only one saved line");
+ }
+ }
+
+ public void testInterrupt() {
+ ReplTest interrupt = (a) -> assertCommand(a, "\u0003", "");
+ for (String s : new String[] { "", "\u0003" }) {
+ test(false, new String[]{"-nostartup"},
+ (a) -> assertCommand(a, "int a = 2 +" + s, ""),
+ interrupt,
+ (a) -> assertCommand(a, "int a\u0003", ""),
+ (a) -> assertCommand(a, "int a = 2 + 2\u0003", ""),
+ (a) -> assertCommandCheckOutput(a, "/v", assertVariables()),
+ (a) -> evaluateExpression(a, "int", "2", "2"),
+ (a) -> assertCommandCheckOutput(a, "/v", assertVariables()),
+ (a) -> assertCommand(a, "void f() {", ""),
+ (a) -> assertCommand(a, "int q = 10;" + s, ""),
+ interrupt,
+ (a) -> assertCommand(a, "void f() {}\u0003", ""),
+ (a) -> assertCommandCheckOutput(a, "/m", assertMethods()),
+ (a) -> assertMethod(a, "int f() { return 0; }", "()int", "f"),
+ (a) -> assertCommandCheckOutput(a, "/m", assertMethods()),
+ (a) -> assertCommand(a, "class A {" + s, ""),
+ interrupt,
+ (a) -> assertCommand(a, "class A {}\u0003", ""),
+ (a) -> assertCommandCheckOutput(a, "/c", assertClasses()),
+ (a) -> assertClass(a, "interface A {}", "interface", "A"),
+ (a) -> assertCommandCheckOutput(a, "/c", assertClasses())
+ );
+ }
+ }
+
+ private final Object lock = new Object();
+ private PrintWriter out;
+ private boolean isStopped;
+ private Thread t;
+ private void assertStop(boolean after, String cmd, String output) {
+ if (!after) {
+ isStopped = false;
+ StringWriter writer = new StringWriter();
+ out = new PrintWriter(writer);
+ setCommandInput(cmd + "\n");
+ t = new Thread(() -> {
+ try {
+ // no chance to know whether cmd is being evaluated
+ Thread.sleep(5000);
+ } catch (InterruptedException ignored) {
+ }
+ int i = 1;
+ int n = 30;
+ synchronized (lock) {
+ do {
+ setCommandInput("\u0003");
+ if (!isStopped) {
+ out.println("Not stopped. Try again: " + i);
+ try {
+ lock.wait(1000);
+ } catch (InterruptedException ignored) {
+ }
+ }
+ } while (i++ < n && !isStopped);
+ if (!isStopped) {
+ System.err.println(writer.toString());
+ fail("Evaluation was not stopped: '" + cmd + "'");
+ }
+ }
+ });
+ t.start();
+ } else {
+ synchronized (lock) {
+ out.println("Evaluation was stopped successfully: '" + cmd + "'");
+ isStopped = true;
+ lock.notify();
+ }
+ try {
+ t.join();
+ t = null;
+ } catch (InterruptedException ignored) {
+ }
+ assertOutput(getCommandOutput(), "", "command");
+ assertOutput(getCommandErrorOutput(), "", "command error");
+ assertOutput(getUserOutput(), output, "user");
+ assertOutput(getUserErrorOutput(), "", "user error");
+ }
+ }
+
+ public void testStop() {
+ test(
+ (a) -> assertStop(a, "while (true) {}", "Killed.\n"),
+ (a) -> assertStop(a, "while (true) { try { Thread.sleep(100); } catch (InterruptedException ex) { } }", "Killed.\n")
+ );
+ }
+
+ @Test(enabled = false) // TODO 8130450
+ public void testRerun() {
+ test(false, new String[] {"-nostartup"},
+ (a) -> assertCommand(a, "/0", "| Cannot find snippet 0\n"),
+ (a) -> assertCommand(a, "/5", "| Cannot find snippet 5\n")
+ );
+ String[] codes = new String[] {
+ "int a = 0;", // var
+ "class A {}", // class
+ "void f() {}", // method
+ "bool b;", // active failed
+ "void g() { h(); }", // active corralled
+ };
+ List<ReplTest> tests = new ArrayList<>();
+ for (String s : codes) {
+ tests.add((a) -> assertCommand(a, s, null));
+ }
+ for (int i = 0; i < codes.length; ++i) {
+ final int finalI = i;
+ Consumer<String> check = (s) -> {
+ String[] ss = s.split("\n");
+ assertEquals(ss[0], codes[finalI]);
+ assertTrue(ss.length > 1, s);
+ };
+ tests.add((a) -> assertCommandCheckOutput(a, "/" + (finalI + 1), check));
+ }
+ for (int i = 0; i < codes.length; ++i) {
+ final int finalI = i;
+ Consumer<String> check = (s) -> {
+ String[] ss = s.split("\n");
+ assertEquals(ss[0], codes[codes.length - finalI - 1]);
+ assertTrue(ss.length > 1, s);
+ };
+ tests.add((a) -> assertCommandCheckOutput(a, "/-" + (finalI + 1), check));
+ }
+ tests.add((a) -> assertCommandCheckOutput(a, "/!", assertStartsWith("void g() { h(); }")));
+ test(false, new String[]{"-nostartup"},
+ tests.toArray(new ReplTest[tests.size()]));
+ }
+
+ public void testRemaining() {
+ test(
+ (a) -> assertCommand(a, "int z; z =", "| Added variable z of type int\n"),
+ (a) -> assertCommand(a, "5", "| Variable z has been assigned the value 5\n"),
+ (a) -> assertCommand(a, "/*nada*/; int q =", ""),
+ (a) -> assertCommand(a, "77", "| Added variable q of type int with initial value 77\n"),
+ (a) -> assertCommand(a, "//comment;", ""),
+ (a) -> assertCommand(a, "int v;", "| Added variable v of type int\n"),
+ (a) -> assertCommand(a, "int v; int c", "| Added variable c of type int\n")
+ );
+ }
+
+ public void testDebug() {
+ test(
+ (a) -> assertCommand(a, "/db", "| Debugging on\n"),
+ (a) -> assertCommand(a, "/debug", "| Debugging off\n"),
+ (a) -> assertCommand(a, "/debug", "| Debugging on\n"),
+ (a) -> assertCommand(a, "/db", "| Debugging off\n")
+ );
+ }
+
+ public void testHelp() {
+ Consumer<String> testOutput = (s) -> {
+ List<String> ss = Stream.of(s.split("\n"))
+ .filter(l -> !l.isEmpty())
+ .collect(Collectors.toList());
+ assertTrue(ss.size() >= 5, "Help does not print enough lines:\n" + s);
+ };
+ test(
+ (a) -> assertCommandCheckOutput(a, "/?", testOutput),
+ (a) -> assertCommandCheckOutput(a, "/help", testOutput)
+ );
+ }
+
+ public void oneLineOfError() {
+ test(
+ (a) -> assertCommand(a, "12+", null),
+ (a) -> assertCommandCheckOutput(a, " true", (s) ->
+ assertTrue(s.contains("12+") && !s.contains("true"), "Output: '" + s + "'"))
+ );
+ }
+
+ public void defineVariables() {
+ test(
+ (a) -> assertCommandCheckOutput(a, "/l", assertList()),
+ (a) -> assertCommandCheckOutput(a, "/list", assertList()),
+ (a) -> assertCommandCheckOutput(a, "/v", assertVariables()),
+ (a) -> assertCommandCheckOutput(a, "/vars", assertVariables()),
+ (a) -> assertVariable(a, "int", "a"),
+ (a) -> assertCommandCheckOutput(a, "/l", assertList()),
+ (a) -> assertCommandCheckOutput(a, "/list", assertList()),
+ (a) -> assertCommandCheckOutput(a, "/v", assertVariables()),
+ (a) -> assertCommandCheckOutput(a, "/vars", assertVariables()),
+ (a) -> assertVariable(a, "double", "a", "1", "1.0"),
+ (a) -> assertCommandCheckOutput(a, "/l", assertList()),
+ (a) -> assertCommandCheckOutput(a, "/list", assertList()),
+ (a) -> assertCommandCheckOutput(a, "/v", assertVariables()),
+ (a) -> assertCommandCheckOutput(a, "/vars", assertVariables()),
+ (a) -> evaluateExpression(a, "double", "2 * a", "2.0"),
+ (a) -> assertCommandCheckOutput(a, "/l", assertList()),
+ (a) -> assertCommandCheckOutput(a, "/list", assertList()),
+ (a) -> assertCommandCheckOutput(a, "/v", assertVariables()),
+ (a) -> assertCommandCheckOutput(a, "/vars", assertVariables())
+ );
+ }
+
+ public void defineMethods() {
+ test(
+ (a) -> assertCommandCheckOutput(a, "/l", assertList()),
+ (a) -> assertCommandCheckOutput(a, "/list", assertList()),
+ (a) -> assertCommandCheckOutput(a, "/m", assertMethods()),
+ (a) -> assertCommandCheckOutput(a, "/methods", assertMethods()),
+ (a) -> assertMethod(a, "int f() { return 0; }", "()int", "f"),
+ (a) -> assertCommandCheckOutput(a, "/l", assertList()),
+ (a) -> assertCommandCheckOutput(a, "/list", assertList()),
+ (a) -> assertCommandCheckOutput(a, "/m", assertMethods()),
+ (a) -> assertCommandCheckOutput(a, "/methods", assertMethods()),
+ (a) -> assertMethod(a, "void f(int a) { g(); }", "(int)void", "f"),
+ (a) -> assertCommandCheckOutput(a, "/l", assertList()),
+ (a) -> assertCommandCheckOutput(a, "/list", assertList()),
+ (a) -> assertCommandCheckOutput(a, "/m", assertMethods()),
+ (a) -> assertCommandCheckOutput(a, "/methods", assertMethods()),
+ (a) -> assertMethod(a, "void g() {}", "()void", "g"),
+ (a) -> assertCommandCheckOutput(a, "/l", assertList()),
+ (a) -> assertCommandCheckOutput(a, "/list", assertList()),
+ (a) -> assertCommandCheckOutput(a, "/m", assertMethods()),
+ (a) -> assertCommandCheckOutput(a, "/methods", assertMethods())
+ );
+ }
+
+ public void defineClasses() {
+ test(
+ (a) -> assertCommandCheckOutput(a, "/l", assertList()),
+ (a) -> assertCommandCheckOutput(a, "/list", assertList()),
+ (a) -> assertCommandCheckOutput(a, "/c", assertClasses()),
+ (a) -> assertCommandCheckOutput(a, "/classes", assertClasses()),
+ (a) -> assertClass(a, "class A { }", "class", "A"),
+ (a) -> assertCommandCheckOutput(a, "/l", assertList()),
+ (a) -> assertCommandCheckOutput(a, "/list", assertList()),
+ (a) -> assertCommandCheckOutput(a, "/c", assertClasses()),
+ (a) -> assertCommandCheckOutput(a, "/classes", assertClasses()),
+ (a) -> assertClass(a, "interface A { }", "interface", "A"),
+ (a) -> assertCommandCheckOutput(a, "/l", assertList()),
+ (a) -> assertCommandCheckOutput(a, "/list", assertList()),
+ (a) -> assertCommandCheckOutput(a, "/c", assertClasses()),
+ (a) -> assertCommandCheckOutput(a, "/classes", assertClasses()),
+ (a) -> assertClass(a, "enum A { }", "enum", "A"),
+ (a) -> assertCommandCheckOutput(a, "/l", assertList()),
+ (a) -> assertCommandCheckOutput(a, "/list", assertList()),
+ (a) -> assertCommandCheckOutput(a, "/c", assertClasses()),
+ (a) -> assertCommandCheckOutput(a, "/classes", assertClasses()),
+ (a) -> assertClass(a, "@interface A { }", "@interface", "A"),
+ (a) -> assertCommandCheckOutput(a, "/l", assertList()),
+ (a) -> assertCommandCheckOutput(a, "/list", assertList()),
+ (a) -> assertCommandCheckOutput(a, "/c", assertClasses()),
+ (a) -> assertCommandCheckOutput(a, "/classes", assertClasses())
+ );
+ }
+
+ public void testClasspathDirectory() {
+ Compiler compiler = new Compiler();
+ Path outDir = Paths.get("testClasspathDirectory");
+ compiler.compile(outDir, "package pkg; public class A { public String toString() { return \"A\"; } }");
+ Path classpath = compiler.getPath(outDir);
+ test(
+ (a) -> assertCommand(a, "/cp " + classpath, String.format("| Path %s added to classpath\n", classpath)),
+ (a) -> evaluateExpression(a, "pkg.A", "new pkg.A();", "\"A\"")
+ );
+ test(new String[] { "-cp", classpath.toString() },
+ (a) -> evaluateExpression(a, "pkg.A", "new pkg.A();", "\"A\"")
+ );
+ test(new String[] { "-classpath", classpath.toString() },
+ (a) -> evaluateExpression(a, "pkg.A", "new pkg.A();", "\"A\"")
+ );
+ }
+
+ public void testClasspathJar() {
+ Compiler compiler = new Compiler();
+ Path outDir = Paths.get("testClasspathJar");
+ compiler.compile(outDir, "package pkg; public class A { public String toString() { return \"A\"; } }");
+ String jarName = "test.jar";
+ compiler.jar(outDir, jarName, "pkg/A.class");
+ Path jarPath = compiler.getPath(outDir).resolve(jarName);
+ test(
+ (a) -> assertCommand(a, "/classpath " + jarPath, String.format("| Path %s added to classpath\n", jarPath)),
+ (a) -> evaluateExpression(a, "pkg.A", "new pkg.A();", "\"A\"")
+ );
+ test(new String[] { "-cp", jarPath.toString() },
+ (a) -> evaluateExpression(a, "pkg.A", "new pkg.A();", "\"A\"")
+ );
+ test(new String[] { "-classpath", jarPath.toString() },
+ (a) -> evaluateExpression(a, "pkg.A", "new pkg.A();", "\"A\"")
+ );
+ }
+
+ public void testStartupFileOption() {
+ try {
+ Compiler compiler = new Compiler();
+ Path startup = compiler.getPath("StartupFileOption/startup.txt");
+ compiler.writeToFile(startup, "class A { public String toString() { return \"A\"; } }");
+ test(new String[]{"-startup", startup.toString()},
+ (a) -> evaluateExpression(a, "A", "new A()", "\"A\"\n")
+ );
+ test(new String[]{"-nostartup"},
+ (a) -> assertCommandCheckOutput(a, "printf(\"\")", assertStartsWith("| Error:\n| cannot find symbol"))
+ );
+ test((a) -> assertCommand(a, "printf(\"A\")", "", "", null, "A", ""));
+ test(false, new String[]{"-startup", "UNKNOWN"}, "| File 'UNKNOWN' for start-up is not found.");
+ } finally {
+ removeStartup();
+ }
+ }
+
+ public void testLoadingFromArgs() {
+ Compiler compiler = new Compiler();
+ Path path = compiler.getPath("loading.repl");
+ compiler.writeToFile(path, "int a = 10; double x = 20; double a = 10;");
+ test(new String[] { path.toString() },
+ (a) -> assertCommand(a, "x", "| Variable x of type double has value 20.0\n"),
+ (a) -> assertCommand(a, "a", "| Variable a of type double has value 10.0\n")
+ );
+ Path unknown = compiler.getPath("UNKNOWN.jar");
+ test(true, new String[]{unknown.toString()},
+ "| File '" + unknown
+ + "' is not found: " + unknown
+ + " (No such file or directory)\n");
+ }
+
+ public void testReset() {
+ test(
+ (a) -> assertReset(a, "/r"),
+ (a) -> assertCommandCheckOutput(a, "/m", assertMethods()),
+ (a) -> assertVariable(a, "int", "x"),
+ (a) -> assertCommandCheckOutput(a, "/v", assertVariables()),
+ (a) -> assertMethod(a, "void f() { }", "()void", "f"),
+ (a) -> assertCommandCheckOutput(a, "/m", assertMethods()),
+ (a) -> assertClass(a, "class A { }", "class", "A"),
+ (a) -> assertCommandCheckOutput(a, "/c", assertClasses()),
+ (a) -> assertReset(a, "/reset"),
+ (a) -> assertCommandCheckOutput(a, "/v", assertVariables()),
+ (a) -> assertCommandCheckOutput(a, "/m", assertMethods()),
+ (a) -> assertCommandCheckOutput(a, "/c", assertClasses())
+ );
+ }
+
+ public void testOpen() {
+ Compiler compiler = new Compiler();
+ Path path = compiler.getPath("testOpen.repl");
+ compiler.writeToFile(path,
+ "int a = 10;\ndouble x = 20;\ndouble a = 10;\n" +
+ "class A { public String toString() { return \"A\"; } }");
+ for (String s : new String[]{"/o", "/open"}) {
+ test(
+ (a) -> assertCommand(a, s + " " + path.toString(), ""),
+ (a) -> assertCommand(a, "a", "| Variable a of type double has value 10.0\n"),
+ (a) -> evaluateExpression(a, "A", "new A();", "\"A\""),
+ (a) -> {
+ loadVariable(a, "double", "x", "20.0", "20.0");
+ loadVariable(a, "double", "a", "10.0", "10.0");
+ loadVariable(a, "A", "$6", "new A();", "A");
+ loadClass(a, "class A { public String toString() { return \"A\"; } }",
+ "class", "A");
+ assertCommandCheckOutput(a, "/c", assertClasses());
+ },
+ (a) -> assertCommandCheckOutput(a, "/m", assertMethods()),
+ (a) -> assertCommandCheckOutput(a, "/v", assertVariables())
+ );
+ Path unknown = compiler.getPath("UNKNOWN.repl");
+ test(
+ (a) -> assertCommand(a, s + " " + unknown,
+ "| File '" + unknown
+ + "' is not found: " + unknown
+ + " (No such file or directory)\n")
+ );
+ }
+ }
+
+ public void testSave() throws IOException {
+ Compiler compiler = new Compiler();
+ Path path = compiler.getPath("testSave.repl");
+ List<String> list = Arrays.asList(
+ "int a;",
+ "class A { public String toString() { return \"A\"; } }"
+ );
+ for (String s : new String[]{"/s", "/save"}) {
+ test(
+ (a) -> assertVariable(a, "int", "a"),
+ (a) -> assertClass(a, "class A { public String toString() { return \"A\"; } }", "class", "A"),
+ (a) -> assertCommand(a, s + " " + path.toString(), "")
+ );
+ assertEquals(Files.readAllLines(path), list);
+ }
+ for (String s : new String[]{"/s", "/save"}) {
+ List<String> output = new ArrayList<>();
+ test(
+ (a) -> assertCommand(a, "int a;", null),
+ (a) -> assertClass(a, "class A { public String toString() { return \"A\"; } }", "class", "A"),
+ (a) -> assertCommandCheckOutput(a, "/list all", (out) ->
+ output.addAll(Stream.of(out.split("\n"))
+ .filter(str -> !str.isEmpty())
+ .map(str -> str.substring(str.indexOf(':') + 2))
+ .filter(str -> !str.startsWith("/"))
+ .collect(Collectors.toList()))),
+ (a) -> assertCommand(a, s + " all " + path.toString(), "")
+ );
+ assertEquals(Files.readAllLines(path), output);
+ }
+ for (String s : new String[]{"/s", "/save"}) {
+ List<String> output = new ArrayList<>();
+ test(
+ (a) -> assertVariable(a, "int", "a"),
+ (a) -> assertClass(a, "class A { public String toString() { return \"A\"; } }", "class", "A"),
+ (a) -> assertCommandCheckOutput(a, "/h", (out) ->
+ output.addAll(Stream.of(out.split("\n"))
+ .filter(str -> !str.isEmpty())
+ .collect(Collectors.toList()))),
+ (a) -> assertCommand(a, s + " history " + path.toString(), "")
+ );
+ output.add(s + " history " + path.toString());
+ assertEquals(Files.readAllLines(path), output);
+ }
+ }
+
+ public void testStartSet() throws BackingStoreException {
+ try {
+ Compiler compiler = new Compiler();
+ Path startUpFile = compiler.getPath("startUp.txt");
+ test(
+ (a) -> assertVariable(a, "int", "a"),
+ (a) -> assertVariable(a, "double", "b", "10", "10.0"),
+ (a) -> assertMethod(a, "void f() {}", "()V", "f"),
+ (a) -> assertCommand(a, "/s " + startUpFile.toString(), null),
+ (a) -> assertCommand(a, "/setstart " + startUpFile.toString(), null)
+ );
+ Path unknown = compiler.getPath("UNKNOWN");
+ test(
+ (a) -> assertCommand(a, "/setstart " + unknown.toString(),
+ "| File '" + unknown + "' for /setstart is not found.\n")
+ );
+ test(false, new String[0],
+ (a) -> {
+ loadVariable(a, "int", "a");
+ loadVariable(a, "double", "b", "10.0", "10.0");
+ loadMethod(a, "void f() {}", "()void", "f");
+ assertCommandCheckOutput(a, "/c", assertClasses());
+ },
+ (a) -> assertCommandCheckOutput(a, "/v", assertVariables()),
+ (a) -> assertCommandCheckOutput(a, "/m", assertMethods())
+ );
+ } finally {
+ removeStartup();
+ }
+ }
+
+ private void removeStartup() {
+ Preferences preferences = Preferences.userRoot().node("tool/REPL");
+ if (preferences != null) {
+ preferences.remove("STARTUP");
+ }
+ }
+
+ public void testUnknownCommand() {
+ test((a) -> assertCommand(a, "/unknown",
+ "| No such command: /unknown\n" +
+ "| Type /help for help.\n"));
+ }
+
+ public void testEmptyClassPath() {
+ String[] commands = {"/cp", "/classpath"};
+ test(Stream.of(commands)
+ .map(cmd -> (ReplTest) after -> assertCommand(after, cmd, "| /classpath requires a path argument\n"))
+ .toArray(ReplTest[]::new));
+
+ }
+
+ public void testNoArgument() {
+ String[] commands = {"/s", "/save", "/o", "/open", "/setstart", "/savestart"};
+ test(Stream.of(commands)
+ .map(cmd -> {
+ String c = cmd;
+ if ("/s".equals(cmd)) {
+ c = "/save";
+ }
+ if ("/o".equals(cmd)) {
+ c = "/open";
+ }
+ final String finalC = c;
+ return (ReplTest) after -> assertCommand(after, cmd,
+ "| The " + finalC + " command requires a filename argument.\n");
+ })
+ .toArray(ReplTest[]::new));
+ }
+
+ public void testStartSave() throws IOException {
+ Compiler compiler = new Compiler();
+ Path startSave = compiler.getPath("startSave.txt");
+ test(a -> assertCommand(a, "/savestart " + startSave.toString(), null));
+ List<String> lines = Files.lines(startSave)
+ .filter(s -> !s.isEmpty())
+ .collect(Collectors.toList());
+ assertEquals(lines, Arrays.asList(
+ "import java.util.*;",
+ "import java.io.*;",
+ "import java.math.*;",
+ "import java.net.*;",
+ "import java.util.concurrent.*;",
+ "import java.util.prefs.*;",
+ "import java.util.regex.*;",
+ "void printf(String format, Object... args) { System.out.printf(format, args); }"));
+ }
+
+ public void testConstrainedUpdates() {
+ test(
+ a -> assertClass(a, "class XYZZY { }", "class", "XYZZY"),
+ a -> assertVariable(a, "XYZZY", "xyzzy"),
+ a -> assertCommandCheckOutput(a, "import java.util.stream.*",
+ (out) -> assertTrue(out.trim().isEmpty(), "Expected no output, got: " + out))
+ );
+ }
+
+ public void testRemoteExit() {
+ test(
+ a -> assertVariable(a, "int", "x"),
+ a -> assertCommandCheckOutput(a, "/v", assertVariables()),
+ a -> assertCommandCheckOutput(a, "System.exit(5);", s ->
+ assertTrue(s.contains("terminated"), s)),
+ a -> assertCommandCheckOutput(a, "/v", s ->
+ assertTrue(s.trim().isEmpty(), s)),
+ a -> assertMethod(a, "void f() { }", "()void", "f"),
+ a -> assertCommandCheckOutput(a, "/m", assertMethods())
+ );
+ }
+
+ public void testListArgs() {
+ Consumer<String> assertList = s -> assertTrue(s.split("\n").length >= 7, s);
+ String arg = "qqqq";
+ Consumer<String> assertError = s -> assertEquals(s, "| Invalid /list argument: " + arg + "\n");
+ test(
+ a -> assertCommandCheckOutput(a, "/l all", assertList),
+ a -> assertCommandCheckOutput(a, "/list all", assertList),
+ a -> assertCommandCheckOutput(a, "/l " + arg, assertError),
+ a -> assertCommandCheckOutput(a, "/list " + arg, assertError),
+ a -> assertVariable(a, "int", "a"),
+ a -> assertCommandCheckOutput(a, "/l history", assertList),
+ a -> assertCommandCheckOutput(a, "/list history", assertList)
+ );
+ }
+
+ public void testFeedbackNegative() {
+ for (String feedback : new String[]{"/f", "/feedback"}) {
+ test(a -> assertCommandCheckOutput(a, feedback + " aaaa",
+ assertStartsWith("| Follow /feedback with of the following")));
+ }
+ }
+
+ public void testFeedbackOff() {
+ for (String feedback : new String[]{"/f", "/feedback"}) {
+ for (String off : new String[]{"o", "off"}) {
+ test(
+ a -> assertCommand(a, feedback + " " + off, ""),
+ a -> assertCommand(a, "int a", ""),
+ a -> assertCommand(a, "void f() {}", ""),
+ a -> assertCommandCheckOutput(a, "aaaa", assertStartsWith("| Error:")),
+ a -> assertCommandCheckOutput(a, "public void f() {}", assertStartsWith("| Warning:"))
+ );
+ }
+ }
+ }
+
+ public void testFeedbackConcise() {
+ Compiler compiler = new Compiler();
+ Path testConciseFile = compiler.getPath("testConciseFeedback");
+ String[] sources = new String[] {"int a", "void f() {}", "class A {}", "a = 10"};
+ compiler.writeToFile(testConciseFile, sources);
+ for (String feedback : new String[]{"/f", "/feedback"}) {
+ for (String concise : new String[]{"c", "concise"}) {
+ test(
+ a -> assertCommand(a, feedback + " " + concise, ""),
+ a -> assertCommand(a, sources[0], ""),
+ a -> assertCommand(a, sources[1], ""),
+ a -> assertCommand(a, sources[2], ""),
+ a -> assertCommand(a, sources[3], "| a : 10\n"),
+ a -> assertCommand(a, "/o " + testConciseFile.toString(), "| a : 10\n")
+ );
+ }
+ }
+ }
+
+ public void testFeedbackNormal() {
+ Compiler compiler = new Compiler();
+ Path testNormalFile = compiler.getPath("testConciseNormal");
+ String[] sources = new String[] {"int a", "void f() {}", "class A {}", "a = 10"};
+ String[] sources2 = new String[] {"int a //again", "void f() {int y = 4;}", "class A {} //again", "a = 10"};
+ String[] output = new String[] {
+ "| Added variable a of type int\n",
+ "| Added method f()\n",
+ "| Added class A\n",
+ "| Variable a has been assigned the value 10\n"
+ };
+ compiler.writeToFile(testNormalFile, sources2);
+ for (String feedback : new String[]{"/f", "/feedback"}) {
+ for (String feedbackState : new String[]{"n", "normal", "v", "verbose"}) {
+ String f = null;
+ if (feedbackState.startsWith("n")) {
+ f = "normal";
+ } else if (feedbackState.startsWith("v")) {
+ f = "verbose";
+ }
+ final String finalF = f;
+ test(
+ a -> assertCommand(a, feedback + " " + feedbackState, "| Feedback mode: " + finalF +"\n"),
+ a -> assertCommand(a, sources[0], output[0]),
+ a -> assertCommand(a, sources[1], output[1]),
+ a -> assertCommand(a, sources[2], output[2]),
+ a -> assertCommand(a, sources[3], output[3]),
+ a -> assertCommand(a, "/o " + testNormalFile.toString(),
+ "| Modified variable a of type int\n" +
+ "| Modified method f()\n" +
+ "| Update overwrote method f()\n" +
+ "| Modified class A\n" +
+ "| Update overwrote class A\n" +
+ "| Variable a has been assigned the value 10\n")
+ );
+ }
+ }
+ }
+
+ public void testFeedbackDefault() {
+ Compiler compiler = new Compiler();
+ Path testDefaultFile = compiler.getPath("testDefaultFeedback");
+ String[] sources = new String[] {"int a", "void f() {}", "class A {}", "a = 10"};
+ String[] output = new String[] {
+ "| Added variable a of type int\n",
+ "| Added method f()\n",
+ "| Added class A\n",
+ "| Variable a has been assigned the value 10\n"
+ };
+ compiler.writeToFile(testDefaultFile, sources);
+ for (String feedback : new String[]{"/f", "/feedback"}) {
+ for (String defaultFeedback : new String[]{"", "d", "default"}) {
+ test(
+ a -> assertCommand(a, "/f o", ""),
+ a -> assertCommand(a, "int x", ""),
+ a -> assertCommand(a, feedback + " " + defaultFeedback, "| Feedback mode: default\n"),
+ a -> assertCommand(a, sources[0], output[0]),
+ a -> assertCommand(a, sources[1], output[1]),
+ a -> assertCommand(a, sources[2], output[2]),
+ a -> assertCommand(a, sources[3], output[3]),
+ a -> assertCommand(a, "/o " + testDefaultFile.toString(), "")
+ );
+ }
+ }
+ }
+
+ public void testDrop() {
+ for (String drop : new String[]{"/d", "/drop"}) {
+ test(false, new String[]{"-nostartup"},
+ a -> assertVariable(a, "int", "a"),
+ a -> dropVariable(a, drop + " 1", "int a = 0"),
+ a -> assertMethod(a, "int b() { return 0; }", "()I", "b"),
+ a -> dropMethod(a, drop + " 2", "b ()I"),
+ a -> assertClass(a, "class A {}", "class", "A"),
+ a -> dropClass(a, drop + " 3", "class A"),
+ a -> assertCommandCheckOutput(a, "/v", assertVariables()),
+ a -> assertCommandCheckOutput(a, "/m", assertMethods()),
+ a -> assertCommandCheckOutput(a, "/c", assertClasses())
+ );
+ test(false, new String[]{"-nostartup"},
+ a -> assertVariable(a, "int", "a"),
+ a -> dropVariable(a, drop + " a", "int a = 0"),
+ a -> assertMethod(a, "int b() { return 0; }", "()I", "b"),
+ a -> dropMethod(a, drop + " b", "b ()I"),
+ a -> assertClass(a, "class A {}", "class", "A"),
+ a -> dropClass(a, drop + " A", "class A"),
+ a -> assertCommandCheckOutput(a, "/v", assertVariables()),
+ a -> assertCommandCheckOutput(a, "/m", assertMethods()),
+ a -> assertCommandCheckOutput(a, "/c", assertClasses())
+ );
+ }
+ }
+
+ public void testDropNegative() {
+ for (String drop : new String[]{"/d", "/drop"}) {
+ test(false, new String[]{"-nostartup"},
+ a -> assertCommand(a, drop + " 0", "| No definition or id named 0 found. See /classes /methods /vars or /list\n"),
+ a -> assertCommand(a, drop + " a", "| No definition or id named a found. See /classes /methods /vars or /list\n"),
+ a -> assertCommandCheckOutput(a, drop,
+ assertStartsWith("| In the /drop argument, please specify an import, variable, method, or class to drop.")),
+ a -> assertVariable(a, "int", "a"),
+ a -> assertCommand(a, "a", "| Variable a of type int has value 0\n"),
+ a -> assertCommand(a, drop + " 2", "| The argument did not specify an import, variable, method, or class to drop.\n")
+ );
+ }
+ }
+
+ public void testAmbiguousDrop() {
+ Consumer<String> check = s -> {
+ assertTrue(s.startsWith("| The argument references more than one import, variable, method, or class"), s);
+ int lines = s.split("\n").length;
+ assertEquals(lines, 5, "Expected 3 ambiguous keys, but found: " + (lines - 2) + "\n" + s);
+ };
+ for (String drop : new String[]{"/d", "/drop"}) {
+ test(
+ a -> assertVariable(a, "int", "a"),
+ a -> assertMethod(a, "int a() { return 0; }", "()int", "a"),
+ a -> assertClass(a, "class a {}", "class", "a"),
+ a -> assertCommandCheckOutput(a, drop + " a", check),
+ a -> assertCommandCheckOutput(a, "/v", assertVariables()),
+ a -> assertCommandCheckOutput(a, "/m", assertMethods()),
+ a -> assertCommandCheckOutput(a, "/c", assertClasses())
+ );
+ test(
+ a -> assertMethod(a, "int a() { return 0; }", "()int", "a"),
+ a -> assertMethod(a, "double a(int a) { return 0; }", "(int)double", "a"),
+ a -> assertMethod(a, "double a(double a) { return 0; }", "(double)double", "a"),
+ a -> assertCommandCheckOutput(a, drop + " a", check),
+ a -> assertCommandCheckOutput(a, "/m", assertMethods())
+ );
+ }
+ }
+
+ public void testHistoryReference() {
+ test(false, new String[]{"-nostartup"},
+ a -> assertCommand(a, "System.err.println(1)", "", "", null, "", "1\n"),
+ a -> assertCommand(a, "System.err.println(2)", "", "", null, "", "2\n"),
+ a -> assertCommand(a, "/-2", "System.err.println(1)\n", "", null, "", "1\n"),
+ a -> assertCommand(a, "/history", "\n" +
+ "/debug 0\n" +
+ "System.err.println(1)\n" +
+ "System.err.println(2)\n" +
+ "System.err.println(1)\n" +
+ "/history\n"),
+ a -> assertCommand(a, "/-2", "System.err.println(2)\n", "", null, "", "2\n"),
+ a -> assertCommand(a, "/!", "System.err.println(2)\n", "", null, "", "2\n"),
+ a -> assertCommand(a, "/2", "System.err.println(2)\n", "", null, "", "2\n"),
+ a -> assertCommand(a, "/1", "System.err.println(1)\n", "", null, "", "1\n")
+ );
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/TypeNameTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary null test
+ * @build KullaTesting TestingInputStream
+ * @run testng TypeNameTest
+ */
+
+import jdk.jshell.Snippet;
+import jdk.jshell.VarSnippet;
+import org.testng.annotations.Test;
+
+import static jdk.jshell.Snippet.Status.VALID;
+import static org.testng.Assert.assertEquals;
+import static jdk.jshell.Snippet.Status.OVERWRITTEN;
+
+@Test
+public class TypeNameTest extends KullaTesting {
+
+ public void testReplClassName() {
+ assertEval("class C {}");
+ VarSnippet sn = (VarSnippet) varKey(assertEval("new C();"));
+ assertEquals(sn.typeName(), "C");
+ }
+
+ public void testReplNestedClassName() {
+ assertEval("class D { static class E {} }");
+ VarSnippet sn = (VarSnippet) varKey(assertEval("new D.E();"));
+ assertEquals(sn.typeName(), "D.E");
+ }
+
+ public void testAnonymousClassName() {
+ assertEval("class C {}");
+ VarSnippet sn = (VarSnippet) varKey(assertEval("new C() { int x; };"));
+ assertEquals(sn.typeName(), "C");
+ }
+
+ public void testCapturedTypeName() {
+ VarSnippet sn = (VarSnippet) varKey(assertEval("\"\".getClass();"));
+ assertEquals(sn.typeName(), "Class<? extends String>");
+ }
+
+ public void testJavaLang() {
+ VarSnippet sn = (VarSnippet) varKey(assertEval("\"\";"));
+ assertEquals(sn.typeName(), "String");
+ }
+
+ public void testNotOverEagerPackageEating() {
+ VarSnippet sn = (VarSnippet) varKey(assertEval("\"\".getClass().getDeclaredMethod(\"hashCode\");"));
+ assertEquals(sn.typeName(), "java.lang.reflect.Method");
+ }
+
+ public void testBounds() {
+ assertEval("java.util.List<? extends String> list1 = java.util.Arrays.asList(\"\");");
+ VarSnippet sn1 = (VarSnippet) varKey(assertEval("list1.iterator().next()"));
+ assertEquals(sn1.typeName(), "String");
+ assertEval("java.util.List<? super String> list2 = java.util.Arrays.asList(\"\");");
+ VarSnippet sn2 = (VarSnippet) varKey(assertEval("list2.iterator().next()"));
+ assertEquals(sn2.typeName(), "Object");
+ assertEval("java.util.List<?> list3 = java.util.Arrays.asList(\"\");");
+ VarSnippet sn3 = (VarSnippet) varKey(assertEval("list3.iterator().next()"));
+ assertEquals(sn3.typeName(), "Object");
+ assertEval("class Test1<X extends CharSequence> { public X get() { return null; } }");
+ Snippet x = varKey(assertEval("Test1<?> x = new Test1<>();"));
+ VarSnippet sn4 = (VarSnippet) varKey(assertEval("x.get()"));
+ assertEquals(sn4.typeName(), "CharSequence");
+ assertEval("class Foo<X extends Number & CharSequence> { public X get() { return null; } }");
+ assertEval("Foo<?> x = new Foo<>();",
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(x, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ VarSnippet sn5 = (VarSnippet) varKey(assertEval("x.get()"));
+ assertEquals(sn5.typeName(), "Object");
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/jshell/VariablesTest.java Thu Oct 22 11:12:55 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.
+ *
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary Tests for EvaluationState.variables
+ * @build KullaTesting TestingInputStream ExpectedDiagnostic
+ * @run testng VariablesTest
+ */
+
+import java.util.List;
+import javax.tools.Diagnostic;
+
+import jdk.jshell.Snippet;
+import jdk.jshell.TypeDeclSnippet;
+import jdk.jshell.VarSnippet;
+import jdk.jshell.Snippet.SubKind;
+import jdk.jshell.SnippetEvent;
+import org.testng.annotations.Test;
+
+import static jdk.jshell.Snippet.Status.*;
+import static jdk.jshell.Snippet.SubKind.VAR_DECLARATION_SUBKIND;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.fail;
+
+@Test
+public class VariablesTest extends KullaTesting {
+
+ public void noVariables() {
+ assertNumberOfActiveVariables(0);
+ }
+
+ private void badVarValue(VarSnippet key) {
+ try {
+ getState().varValue(key);
+ fail("Expected exception for: " + key.source());
+ } catch (IllegalArgumentException e) {
+ // ok
+ }
+ }
+
+ public void testVarValue1() {
+ VarSnippet v1 = varKey(assertEval("und1 a;", added(RECOVERABLE_NOT_DEFINED)));
+ badVarValue(v1);
+ VarSnippet v2 = varKey(assertEval("und2 a;",
+ ste(MAIN_SNIPPET, RECOVERABLE_NOT_DEFINED, RECOVERABLE_NOT_DEFINED, false, null),
+ ste(v1, RECOVERABLE_NOT_DEFINED, OVERWRITTEN, false, MAIN_SNIPPET)));
+ badVarValue(v2);
+ TypeDeclSnippet und = classKey(assertEval("class und2 {}",
+ added(VALID),
+ ste(v2, RECOVERABLE_NOT_DEFINED, VALID, true, MAIN_SNIPPET)));
+ assertVarValue(v2, "null");
+ assertDrop(und,
+ DiagCheck.DIAG_OK,
+ DiagCheck.DIAG_ERROR,
+ ste(und, VALID, DROPPED, true, null),
+ ste(v2, VALID, RECOVERABLE_NOT_DEFINED, true, und));
+ badVarValue(v1);
+ badVarValue(v2);
+ }
+
+ public void testVarValue2() {
+ VarSnippet v1 = (VarSnippet) assertDeclareFail("int a = 0.0;", "compiler.err.prob.found.req");
+ badVarValue(v1);
+ VarSnippet v2 = varKey(assertEval("int a = 0;", ste(v1, REJECTED, VALID, true, null)));
+ assertDrop(v2, ste(MAIN_SNIPPET, VALID, DROPPED, true, null));
+ badVarValue(v2);
+ }
+
+ public void testSignature1() {
+ VarSnippet v1 = varKey(assertEval("und1 a;", added(RECOVERABLE_NOT_DEFINED)));
+ assertVariableDeclSnippet(v1, "a", "und1", RECOVERABLE_NOT_DEFINED, VAR_DECLARATION_SUBKIND, 1, 0);
+ VarSnippet v2 = varKey(assertEval("und2 a;",
+ ste(MAIN_SNIPPET, RECOVERABLE_NOT_DEFINED, RECOVERABLE_NOT_DEFINED, false, null),
+ ste(v1, RECOVERABLE_NOT_DEFINED, OVERWRITTEN, false, MAIN_SNIPPET)));
+ assertVariableDeclSnippet(v2, "a", "und2", RECOVERABLE_NOT_DEFINED, VAR_DECLARATION_SUBKIND, 1, 0);
+ TypeDeclSnippet und = classKey(assertEval("class und2 {}",
+ added(VALID),
+ ste(v2, RECOVERABLE_NOT_DEFINED, VALID, true, MAIN_SNIPPET)));
+ assertVariableDeclSnippet(v2, "a", "und2", VALID, VAR_DECLARATION_SUBKIND, 0, 0);
+ assertDrop(und,
+ DiagCheck.DIAG_OK,
+ DiagCheck.DIAG_ERROR,
+ ste(und, VALID, DROPPED, true, null),
+ ste(v2, VALID, RECOVERABLE_NOT_DEFINED, true, und));
+ assertVariableDeclSnippet(v2, "a", "und2", RECOVERABLE_NOT_DEFINED, VAR_DECLARATION_SUBKIND, 1, 0);
+ }
+
+ public void testSignature2() {
+ VarSnippet v1 = (VarSnippet) assertDeclareFail("int a = 0.0;", "compiler.err.prob.found.req");
+ assertVariableDeclSnippet(v1, "a", "int", REJECTED, SubKind.VAR_DECLARATION_WITH_INITIALIZER_SUBKIND, 0, 1);
+ VarSnippet v2 = varKey(assertEval("int a = 0;",
+ ste(v1, REJECTED, VALID, true, null)));
+ assertVariableDeclSnippet(v2, "a", "int", VALID, SubKind.VAR_DECLARATION_WITH_INITIALIZER_SUBKIND, 0, 0);
+ assertDrop(v2, ste(MAIN_SNIPPET, VALID, DROPPED, true, null));
+ assertVariableDeclSnippet(v2, "a", "int", DROPPED, SubKind.VAR_DECLARATION_WITH_INITIALIZER_SUBKIND, 0, 0);
+ }
+
+ public void variables() {
+ VarSnippet snx = varKey(assertEval("int x = 10;"));
+ VarSnippet sny = varKey(assertEval("String y = \"hi\";"));
+ VarSnippet snz = varKey(assertEval("long z;"));
+ assertVariables(variable("int", "x"), variable("String", "y"), variable("long", "z"));
+ assertVarValue(snx, "10");
+ assertVarValue(sny, "\"hi\"");
+ assertVarValue(snz, "0");
+ assertActiveKeys();
+ }
+
+ public void variablesArray() {
+ VarSnippet sn = varKey(assertEval("int[] a = new int[12];"));
+ assertEquals(sn.typeName(), "int[]");
+ assertEval("int len = a.length;", "12");
+ assertVariables(variable("int[]", "a"), variable("int", "len"));
+ assertActiveKeys();
+ }
+
+ public void variablesArrayOld() {
+ VarSnippet sn = varKey(assertEval("int a[] = new int[12];"));
+ assertEquals(sn.typeName(), "int[]");
+ assertEval("int len = a.length;", "12");
+ assertVariables(variable("int[]", "a"), variable("int", "len"));
+ assertActiveKeys();
+ }
+
+ public void variablesRedefinition() {
+ Snippet x = varKey(assertEval("int x = 10;"));
+ Snippet y = varKey(assertEval("String y = \"\";", added(VALID)));
+ assertVariables(variable("int", "x"), variable("String", "y"));
+ assertActiveKeys();
+ assertEval("long x;",
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(x, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertVariables(variable("long", "x"), variable("String", "y"));
+ assertActiveKeys();
+ assertEval("String y;",
+ ste(MAIN_SNIPPET, VALID, VALID, false, null),
+ ste(y, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertVariables(variable("long", "x"), variable("String", "y"));
+ assertActiveKeys();
+ }
+
+ public void variablesTemporary() {
+ assertEval("int $1 = 10;", added(VALID));
+ assertEval("2 * $1;", added(VALID));
+ assertVariables(variable("int", "$1"), variable("int", "$2"));
+ assertActiveKeys();
+ assertEval("String y;", added(VALID));
+ assertVariables(variable("int", "$1"), variable("int", "$2"), variable("String", "y"));
+ assertActiveKeys();
+ }
+
+ public void variablesTemporaryNull() {
+ assertEval("null;", added(VALID));
+ assertVariables(variable("Object", "$1"));
+ assertEval("(String) null;", added(VALID));
+ assertVariables(variable("Object", "$1"), variable("String", "$2"));
+ assertActiveKeys();
+ assertEval("\"\";", added(VALID));
+ assertVariables(
+ variable("Object", "$1"),
+ variable("String", "$2"),
+ variable("String", "$3"));
+ assertActiveKeys();
+ }
+
+ public void variablesClassReplace() {
+ assertEval("import java.util.*;", added(VALID));
+ Snippet var = varKey(assertEval("List<Integer> list = new ArrayList<>();", "[]",
+ added(VALID)));
+ assertVariables(variable("List<Integer>", "list"));
+ assertEval("class List {}",
+ DiagCheck.DIAG_OK,
+ DiagCheck.DIAG_ERROR,
+ added(VALID),
+ ste(var, VALID, RECOVERABLE_NOT_DEFINED, true, MAIN_SNIPPET));
+ assertVariables();
+ assertEval("List list = new List();",
+ DiagCheck.DIAG_OK, DiagCheck.DIAG_IGNORE,
+ ste(MAIN_SNIPPET, RECOVERABLE_NOT_DEFINED, VALID, true, null),
+ ste(var, RECOVERABLE_NOT_DEFINED, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertVariables(variable("List", "list"));
+ assertActiveKeys();
+ }
+
+ public void variablesErrors() {
+ assertDeclareFail("String;", new ExpectedDiagnostic("compiler.err.cant.resolve.location", 0, 6, 0, -1, -1, Diagnostic.Kind.ERROR));
+ assertNumberOfActiveVariables(0);
+ assertActiveKeys();
+ }
+
+ public void variablesUnresolvedActiveFailed() {
+ VarSnippet key = varKey(assertEval("und x;", added(RECOVERABLE_NOT_DEFINED)));
+ assertVariableDeclSnippet(key, "x", "und", RECOVERABLE_NOT_DEFINED, VAR_DECLARATION_SUBKIND, 1, 0);
+ assertUnresolvedDependencies1(key, RECOVERABLE_NOT_DEFINED, "class und");
+ assertNumberOfActiveVariables(1);
+ assertActiveKeys();
+ }
+
+ public void variablesUnresolvedError() {
+ assertDeclareFail("und y = null;", new ExpectedDiagnostic("compiler.err.cant.resolve.location", 0, 3, 0, -1, -1, Diagnostic.Kind.ERROR));
+ assertNumberOfActiveVariables(0);
+ assertActiveKeys();
+ }
+
+ public void variablesMultiByteCharacterType() {
+ assertEval("class \u3042 {}");
+ assertEval("\u3042 \u3042 = null;", added(VALID));
+ assertVariables(variable("\u3042", "\u3042"));
+ assertEval("new \u3042()", added(VALID));
+ assertVariables(variable("\u3042", "\u3042"), variable("\u3042", "$1"));
+
+ assertEval("class \u3042\u3044\u3046\u3048\u304a {}");
+ assertEval("\u3042\u3044\u3046\u3048\u304a \u3042\u3044\u3046\u3048\u304a = null;", added(VALID));
+ assertVariables(variable("\u3042", "\u3042"), variable("\u3042", "$1"),
+ variable("\u3042\u3044\u3046\u3048\u304a", "\u3042\u3044\u3046\u3048\u304a"));
+ assertEval("new \u3042\u3044\u3046\u3048\u304a();");
+ assertVariables(variable("\u3042", "\u3042"), variable("\u3042", "$1"),
+ variable("\u3042\u3044\u3046\u3048\u304a", "\u3042\u3044\u3046\u3048\u304a"),
+ variable("\u3042\u3044\u3046\u3048\u304a", "$2"));
+ assertActiveKeys();
+ }
+
+ @Test(enabled = false) // TODO 8081689
+ public void methodVariablesAreNotVisible() {
+ Snippet foo = varKey(assertEval("int foo() {" +
+ "int x = 10;" +
+ "int y = 2 * x;" +
+ "return x * y;" +
+ "}", added(VALID)));
+ assertNumberOfActiveVariables(0);
+ assertActiveKeys();
+ assertEval("int x = 10;", "10");
+ assertEval("int foo() {" +
+ "int y = 2 * x;" +
+ "return x * y;" +
+ "}",
+ ste(foo, VALID, VALID, false, null));
+ assertVariables(variable("int", "x"));
+ assertActiveKeys();
+ assertEval("foo();", "200");
+ assertVariables(variable("int", "x"), variable("int", "$1"));
+ assertActiveKeys();
+ }
+
+ @Test(enabled = false) // TODO 8081689
+ public void classFieldsAreNotVisible() {
+ Snippet key = classKey(assertEval("class clazz {" +
+ "int x = 10;" +
+ "int y = 2 * x;" +
+ "}"));
+ assertNumberOfActiveVariables(0);
+ assertEval("int x = 10;", "10");
+ assertActiveKeys();
+ assertEval(
+ "class clazz {" +
+ "int y = 2 * x;" +
+ "}",
+ ste(key, VALID, VALID, true, null));
+ assertVariables(variable("int", "x"));
+ assertEval("new clazz().y;", "20");
+ assertVariables(variable("int", "x"), variable("int", "$1"));
+ assertActiveKeys();
+ }
+
+ public void multiVariables() {
+ List<SnippetEvent> abc = assertEval("int a, b, c = 10;",
+ DiagCheck.DIAG_OK, DiagCheck.DIAG_OK,
+ chain(added(VALID)),
+ chain(added(VALID)),
+ chain(added(VALID)));
+ Snippet a = abc.get(0).snippet();
+ Snippet b = abc.get(1).snippet();
+ Snippet c = abc.get(2).snippet();
+ assertVariables(variable("int", "a"), variable("int", "b"), variable("int", "c"));
+ assertEval("double a = 1.4, b = 8.8;", DiagCheck.DIAG_OK, DiagCheck.DIAG_OK,
+ chain(ste(MAIN_SNIPPET, VALID, VALID, true, null), ste(a, VALID, OVERWRITTEN, false, MAIN_SNIPPET)),
+ chain(ste(MAIN_SNIPPET, VALID, VALID, true, null), ste(b, VALID, OVERWRITTEN, false, MAIN_SNIPPET)));
+ assertVariables(variable("double", "a"), variable("double", "b"), variable("int", "c"));
+ assertEval("double c = a + b;",
+ ste(MAIN_SNIPPET, VALID, VALID, true, null),
+ ste(c, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
+ assertVariables(variable("double", "a"), variable("double", "b"), variable("double", "c"));
+ assertActiveKeys();
+ }
+
+ public void syntheticVariables() {
+ assertEval("assert false;");
+ assertNumberOfActiveVariables(0);
+ assertActiveKeys();
+ }
+
+ public void undefinedReplaceVariable() {
+ Snippet key = varKey(assertEval("int d = 234;", "234"));
+ assertVariables(variable("int", "d"));
+ String src = "undefined d;";
+ Snippet undefKey = varKey(assertEval(src,
+ ste(MAIN_SNIPPET, VALID, RECOVERABLE_NOT_DEFINED, true, null),
+ ste(key, VALID, OVERWRITTEN, false, MAIN_SNIPPET)));
+ //assertEquals(getState().source(snippet), src);
+ //assertEquals(snippet, undefKey);
+ assertEquals(getState().status(undefKey), RECOVERABLE_NOT_DEFINED);
+ List<String> unr = getState().unresolvedDependencies((VarSnippet) undefKey);
+ assertEquals(unr.size(), 1);
+ assertEquals(unr.get(0), "class undefined");
+ assertVariables(variable("undefined", "d"));
+ }
+
+ public void variableTypeName() {
+ assertEquals(varKey(assertEval("\"x\"")).typeName(), "String");
+
+ assertEquals(varKey(assertEval("java.util.regex.Pattern.compile(\"x\")")).typeName(), "java.util.regex.Pattern");
+ assertEval("import java.util.regex.*;", added(VALID));
+ assertEquals(varKey(assertEval("java.util.regex.Pattern.compile(\"x\")")).typeName(), "Pattern");
+
+ assertEquals(varKey(assertEval("new java.util.ArrayList()")).typeName(), "java.util.ArrayList");
+ assertEval("import java.util.ArrayList;", added(VALID));
+ assertEquals(varKey(assertEval("new java.util.ArrayList()")).typeName(), "ArrayList");
+
+ assertEquals(varKey(assertEval("java.util.Locale.Category.FORMAT")).typeName(), "java.util.Locale.Category");
+ assertEval("import static java.util.Locale.Category;", added(VALID));
+ assertEquals(varKey(assertEval("java.util.Locale.Category.FORMAT")).typeName(), "Category");
+ }
+}
--- a/langtools/test/tools/javac/MethodParameters/ClassFileVisitor.java Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/test/tools/javac/MethodParameters/ClassFileVisitor.java Thu Oct 22 11:12:55 2015 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 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
@@ -147,7 +147,6 @@
public int mAttrs;
public int mNumParams;
public boolean mSynthetic;
- public boolean mIsLambda;
public boolean mIsConstructor;
public boolean mIsClinit;
public boolean mIsBridge;
@@ -166,7 +165,6 @@
mIsClinit = mName.equals("<clinit>");
prefix = cname + "." + mName + "() - ";
mIsBridge = method.access_flags.is(AccessFlags.ACC_BRIDGE);
- mIsLambda = mSynthetic && mName.startsWith("lambda$");
if (mIsClinit) {
sb = new StringBuilder(); // Discard output
@@ -227,7 +225,7 @@
// IMPL: Whether MethodParameters attributes will be generated
// for some synthetics is unresolved. For now, assume no.
- if (mSynthetic && !mIsLambda) {
+ if (mSynthetic) {
warn(prefix + "synthetic has MethodParameter attribute");
}
@@ -351,12 +349,10 @@
} else if (isEnum && mNumParams == 1 && index == 0 && mName.equals("valueOf")) {
expect = "name";
allowMandated = true;
- } else if (mIsBridge || mIsLambda) {
+ } else if (mIsBridge) {
allowSynthetic = true;
/* you can't expect an special name for bridges' parameters.
- * The name of the original parameters are now copied. Likewise
- * for a method encoding the lambda expression, names are derived
- * from source lambda's parameters and captured enclosing locals.
+ * The name of the original parameters are now copied.
*/
expect = null;
}
--- a/langtools/test/tools/javac/MethodParameters/LambdaTest.java Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/test/tools/javac/MethodParameters/LambdaTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -23,7 +23,7 @@
/*
* @test
- * @bug 8006582 8037546
+ * @bug 8006582 8037546 8138729
* @summary javac should generate method parameters correctly.
* @modules jdk.jdeps/com.sun.tools.classfile
* @build Tester
@@ -32,8 +32,8 @@
*/
/**
- * Post https://bugs.openjdk.java.net/browse/JDK-8037546, this test verifies
- * that MethodParameters attribute for lambdas are emitted properly.
+ * Post https://bugs.openjdk.java.net/browse/JDK-8138729, this test verifies
+ * that MethodParameters attribute are NOT emitted for lambdas.
*/
class LambdaTest {
--- a/langtools/test/tools/javac/MethodParameters/LambdaTest.out Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/test/tools/javac/MethodParameters/LambdaTest.out Thu Oct 22 11:12:55 2015 -0700
@@ -1,7 +1,7 @@
class LambdaTest --
LambdaTest.<init>()
LambdaTest.foo(i)
-LambdaTest.lambda$static$1(x1/*synthetic*/)/*synthetic*/
-LambdaTest.lambda$null$0(final x1/*synthetic*/, x2/*synthetic*/)/*synthetic*/
+LambdaTest.lambda$static$1(arg0)/*synthetic*/
+LambdaTest.lambda$null$0(arg0, arg1)/*synthetic*/
static interface LambdaTest$I -- inner
LambdaTest$I.m(x)
--- a/langtools/test/tools/javac/MethodParameters/ReflectionVisitor.java Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/test/tools/javac/MethodParameters/ReflectionVisitor.java Thu Oct 22 11:12:55 2015 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 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
@@ -277,7 +277,7 @@
param = "final " + param;
}
sb.append(sep).append(param);
- if (!m.isBridge() && !m.getName().startsWith("lambda$") && !expect.equals(param)) {
+ if (!m.isBridge() && !expect.equals(param)) {
error(prefix + "param[" + i + "]='"
+ param + "' expected '" + expect + "'");
break;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/NameClash/NameClashTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @bug 8074803
+ * @summary Incorrect name clash error
+ *
+ * @compile NameClashTest.java
+ */
+
+public class NameClashTest {
+
+ String log = "";
+
+ interface A1 {
+ A1 m(String s);
+ }
+
+ abstract class A2 implements A1 {
+ public abstract A2 m(String s);
+ }
+
+ interface B1 {
+ A1 m(String s);
+ }
+
+ interface B2 extends B1 {
+ A2 m(String s);
+ }
+
+ abstract class C extends A2 implements B2 {}
+
+ class D extends C {
+
+ public A2 m(String s) {
+ log += s;
+ return null;
+ }
+ }
+
+ public static void main(String[] args) {
+ NameClashTest nct = new NameClashTest();
+ A1 a1 = nct.new D();
+ a1.m("A1.m ");
+ A2 a2 = nct.new D();
+ a2.m("A2.m ");
+ B1 b1 = nct.new D();
+ b1.m("B1.m ");
+ B2 b2 = nct.new D();
+ b2.m("B2.m ");
+ C c = nct.new D();
+ c.m("C.m ");
+ D d = nct.new D();
+ d.m("D.m ");
+ if (!nct.log.equals("A1.m A2.m B1.m B2.m C.m D.m "))
+ throw new AssertionError("unexpected output: " + nct.log);
+ }
+}
--- a/langtools/test/tools/javac/doctree/DocCommentTester.java Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/test/tools/javac/doctree/DocCommentTester.java Thu Oct 22 11:12:55 2015 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 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
@@ -258,7 +258,6 @@
} catch (IOException e) {
source = "";
}
-
// remove existing gold by removing all block comments after the first '{'.
int start = source.indexOf("{");
while ((start = source.indexOf("\n/*\n", start)) != -1) {
@@ -663,8 +662,6 @@
else
return s;
}
-
-
}
}
@@ -763,17 +760,15 @@
* Normalize white space in places where the tree does not preserve it.
*/
String normalize(String s) {
- return s.trim()
- .replaceFirst("\\.\\s++([^@])", ". $1")
+ s = s.trim()
.replaceFirst("\\.\\s*\\n *@", ".\n@")
- .replaceFirst("\\s+<(/?p|pre|h[1-6])>", " <$1>")
.replaceAll("\\{@docRoot\\s+\\}", "{@docRoot}")
.replaceAll("\\{@inheritDoc\\s+\\}", "{@inheritDoc}")
.replaceAll("(\\{@value\\s+[^}]+)\\s+(\\})", "$1$2")
- .replaceAll("\n[ \t]+@", "\n@");
+ .replaceAll("\n[ \t]+@", "\n@")
+ .replaceAll("(\\{@code)(\\x20)(\\s+.*)", "$1$3");
+ return s;
}
-
}
-
}
--- a/langtools/test/tools/javac/doctree/ElementTest.java Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/test/tools/javac/doctree/ElementTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -23,7 +23,7 @@
/*
* @test
- * @bug 7021614
+ * @bug 7021614 8078320
* @summary extend com.sun.source API to support parsing javadoc comments
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.file
@@ -40,13 +40,13 @@
void simple() { }
/*
DocComment[DOC_COMMENT, pos:1
- firstSentence: empty
- body: 3
+ firstSentence: 2
StartElement[START_ELEMENT, pos:1
name:p
attributes: empty
]
Text[TEXT, pos:4, para]
+ body: 1
EndElement[END_ELEMENT, pos:8, p]
block tags: empty
]
--- a/langtools/test/tools/javac/doctree/FirstSentenceTest.java Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/test/tools/javac/doctree/FirstSentenceTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -23,7 +23,7 @@
/*
* @test
- * @bug 7021614
+ * @bug 7021614 8078320
* @summary extend com.sun.source API to support parsing javadoc comments
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.file
@@ -117,6 +117,26 @@
*/
/**
+ *
+ * <p>abc def ghi.
+ * jdl mno pqf
+ */
+ void newline_p() { }
+/*
+DocComment[DOC_COMMENT, pos:2
+ firstSentence: 2
+ StartElement[START_ELEMENT, pos:2
+ name:p
+ attributes: empty
+ ]
+ Text[TEXT, pos:5, abc_def_ghi.]
+ body: 1
+ Text[TEXT, pos:19, jdl_mno_pqf]
+ block tags: empty
+]
+*/
+
+ /**
* abc def ghi
* </p>jkl mno pqr
*/
@@ -197,6 +217,40 @@
]
]
*/
-
+ /**
+ * <p> abc def.
+ * ghi jkl
+ */
+ void p_at_zero() { }
+/*
+DocComment[DOC_COMMENT, pos:1
+ firstSentence: 2
+ StartElement[START_ELEMENT, pos:1
+ name:p
+ attributes: empty
+ ]
+ Text[TEXT, pos:4, _abc_def.]
+ body: 1
+ Text[TEXT, pos:15, ghi_jkl]
+ block tags: empty
+]
+*/
+ /**
+ * abc <p> def. ghi jkl
+ */
+ void p_at_nonzero() { }
+/*
+DocComment[DOC_COMMENT, pos:1
+ firstSentence: 1
+ Text[TEXT, pos:1, abc]
+ body: 2
+ StartElement[START_ELEMENT, pos:5
+ name:p
+ attributes: empty
+ ]
+ Text[TEXT, pos:8, _def._ghi_jkl]
+ block tags: empty
+]
+*/
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/doctree/InPreTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,199 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @bug 8078320
+ * @summary extend com.sun.source API to support parsing javadoc comments
+ * @modules jdk.compiler/com.sun.tools.javac.api
+ * jdk.compiler/com.sun.tools.javac.file
+ * jdk.compiler/com.sun.tools.javac.tree
+ * jdk.compiler/com.sun.tools.javac.util
+ * @build DocCommentTester
+ * @run main DocCommentTester InPreTest.java
+ */
+
+class InPreTest {
+ /**
+ * xyz<pre> pqr </pre> abc{@code def }ghi
+ */
+ public void after_pre() { }
+/*
+DocComment[DOC_COMMENT, pos:1
+ firstSentence: 1
+ Text[TEXT, pos:1, xyz]
+ body: 6
+ StartElement[START_ELEMENT, pos:4
+ name:pre
+ attributes: empty
+ ]
+ Text[TEXT, pos:9, _pqr_]
+ EndElement[END_ELEMENT, pos:14, pre]
+ Text[TEXT, pos:20, _abc]
+ Literal[CODE, pos:24, _def__]
+ Text[TEXT, pos:38, ghi]
+ block tags: empty
+]
+*/
+ /**
+ * abc{@code def}ghi
+ */
+ public void no_pre() { }
+/*
+DocComment[DOC_COMMENT, pos:1
+ firstSentence: 3
+ Text[TEXT, pos:1, abc]
+ Literal[CODE, pos:4, def]
+ Text[TEXT, pos:15, ghi]
+ body: empty
+ block tags: empty
+]
+*/
+ /**
+ * xyz<pre> abc{@code def }ghi</pre>
+ */
+ public void pre_after_text() {}
+/*
+DocComment[DOC_COMMENT, pos:1
+ firstSentence: 1
+ Text[TEXT, pos:1, xyz]
+ body: 5
+ StartElement[START_ELEMENT, pos:4
+ name:pre
+ attributes: empty
+ ]
+ Text[TEXT, pos:9, _abc]
+ Literal[CODE, pos:13, _def__]
+ Text[TEXT, pos:27, ghi]
+ EndElement[END_ELEMENT, pos:30, pre]
+ block tags: empty
+]
+*/
+
+ /**
+ * abc{@code def }ghi
+ */
+ public void no_pre_extra_whitespace() { }
+/*
+DocComment[DOC_COMMENT, pos:1
+ firstSentence: 3
+ Text[TEXT, pos:1, abc]
+ Literal[CODE, pos:4, _def__]
+ Text[TEXT, pos:18, ghi]
+ body: empty
+ block tags: empty
+]
+*/
+ /**
+ * <pre> abc{@code def }ghi</pre>
+ */
+ public void in_pre() { }
+/*
+DocComment[DOC_COMMENT, pos:1
+ firstSentence: 4
+ StartElement[START_ELEMENT, pos:1
+ name:pre
+ attributes: empty
+ ]
+ Text[TEXT, pos:6, _abc]
+ Literal[CODE, pos:10, _def__]
+ Text[TEXT, pos:24, ghi]
+ body: 1
+ EndElement[END_ELEMENT, pos:27, pre]
+ block tags: empty
+]
+*/
+ /**
+ * <pre> abc{@code
+ * def }ghi</pre>
+ */
+ public void in_pre_with_space_nl() { }
+/*
+DocComment[DOC_COMMENT, pos:1
+ firstSentence: 4
+ StartElement[START_ELEMENT, pos:1
+ name:pre
+ attributes: empty
+ ]
+ Text[TEXT, pos:6, _abc]
+ Literal[CODE, pos:10, |_def__]
+ Text[TEXT, pos:24, ghi]
+ body: 1
+ EndElement[END_ELEMENT, pos:27, pre]
+ block tags: empty
+]
+*/
+
+ /**
+ * <pre> abc{@code
+ *def }ghi</pre>
+ */
+ public void in_pre_with_nl() { }
+/*
+DocComment[DOC_COMMENT, pos:1
+ firstSentence: 4
+ StartElement[START_ELEMENT, pos:1
+ name:pre
+ attributes: empty
+ ]
+ Text[TEXT, pos:6, _abc]
+ Literal[CODE, pos:10, |def__]
+ Text[TEXT, pos:23, ghi]
+ body: 1
+ EndElement[END_ELEMENT, pos:26, pre]
+ block tags: empty
+]
+*/
+ /**
+ * abc {@code
+ */
+ public void bad_code_no_content() { }
+/*
+DocComment[DOC_COMMENT, pos:1
+ firstSentence: 2
+ Text[TEXT, pos:1, abc_]
+ Erroneous[ERRONEOUS, pos:5
+ code: compiler.err.dc.unterminated.inline.tag
+ body: {@code
+ ]
+ body: empty
+ block tags: empty
+]
+*/
+ /**
+ * abc {@code abc
+ */
+ public void bad_code_content() { }
+/*
+DocComment[DOC_COMMENT, pos:1
+ firstSentence: 2
+ Text[TEXT, pos:1, abc_]
+ Erroneous[ERRONEOUS, pos:5
+ code: compiler.err.dc.unterminated.inline.tag
+ body: {@code_abc
+ ]
+ body: empty
+ block tags: empty
+]
+*/
+}
--- a/langtools/test/tools/javac/doctree/TagTest.java Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/test/tools/javac/doctree/TagTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -23,7 +23,7 @@
/*
* @test
- * @bug 7021614
+ * @bug 7021614 8078320
* @summary extend com.sun.source API to support parsing javadoc comments
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.file
@@ -35,6 +35,40 @@
class TagTest {
/**
+ * @tag:colon abc
+ */
+ void custom_tag_with_a_colon() {}
+/*
+DocComment[DOC_COMMENT, pos:1
+ firstSentence: empty
+ body: empty
+ block tags: 1
+ UnknownBlockTag[UNKNOWN_BLOCK_TAG, pos:1
+ tag:tag:colon
+ content: 1
+ Text[TEXT, pos:12, abc]
+ ]
+]
+*/
+
+ /**
+ * @tag-hyphen abc
+ */
+ void custom_tag_with_a_hyphen() {}
+/*
+DocComment[DOC_COMMENT, pos:1
+ firstSentence: empty
+ body: empty
+ block tags: 1
+ UnknownBlockTag[UNKNOWN_BLOCK_TAG, pos:1
+ tag:tag-hyphen
+ content: 1
+ Text[TEXT, pos:13, abc]
+ ]
+]
+*/
+
+ /**
* @author jjg
*/
void simple_standard_block() { }
--- a/langtools/test/tools/javac/importscope/dependencies/DependenciesTest.java Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/test/tools/javac/importscope/dependencies/DependenciesTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -194,11 +194,6 @@
}
@Override
- public void push(AttributionKind ak, JCTree t) {
- inProcess.push(null);
- }
-
- @Override
public void pop() {
inProcess.pop();
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/lambda/MethodReference74.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @bug 8139836
+ * @summary Can't use super::x method reference when x is protected
+ * @run main MethodReference74
+ */
+
+public class MethodReference74 extends pkg.Parent {
+
+ public void protectedMethod() {
+ log = "In child, calling ... ";
+ Runnable r = super::protectedMethod;
+ r.run();
+ run(super::protectedMethod); // test also in invocation context.
+ }
+
+ private void run(Runnable r) {
+ r.run();
+ }
+
+ public static void main(String[] args) {
+ MethodReference74 child = new MethodReference74();
+ child.protectedMethod();
+ if (!child.log.equals("In child, calling ... parent's parent's ")) {
+ throw new AssertionError("Unexpected output" + child.log);
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/lambda/pkg/Parent.java Thu Oct 22 11:12:55 2015 -0700
@@ -0,0 +1,33 @@
+/*
+ * 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 pkg;
+
+public class Parent {
+
+ public String log;
+
+ protected void protectedMethod() {
+ log += " parent's ";
+ }
+}
--- a/langtools/test/tools/javac/parser/StringFoldingTest.java Thu Oct 22 08:47:52 2015 -0700
+++ b/langtools/test/tools/javac/parser/StringFoldingTest.java Thu Oct 22 11:12:55 2015 -0700
@@ -23,7 +23,7 @@
/*
* @test
- * @bug 7068902
+ * @bug 7068902 8139751
* @summary verify that string folding can be enabled or disabled
* @modules jdk.compiler
*/
@@ -86,11 +86,14 @@
if (disableStringFolding) {
if (text.contains("FOLDED")) {
- throw new AssertionError("Expected string folding");
+ throw new AssertionError("Expected no string folding");
+ }
+ if (!text.contains("\"F\"")) {
+ throw new AssertionError("Expected content not found");
}
} else {
if (!text.contains("FOLDED")) {
- throw new AssertionError("Expected no string folding");
+ throw new AssertionError("Expected string folding");
}
}
}