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