mirror of
https://github.com/openjdk/jdk.git
synced 2026-03-31 10:09:59 +00:00
Merge
This commit is contained in:
commit
574f2d4b2d
@ -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.source = 8
|
||||
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
|
||||
|
||||
@ -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}"/>
|
||||
|
||||
34
langtools/make/gensrc/Gensrc-jdk.jshell.gmk
Normal file
34
langtools/make/gensrc/Gensrc-jdk.jshell.gmk
Normal file
@ -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)
|
||||
@ -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" />
|
||||
|
||||
@ -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" />
|
||||
|
||||
@ -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 @@ done
|
||||
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}
|
||||
|
||||
@ -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 . -->
|
||||
|
||||
@ -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>
|
||||
@ -389,6 +444,10 @@
|
||||
<label>Source files - jdk.javadoc</label>
|
||||
<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>
|
||||
@ -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>
|
||||
|
||||
81
langtools/make/tools/anttasks/DumpClassesTask.java
Normal file
81
langtools/make/tools/anttasks/DumpClassesTask.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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 @@ public class SelectToolTask extends Task {
|
||||
},
|
||||
JAVADOC("javadoc"),
|
||||
JAVAH("javah"),
|
||||
JAVAP("javap");
|
||||
JAVAP("javap"),
|
||||
JSHELL("jshell");
|
||||
|
||||
String toolName;
|
||||
boolean bootstrap;
|
||||
|
||||
@ -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;
|
||||
|
||||
/**
|
||||
@ -43,6 +44,20 @@ public interface DocCommentTree extends DocTree {
|
||||
*/
|
||||
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.
|
||||
|
||||
@ -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.
|
||||
@ -77,6 +80,17 @@ public abstract class DocTrees extends Trees {
|
||||
*/
|
||||
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.
|
||||
|
||||
@ -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 static com.sun.tools.doclint.HtmlTag.Attr.*;
|
||||
|
||||
import com.sun.tools.javac.util.StringUtils;
|
||||
|
||||
import static com.sun.tools.doclint.HtmlTag.Attr.*;
|
||||
|
||||
/**
|
||||
* Enum representing HTML tags.
|
||||
*
|
||||
@ -646,15 +645,14 @@ public enum HtmlTag {
|
||||
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()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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.DCIdentifier;
|
||||
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.Name;
|
||||
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 @@ public class JavacTrees extends DocTrees {
|
||||
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 @@ public class JavacTrees extends DocTrees {
|
||||
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 @@ public class JavacTrees extends DocTrees {
|
||||
|
||||
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 @@ public class JavacTrees extends DocTrees {
|
||||
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 @@ public class JavacTrees extends DocTrees {
|
||||
javacTaskImpl.enter(null);
|
||||
}
|
||||
|
||||
|
||||
JCCompilationUnit unit = (JCCompilationUnit) path.getCompilationUnit();
|
||||
Copier copier = createCopier(treeMaker.forToplevel(unit));
|
||||
|
||||
|
||||
@ -1597,6 +1597,10 @@ public abstract class Symbol extends AnnoConstruct implements Element {
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
@ -263,6 +263,8 @@ public class ArgumentAttr extends JCTree.Visitor {
|
||||
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);
|
||||
|
||||
@ -2817,8 +2817,10 @@ public class Attr extends JCTree.Visitor {
|
||||
//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) {
|
||||
|
||||
@ -1107,8 +1107,10 @@ public class ClassWriter extends ClassFile {
|
||||
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);
|
||||
|
||||
@ -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.parser.Tokens.TokenKind;
|
||||
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.ListBuffer;
|
||||
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 @@ public class DocCommentParser {
|
||||
|
||||
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 @@ public class DocCommentParser {
|
||||
|
||||
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 @@ public class DocCommentParser {
|
||||
nextChar();
|
||||
if (isIdentifierStart(ch)) {
|
||||
Name name = readTagName();
|
||||
skipWhitespace();
|
||||
|
||||
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 {
|
||||
inlineText(); // skip content
|
||||
nextChar();
|
||||
if (!tp.retainWhiteSpace) {
|
||||
skipWhitespace();
|
||||
}
|
||||
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 @@ public class DocCommentParser {
|
||||
}
|
||||
}
|
||||
|
||||
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 @@ public class DocCommentParser {
|
||||
}
|
||||
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 @@ public class DocCommentParser {
|
||||
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 @@ public class DocCommentParser {
|
||||
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 @@ public class DocCommentParser {
|
||||
}
|
||||
|
||||
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 @@ public class DocCommentParser {
|
||||
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 @@ public class DocCommentParser {
|
||||
},
|
||||
|
||||
// {@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 @@ public class DocCommentParser {
|
||||
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 @@ public class DocCommentParser {
|
||||
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 @@ public class DocCommentParser {
|
||||
},
|
||||
|
||||
// {@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);
|
||||
}
|
||||
|
||||
@ -973,7 +973,7 @@ public class JavacParser implements Parser {
|
||||
*/
|
||||
protected JCExpression foldStrings(JCExpression tree) {
|
||||
if (!allowStringFolding)
|
||||
return null;
|
||||
return tree;
|
||||
ListBuffer<JCExpression> opStack = new ListBuffer<>();
|
||||
ListBuffer<JCLiteral> litBuf = new ListBuffer<>();
|
||||
boolean needsFolding = false;
|
||||
|
||||
@ -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.JCDiagnostic.SimpleDiagnosticPosition;
|
||||
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 abstract class DCTree implements DocTree {
|
||||
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;
|
||||
}
|
||||
@ -131,6 +137,11 @@ public abstract class DCTree implements DocTree {
|
||||
return firstSentence;
|
||||
}
|
||||
|
||||
@DefinedBy(Api.COMPILER_TREE)
|
||||
public List<? extends DocTree> getFullBody() {
|
||||
return fullBody;
|
||||
}
|
||||
|
||||
@DefinedBy(Api.COMPILER_TREE)
|
||||
public List<? extends DocTree> getBody() {
|
||||
return body;
|
||||
|
||||
@ -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 @@ public class DocPretty implements DocTreeVisitor<Void,Void> {
|
||||
@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 @@ public class DocPretty implements DocTreeVisitor<Void,Void> {
|
||||
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) {
|
||||
|
||||
@ -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 @@ public class DocTreeMaker {
|
||||
/** 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 @@ public class DocTreeMaker {
|
||||
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 @@ public class DocTreeMaker {
|
||||
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 @@ public class DocTreeMaker {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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 + "[\\$\\.]?");
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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 {}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@ -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)
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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";
|
||||
}
|
||||
}
|
||||
130
langtools/src/jdk.jshell/share/classes/jdk/jshell/Diag.java
Normal file
130
langtools/src/jdk.jshell/share/classes/jdk/jshell/Diag.java
Normal file
@ -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");
|
||||
}
|
||||
}
|
||||
133
langtools/src/jdk.jshell/share/classes/jdk/jshell/DiagList.java
Normal file
133
langtools/src/jdk.jshell/share/classes/jdk/jshell/DiagList.java
Normal file
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
730
langtools/src/jdk.jshell/share/classes/jdk/jshell/Eval.java
Normal file
730
langtools/src/jdk.jshell/share/classes/jdk/jshell/Eval.java
Normal file
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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)));
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
676
langtools/src/jdk.jshell/share/classes/jdk/jshell/JShell.java
Normal file
676
langtools/src/jdk.jshell/share/classes/jdk/jshell/JShell.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
290
langtools/src/jdk.jshell/share/classes/jdk/jshell/Key.java
Normal file
290
langtools/src/jdk.jshell/share/classes/jdk/jshell/Key.java
Normal file
@ -0,0 +1,290 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jshell;
|
||||
|
||||
import jdk.jshell.Snippet.Kind;
|
||||
import jdk.jshell.Snippet.SubKind;
|
||||
|
||||
/**
|
||||
* The Key is unique for a given signature. Used internal to the implementation
|
||||
* to group signature matched Snippets. Snippet kinds without a signature
|
||||
* (for example expressions) each have their own UniqueKey.
|
||||
* @author Robert Field
|
||||
*/
|
||||
abstract class Key {
|
||||
|
||||
/**
|
||||
* The unique index for this Key
|
||||
*/
|
||||
private final int index;
|
||||
|
||||
/**
|
||||
* The JShell corresponding to this Key
|
||||
*/
|
||||
private final JShell state;
|
||||
|
||||
Key(JShell state) {
|
||||
this.index = state.nextKeyIndex();
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* The unique numeric identifier for the snippet. Snippets which replace or
|
||||
* redefine existing snippets take on the same key index as the replaced or
|
||||
* redefined snippet.
|
||||
* The index values are monotonically increasing integers.
|
||||
* @return the Key index
|
||||
*/
|
||||
int index() { return index; }
|
||||
|
||||
/**
|
||||
* The kind for the key. Indicates the subinterface of Key.
|
||||
* @return the Kind of the Key
|
||||
*/
|
||||
abstract Kind kind();
|
||||
|
||||
/**
|
||||
* For foreign key testing.
|
||||
*/
|
||||
JShell state() { return state; }
|
||||
|
||||
/**
|
||||
* Grouping for snippets which persist and influence future code.
|
||||
* They are keyed off at least the name. They may be Modified/Replaced
|
||||
* with new input and can be dropped (JShell#drop).
|
||||
*/
|
||||
static abstract class PersistentKey extends Key {
|
||||
|
||||
private final String name;
|
||||
|
||||
PersistentKey(JShell state, String name) {
|
||||
super(state);
|
||||
this.name = name;
|
||||
}
|
||||
/**
|
||||
* Name of the snippet.
|
||||
*
|
||||
* @return the name of the snippet.
|
||||
*/
|
||||
String name() { return name; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Grouping for snippets which reference declarations
|
||||
*/
|
||||
static abstract class DeclarationKey extends PersistentKey {
|
||||
|
||||
DeclarationKey(JShell state, String name) {
|
||||
super(state, name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Key for a class definition Keys of TYPE_DECL. Keyed off class name.
|
||||
*/
|
||||
static class TypeDeclKey extends DeclarationKey {
|
||||
|
||||
TypeDeclKey(JShell state, String name) {
|
||||
super(state, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
Kind kind() { return Kind.TYPE_DECL; }
|
||||
|
||||
@Override
|
||||
public String toString() { return "ClassKey(" + name() + ")#" + index(); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Key for a method definition Keys of METHOD. Keyed off method name and
|
||||
* parameter types.
|
||||
*/
|
||||
static class MethodKey extends DeclarationKey {
|
||||
|
||||
private final String parameterTypes;
|
||||
|
||||
MethodKey(JShell state, String name, String parameterTypes) {
|
||||
super(state, name);
|
||||
this.parameterTypes = parameterTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
Kind kind() { return Kind.METHOD; }
|
||||
|
||||
/**
|
||||
* The parameter types of the method. Because of overloading of methods,
|
||||
* the parameter types are part of the key.
|
||||
*
|
||||
* @return a String representation of the parameter types of the method.
|
||||
*/
|
||||
String parameterTypes() { return parameterTypes; }
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() { return "MethodKey(" + name() +
|
||||
"(" + parameterTypes() + "))#" + index(); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Key for a variable definition Keys of VARIABLE. Keyed off variable
|
||||
* name.
|
||||
*/
|
||||
static class VarKey extends DeclarationKey {
|
||||
|
||||
VarKey(JShell state, String name) {
|
||||
super(state, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Kind kind() { return Kind.VAR; }
|
||||
|
||||
@Override
|
||||
public String toString() { return "VariableKey(" + name() + ")#" + index(); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Key for an import. Keys of IMPORT. Keyed off import text and whether
|
||||
* import is static.
|
||||
*/
|
||||
static class ImportKey extends PersistentKey {
|
||||
|
||||
private final SubKind snippetKind;
|
||||
|
||||
ImportKey(JShell state, String name,SubKind snippetKind) {
|
||||
super(state, name);
|
||||
this.snippetKind = snippetKind;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Kind kind() { return Kind.IMPORT; }
|
||||
|
||||
/**
|
||||
* Which kind of import.
|
||||
*
|
||||
* @return the appropriate SubKind.
|
||||
*/
|
||||
SubKind snippetKind() {
|
||||
return snippetKind;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() { return "ImportKey(" + name() + "," +
|
||||
snippetKind + ")#" + index(); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Grouping for snippets which are the key for only one input -- even if the
|
||||
* exactly same entry is made again. The referenced snippets are thus
|
||||
* unmodifiable.
|
||||
*/
|
||||
static abstract class UniqueKey extends Key {
|
||||
|
||||
UniqueKey(JShell state) {
|
||||
super(state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Key for a statement snippet. Keys of STATEMENT. Uniquely keyed, see
|
||||
* UniqueKey.
|
||||
*/
|
||||
static class StatementKey extends UniqueKey {
|
||||
|
||||
StatementKey(JShell state) {
|
||||
super(state);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Kind kind() {
|
||||
return Kind.STATEMENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() { return "StatementKey#" + index(); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Key for an expression. Keys of EXPRESSION. Uniquely keyed, see
|
||||
* UniqueKey.
|
||||
*/
|
||||
static class ExpressionKey extends UniqueKey {
|
||||
|
||||
private final String name;
|
||||
private final String typeName;
|
||||
|
||||
ExpressionKey(JShell state, String name, String typeName) {
|
||||
super(state);
|
||||
this.name = name;
|
||||
this.typeName = typeName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Kind kind() { return Kind.EXPRESSION; }
|
||||
|
||||
|
||||
/**
|
||||
* Variable name which is the value of the expression. Since the
|
||||
* expression is either just an identifier which is a variable or it is
|
||||
* an assignment to a variable, there is always a variable which is the
|
||||
* subject of the expression. All other expression snippets become
|
||||
* temporary variables referenced by a VariableKey.
|
||||
*
|
||||
* @return the name of the variable which is the subject of the
|
||||
* expression.
|
||||
*/
|
||||
String name() { return name; }
|
||||
|
||||
/**
|
||||
* Type of the expression
|
||||
*
|
||||
* @return String representation of the type of the expression.
|
||||
*/
|
||||
String typeName() { return typeName; }
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() { return "ExpressionKey(" + name() + ")#" + index(); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Key for an erroneous snippet. Keys of ERRONEOUS. Uniquely keyed, see
|
||||
* UniqueKey.
|
||||
*/
|
||||
static class ErroneousKey extends UniqueKey {
|
||||
|
||||
ErroneousKey(JShell state) {
|
||||
super(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
Kind kind() { return Kind.ERRONEOUS; }
|
||||
|
||||
@Override
|
||||
public String toString() { return "ErroneousKey#" + index(); }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jshell;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
import jdk.jshell.Key.TypeDeclKey;
|
||||
import jdk.jshell.Key.ErroneousKey;
|
||||
import jdk.jshell.Key.ExpressionKey;
|
||||
import jdk.jshell.Key.ImportKey;
|
||||
import jdk.jshell.Key.MethodKey;
|
||||
import jdk.jshell.Key.StatementKey;
|
||||
import jdk.jshell.Key.VarKey;
|
||||
import jdk.jshell.Snippet.SubKind;
|
||||
import static jdk.jshell.Snippet.SubKind.SINGLE_STATIC_IMPORT_SUBKIND;
|
||||
import static jdk.jshell.Snippet.SubKind.SINGLE_TYPE_IMPORT_SUBKIND;
|
||||
|
||||
/**
|
||||
* Maps signature to Key by Kind.
|
||||
* @author Robert Field
|
||||
*/
|
||||
class KeyMap {
|
||||
private final JShell state;
|
||||
|
||||
KeyMap(JShell state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
private final Map<String, TypeDeclKey> classMap = new LinkedHashMap<>();
|
||||
private final Map<String, MethodKey> methodMap = new LinkedHashMap<>();
|
||||
private final Map<String, VarKey> varMap = new LinkedHashMap<>();
|
||||
private final Map<String, ImportKey> importMap = new LinkedHashMap<>();
|
||||
|
||||
TypeDeclKey keyForClass(String name) {
|
||||
return classMap.computeIfAbsent(name, k -> new TypeDeclKey(state, name));
|
||||
}
|
||||
|
||||
MethodKey keyForMethod(String name, String parameterTypes) {
|
||||
return methodMap.computeIfAbsent(name + ":" + parameterTypes,
|
||||
k -> new MethodKey(state, name, parameterTypes));
|
||||
}
|
||||
|
||||
VarKey keyForVariable(String name) {
|
||||
return varMap.computeIfAbsent(name, k -> new VarKey(state, name));
|
||||
}
|
||||
|
||||
ImportKey keyForImport(String name, SubKind snippetKind) {
|
||||
return importMap.computeIfAbsent(name + ":"
|
||||
+ ((snippetKind == SINGLE_TYPE_IMPORT_SUBKIND || snippetKind == SINGLE_STATIC_IMPORT_SUBKIND)
|
||||
? "single"
|
||||
: snippetKind.toString()),
|
||||
k -> new ImportKey(state, name, snippetKind));
|
||||
}
|
||||
|
||||
StatementKey keyForStatement() {
|
||||
return new StatementKey(state);
|
||||
}
|
||||
|
||||
ExpressionKey keyForExpression(String name, String type) {
|
||||
return new ExpressionKey(state, name, type);
|
||||
}
|
||||
|
||||
ErroneousKey keyForErroneous() {
|
||||
return new ErroneousKey(state);
|
||||
}
|
||||
|
||||
boolean doesVariableNameExist(String name) {
|
||||
return varMap.get(name) != null;
|
||||
}
|
||||
|
||||
Stream<ImportKey> importKeys() {
|
||||
return importMap.values().stream();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,202 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jshell;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Within a String, mask code comments and ignored modifiers (within context).
|
||||
*
|
||||
* @author Robert Field
|
||||
*/
|
||||
class MaskCommentsAndModifiers {
|
||||
|
||||
private final static Set<String> IGNORED_MODIFERS =
|
||||
Stream.of( "public", "protected", "private", "static", "final" )
|
||||
.collect( Collectors.toSet() );
|
||||
|
||||
private final StringBuilder sbCleared = new StringBuilder();
|
||||
private final StringBuilder sbMask = new StringBuilder();
|
||||
private final String str;
|
||||
private final int length;
|
||||
private final boolean maskModifiers;
|
||||
private int next = 0;
|
||||
private boolean wasMasked = false;
|
||||
private boolean inside = false;
|
||||
|
||||
@SuppressWarnings("empty-statement")
|
||||
public MaskCommentsAndModifiers(String s, boolean maskModifiers) {
|
||||
this.str = s;
|
||||
this.length = s.length();
|
||||
this.maskModifiers = maskModifiers;
|
||||
do { } while (next());
|
||||
}
|
||||
|
||||
public String cleared() {
|
||||
return sbCleared.toString();
|
||||
}
|
||||
|
||||
public String mask() {
|
||||
return sbMask.toString();
|
||||
}
|
||||
|
||||
public boolean wasMasked() {
|
||||
return wasMasked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the next character
|
||||
*/
|
||||
private int read() {
|
||||
if (next >= length) {
|
||||
return -1;
|
||||
}
|
||||
return str.charAt(next++);
|
||||
}
|
||||
|
||||
private void write(StringBuilder sb, int ch) {
|
||||
sb.append((char)ch);
|
||||
}
|
||||
|
||||
private void write(int ch) {
|
||||
write(sbCleared, ch);
|
||||
write(sbMask, Character.isWhitespace(ch) ? ch : ' ');
|
||||
}
|
||||
|
||||
private void writeMask(int ch) {
|
||||
wasMasked = true;
|
||||
write(sbMask, ch);
|
||||
write(sbCleared, Character.isWhitespace(ch) ? ch : ' ');
|
||||
}
|
||||
|
||||
private void write(CharSequence s) {
|
||||
for (int cp : s.chars().toArray()) {
|
||||
write(cp);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeMask(CharSequence s) {
|
||||
for (int cp : s.chars().toArray()) {
|
||||
writeMask(cp);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean next() {
|
||||
return next(read());
|
||||
}
|
||||
|
||||
private boolean next(int c) {
|
||||
if (c < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (c == '\'' || c == '"') {
|
||||
inside = true;
|
||||
write(c);
|
||||
int match = c;
|
||||
c = read();
|
||||
while (c != match) {
|
||||
if (c < 0) {
|
||||
return false;
|
||||
}
|
||||
if (c == '\n' || c == '\r') {
|
||||
write(c);
|
||||
return true;
|
||||
}
|
||||
if (c == '\\') {
|
||||
write(c);
|
||||
c = read();
|
||||
}
|
||||
write(c);
|
||||
c = read();
|
||||
}
|
||||
write(c);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (c == '/') {
|
||||
c = read();
|
||||
if (c == '*') {
|
||||
writeMask('/');
|
||||
writeMask(c);
|
||||
int prevc = 0;
|
||||
while ((c = read()) != '/' || prevc != '*') {
|
||||
if (c < 0) {
|
||||
return false;
|
||||
}
|
||||
writeMask(c);
|
||||
prevc = c;
|
||||
}
|
||||
writeMask(c);
|
||||
return true;
|
||||
} else if (c == '/') {
|
||||
writeMask('/');
|
||||
writeMask(c);
|
||||
while ((c = read()) != '\n' && c != '\r') {
|
||||
if (c < 0) {
|
||||
return false;
|
||||
}
|
||||
writeMask(c);
|
||||
}
|
||||
writeMask(c);
|
||||
return true;
|
||||
} else {
|
||||
inside = true;
|
||||
write('/');
|
||||
// read character falls through
|
||||
}
|
||||
}
|
||||
|
||||
if (Character.isJavaIdentifierStart(c)) {
|
||||
if (maskModifiers && !inside) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
do {
|
||||
write(sb, c);
|
||||
c = read();
|
||||
} while (Character.isJavaIdentifierPart(c));
|
||||
String id = sb.toString();
|
||||
if (IGNORED_MODIFERS.contains(id)) {
|
||||
writeMask(sb);
|
||||
} else {
|
||||
write(sb);
|
||||
if (id.equals("import")) {
|
||||
inside = true;
|
||||
}
|
||||
}
|
||||
return next(c); // recurse to handle left-over character
|
||||
}
|
||||
} else if (!Character.isWhitespace(c)) {
|
||||
inside = true;
|
||||
}
|
||||
|
||||
if (c < 0) {
|
||||
return false;
|
||||
}
|
||||
write(c);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,651 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jshell;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URI;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import javax.tools.JavaFileObject.Kind;
|
||||
import static javax.tools.StandardLocation.CLASS_PATH;
|
||||
import javax.tools.FileObject;
|
||||
import javax.tools.JavaFileManager;
|
||||
import javax.tools.JavaFileObject;
|
||||
import javax.tools.SimpleJavaFileObject;
|
||||
import javax.tools.StandardJavaFileManager;
|
||||
|
||||
import com.sun.tools.javac.util.DefinedBy;
|
||||
import com.sun.tools.javac.util.DefinedBy.Api;
|
||||
|
||||
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_FMGR;
|
||||
|
||||
/**
|
||||
* File manager for the compiler API. Reads from memory (Strings) and writes
|
||||
* class files to memory (cached OutputMemoryJavaFileObject).
|
||||
*
|
||||
* @author Robert Field
|
||||
*/
|
||||
class MemoryFileManager implements JavaFileManager {
|
||||
|
||||
private final StandardJavaFileManager stdFileManager;
|
||||
|
||||
private final Map<String, OutputMemoryJavaFileObject> classObjects = new TreeMap<>();
|
||||
|
||||
private ClassFileCreationListener classListener = null;
|
||||
|
||||
private final ClassLoader loader = new REPLClassLoader();
|
||||
|
||||
private final JShell proc;
|
||||
|
||||
// Upcoming Jigsaw
|
||||
private Method inferModuleNameMethod = null;
|
||||
private Method listModuleLocationsMethod = null;
|
||||
|
||||
static abstract class MemoryJavaFileObject extends SimpleJavaFileObject {
|
||||
|
||||
public MemoryJavaFileObject(String name, JavaFileObject.Kind kind) {
|
||||
super(URI.create("string:///" + name.replace('.', '/')
|
||||
+ kind.extension), kind);
|
||||
}
|
||||
}
|
||||
|
||||
class SourceMemoryJavaFileObject extends MemoryJavaFileObject {
|
||||
private final String src;
|
||||
private final Object origin;
|
||||
|
||||
SourceMemoryJavaFileObject(Object origin, String className, String code) {
|
||||
super(className, JavaFileObject.Kind.SOURCE);
|
||||
this.origin = origin;
|
||||
this.src = code;
|
||||
}
|
||||
|
||||
public Object getOrigin() {
|
||||
return origin;
|
||||
}
|
||||
|
||||
@Override @DefinedBy(Api.COMPILER)
|
||||
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
|
||||
return src;
|
||||
}
|
||||
}
|
||||
|
||||
static class OutputMemoryJavaFileObject extends MemoryJavaFileObject {
|
||||
|
||||
/**
|
||||
* Byte code created by the compiler will be stored in this
|
||||
* ByteArrayOutputStream.
|
||||
*/
|
||||
private ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
private byte[] bytes = null;
|
||||
|
||||
private final String className;
|
||||
|
||||
public OutputMemoryJavaFileObject(String name, JavaFileObject.Kind kind) {
|
||||
super(name, kind);
|
||||
this.className = name;
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
if (bytes == null) {
|
||||
bytes = bos.toByteArray();
|
||||
bos = null;
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public void dump() {
|
||||
try {
|
||||
Path dumpDir = FileSystems.getDefault().getPath("dump");
|
||||
if (Files.notExists(dumpDir)) {
|
||||
Files.createDirectory(dumpDir);
|
||||
}
|
||||
Path file = FileSystems.getDefault().getPath("dump", getName() + ".class");
|
||||
Files.write(file, getBytes());
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override @DefinedBy(Api.COMPILER)
|
||||
public String getName() {
|
||||
return className;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will provide the compiler with an output stream that leads to our
|
||||
* byte array.
|
||||
*/
|
||||
@Override @DefinedBy(Api.COMPILER)
|
||||
public OutputStream openOutputStream() throws IOException {
|
||||
return bos;
|
||||
}
|
||||
|
||||
@Override @DefinedBy(Api.COMPILER)
|
||||
public InputStream openInputStream() throws IOException {
|
||||
return new ByteArrayInputStream(getBytes());
|
||||
}
|
||||
}
|
||||
|
||||
// For restoring process-local execution support
|
||||
class REPLClassLoader extends ClassLoader {
|
||||
|
||||
@Override
|
||||
protected Class<?> findClass(String name) throws ClassNotFoundException {
|
||||
OutputMemoryJavaFileObject fo = classObjects.get(name);
|
||||
proc.debug(DBG_FMGR, "findClass %s = %s\n", name, fo);
|
||||
if (fo == null) {
|
||||
throw new ClassNotFoundException("Not ours");
|
||||
}
|
||||
byte[] b = fo.getBytes();
|
||||
return super.defineClass(name, b, 0, b.length, null);
|
||||
}
|
||||
}
|
||||
|
||||
public MemoryFileManager(StandardJavaFileManager standardManager, JShell proc) {
|
||||
this.stdFileManager = standardManager;
|
||||
this.proc = proc;
|
||||
}
|
||||
|
||||
private Collection<OutputMemoryJavaFileObject> generatedClasses() {
|
||||
return classObjects.values();
|
||||
}
|
||||
|
||||
// For debugging dumps
|
||||
public void dumpClasses() {
|
||||
for (OutputMemoryJavaFileObject co : generatedClasses()) {
|
||||
co.dump();
|
||||
}
|
||||
}
|
||||
|
||||
// For restoring process-local execution support
|
||||
public Class<?> findGeneratedClass(String genClassFullName) throws ClassNotFoundException {
|
||||
for (OutputMemoryJavaFileObject co : generatedClasses()) {
|
||||
if (co.className.equals(genClassFullName)) {
|
||||
Class<?> klass = loadClass(co.className);
|
||||
proc.debug(DBG_FMGR, "Loaded %s\n", klass);
|
||||
return klass;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// For restoring process-local execution support
|
||||
public byte[] findGeneratedBytes(String genClassFullName) throws ClassNotFoundException {
|
||||
for (OutputMemoryJavaFileObject co : generatedClasses()) {
|
||||
if (co.className.equals(genClassFullName)) {
|
||||
return co.getBytes();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// For restoring process-local execution support
|
||||
public Class<?> loadClass(String name) throws ClassNotFoundException {
|
||||
return getClassLoader(null).loadClass(name);
|
||||
}
|
||||
|
||||
public JavaFileObject createSourceFileObject(Object origin, String name, String code) {
|
||||
return new SourceMemoryJavaFileObject(origin, name, code);
|
||||
}
|
||||
|
||||
// Make compatible with Jigsaw
|
||||
public String inferModuleName(Location location) {
|
||||
try {
|
||||
if (inferModuleNameMethod == null) {
|
||||
inferModuleNameMethod = JavaFileManager.class.getDeclaredMethod("inferModuleName", Location.class);
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
String result = (String) inferModuleNameMethod.invoke(stdFileManager, location);
|
||||
return result;
|
||||
} catch (NoSuchMethodException | SecurityException ex) {
|
||||
throw new InternalError("Cannot lookup JavaFileManager method", ex);
|
||||
} catch (IllegalAccessException |
|
||||
IllegalArgumentException |
|
||||
InvocationTargetException ex) {
|
||||
throw new InternalError("Cannot invoke JavaFileManager method", ex);
|
||||
}
|
||||
}
|
||||
|
||||
// Make compatible with Jigsaw
|
||||
public Iterable<Set<Location>> listModuleLocations(Location location) throws IOException {
|
||||
try {
|
||||
if (listModuleLocationsMethod == null) {
|
||||
listModuleLocationsMethod = JavaFileManager.class.getDeclaredMethod("listModuleLocations", Location.class);
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Iterable<Set<Location>> result = (Iterable<Set<Location>>) listModuleLocationsMethod.invoke(stdFileManager, location);
|
||||
return result;
|
||||
} catch (NoSuchMethodException | SecurityException ex) {
|
||||
throw new InternalError("Cannot lookup JavaFileManager method", ex);
|
||||
} catch (IllegalAccessException |
|
||||
IllegalArgumentException |
|
||||
InvocationTargetException ex) {
|
||||
throw new InternalError("Cannot invoke JavaFileManager method", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a class loader for loading plug-ins from the given location. For
|
||||
* example, to load annotation processors, a compiler will request a class
|
||||
* loader for the {@link
|
||||
* StandardLocation#ANNOTATION_PROCESSOR_PATH
|
||||
* ANNOTATION_PROCESSOR_PATH} location.
|
||||
*
|
||||
* @param location a location
|
||||
* @return a class loader for the given location; or {@code null}
|
||||
* if loading plug-ins from the given location is disabled or if
|
||||
* the location is not known
|
||||
* @throws SecurityException if a class loader can not be created
|
||||
* in the current security context
|
||||
* @throws IllegalStateException if {@link #close} has been called
|
||||
* and this file manager cannot be reopened
|
||||
*/
|
||||
@Override @DefinedBy(Api.COMPILER)
|
||||
public ClassLoader getClassLoader(JavaFileManager.Location location) {
|
||||
proc.debug(DBG_FMGR, "getClassLoader: location\n", location);
|
||||
return loader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all file objects matching the given criteria in the given
|
||||
* location. List file objects in "subpackages" if recurse is
|
||||
* true.
|
||||
*
|
||||
* <p>Note: even if the given location is unknown to this file
|
||||
* manager, it may not return {@code null}. Also, an unknown
|
||||
* location may not cause an exception.
|
||||
*
|
||||
* @param location a location
|
||||
* @param packageName a package name
|
||||
* @param kinds return objects only of these kinds
|
||||
* @param recurse if true include "subpackages"
|
||||
* @return an Iterable of file objects matching the given criteria
|
||||
* @throws IOException if an I/O error occurred, or if {@link
|
||||
* #close} has been called and this file manager cannot be
|
||||
* reopened
|
||||
* @throws IllegalStateException if {@link #close} has been called
|
||||
* and this file manager cannot be reopened
|
||||
*/
|
||||
@Override @DefinedBy(Api.COMPILER)
|
||||
public Iterable<JavaFileObject> list(JavaFileManager.Location location,
|
||||
String packageName,
|
||||
Set<JavaFileObject.Kind> kinds,
|
||||
boolean recurse)
|
||||
throws IOException {
|
||||
Iterable<JavaFileObject> stdList = stdFileManager.list(location, packageName, kinds, recurse);
|
||||
if (location==CLASS_PATH && packageName.equals("REPL")) {
|
||||
// if the desired list is for our JShell package, lazily iterate over
|
||||
// first the standard list then any generated classes.
|
||||
return () -> new Iterator<JavaFileObject>() {
|
||||
boolean stdDone = false;
|
||||
Iterator<? extends JavaFileObject> it;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
if (it == null) {
|
||||
it = stdList.iterator();
|
||||
}
|
||||
if (it.hasNext()) {
|
||||
return true;
|
||||
}
|
||||
if (stdDone) {
|
||||
return false;
|
||||
} else {
|
||||
stdDone = true;
|
||||
it = generatedClasses().iterator();
|
||||
return it.hasNext();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaFileObject next() {
|
||||
if (!hasNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
return it.next();
|
||||
}
|
||||
|
||||
};
|
||||
} else {
|
||||
return stdList;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Infers a binary name of a file object based on a location. The
|
||||
* binary name returned might not be a valid binary name according to
|
||||
* <cite>The Java&trade { throw new UnsupportedOperationException("Not supported yet."); } Language Specification</cite>.
|
||||
*
|
||||
* @param location a location
|
||||
* @param file a file object
|
||||
* @return a binary name or {@code null} the file object is not
|
||||
* found in the given location
|
||||
* @throws IllegalStateException if {@link #close} has been called
|
||||
* and this file manager cannot be reopened
|
||||
*/
|
||||
@Override @DefinedBy(Api.COMPILER)
|
||||
public String inferBinaryName(JavaFileManager.Location location, JavaFileObject file) {
|
||||
if (file instanceof OutputMemoryJavaFileObject) {
|
||||
OutputMemoryJavaFileObject ofo = (OutputMemoryJavaFileObject) file;
|
||||
proc.debug(DBG_FMGR, "inferBinaryName %s => %s\n", file, ofo.getName());
|
||||
return ofo.getName();
|
||||
} else {
|
||||
return stdFileManager.inferBinaryName(location, file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two file objects and return true if they represent the
|
||||
* same underlying object.
|
||||
*
|
||||
* @param a a file object
|
||||
* @param b a file object
|
||||
* @return true if the given file objects represent the same
|
||||
* underlying object
|
||||
*
|
||||
* @throws IllegalArgumentException if either of the arguments
|
||||
* were created with another file manager and this file manager
|
||||
* does not support foreign file objects
|
||||
*/
|
||||
@Override @DefinedBy(Api.COMPILER)
|
||||
public boolean isSameFile(FileObject a, FileObject b) {
|
||||
return stdFileManager.isSameFile(b, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the given option is supported and if so, the
|
||||
* number of arguments the option takes.
|
||||
*
|
||||
* @param option an option
|
||||
* @return the number of arguments the given option takes or -1 if
|
||||
* the option is not supported
|
||||
*/
|
||||
@Override @DefinedBy(Api.COMPILER)
|
||||
public int isSupportedOption(String option) {
|
||||
proc.debug(DBG_FMGR, "isSupportedOption: %s\n", option);
|
||||
return stdFileManager.isSupportedOption(option);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles one option. If {@code current} is an option to this
|
||||
* file manager it will consume any arguments to that option from
|
||||
* {@code remaining} and return true, otherwise return false.
|
||||
*
|
||||
* @param current current option
|
||||
* @param remaining remaining options
|
||||
* @return true if this option was handled by this file manager,
|
||||
* false otherwise
|
||||
* @throws IllegalArgumentException if this option to this file
|
||||
* manager is used incorrectly
|
||||
* @throws IllegalStateException if {@link #close} has been called
|
||||
* and this file manager cannot be reopened
|
||||
*/
|
||||
@Override @DefinedBy(Api.COMPILER)
|
||||
public boolean handleOption(String current, Iterator<String> remaining) {
|
||||
proc.debug(DBG_FMGR, "handleOption: current: %s\n", current +
|
||||
", remaining: " + remaining);
|
||||
return stdFileManager.handleOption(current, remaining);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a location is known to this file manager.
|
||||
*
|
||||
* @param location a location
|
||||
* @return true if the location is known
|
||||
*/
|
||||
@Override @DefinedBy(Api.COMPILER)
|
||||
public boolean hasLocation(JavaFileManager.Location location) {
|
||||
proc.debug(DBG_FMGR, "hasLocation: location: %s\n", location);
|
||||
return stdFileManager.hasLocation(location);
|
||||
}
|
||||
|
||||
interface ClassFileCreationListener {
|
||||
void newClassFile(OutputMemoryJavaFileObject jfo, JavaFileManager.Location location,
|
||||
String className, Kind kind, FileObject sibling);
|
||||
}
|
||||
|
||||
void registerClassFileCreationListener(ClassFileCreationListener listen) {
|
||||
this.classListener = listen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@linkplain JavaFileObject file object} for input
|
||||
* representing the specified class of the specified kind in the
|
||||
* given location.
|
||||
*
|
||||
* @param location a location
|
||||
* @param className the name of a class
|
||||
* @param kind the kind of file, must be one of {@link
|
||||
* JavaFileObject.Kind#SOURCE SOURCE} or {@link
|
||||
* JavaFileObject.Kind#CLASS CLASS}
|
||||
* @return a file object, might return {@code null} if the
|
||||
* file does not exist
|
||||
* @throws IllegalArgumentException if the location is not known
|
||||
* to this file manager and the file manager does not support
|
||||
* unknown locations, or if the kind is not valid
|
||||
* @throws IOException if an I/O error occurred, or if {@link
|
||||
* #close} has been called and this file manager cannot be
|
||||
* reopened
|
||||
* @throws IllegalStateException if {@link #close} has been called
|
||||
* and this file manager cannot be reopened
|
||||
*/
|
||||
@Override @DefinedBy(Api.COMPILER)
|
||||
public JavaFileObject getJavaFileForInput(JavaFileManager.Location location,
|
||||
String className,
|
||||
JavaFileObject.Kind kind)
|
||||
throws IOException {
|
||||
return stdFileManager.getJavaFileForInput(location, className, kind);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@linkplain JavaFileObject file object} for output
|
||||
* representing the specified class of the specified kind in the
|
||||
* given location.
|
||||
*
|
||||
* <p>Optionally, this file manager might consider the sibling as
|
||||
* a hint for where to place the output. The exact semantics of
|
||||
* this hint is unspecified. The JDK compiler, javac, for
|
||||
* example, will place class files in the same directories as
|
||||
* originating source files unless a class file output directory
|
||||
* is provided. To facilitate this behavior, javac might provide
|
||||
* the originating source file as sibling when calling this
|
||||
* method.
|
||||
*
|
||||
* @param location a location
|
||||
* @param className the name of a class
|
||||
* @param kind the kind of file, must be one of {@link
|
||||
* JavaFileObject.Kind#SOURCE SOURCE} or {@link
|
||||
* JavaFileObject.Kind#CLASS CLASS}
|
||||
* @param sibling a file object to be used as hint for placement;
|
||||
* might be {@code null}
|
||||
* @return a file object for output
|
||||
* @throws IllegalArgumentException if sibling is not known to
|
||||
* this file manager, or if the location is not known to this file
|
||||
* manager and the file manager does not support unknown
|
||||
* locations, or if the kind is not valid
|
||||
* @throws IOException if an I/O error occurred, or if {@link
|
||||
* #close} has been called and this file manager cannot be
|
||||
* reopened
|
||||
* @throws IllegalStateException {@link #close} has been called
|
||||
* and this file manager cannot be reopened
|
||||
*/
|
||||
@Override @DefinedBy(Api.COMPILER)
|
||||
public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location,
|
||||
String className, Kind kind, FileObject sibling) throws IOException {
|
||||
|
||||
OutputMemoryJavaFileObject fo;
|
||||
fo = new OutputMemoryJavaFileObject(className, kind);
|
||||
classObjects.put(className, fo);
|
||||
proc.debug(DBG_FMGR, "Set out file: %s = %s\n", className, fo);
|
||||
if (classListener != null) {
|
||||
classListener.newClassFile(fo, location, className, kind, sibling);
|
||||
}
|
||||
return fo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@linkplain FileObject file object} for input
|
||||
* representing the specified <a href="JavaFileManager.html#relative_name">relative
|
||||
* name</a> in the specified package in the given location.
|
||||
*
|
||||
* <p>If the returned object represents a {@linkplain
|
||||
* JavaFileObject.Kind#SOURCE source} or {@linkplain
|
||||
* JavaFileObject.Kind#CLASS class} file, it must be an instance
|
||||
* of {@link JavaFileObject}.
|
||||
*
|
||||
* <p>Informally, the file object returned by this method is
|
||||
* located in the concatenation of the location, package name, and
|
||||
* relative name. For example, to locate the properties file
|
||||
* "resources/compiler.properties" in the package
|
||||
* "com.sun.tools.javac" in the {@linkplain
|
||||
* StandardLocation#SOURCE_PATH SOURCE_PATH} location, this method
|
||||
* might be called like so:
|
||||
*
|
||||
* <pre>getFileForInput(SOURCE_PATH, "com.sun.tools.javac", "resources/compiler.properties");</pre>
|
||||
*
|
||||
* <p>If the call was executed on Windows, with SOURCE_PATH set to
|
||||
* <code>"C:\Documents and Settings\UncleBob\src\share\classes"</code>,
|
||||
* a valid result would be a file object representing the file
|
||||
* <code>"C:\Documents and Settings\UncleBob\src\share\classes\com\sun\tools\javac\resources\compiler.properties"</code>.
|
||||
*
|
||||
* @param location a location
|
||||
* @param packageName a package name
|
||||
* @param relativeName a relative name
|
||||
* @return a file object, might return {@code null} if the file
|
||||
* does not exist
|
||||
* @throws IllegalArgumentException if the location is not known
|
||||
* to this file manager and the file manager does not support
|
||||
* unknown locations, or if {@code relativeName} is not valid
|
||||
* @throws IOException if an I/O error occurred, or if {@link
|
||||
* #close} has been called and this file manager cannot be
|
||||
* reopened
|
||||
* @throws IllegalStateException if {@link #close} has been called
|
||||
* and this file manager cannot be reopened
|
||||
*/
|
||||
@Override @DefinedBy(Api.COMPILER)
|
||||
public FileObject getFileForInput(JavaFileManager.Location location,
|
||||
String packageName,
|
||||
String relativeName)
|
||||
throws IOException {
|
||||
proc.debug(DBG_FMGR, "getFileForInput location=%s packageName=%s\n", location, packageName);
|
||||
return stdFileManager.getFileForInput(location, packageName, relativeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@linkplain FileObject file object} for output
|
||||
* representing the specified <a href="JavaFileManager.html#relative_name">relative
|
||||
* name</a> in the specified package in the given location.
|
||||
*
|
||||
* <p>Optionally, this file manager might consider the sibling as
|
||||
* a hint for where to place the output. The exact semantics of
|
||||
* this hint is unspecified. The JDK compiler, javac, for
|
||||
* example, will place class files in the same directories as
|
||||
* originating source files unless a class file output directory
|
||||
* is provided. To facilitate this behavior, javac might provide
|
||||
* the originating source file as sibling when calling this
|
||||
* method.
|
||||
*
|
||||
* <p>If the returned object represents a {@linkplain
|
||||
* JavaFileObject.Kind#SOURCE source} or {@linkplain
|
||||
* JavaFileObject.Kind#CLASS class} file, it must be an instance
|
||||
* of {@link JavaFileObject}.
|
||||
*
|
||||
* <p>Informally, the file object returned by this method is
|
||||
* located in the concatenation of the location, package name, and
|
||||
* relative name or next to the sibling argument. See {@link
|
||||
* #getFileForInput getFileForInput} for an example.
|
||||
*
|
||||
* @param location a location
|
||||
* @param packageName a package name
|
||||
* @param relativeName a relative name
|
||||
* @param sibling a file object to be used as hint for placement;
|
||||
* might be {@code null}
|
||||
* @return a file object
|
||||
* @throws IllegalArgumentException if sibling is not known to
|
||||
* this file manager, or if the location is not known to this file
|
||||
* manager and the file manager does not support unknown
|
||||
* locations, or if {@code relativeName} is not valid
|
||||
* @throws IOException if an I/O error occurred, or if {@link
|
||||
* #close} has been called and this file manager cannot be
|
||||
* reopened
|
||||
* @throws IllegalStateException if {@link #close} has been called
|
||||
* and this file manager cannot be reopened
|
||||
*/
|
||||
@Override @DefinedBy(Api.COMPILER)
|
||||
public FileObject getFileForOutput(JavaFileManager.Location location,
|
||||
String packageName,
|
||||
String relativeName,
|
||||
FileObject sibling)
|
||||
throws IOException {
|
||||
throw new UnsupportedOperationException("getFileForOutput: location: " + location +
|
||||
", packageName: " + packageName +
|
||||
", relativeName: " + relativeName +
|
||||
", sibling: " + sibling);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes any resources opened for output by this file manager
|
||||
* directly or indirectly. Flushing a closed file manager has no
|
||||
* effect.
|
||||
*
|
||||
* @throws IOException if an I/O error occurred
|
||||
* @see #close
|
||||
*/
|
||||
@Override @DefinedBy(Api.COMPILER)
|
||||
public void flush() throws IOException {
|
||||
// Nothing to flush
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases any resources opened by this file manager directly or
|
||||
* indirectly. This might render this file manager useless and
|
||||
* the effect of subsequent calls to methods on this object or any
|
||||
* objects obtained through this object is undefined unless
|
||||
* explicitly allowed. However, closing a file manager which has
|
||||
* already been closed has no effect.
|
||||
*
|
||||
* @throws IOException if an I/O error occurred
|
||||
* @see #flush
|
||||
*/
|
||||
@Override @DefinedBy(Api.COMPILER)
|
||||
public void close() throws IOException {
|
||||
// Nothing to close
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jshell;
|
||||
|
||||
import java.util.Collection;
|
||||
import jdk.jshell.Key.MethodKey;
|
||||
|
||||
/**
|
||||
* Snippet for a method definition.
|
||||
* The Kind is {@link jdk.jshell.Snippet.Kind#METHOD}.
|
||||
* <p>
|
||||
* <code>MethodSnippet</code> is immutable: an access to
|
||||
* any of its methods will always return the same result.
|
||||
* and thus is thread-safe.
|
||||
* @jls 8.4: MethodDeclaration.
|
||||
*/
|
||||
public class MethodSnippet extends DeclarationSnippet {
|
||||
|
||||
final String signature;
|
||||
private String qualifiedParamaterTypes;
|
||||
|
||||
MethodSnippet(MethodKey key, String userSource, Wrap guts,
|
||||
String name, String signature, Wrap corralled,
|
||||
Collection<String> declareReferences, Collection<String> bodyReferences) {
|
||||
super(key, userSource, guts, name, SubKind.METHOD_SUBKIND, corralled, declareReferences, bodyReferences);
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* A String representation of the parameter types of the method.
|
||||
* @return a comma separated list of user entered parameter types for the
|
||||
* method.
|
||||
*/
|
||||
public String parameterTypes() {
|
||||
return key().parameterTypes();
|
||||
}
|
||||
|
||||
/**
|
||||
* The full type signature of the method, including return type.
|
||||
* @return A String representation of the parameter and return types
|
||||
*/
|
||||
public String signature() {
|
||||
return signature;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("MethodSnippet:");
|
||||
sb.append(name());
|
||||
sb.append('/');
|
||||
sb.append(signature());
|
||||
sb.append('-');
|
||||
sb.append(source());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**** internal access ****/
|
||||
|
||||
@Override
|
||||
MethodKey key() {
|
||||
return (MethodKey) super.key();
|
||||
}
|
||||
|
||||
String qualifiedParameterTypes() {
|
||||
return qualifiedParamaterTypes;
|
||||
}
|
||||
|
||||
void setQualifiedParamaterTypes(String sig) {
|
||||
qualifiedParamaterTypes = sig;
|
||||
}
|
||||
}
|
||||
189
langtools/src/jdk.jshell/share/classes/jdk/jshell/OuterWrap.java
Normal file
189
langtools/src/jdk.jshell/share/classes/jdk/jshell/OuterWrap.java
Normal file
@ -0,0 +1,189 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jshell;
|
||||
|
||||
import jdk.jshell.Wrap.CompoundWrap;
|
||||
import static jdk.jshell.Util.*;
|
||||
import java.util.Locale;
|
||||
import javax.tools.Diagnostic;
|
||||
import javax.tools.JavaFileObject;
|
||||
import jdk.jshell.MemoryFileManager.SourceMemoryJavaFileObject;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Robert Field
|
||||
*/
|
||||
final class OuterWrap implements GeneralWrap {
|
||||
|
||||
private final String packageName;
|
||||
private final String className;
|
||||
private final String userSource;
|
||||
private final GeneralWrap w;
|
||||
private final Wrap guts;
|
||||
|
||||
public static OuterWrap wrapInClass(String packageName, String className,
|
||||
String imports, String userSource, Wrap guts) {
|
||||
GeneralWrap kw = new CompoundWrap(
|
||||
imports
|
||||
+ "class " + className + " {\n",
|
||||
guts,
|
||||
"}\n");
|
||||
return new OuterWrap(packageName, className, userSource, kw, guts);
|
||||
}
|
||||
|
||||
public static OuterWrap wrapImport(String userSource, Wrap guts) {
|
||||
return new OuterWrap("", "", userSource, guts, guts);
|
||||
}
|
||||
|
||||
private OuterWrap(String packageName, String className, String userSource,
|
||||
GeneralWrap w, Wrap guts) {
|
||||
this.packageName = packageName;
|
||||
this.className = className;
|
||||
this.userSource = userSource;
|
||||
this.w = w;
|
||||
this.guts = guts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String wrapped() {
|
||||
return w.wrapped();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int snippetIndexToWrapIndex(int ui) {
|
||||
return w.snippetIndexToWrapIndex(ui);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int wrapIndexToSnippetIndex(int si) {
|
||||
return w.wrapIndexToSnippetIndex(si);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int firstSnippetIndex() {
|
||||
return w.firstSnippetIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lastSnippetIndex() {
|
||||
return w.lastSnippetIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int snippetLineToWrapLine(int snline) {
|
||||
return w.snippetLineToWrapLine(snline);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int wrapLineToSnippetLine(int wline) {
|
||||
return w.wrapLineToSnippetLine(wline);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int firstSnippetLine() {
|
||||
return w.firstSnippetLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lastSnippetLine() {
|
||||
return w.lastSnippetLine();
|
||||
}
|
||||
|
||||
public String className() {
|
||||
return className;
|
||||
}
|
||||
|
||||
public String classFullName() {
|
||||
return packageName + "." + className;
|
||||
}
|
||||
|
||||
public String getUserSource() {
|
||||
return userSource;
|
||||
}
|
||||
|
||||
Wrap guts() {
|
||||
return guts;
|
||||
}
|
||||
|
||||
Diag wrapDiag(Diagnostic<? extends JavaFileObject> d) {
|
||||
return new WrappedDiagnostic(d);
|
||||
}
|
||||
|
||||
class WrappedDiagnostic extends Diag {
|
||||
|
||||
private final Diagnostic<? extends JavaFileObject> diag;
|
||||
|
||||
WrappedDiagnostic(Diagnostic<? extends JavaFileObject> diag) {
|
||||
this.diag = diag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isError() {
|
||||
return diag.getKind() == Diagnostic.Kind.ERROR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getPosition() {
|
||||
return wrapIndexToSnippetIndex(diag.getPosition());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getStartPosition() {
|
||||
return wrapIndexToSnippetIndex(diag.getStartPosition());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEndPosition() {
|
||||
return wrapIndexToSnippetIndex(diag.getEndPosition());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCode() {
|
||||
return diag.getCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage(Locale locale) {
|
||||
return expunge(diag.getMessage(locale));
|
||||
}
|
||||
|
||||
@Override
|
||||
Unit unitOrNull() {
|
||||
JavaFileObject fo = diag.getSource();
|
||||
if (fo instanceof SourceMemoryJavaFileObject) {
|
||||
SourceMemoryJavaFileObject sfo = (SourceMemoryJavaFileObject) fo;
|
||||
if (sfo.getOrigin() instanceof Unit) {
|
||||
return (Unit) sfo.getOrigin();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "WrappedDiagnostic(" + getMessage(null) + ":" + getPosition() + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jshell;
|
||||
|
||||
/**
|
||||
* Grouping for Snippets which persist and influence future code.
|
||||
* A persistent snippet can be
|
||||
* {@linkplain jdk.jshell.Snippet.Status#OVERWRITTEN overwritten)}
|
||||
* with new input and can be dropped {@link JShell#drop}.
|
||||
* <p>
|
||||
* <code>PersistentSnippet</code> is immutable: an access to
|
||||
* any of its methods will always return the same result.
|
||||
* and thus is thread-safe.
|
||||
*/
|
||||
public abstract class PersistentSnippet extends Snippet {
|
||||
|
||||
PersistentSnippet(Key key, String userSource, Wrap guts, String unitName, SubKind subkind) {
|
||||
super(key, userSource, guts, unitName, subkind);
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of the Snippet.
|
||||
*
|
||||
* @return the name of the snippet.
|
||||
*/
|
||||
@Override
|
||||
public String name() {
|
||||
return unitName;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,272 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jshell;
|
||||
|
||||
import com.sun.tools.javac.code.TypeTag;
|
||||
import com.sun.tools.javac.parser.JavacParser;
|
||||
import com.sun.tools.javac.parser.ParserFactory;
|
||||
import com.sun.tools.javac.parser.Tokens.Comment;
|
||||
import com.sun.tools.javac.parser.Tokens.Comment.CommentStyle;
|
||||
import com.sun.tools.javac.parser.Tokens.Token;
|
||||
import static com.sun.tools.javac.parser.Tokens.TokenKind.CLASS;
|
||||
import static com.sun.tools.javac.parser.Tokens.TokenKind.COLON;
|
||||
import static com.sun.tools.javac.parser.Tokens.TokenKind.ENUM;
|
||||
import static com.sun.tools.javac.parser.Tokens.TokenKind.EOF;
|
||||
import static com.sun.tools.javac.parser.Tokens.TokenKind.IMPORT;
|
||||
import static com.sun.tools.javac.parser.Tokens.TokenKind.INTERFACE;
|
||||
import static com.sun.tools.javac.parser.Tokens.TokenKind.LPAREN;
|
||||
import static com.sun.tools.javac.parser.Tokens.TokenKind.MONKEYS_AT;
|
||||
import static com.sun.tools.javac.parser.Tokens.TokenKind.PACKAGE;
|
||||
import static com.sun.tools.javac.parser.Tokens.TokenKind.SEMI;
|
||||
import static com.sun.tools.javac.parser.Tokens.TokenKind.VOID;
|
||||
import com.sun.tools.javac.tree.JCTree;
|
||||
import com.sun.tools.javac.tree.JCTree.JCAnnotation;
|
||||
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
|
||||
import com.sun.tools.javac.tree.JCTree.JCExpression;
|
||||
import com.sun.tools.javac.tree.JCTree.JCExpressionStatement;
|
||||
import com.sun.tools.javac.tree.JCTree.JCModifiers;
|
||||
import com.sun.tools.javac.tree.JCTree.JCPackageDecl;
|
||||
import com.sun.tools.javac.tree.JCTree.JCStatement;
|
||||
import com.sun.tools.javac.tree.JCTree.JCTypeParameter;
|
||||
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
|
||||
import com.sun.tools.javac.tree.JCTree.Tag;
|
||||
import static com.sun.tools.javac.tree.JCTree.Tag.IDENT;
|
||||
import com.sun.tools.javac.util.List;
|
||||
import com.sun.tools.javac.util.ListBuffer;
|
||||
import com.sun.tools.javac.util.Name;
|
||||
import com.sun.tools.javac.util.Position;
|
||||
|
||||
/**
|
||||
* This is a subclass of JavacParser which overrides one method with a modified
|
||||
* verson of that method designed to allow parsing of one "snippet" of Java
|
||||
* code without the surrounding context of class, method, etc.
|
||||
* Accepts an expression, a statement, an import, or the declaration of a
|
||||
* method, variable, or type (class, interface, ...).
|
||||
*/
|
||||
class ReplParser extends JavacParser {
|
||||
|
||||
public ReplParser(ParserFactory fac,
|
||||
com.sun.tools.javac.parser.Lexer S,
|
||||
boolean keepDocComments,
|
||||
boolean keepLineMap,
|
||||
boolean keepEndPositions) {
|
||||
super(fac, S, keepDocComments, keepLineMap, keepEndPositions);
|
||||
}
|
||||
|
||||
/**
|
||||
* As faithful a clone of the overridden method as possible while still
|
||||
* achieving the goal of allowing the parse of a stand-alone snippet.
|
||||
* As a result, some variables are assigned and never used, tests are
|
||||
* always true, loops don't, etc. This is to allow easy transition as the
|
||||
* underlying method changes.
|
||||
* @return a snippet wrapped in a compilation unit
|
||||
*/
|
||||
@Override
|
||||
public JCCompilationUnit parseCompilationUnit() {
|
||||
Token firstToken = token;
|
||||
JCModifiers mods = null;
|
||||
boolean seenImport = false;
|
||||
boolean seenPackage = false;
|
||||
ListBuffer<JCTree> defs = new ListBuffer<>();
|
||||
if (token.kind == MONKEYS_AT) {
|
||||
mods = modifiersOpt();
|
||||
}
|
||||
|
||||
if (token.kind == PACKAGE) {
|
||||
int packagePos = token.pos;
|
||||
List<JCAnnotation> annotations = List.nil();
|
||||
seenPackage = true;
|
||||
if (mods != null) {
|
||||
checkNoMods(mods.flags);
|
||||
annotations = mods.annotations;
|
||||
mods = null;
|
||||
}
|
||||
nextToken();
|
||||
JCExpression pid = qualident(false);
|
||||
accept(SEMI);
|
||||
JCPackageDecl pd = F.at(packagePos).PackageDecl(annotations, pid);
|
||||
attach(pd, firstToken.comment(CommentStyle.JAVADOC));
|
||||
storeEnd(pd, token.pos);
|
||||
defs.append(pd);
|
||||
}
|
||||
|
||||
boolean firstTypeDecl = true;
|
||||
while (token.kind != EOF) {
|
||||
if (token.pos > 0 && token.pos <= endPosTable.errorEndPos) {
|
||||
// error recovery
|
||||
skip(true, false, false, false);
|
||||
if (token.kind == EOF) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (mods == null && token.kind == IMPORT) {
|
||||
seenImport = true;
|
||||
defs.append(importDeclaration());
|
||||
} else {
|
||||
Comment docComment = token.comment(CommentStyle.JAVADOC);
|
||||
if (firstTypeDecl && !seenImport && !seenPackage) {
|
||||
docComment = firstToken.comment(CommentStyle.JAVADOC);
|
||||
}
|
||||
List<? extends JCTree> udefs = replUnit(mods, docComment);
|
||||
// if (def instanceof JCExpressionStatement)
|
||||
// def = ((JCExpressionStatement)def).expr;
|
||||
for (JCTree def : udefs) {
|
||||
defs.append(def);
|
||||
}
|
||||
mods = null;
|
||||
firstTypeDecl = false;
|
||||
}
|
||||
break; // Remove to process more than one snippet
|
||||
}
|
||||
List<JCTree> rdefs = defs.toList();
|
||||
class ReplUnit extends JCCompilationUnit {
|
||||
|
||||
public ReplUnit(List<JCTree> defs) {
|
||||
super(defs);
|
||||
}
|
||||
}
|
||||
JCCompilationUnit toplevel = new ReplUnit(rdefs);
|
||||
if (rdefs.isEmpty()) {
|
||||
storeEnd(toplevel, S.prevToken().endPos);
|
||||
}
|
||||
toplevel.lineMap = S.getLineMap();
|
||||
this.endPosTable.setParser(null); // remove reference to parser
|
||||
toplevel.endPositions = this.endPosTable;
|
||||
return toplevel;
|
||||
}
|
||||
|
||||
@SuppressWarnings("fallthrough")
|
||||
List<? extends JCTree> replUnit(JCModifiers pmods, Comment dc) {
|
||||
switch (token.kind) {
|
||||
case EOF:
|
||||
return List.nil();
|
||||
case RBRACE:
|
||||
case CASE:
|
||||
case DEFAULT:
|
||||
// These are illegal, fall through to handle as illegal statement
|
||||
case LBRACE:
|
||||
case IF:
|
||||
case FOR:
|
||||
case WHILE:
|
||||
case DO:
|
||||
case TRY:
|
||||
case SWITCH:
|
||||
case RETURN:
|
||||
case THROW:
|
||||
case BREAK:
|
||||
case CONTINUE:
|
||||
case SEMI:
|
||||
case ELSE:
|
||||
case FINALLY:
|
||||
case CATCH:
|
||||
case ASSERT:
|
||||
return List.<JCTree>of(parseStatement());
|
||||
case SYNCHRONIZED:
|
||||
if (peekToken(LPAREN)) {
|
||||
return List.<JCTree>of(parseStatement());
|
||||
}
|
||||
//fall-through
|
||||
default:
|
||||
JCModifiers mods = modifiersOpt(pmods);
|
||||
if (token.kind == CLASS
|
||||
|| token.kind == INTERFACE
|
||||
|| token.kind == ENUM) {
|
||||
return List.<JCTree>of(classOrInterfaceOrEnumDeclaration(mods, dc));
|
||||
} else {
|
||||
int pos = token.pos;
|
||||
List<JCTypeParameter> typarams = typeParametersOpt();
|
||||
// if there are type parameters but no modifiers, save the start
|
||||
// position of the method in the modifiers.
|
||||
if (typarams.nonEmpty() && mods.pos == Position.NOPOS) {
|
||||
mods.pos = pos;
|
||||
storeEnd(mods, pos);
|
||||
}
|
||||
List<JCAnnotation> annosAfterParams = annotationsOpt(Tag.ANNOTATION);
|
||||
|
||||
if (annosAfterParams.nonEmpty()) {
|
||||
checkAnnotationsAfterTypeParams(annosAfterParams.head.pos);
|
||||
mods.annotations = mods.annotations.appendList(annosAfterParams);
|
||||
if (mods.pos == Position.NOPOS) {
|
||||
mods.pos = mods.annotations.head.pos;
|
||||
}
|
||||
}
|
||||
|
||||
Token prevToken = token;
|
||||
pos = token.pos;
|
||||
JCExpression t;
|
||||
boolean isVoid = token.kind == VOID;
|
||||
if (isVoid) {
|
||||
t = to(F.at(pos).TypeIdent(TypeTag.VOID));
|
||||
nextToken();
|
||||
} else {
|
||||
// return type of method, declared type of variable, or an expression
|
||||
t = term(EXPR | TYPE);
|
||||
}
|
||||
if (token.kind == COLON && t.hasTag(IDENT)) {
|
||||
// labelled statement
|
||||
nextToken();
|
||||
JCStatement stat = parseStatement();
|
||||
return List.<JCTree>of(F.at(pos).Labelled(prevToken.name(), stat));
|
||||
} else if ((isVoid || (lastmode & TYPE) != 0) && LAX_IDENTIFIER.accepts(token.kind)) {
|
||||
// we have "Type Ident", so we can assume it is variable or method declaration
|
||||
pos = token.pos;
|
||||
Name name = ident();
|
||||
if (token.kind == LPAREN) {
|
||||
// method declaration
|
||||
//mods.flags |= Flags.STATIC;
|
||||
return List.of(methodDeclaratorRest(
|
||||
pos, mods, t, name, typarams,
|
||||
false, isVoid, dc));
|
||||
} else if (!isVoid && typarams.isEmpty()) {
|
||||
// variable declaration
|
||||
//mods.flags |= Flags.STATIC;
|
||||
List<JCTree> defs
|
||||
= variableDeclaratorsRest(pos, mods, t, name, false, dc,
|
||||
new ListBuffer<JCTree>()).toList();
|
||||
accept(SEMI);
|
||||
storeEnd(defs.last(), S.prevToken().endPos);
|
||||
return defs;
|
||||
} else {
|
||||
// malformed declaration, return error
|
||||
pos = token.pos;
|
||||
List<JCTree> err = isVoid
|
||||
? List.<JCTree>of(toP(F.at(pos).MethodDef(mods, name, t, typarams,
|
||||
List.<JCVariableDecl>nil(), List.<JCExpression>nil(), null, null)))
|
||||
: null;
|
||||
return List.<JCTree>of(syntaxError(token.pos, err, "expected", LPAREN));
|
||||
}
|
||||
} else if (!typarams.isEmpty()) {
|
||||
// type parameters on non-variable non-method -- error
|
||||
return List.<JCTree>of(syntaxError(token.pos, "illegal.start.of.type"));
|
||||
} else {
|
||||
// expression-statement or expression to evaluate
|
||||
JCExpressionStatement expr = toP(F.at(pos).Exec(t));
|
||||
return List.<JCTree>of(expr);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jshell;
|
||||
|
||||
import com.sun.tools.javac.parser.JavacParser;
|
||||
import com.sun.tools.javac.parser.ParserFactory;
|
||||
import com.sun.tools.javac.parser.ScannerFactory;
|
||||
import com.sun.tools.javac.util.Context;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Robert Field
|
||||
*/
|
||||
class ReplParserFactory extends ParserFactory {
|
||||
|
||||
public static ParserFactory instance(Context context) {
|
||||
ParserFactory instance = context.get(parserFactoryKey);
|
||||
if (instance == null) {
|
||||
instance = new ReplParserFactory(context);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
private final ScannerFactory scannerFactory;
|
||||
|
||||
protected ReplParserFactory(Context context) {
|
||||
super(context);
|
||||
this.scannerFactory = ScannerFactory.instance(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavacParser newParser(CharSequence input, boolean keepDocComments, boolean keepEndPos, boolean keepLineMap) {
|
||||
com.sun.tools.javac.parser.Lexer lexer = scannerFactory.newScanner(input, keepDocComments);
|
||||
return new ReplParser(this, lexer, keepDocComments, keepLineMap, keepEndPos);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jshell;
|
||||
|
||||
import com.sun.tools.javac.comp.AttrContext;
|
||||
import com.sun.tools.javac.comp.Env;
|
||||
import com.sun.tools.javac.comp.Resolve;
|
||||
import com.sun.tools.javac.util.Context;
|
||||
|
||||
/**
|
||||
* Just need access to isStatic
|
||||
*/
|
||||
class ReplResolve extends Resolve {
|
||||
|
||||
ReplResolve(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public static boolean isStatic(Env<AttrContext> env) {
|
||||
return Resolve.isStatic(env);
|
||||
}
|
||||
}
|
||||
681
langtools/src/jdk.jshell/share/classes/jdk/jshell/Snippet.java
Normal file
681
langtools/src/jdk.jshell/share/classes/jdk/jshell/Snippet.java
Normal file
@ -0,0 +1,681 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jshell;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import static jdk.jshell.Util.REPL_CLASS_PREFIX;
|
||||
import static jdk.jshell.Util.asLetters;
|
||||
|
||||
/**
|
||||
* A Snippet represents a snippet of Java source code as passed to
|
||||
* {@link jdk.jshell.JShell#eval}. It is associated only with the
|
||||
* {@link jdk.jshell.JShell JShell} instance that created it.
|
||||
* An instance of Snippet (including its subclasses) is immutable: an access to
|
||||
* any of its methods will always return the same result.
|
||||
* For information about the current state of the snippet within the JShell
|
||||
* state engine, query <code>JShell</code> passing the Snippet.
|
||||
* <p>
|
||||
* Because it is immutable, <code>Snippet</code> (and subclasses) is thread-safe.
|
||||
* @author Robert Field
|
||||
* @see jdk.jshell.JShell#status
|
||||
*/
|
||||
public abstract class Snippet {
|
||||
|
||||
/**
|
||||
* Describes the general kind of snippet.
|
||||
* The <code>Kind</code> is an immutable property of a Snippet.
|
||||
* It is accessed with {@link jdk.jshell.Snippet#kind()}.
|
||||
* The <code>Kind</code> can be used to determine which
|
||||
* subclass of Snippet it is. For example,
|
||||
* {@link jdk.jshell.JShell#eval eval("int three() { return 3; }")} will
|
||||
* return a snippet creation event. The <code>Kind</code> of that Snippet
|
||||
* will be <code>METHOD</code>, from which you know that the subclass
|
||||
* of <code>Snippet</code> is <code>MethodSnippet</code> and it can be
|
||||
* cast as such.
|
||||
*/
|
||||
public enum Kind {
|
||||
/**
|
||||
* An import declaration: <code>import</code> ...
|
||||
* The snippet is an instance of {@link jdk.jshell.ImportSnippet}.
|
||||
* An import can be a single type import
|
||||
* ({@link jdk.jshell.Snippet.SubKind#SINGLE_TYPE_IMPORT_SUBKIND}),
|
||||
* a static single import
|
||||
* ({@link jdk.jshell.Snippet.SubKind#SINGLE_STATIC_IMPORT_SUBKIND}),
|
||||
* an on-demand type import
|
||||
* ({@link jdk.jshell.Snippet.SubKind#TYPE_IMPORT_ON_DEMAND_SUBKIND}),
|
||||
* or a static on-demand type import
|
||||
* ({@link jdk.jshell.Snippet.SubKind#SINGLE_STATIC_IMPORT_SUBKIND}) --
|
||||
* use {@link jdk.jshell.Snippet#subKind()} to distinguish.
|
||||
* @jls 8.3: importDeclaration.
|
||||
*/
|
||||
IMPORT(true),
|
||||
|
||||
/**
|
||||
* A type declaration.
|
||||
* Which includes: NormalClassDeclaration, EnumDeclaration,
|
||||
* NormalInterfaceDeclaration, and AnnotationTypeDeclaration.
|
||||
* The snippet is an instance of {@link jdk.jshell.TypeDeclSnippet}.
|
||||
* A type declaration may be an interface
|
||||
* {@link jdk.jshell.Snippet.SubKind#INTERFACE_SUBKIND},
|
||||
* classes {@link jdk.jshell.Snippet.SubKind#CLASS_SUBKIND}, enums, and
|
||||
* annotation interfaces -- see {@link jdk.jshell.Snippet.SubKind} to
|
||||
* differentiate.
|
||||
* @jls 7.6: TypeDeclaration.
|
||||
*/
|
||||
TYPE_DECL(true),
|
||||
|
||||
/**
|
||||
* A method declaration.
|
||||
* The snippet is an instance of {@link jdk.jshell.MethodSnippet}.
|
||||
* @jls 8.4: MethodDeclaration.
|
||||
*/
|
||||
METHOD(true),
|
||||
|
||||
/**
|
||||
* One variable declaration.
|
||||
* Corresponding to one <i>VariableDeclarator</i>.
|
||||
* The snippet is an instance of {@link jdk.jshell.VarSnippet}.
|
||||
* The variable may be with or without initializer, or be a temporary
|
||||
* variable representing an expression -- see
|
||||
* {@link jdk.jshell.Snippet.SubKind}to differentiate.
|
||||
* @jls 8.3: FieldDeclaration.
|
||||
*/
|
||||
VAR(true),
|
||||
|
||||
/**
|
||||
* An expression, with or without side-effects.
|
||||
* The snippet is an instance of {@link jdk.jshell.ExpressionSnippet}.
|
||||
* The expression is currently either a simple named reference to a
|
||||
* variable ({@link jdk.jshell.Snippet.SubKind#VAR_VALUE_SUBKIND}) or an
|
||||
* assignment (both of which have natural referencing
|
||||
* names) -- see {@link jdk.jshell.Snippet.SubKind} to differentiate.
|
||||
* @jls 15: Expression.
|
||||
*/
|
||||
EXPRESSION(false),
|
||||
|
||||
/**
|
||||
* A statement.
|
||||
* The snippet is an instance of {@link jdk.jshell.StatementSnippet}.
|
||||
* @jls 14.5: Statement.
|
||||
*/
|
||||
STATEMENT(false),
|
||||
|
||||
/**
|
||||
* A syntactically incorrect input for which the specific
|
||||
* kind could not be determined.
|
||||
* The snippet is an instance of {@link jdk.jshell.ErroneousSnippet}.
|
||||
*/
|
||||
ERRONEOUS(false);
|
||||
|
||||
/**
|
||||
* True if this kind of snippet adds a declaration or declarations
|
||||
* which are visible to subsequent evaluations.
|
||||
*/
|
||||
public final boolean isPersistent;
|
||||
|
||||
Kind(boolean isPersistent) {
|
||||
this.isPersistent = isPersistent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The detailed variety of a snippet. This is a sub-classification of the
|
||||
* Kind. The Kind of a SubKind is accessible with
|
||||
* {@link jdk.jshell.Snippet.SubKind#kind}.
|
||||
*/
|
||||
public enum SubKind {
|
||||
|
||||
/**
|
||||
* Single-Type-Import Declaration.
|
||||
* An import declaration of a single type.
|
||||
* @jls 7.5.1 SingleTypeImportDeclaration.
|
||||
*/
|
||||
SINGLE_TYPE_IMPORT_SUBKIND(Kind.IMPORT),
|
||||
|
||||
/**
|
||||
* Type-Import-on-Demand Declaration.
|
||||
* A non-static "star" import.
|
||||
* @jls 7.5.2. TypeImportOnDemandDeclaration.
|
||||
*/
|
||||
TYPE_IMPORT_ON_DEMAND_SUBKIND(Kind.IMPORT),
|
||||
|
||||
/**
|
||||
* Single-Static-Import Declaration.
|
||||
* An import of a static member.
|
||||
* @jls 7.5.3 Single-Static-Import.
|
||||
*/
|
||||
SINGLE_STATIC_IMPORT_SUBKIND(Kind.IMPORT),
|
||||
|
||||
/**
|
||||
* Static-Import-on-Demand Declaration.
|
||||
* A static "star" import of all static members of a named type.
|
||||
* @jls 7.5.4. Static-Import-on-Demand Static "star" import.
|
||||
*/
|
||||
STATIC_IMPORT_ON_DEMAND_SUBKIND(Kind.IMPORT),
|
||||
|
||||
/**
|
||||
* A class declaration.
|
||||
* A <code>SubKind</code> of {@link Kind#TYPE_DECL}.
|
||||
* @jls 8.1. NormalClassDeclaration.
|
||||
*/
|
||||
CLASS_SUBKIND(Kind.TYPE_DECL),
|
||||
|
||||
/**
|
||||
* An interface declaration.
|
||||
* A <code>SubKind</code> of {@link Kind#TYPE_DECL}.
|
||||
* @jls 9.1. NormalInterfaceDeclaration.
|
||||
*/
|
||||
INTERFACE_SUBKIND(Kind.TYPE_DECL),
|
||||
|
||||
/**
|
||||
* An enum declaration.
|
||||
* A <code>SubKind</code> of {@link Kind#TYPE_DECL}.
|
||||
* @jls 8.9. EnumDeclaration.
|
||||
*/
|
||||
ENUM_SUBKIND(Kind.TYPE_DECL),
|
||||
|
||||
/**
|
||||
* An annotation interface declaration. A <code>SubKind</code> of
|
||||
* {@link Kind#TYPE_DECL}.
|
||||
* @jls 9.6. AnnotationTypeDeclaration.
|
||||
*/
|
||||
ANNOTATION_TYPE_SUBKIND(Kind.TYPE_DECL),
|
||||
|
||||
/**
|
||||
* A method. The only <code>SubKind</code> for {@link Kind#METHOD}.
|
||||
* @jls 8.4. MethodDeclaration.
|
||||
*/
|
||||
METHOD_SUBKIND(Kind.METHOD),
|
||||
|
||||
/**
|
||||
* A variable declaration without initializer.
|
||||
* A <code>SubKind</code> of {@link Kind#VAR}.
|
||||
* @jls 8.3. VariableDeclarator without VariableInitializer in
|
||||
* FieldDeclaration.
|
||||
*/
|
||||
VAR_DECLARATION_SUBKIND(Kind.VAR),
|
||||
|
||||
/**
|
||||
* A variable declaration with an initializer expression. A
|
||||
* <code>SubKind</code> of {@link Kind#VAR}.
|
||||
* @jls 8.3. VariableDeclarator with VariableInitializer in
|
||||
* FieldDeclaration.
|
||||
*/
|
||||
VAR_DECLARATION_WITH_INITIALIZER_SUBKIND(Kind.VAR, true, true),
|
||||
|
||||
/**
|
||||
* An expression whose value has been stored in a temporary variable. A
|
||||
* <code>SubKind</code> of {@link Kind#VAR}.
|
||||
* @jls 15. Primary.
|
||||
*/
|
||||
TEMP_VAR_EXPRESSION_SUBKIND(Kind.VAR, true, true),
|
||||
|
||||
/**
|
||||
* A simple variable reference expression. A <code>SubKind</code> of
|
||||
* {@link Kind#EXPRESSION}.
|
||||
* @jls 15.11. Field Access as 3.8. Identifier.
|
||||
*/
|
||||
VAR_VALUE_SUBKIND(Kind.EXPRESSION, true, true),
|
||||
|
||||
/**
|
||||
* An assignment expression. A <code>SubKind</code> of
|
||||
* {@link Kind#EXPRESSION}.
|
||||
* @jls 15.26. Assignment.
|
||||
*/
|
||||
ASSIGNMENT_SUBKIND(Kind.EXPRESSION, true, true),
|
||||
|
||||
/**
|
||||
* An expression which has not been wrapped in a temporary variable
|
||||
* (reserved). A <code>SubKind</code> of {@link Kind#EXPRESSION}.
|
||||
*/
|
||||
OTHER_EXPRESSION_SUBKIND(Kind.EXPRESSION, true, true),
|
||||
|
||||
/**
|
||||
* A statement. The only <code>SubKind</code> for {@link Kind#STATEMENT}.
|
||||
* @jls 14.5. Statement.
|
||||
*/
|
||||
STATEMENT_SUBKIND(Kind.STATEMENT, true, false),
|
||||
|
||||
/**
|
||||
* An unknown snippet. The only <code>SubKind</code> for
|
||||
* {@link Kind#ERRONEOUS}.
|
||||
*/
|
||||
UNKNOWN_SUBKIND(Kind.ERRONEOUS, false, false);
|
||||
|
||||
private final boolean isExecutable;
|
||||
private final boolean hasValue;
|
||||
private final Kind kind;
|
||||
|
||||
SubKind(Kind kind) {
|
||||
this.kind = kind;
|
||||
this.isExecutable = false;
|
||||
this.hasValue = false;
|
||||
}
|
||||
|
||||
SubKind(Kind kind, boolean isExecutable, boolean hasValue) {
|
||||
this.kind = kind;
|
||||
this.isExecutable = isExecutable;
|
||||
this.hasValue = hasValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this <code>SubKind</code> executable?
|
||||
*
|
||||
* @return true if this <code>SubKind</code> can be executed.
|
||||
*/
|
||||
public boolean isExecutable() {
|
||||
return isExecutable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this <code>SubKind</code> executable and is non-<code>void</code>.
|
||||
*
|
||||
* @return true if this <code>SubKind</code> has a value.
|
||||
*/
|
||||
public boolean hasValue() {
|
||||
return hasValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link Snippet.Kind} that corresponds to this <code>SubKind</code>.
|
||||
*
|
||||
* @return the fixed <code>Kind</code> for this <code>SubKind</code>
|
||||
*/
|
||||
public Kind kind() {
|
||||
return kind;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the current state of a Snippet.
|
||||
* This is a dynamic property of a Snippet within the JShell state --
|
||||
* thus is retrieved with a {@linkplain
|
||||
* jdk.jshell.JShell#status(jdk.jshell.Snippet) query on <code>JShell</code>}.
|
||||
* <p>
|
||||
* The <code>Status</code> changes as the state changes.
|
||||
* For example, creation of another snippet with
|
||||
* {@link jdk.jshell.JShell#eval(java.lang.String) eval}
|
||||
* may resolve dependencies of this Snippet (or invalidate those dependencies), or
|
||||
* {@linkplain jdk.jshell.Snippet.Status#OVERWRITTEN overwrite}
|
||||
* this Snippet changing its
|
||||
* <code>Status</code>.
|
||||
* <p>
|
||||
* Important properties associated with <code>Status</code> are:
|
||||
* {@link jdk.jshell.Snippet.Status#isDefined}, if it is visible to other
|
||||
* existing and new snippets; and
|
||||
* {@link jdk.jshell.Snippet.Status#isActive}, if, as the
|
||||
* JShell state changes, the snippet will update, possibly
|
||||
* changing <code>Status</code>.
|
||||
* An executable Snippet can only be executed if it is in the the
|
||||
* {@link jdk.jshell.Snippet.Status#VALID} <code>Status</code>.
|
||||
* @see JShell#status(jdk.jshell.Snippet)
|
||||
*/
|
||||
public enum Status {
|
||||
/**
|
||||
* The snippet is a valid snippet
|
||||
* (in the context of current <code>JShell</code> state).
|
||||
* Only snippets with <code>VALID</code>
|
||||
* <code>Status</code> can be executed (though not all
|
||||
* <code>VALID</code> snippets have executable code).
|
||||
* If the snippet is a declaration or import, it is visible to other
|
||||
* snippets ({@link Status#isDefined isDefined} <code> == true</code>).
|
||||
* <p>
|
||||
* The snippet will update as dependents change
|
||||
* ({@link Status#isActive isActive} <code> == true</code>), its
|
||||
* status could become <code>RECOVERABLE_DEFINED</code>, <code>RECOVERABLE_NOT_DEFINED</code>,
|
||||
* <code>DROPPED</code>, or <code>OVERWRITTEN</code>.
|
||||
*/
|
||||
VALID(true, true),
|
||||
|
||||
/**
|
||||
* The snippet is a declaration snippet with potentially recoverable
|
||||
* unresolved references or other issues in its body
|
||||
* (in the context of current <code>JShell</code> state).
|
||||
* Only a {@link jdk.jshell.DeclarationSnippet} can have this
|
||||
* <code>Status</code>.
|
||||
* The snippet has a valid signature and it is visible to other
|
||||
* snippets ({@link Status#isDefined isDefined} <code> == true</code>)
|
||||
* and thus can be referenced in existing or new snippets
|
||||
* but the snippet cannot be executed.
|
||||
* An {@link UnresolvedReferenceException} will be thrown on an attempt
|
||||
* to execute it.
|
||||
* <p>
|
||||
* The snippet will update as dependents change
|
||||
* ({@link Status#isActive isActive} <code> == true</code>), its
|
||||
* status could become <code>VALID</code>, <code>RECOVERABLE_NOT_DEFINED</code>,
|
||||
* <code>DROPPED</code>, or <code>OVERWRITTEN</code>.
|
||||
* <p>
|
||||
* Note: both <code>RECOVERABLE_DEFINED</code> and <code>RECOVERABLE_NOT_DEFINED</code>
|
||||
* indicate potentially recoverable errors, they differ in that, for
|
||||
* <code>RECOVERABLE_DEFINED</code>, the snippet is
|
||||
* {@linkplain Status#isDefined defined}.
|
||||
*/
|
||||
RECOVERABLE_DEFINED(true, true),
|
||||
|
||||
/**
|
||||
* The snippet is a declaration snippet with potentially recoverable
|
||||
* unresolved references or other issues
|
||||
* (in the context of current <code>JShell</code> state).
|
||||
* Only a {@link jdk.jshell.DeclarationSnippet} can have this
|
||||
* <code>Status</code>.
|
||||
* The snippet has an invalid signature or the implementation is
|
||||
* otherwise unable to define it.
|
||||
* The snippet it is not visible to other snippets
|
||||
* ({@link Status#isDefined isDefined} <code> == false</code>)
|
||||
* and thus cannot be referenced or executed.
|
||||
* <p>
|
||||
* The snippet will update as dependents change
|
||||
* ({@link Status#isActive isActive} <code> == true</code>), its
|
||||
* status could become <code>VALID</code>, <code>RECOVERABLE_DEFINED</code>,
|
||||
* <code>DROPPED</code>, or <code>OVERWRITTEN</code>.
|
||||
* <p>
|
||||
* Note: both <code>RECOVERABLE_DEFINED</code> and <code>RECOVERABLE_NOT_DEFINED</code>
|
||||
* indicate potentially recoverable errors, they differ in that, for
|
||||
* <code>RECOVERABLE_DEFINED</code>, the snippet is
|
||||
* {@linkplain Status#isDefined defined}.
|
||||
*/
|
||||
RECOVERABLE_NOT_DEFINED(true, false),
|
||||
|
||||
/**
|
||||
* The snippet is inactive because of an explicit call to
|
||||
* the {@link JShell#drop(jdk.jshell.PersistentSnippet)}.
|
||||
* Only a {@link jdk.jshell.PersistentSnippet} can have this
|
||||
* <code>Status</code>.
|
||||
* The snippet is not visible to other snippets
|
||||
* ({@link Status#isDefined isDefined} <code> == false</code>)
|
||||
* and thus cannot be referenced or executed.
|
||||
* <p>
|
||||
* The snippet will not update as dependents change
|
||||
* ({@link Status#isActive isActive} <code> == false</code>), its
|
||||
* <code>Status</code> will never change again.
|
||||
*/
|
||||
DROPPED(false, false),
|
||||
|
||||
/**
|
||||
* The snippet is inactive because it has been replaced by a new
|
||||
* snippet. This occurs when the new snippet added with
|
||||
* {@link jdk.jshell.JShell#eval} matches a previous snippet.
|
||||
* A <code>TypeDeclSnippet</code> will match another
|
||||
* <code>TypeDeclSnippet</code> if the names match.
|
||||
* For example <code>class X { }</code> will overwrite
|
||||
* <code>class X { int ii; }</code> or
|
||||
* <code>interface X { }</code>.
|
||||
* A <code>MethodSnippet</code> will match another
|
||||
* <code>MethodSnippet</code> if the names and parameter types
|
||||
* match.
|
||||
* For example <code>void m(int a) { }</code> will overwrite
|
||||
* <code>int m(int a) { return a+a; }</code>.
|
||||
* A <code>VarSnippet</code> will match another
|
||||
* <code>VarSnippet</code> if the names match.
|
||||
* For example <code>double z;</code> will overwrite
|
||||
* <code>long z = 2L;</code>.
|
||||
* Only a {@link jdk.jshell.PersistentSnippet} can have this
|
||||
* <code>Status</code>.
|
||||
* The snippet is not visible to other snippets
|
||||
* ({@link Status#isDefined isDefined} <code> == false</code>)
|
||||
* and thus cannot be referenced or executed.
|
||||
* <p>
|
||||
* The snippet will not update as dependents change
|
||||
* ({@link Status#isActive isActive} <code> == false</code>), its
|
||||
* <code>Status</code> will never change again.
|
||||
*/
|
||||
OVERWRITTEN(false, false),
|
||||
|
||||
/**
|
||||
* The snippet is inactive because it failed compilation on initial
|
||||
* evaluation and it is not capable of becoming valid with further
|
||||
* changes to the JShell state.
|
||||
* The snippet is not visible to other snippets
|
||||
* ({@link Status#isDefined isDefined} <code> == false</code>)
|
||||
* and thus cannot be referenced or executed.
|
||||
* <p>
|
||||
* The snippet will not update as dependents change
|
||||
* ({@link Status#isActive isActive} <code> == false</code>), its
|
||||
* <code>Status</code> will never change again.
|
||||
*/
|
||||
REJECTED(false, false),
|
||||
|
||||
/**
|
||||
* The snippet is inactive because it does not yet exist.
|
||||
* Used only in {@link SnippetEvent#previousStatus} for new
|
||||
* snippets.
|
||||
* {@link jdk.jshell.JShell#status(jdk.jshell.Snippet) JShell.status(Snippet)}
|
||||
* will never return this <code>Status</code>.
|
||||
* Vacuously, {@link Status#isDefined isDefined} and
|
||||
* {@link Status#isActive isActive} are both defined <code>false</code>.
|
||||
*/
|
||||
NONEXISTENT(false, false);
|
||||
|
||||
/**
|
||||
* Is the Snippet active, that is, will the snippet
|
||||
* be re-evaluated when a new
|
||||
* {@link JShell#eval(java.lang.String) JShell.eval(String)} or
|
||||
* {@link JShell#drop(jdk.jshell.PersistentSnippet)
|
||||
* JShell.drop(PersistentSnippet)} that could change
|
||||
* its status is invoked? This is more broad than
|
||||
* {@link Status#isDefined} since a Snippet which is
|
||||
* {@link Status#RECOVERABLE_NOT_DEFINED}
|
||||
* will be updated.
|
||||
*/
|
||||
public final boolean isActive;
|
||||
|
||||
/**
|
||||
* Is the snippet currently part of the defined state of the JShell?
|
||||
* Is it visible to compilation of other snippets?
|
||||
*/
|
||||
public final boolean isDefined;
|
||||
|
||||
Status(boolean isActive, boolean isDefined) {
|
||||
this.isActive = isActive;
|
||||
this.isDefined = isDefined;
|
||||
}
|
||||
}
|
||||
|
||||
private final Key key;
|
||||
private final String source;
|
||||
private final Wrap guts;
|
||||
final String unitName;
|
||||
private final SubKind subkind;
|
||||
|
||||
private int seq;
|
||||
private String className;
|
||||
private String id;
|
||||
private OuterWrap outer;
|
||||
private Status status;
|
||||
private List<String> unresolved;
|
||||
private DiagList diagnostics;
|
||||
|
||||
Snippet(Key key, String userSource, Wrap guts, String unitName, SubKind subkind) {
|
||||
this.key = key;
|
||||
this.source = userSource;
|
||||
this.guts = guts;
|
||||
this.unitName = unitName;
|
||||
this.subkind = subkind;
|
||||
this.status = Status.NONEXISTENT;
|
||||
setSequenceNumber(0);
|
||||
}
|
||||
|
||||
/**** public access ****/
|
||||
|
||||
/**
|
||||
* The unique identifier for the snippet. No two active snippets will have
|
||||
* the same id().
|
||||
* @return the snippet id string.
|
||||
*/
|
||||
public String id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link jdk.jshell.Snippet.Kind} for the snippet.
|
||||
* Indicates the subclass of Snippet.
|
||||
* @return the Kind of the snippet
|
||||
* @see Snippet.Kind
|
||||
*/
|
||||
public Kind kind() {
|
||||
return subkind.kind();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link SubKind} of snippet.
|
||||
* The SubKind is useful for feedback to users.
|
||||
* @return the SubKind corresponding to this snippet
|
||||
*/
|
||||
public SubKind subKind() {
|
||||
return subkind;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the source code of the snippet.
|
||||
* @return the source code corresponding to this snippet
|
||||
*/
|
||||
public String source() {
|
||||
return source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Snippet:");
|
||||
if (key() != null) {
|
||||
sb.append(key().toString());
|
||||
}
|
||||
sb.append('-');
|
||||
sb.append(source);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
//**** internal access ****
|
||||
|
||||
String name() {
|
||||
return unitName;
|
||||
}
|
||||
|
||||
Key key() {
|
||||
return key;
|
||||
}
|
||||
|
||||
List<String> unresolved() {
|
||||
return Collections.unmodifiableList(unresolved);
|
||||
}
|
||||
|
||||
DiagList diagnostics() {
|
||||
return diagnostics;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the corralled guts
|
||||
*/
|
||||
Wrap corralled() {
|
||||
return null;
|
||||
}
|
||||
|
||||
Collection<String> declareReferences() {
|
||||
return null;
|
||||
}
|
||||
|
||||
Collection<String> bodyReferences() {
|
||||
return null;
|
||||
}
|
||||
|
||||
String importLine(JShell state) {
|
||||
return "";
|
||||
}
|
||||
|
||||
void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
final void setSequenceNumber(int seq) {
|
||||
this.seq = seq;
|
||||
this.className = REPL_CLASS_PREFIX + key().index() + asLetters(seq);
|
||||
}
|
||||
|
||||
void setOuterWrap(OuterWrap outer) {
|
||||
this.outer = outer;
|
||||
}
|
||||
|
||||
void setCompilationStatus(Status status, List<String> unresolved, DiagList diagnostics) {
|
||||
this.status = status;
|
||||
this.unresolved = unresolved;
|
||||
this.diagnostics = diagnostics;
|
||||
}
|
||||
|
||||
void setDiagnostics(DiagList diagnostics) {
|
||||
this.diagnostics = diagnostics;
|
||||
}
|
||||
|
||||
void setFailed(DiagList diagnostics) {
|
||||
this.seq = -1;
|
||||
this.outer = null;
|
||||
this.status = Status.REJECTED;
|
||||
this.unresolved = Collections.emptyList();
|
||||
this.diagnostics = diagnostics;
|
||||
}
|
||||
|
||||
void setDropped() {
|
||||
this.status = Status.DROPPED;
|
||||
}
|
||||
|
||||
void setOverwritten() {
|
||||
this.status = Status.OVERWRITTEN;
|
||||
}
|
||||
|
||||
Status status() {
|
||||
return status;
|
||||
}
|
||||
|
||||
String className() {
|
||||
return className;
|
||||
}
|
||||
|
||||
/**
|
||||
* Top-level wrap
|
||||
* @return
|
||||
*/
|
||||
OuterWrap outerWrap() {
|
||||
return outer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Basically, class version for this Key.
|
||||
* @return int
|
||||
*/
|
||||
int sequenceNumber() {
|
||||
return seq;
|
||||
}
|
||||
|
||||
Wrap guts() {
|
||||
return guts;
|
||||
}
|
||||
|
||||
boolean isExecutable() {
|
||||
return subkind.isExecutable();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jshell;
|
||||
|
||||
import jdk.jshell.Snippet.Status;
|
||||
|
||||
/**
|
||||
* A description of a change to a Snippet. These are generated by direct changes
|
||||
* to state with {@link JShell#eval(java.lang.String) JShell.eval(String)} or
|
||||
* {@link JShell#drop(jdk.jshell.PersistentSnippet) JShell.drop(PersistentSnippet)},
|
||||
* or indirectly by these same methods as
|
||||
* dependencies change or Snippets are overwritten. For direct changes, the
|
||||
* {@link SnippetEvent#causeSnippet()} is <code>null</code>.
|
||||
* <p>
|
||||
* <code>SnippetEvent</code> is immutable: an access to
|
||||
* any of its methods will always return the same result.
|
||||
* and thus is thread-safe.
|
||||
* @author Robert Field
|
||||
*/
|
||||
public class SnippetEvent {
|
||||
|
||||
SnippetEvent(Snippet snippet, Status previousStatus, Status status,
|
||||
boolean isSignatureChange, Snippet causeSnippet,
|
||||
String value, Exception exception) {
|
||||
this.snippet = snippet;
|
||||
this.previousStatus = previousStatus;
|
||||
this.status = status;
|
||||
this.isSignatureChange = isSignatureChange;
|
||||
this.causeSnippet = causeSnippet;
|
||||
this.value = value;
|
||||
this.exception = exception;
|
||||
}
|
||||
|
||||
private final Snippet snippet;
|
||||
private final Status previousStatus;
|
||||
private final Status status;
|
||||
private final boolean isSignatureChange;
|
||||
private final Snippet causeSnippet;
|
||||
private final String value;
|
||||
private final Exception exception;
|
||||
|
||||
/**
|
||||
* The Snippet which has changed
|
||||
* @return the return the Snippet whose <code>Status</code> has changed.
|
||||
*/
|
||||
public Snippet snippet() {
|
||||
return snippet;
|
||||
}
|
||||
|
||||
/**
|
||||
* The status before the transition. If this event describes a Snippet
|
||||
* creation return {@link Snippet.Status#NONEXISTENT NONEXISTENT}.
|
||||
* @return the previousStatus
|
||||
*/
|
||||
public Status previousStatus() {
|
||||
return previousStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* The after status. Note: this may be the same as the previous status (not
|
||||
* all changes cause a <code>Status</code> change.
|
||||
* @return the status
|
||||
*/
|
||||
public Status status() {
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Has the signature changed? Coming in or out of definition
|
||||
* (status.isDefined) is always a signature change. An overwritten Snippet
|
||||
* {@link jdk.jshell.Snippet.Status#OVERWRITTEN (status == OVERWRITTEN)}
|
||||
* is always <code>false</code> as responsibility for the
|
||||
* definition has passed to the overwriting definition.
|
||||
* @return <code>true</code> if the signature changed.
|
||||
*/
|
||||
public boolean isSignatureChange() {
|
||||
return isSignatureChange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Either the snippet whose change caused this update or
|
||||
* <code>null</code>. This returns <code>null</code> if this change is the
|
||||
* creation of a new Snippet via
|
||||
* {@link jdk.jshell.JShell#eval(java.lang.String) eval} or it is the
|
||||
* explicit drop of a Snippet with
|
||||
* {@link jdk.jshell.JShell#drop(jdk.jshell.PersistentSnippet) drop}.
|
||||
*
|
||||
* @return the Snippet which caused this change or <code>null</code> if
|
||||
* directly caused by an API action.
|
||||
*/
|
||||
public Snippet causeSnippet() {
|
||||
return causeSnippet;
|
||||
}
|
||||
|
||||
/**
|
||||
* An instance of {@link jdk.jshell.UnresolvedReferenceException}, if an unresolved reference was
|
||||
* encountered, or an instance of {@link jdk.jshell.EvalException} if an exception was thrown
|
||||
* during execution, otherwise <code>null</code>.
|
||||
* @return the exception or <code>null</code>.
|
||||
*/
|
||||
public Exception exception() {
|
||||
return exception;
|
||||
}
|
||||
|
||||
/**
|
||||
* The result value of successful run. The value is null if not executed
|
||||
* or an exception was thrown.
|
||||
* @return the value or <code>null</code>.
|
||||
*/
|
||||
public String value() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string representation of the event
|
||||
* @return a descriptive representation of the SnippetEvent
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SnippetEvent(snippet=" + snippet +
|
||||
",previousStatus=" + previousStatus +
|
||||
",status=" + status +
|
||||
",isSignatureChange=" + isSignatureChange +
|
||||
",causeSnippet" + causeSnippet +
|
||||
(value == null? "" : "value=" + value) +
|
||||
(exception == null? "" : "exception=" + exception) +
|
||||
")";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jshell;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static jdk.internal.jshell.remote.RemoteCodes.prefixPattern;
|
||||
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_DEP;
|
||||
|
||||
/**
|
||||
* Maintain relationships between the significant entities: Snippets,
|
||||
* internal snippet index, Keys, etc.
|
||||
* @author Robert Field
|
||||
*/
|
||||
final class SnippetMaps {
|
||||
|
||||
private String packageName;
|
||||
private final List<Snippet> keyIndexToSnippet = new ArrayList<>();
|
||||
private final Set<Snippet> snippets = new LinkedHashSet<>();
|
||||
private final Map<String, Set<Integer>> dependencies = new HashMap<>();
|
||||
private final JShell state;
|
||||
|
||||
SnippetMaps(JShell proc) {
|
||||
this.state = proc;
|
||||
}
|
||||
|
||||
void installSnippet(Snippet sn) {
|
||||
if (sn != null && snippets.add(sn)) {
|
||||
if (sn.key() != null) {
|
||||
sn.setId((state.idGenerator != null)
|
||||
? state.idGenerator.apply(sn, sn.key().index())
|
||||
: "" + sn.key().index());
|
||||
setSnippet(sn.key().index(), sn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setSnippet(int ki, Snippet snip) {
|
||||
while (ki >= keyIndexToSnippet.size()) {
|
||||
keyIndexToSnippet.add(null);
|
||||
}
|
||||
keyIndexToSnippet.set(ki, snip);
|
||||
}
|
||||
|
||||
Snippet getSnippet(Key key) {
|
||||
return getSnippet(key.index());
|
||||
}
|
||||
|
||||
Snippet getSnippet(int ki) {
|
||||
if (ki >= keyIndexToSnippet.size()) {
|
||||
return null;
|
||||
}
|
||||
return keyIndexToSnippet.get(ki);
|
||||
}
|
||||
|
||||
List<Snippet> snippetList() {
|
||||
return new ArrayList<>(snippets);
|
||||
}
|
||||
|
||||
void setPackageName(String n) {
|
||||
packageName = n;
|
||||
}
|
||||
|
||||
String packageName() {
|
||||
return packageName;
|
||||
}
|
||||
|
||||
String classFullName(Snippet sn) {
|
||||
return packageName + "." + sn.className();
|
||||
}
|
||||
|
||||
String packageAndImportsExcept(Set<Key> except, Collection<Snippet> plus) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("package ").append(packageName()).append(";\n");
|
||||
for (Snippet si : keyIndexToSnippet) {
|
||||
if (si != null && si.status().isDefined && (except == null || !except.contains(si.key()))) {
|
||||
sb.append(si.importLine(state));
|
||||
}
|
||||
}
|
||||
if (plus != null) {
|
||||
plus.stream()
|
||||
.forEach(psi -> sb.append(psi.importLine(state)));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
List<Snippet> getDependents(Snippet snip) {
|
||||
if (!snip.kind().isPersistent) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
Set<Integer> depset;
|
||||
if (snip.unitName.equals("*")) {
|
||||
// star import
|
||||
depset = new HashSet<>();
|
||||
for (Set<Integer> as : dependencies.values()) {
|
||||
depset.addAll(as);
|
||||
}
|
||||
} else {
|
||||
depset = dependencies.get(snip.name());
|
||||
}
|
||||
if (depset == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<Snippet> deps = new ArrayList<>();
|
||||
for (Integer dss : depset) {
|
||||
Snippet dep = getSnippet(dss);
|
||||
if (dep != null) {
|
||||
deps.add(dep);
|
||||
state.debug(DBG_DEP, "Found dependency %s -> %s\n", snip.name(), dep.name());
|
||||
}
|
||||
}
|
||||
return deps;
|
||||
}
|
||||
|
||||
void mapDependencies(Snippet snip) {
|
||||
addDependencies(snip.declareReferences(), snip);
|
||||
addDependencies(snip.bodyReferences(), snip);
|
||||
}
|
||||
|
||||
private void addDependencies(Collection<String> refs, Snippet snip) {
|
||||
if (refs == null) return;
|
||||
for (String ref : refs) {
|
||||
dependencies.computeIfAbsent(ref, k -> new HashSet<>())
|
||||
.add(snip.key().index());
|
||||
state.debug(DBG_DEP, "Added dependency %s -> %s\n", ref, snip.name());
|
||||
}
|
||||
}
|
||||
|
||||
String fullClassNameAndPackageToClass(String full, String pkg) {
|
||||
Matcher mat = prefixPattern.matcher(full);
|
||||
if (mat.lookingAt()) {
|
||||
return full.substring(mat.end());
|
||||
}
|
||||
state.debug(DBG_DEP, "SM %s %s\n", full, pkg);
|
||||
List<String> klasses = importSnippets()
|
||||
.filter(isi -> !isi.isStar)
|
||||
.map(isi -> isi.fullname)
|
||||
.collect(toList());
|
||||
for (String k : klasses) {
|
||||
if (k.equals(full)) {
|
||||
return full.substring(full.lastIndexOf(".")+1, full.length());
|
||||
}
|
||||
}
|
||||
List<String> pkgs = importSnippets()
|
||||
.filter(isi -> isi.isStar)
|
||||
.map(isi -> isi.fullname.substring(0, isi.fullname.lastIndexOf(".")))
|
||||
.collect(toList());
|
||||
pkgs.add(0, "java.lang");
|
||||
for (String ipkg : pkgs) {
|
||||
if (!ipkg.isEmpty() && ipkg.equals(pkg)) {
|
||||
return full.substring(pkg.length() + 1);
|
||||
}
|
||||
}
|
||||
return full;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the set of imports to prepend to a snippet
|
||||
* @return a stream of the import needed
|
||||
*/
|
||||
private Stream<ImportSnippet> importSnippets() {
|
||||
return state.keyMap.importKeys()
|
||||
.map(key -> (ImportSnippet)getSnippet(key))
|
||||
.filter(sn -> state.status(sn).isDefined);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jshell;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Provides analysis utilities for source code input.
|
||||
* Optional functionality that provides for a richer interactive experience.
|
||||
* Includes completion analysis:
|
||||
* Is the input a complete snippet of code?
|
||||
* Do I need to prompt for more input?
|
||||
* Would adding a semicolon make it complete?
|
||||
* Is there more than one snippet?
|
||||
* etc.
|
||||
* Also includes completion suggestions, as might be used in tab-completion.
|
||||
*
|
||||
*/
|
||||
public abstract class SourceCodeAnalysis {
|
||||
|
||||
/**
|
||||
* Given an input string, find the first snippet of code (one statement,
|
||||
* definition, import, or expression) and evaluate if it is complete.
|
||||
* @param input the input source string
|
||||
* @return a CompletionInfo instance with location and completeness info
|
||||
*/
|
||||
public abstract CompletionInfo analyzeCompletion(String input);
|
||||
|
||||
/**
|
||||
* Compute possible follow-ups for the given input.
|
||||
* Uses information from the current <code>JShell</code> state, including
|
||||
* type information, to filter the suggestions.
|
||||
* @param input the user input, so far
|
||||
* @param cursor the current position of the cursors in the given {@code input} text
|
||||
* @param anchor outgoing parameter - when an option will be completed, the text between
|
||||
* the anchor and cursor will be deleted and replaced with the given option
|
||||
* @return list of candidate continuations of the given input.
|
||||
*/
|
||||
public abstract List<Suggestion> completionSuggestions(String input, int cursor, int[] anchor);
|
||||
|
||||
/**
|
||||
* Compute a description/help string for the given user's input.
|
||||
* @param input the snippet the user wrote so far
|
||||
* @param cursor the current position of the cursors in the given {@code input} text
|
||||
* @return description/help string for the given user's input
|
||||
*/
|
||||
public abstract String documentation(String input, int cursor);
|
||||
|
||||
/**
|
||||
* Internal only constructor
|
||||
*/
|
||||
SourceCodeAnalysis() {}
|
||||
|
||||
/**
|
||||
* The result of <code>analyzeCompletion(String input)</code>.
|
||||
* Describes the completeness and position of the first snippet in the given input.
|
||||
*/
|
||||
public static class CompletionInfo {
|
||||
|
||||
public CompletionInfo(Completeness completeness, int unitEndPos, String source, String remaining) {
|
||||
this.completeness = completeness;
|
||||
this.unitEndPos = unitEndPos;
|
||||
this.source = source;
|
||||
this.remaining = remaining;
|
||||
}
|
||||
|
||||
/**
|
||||
* The analyzed completeness of the input.
|
||||
*/
|
||||
public final Completeness completeness;
|
||||
|
||||
/**
|
||||
* The end of the first unit of source.
|
||||
*/
|
||||
public final int unitEndPos;
|
||||
|
||||
/**
|
||||
* Source code for the first unit of code input. For example, first
|
||||
* statement, or first method declaration. Trailing semicolons will
|
||||
* be added, as needed
|
||||
*/
|
||||
public final String source;
|
||||
|
||||
/**
|
||||
* Input remaining after the source
|
||||
*/
|
||||
public final String remaining;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the completeness of the given input.
|
||||
*/
|
||||
public enum Completeness {
|
||||
/**
|
||||
* The input is a complete source snippet (declaration or statement) as is.
|
||||
*/
|
||||
COMPLETE(true),
|
||||
|
||||
/**
|
||||
* With this addition of a semicolon the input is a complete source snippet.
|
||||
* This will only be returned when the end of input is encountered.
|
||||
*/
|
||||
COMPLETE_WITH_SEMI(true),
|
||||
|
||||
/**
|
||||
* There must be further source beyond the given input in order for it
|
||||
* to be complete. A semicolon would not complete it.
|
||||
* This will only be returned when the end of input is encountered.
|
||||
*/
|
||||
DEFINITELY_INCOMPLETE(false),
|
||||
|
||||
/**
|
||||
* A statement with a trailing (non-terminated) empty statement.
|
||||
* Though technically it would be a complete statement
|
||||
* with the addition of a semicolon, it is rare
|
||||
* that that assumption is the desired behavior.
|
||||
* The input is considered incomplete. Comments and white-space are
|
||||
* still considered empty.
|
||||
*/
|
||||
CONSIDERED_INCOMPLETE(false),
|
||||
|
||||
|
||||
/**
|
||||
* An empty input.
|
||||
* The input is considered incomplete. Comments and white-space are
|
||||
* still considered empty.
|
||||
*/
|
||||
EMPTY(false),
|
||||
|
||||
/**
|
||||
* The completeness of the input could not be determined because it
|
||||
* contains errors. Error detection is not a goal of completeness
|
||||
* analysis, however errors interfered with determining its completeness.
|
||||
* The input is considered complete because evaluating is the best
|
||||
* mechanism to get error information.
|
||||
*/
|
||||
UNKNOWN(true);
|
||||
|
||||
/**
|
||||
* Is the first snippet of source complete. For example, "x=" is not
|
||||
* complete, but "x=2" is complete, even though a subsequent line could
|
||||
* make it "x=2+2". Already erroneous code is marked complete.
|
||||
*/
|
||||
public final boolean isComplete;
|
||||
|
||||
Completeness(boolean isComplete) {
|
||||
this.isComplete = isComplete;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A candidate for continuation of the given user's input.
|
||||
*/
|
||||
public static class Suggestion {
|
||||
|
||||
/**
|
||||
* Create a {@code Suggestion} instance.
|
||||
* @param continuation a candidate continuation of the user's input
|
||||
* @param isSmart is the candidate "smart"
|
||||
*/
|
||||
public Suggestion(String continuation, boolean isSmart) {
|
||||
this.continuation = continuation;
|
||||
this.isSmart = isSmart;
|
||||
}
|
||||
|
||||
/**
|
||||
* The candidate continuation of the given user's input.
|
||||
*/
|
||||
public final String continuation;
|
||||
|
||||
/**
|
||||
* Is it an input continuation that matches the target type and is thus more
|
||||
* likely to be the desired continuation. A smart continuation
|
||||
* is preferred.
|
||||
*/
|
||||
public final boolean isSmart;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jshell;
|
||||
|
||||
import jdk.jshell.Key.StatementKey;
|
||||
|
||||
/**
|
||||
* Snippet for a statement.
|
||||
* The Kind is {@link jdk.jshell.Snippet.Kind#STATEMENT}.
|
||||
* <p>
|
||||
* <code>StatementSnippet</code> is immutable: an access to
|
||||
* any of its methods will always return the same result.
|
||||
* and thus is thread-safe.
|
||||
* @jls 14.5: Statement.
|
||||
*/
|
||||
public class StatementSnippet extends Snippet {
|
||||
|
||||
StatementSnippet(StatementKey key, String userSource, Wrap guts) {
|
||||
super(key, userSource, guts, null, SubKind.STATEMENT_SUBKIND);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,465 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jshell;
|
||||
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.source.util.Trees;
|
||||
import com.sun.tools.javac.api.JavacTaskImpl;
|
||||
import com.sun.tools.javac.api.JavacTool;
|
||||
import com.sun.tools.javac.util.Context;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import javax.tools.Diagnostic;
|
||||
import javax.tools.DiagnosticCollector;
|
||||
import javax.tools.JavaCompiler;
|
||||
import javax.tools.JavaFileManager;
|
||||
import javax.tools.JavaFileObject;
|
||||
import javax.tools.ToolProvider;
|
||||
import static jdk.jshell.Util.*;
|
||||
import com.sun.source.tree.ImportTree;
|
||||
import com.sun.tools.javac.code.Types;
|
||||
import com.sun.tools.javac.util.JavacMessages;
|
||||
import jdk.jshell.MemoryFileManager.OutputMemoryJavaFileObject;
|
||||
import java.util.Collections;
|
||||
import java.util.Locale;
|
||||
import static javax.tools.StandardLocation.CLASS_OUTPUT;
|
||||
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import javax.lang.model.util.Elements;
|
||||
import javax.tools.FileObject;
|
||||
import jdk.jshell.MemoryFileManager.SourceMemoryJavaFileObject;
|
||||
import jdk.jshell.ClassTracker.ClassInfo;
|
||||
|
||||
/**
|
||||
* The primary interface to the compiler API. Parsing, analysis, and
|
||||
* compilation to class files (in memory).
|
||||
* @author Robert Field
|
||||
*/
|
||||
class TaskFactory {
|
||||
|
||||
private final JavaCompiler compiler;
|
||||
private final MemoryFileManager fileManager;
|
||||
private final JShell state;
|
||||
private String classpath = System.getProperty("java.class.path");
|
||||
|
||||
TaskFactory(JShell state) {
|
||||
this.state = state;
|
||||
this.compiler = ToolProvider.getSystemJavaCompiler();
|
||||
if (compiler == null) {
|
||||
throw new UnsupportedOperationException("Compiler not available, must be run with full JDK 9.");
|
||||
}
|
||||
if (!System.getProperty("java.specification.version").equals("1.9")) {
|
||||
throw new UnsupportedOperationException("Wrong compiler, must be run with full JDK 9.");
|
||||
}
|
||||
this.fileManager = new MemoryFileManager(
|
||||
compiler.getStandardFileManager(null, null, null), state);
|
||||
}
|
||||
|
||||
void addToClasspath(String path) {
|
||||
classpath = classpath + File.pathSeparator + path;
|
||||
List<String> args = new ArrayList<>();
|
||||
args.add(classpath);
|
||||
fileManager().handleOption("-classpath", args.iterator());
|
||||
}
|
||||
|
||||
MemoryFileManager fileManager() {
|
||||
return fileManager;
|
||||
}
|
||||
|
||||
private interface SourceHandler<T> {
|
||||
|
||||
JavaFileObject sourceToFileObject(MemoryFileManager fm, T t);
|
||||
|
||||
Diag diag(Diagnostic<? extends JavaFileObject> d);
|
||||
}
|
||||
|
||||
private class StringSourceHandler implements SourceHandler<String> {
|
||||
|
||||
@Override
|
||||
public JavaFileObject sourceToFileObject(MemoryFileManager fm, String src) {
|
||||
return fm.createSourceFileObject(src, "$NeverUsedName$", src);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Diag diag(final Diagnostic<? extends JavaFileObject> d) {
|
||||
return new Diag() {
|
||||
|
||||
@Override
|
||||
public boolean isError() {
|
||||
return d.getKind() == Diagnostic.Kind.ERROR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getPosition() {
|
||||
return d.getPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getStartPosition() {
|
||||
return d.getStartPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEndPosition() {
|
||||
return d.getEndPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCode() {
|
||||
return d.getCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage(Locale locale) {
|
||||
return expunge(d.getMessage(locale));
|
||||
}
|
||||
|
||||
@Override
|
||||
Unit unitOrNull() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private class WrapSourceHandler implements SourceHandler<OuterWrap> {
|
||||
|
||||
final OuterWrap wrap;
|
||||
|
||||
WrapSourceHandler(OuterWrap wrap) {
|
||||
this.wrap = wrap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaFileObject sourceToFileObject(MemoryFileManager fm, OuterWrap w) {
|
||||
return fm.createSourceFileObject(w, w.classFullName(), w.wrapped());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Diag diag(Diagnostic<? extends JavaFileObject> d) {
|
||||
return wrap.wrapDiag(d);
|
||||
}
|
||||
}
|
||||
|
||||
private class UnitSourceHandler implements SourceHandler<Unit> {
|
||||
|
||||
@Override
|
||||
public JavaFileObject sourceToFileObject(MemoryFileManager fm, Unit u) {
|
||||
return fm.createSourceFileObject(u,
|
||||
state.maps.classFullName(u.snippet()),
|
||||
u.snippet().outerWrap().wrapped());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Diag diag(Diagnostic<? extends JavaFileObject> d) {
|
||||
SourceMemoryJavaFileObject smjfo = (SourceMemoryJavaFileObject) d.getSource();
|
||||
Unit u = (Unit) smjfo.getOrigin();
|
||||
return u.snippet().outerWrap().wrapDiag(d);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a snippet of code (as a String) using the parser subclass. Return
|
||||
* the parse tree (and errors).
|
||||
*/
|
||||
class ParseTask extends BaseTask {
|
||||
|
||||
private final CompilationUnitTree cut;
|
||||
private final List<? extends Tree> units;
|
||||
|
||||
ParseTask(final String source) {
|
||||
super(Stream.of(source),
|
||||
new StringSourceHandler(),
|
||||
"-XDallowStringFolding=false", "-proc:none");
|
||||
ReplParserFactory.instance(getContext());
|
||||
Iterable<? extends CompilationUnitTree> asts = parse();
|
||||
Iterator<? extends CompilationUnitTree> it = asts.iterator();
|
||||
if (it.hasNext()) {
|
||||
this.cut = it.next();
|
||||
List<? extends ImportTree> imps = cut.getImports();
|
||||
this.units = !imps.isEmpty() ? imps : cut.getTypeDecls();
|
||||
} else {
|
||||
this.cut = null;
|
||||
this.units = Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
private Iterable<? extends CompilationUnitTree> parse() {
|
||||
try {
|
||||
return task.parse();
|
||||
} catch (Exception ex) {
|
||||
throw new InternalError("Exception during parse - " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
List<? extends Tree> units() {
|
||||
return units;
|
||||
}
|
||||
|
||||
@Override
|
||||
CompilationUnitTree cuTree() {
|
||||
return cut;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the normal "analyze()" pass of the compiler over the wrapped snippet.
|
||||
*/
|
||||
class AnalyzeTask extends BaseTask {
|
||||
|
||||
private final CompilationUnitTree cut;
|
||||
|
||||
AnalyzeTask(final OuterWrap wrap) {
|
||||
this(Stream.of(wrap),
|
||||
new WrapSourceHandler(wrap),
|
||||
"-XDshouldStopPolicy=FLOW", "-proc:none");
|
||||
}
|
||||
|
||||
AnalyzeTask(final Collection<Unit> units) {
|
||||
this(units.stream(), new UnitSourceHandler(),
|
||||
"-XDshouldStopPolicy=FLOW", "-Xlint:unchecked", "-proc:none");
|
||||
}
|
||||
|
||||
<T>AnalyzeTask(final Stream<T> stream, SourceHandler<T> sourceHandler,
|
||||
String... extraOptions) {
|
||||
super(stream, sourceHandler, extraOptions);
|
||||
Iterator<? extends CompilationUnitTree> cuts = analyze().iterator();
|
||||
if (cuts.hasNext()) {
|
||||
this.cut = cuts.next();
|
||||
//proc.debug("AnalyzeTask element=%s cutp=%s cut=%s\n", e, cutp, cut);
|
||||
} else {
|
||||
this.cut = null;
|
||||
//proc.debug("AnalyzeTask -- no elements -- %s\n", getDiagnostics());
|
||||
}
|
||||
}
|
||||
|
||||
private Iterable<? extends CompilationUnitTree> analyze() {
|
||||
try {
|
||||
Iterable<? extends CompilationUnitTree> cuts = task.parse();
|
||||
task.analyze();
|
||||
return cuts;
|
||||
} catch (Exception ex) {
|
||||
throw new InternalError("Exception during analyze - " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
CompilationUnitTree cuTree() {
|
||||
return cut;
|
||||
}
|
||||
|
||||
Elements getElements() {
|
||||
return task.getElements();
|
||||
}
|
||||
|
||||
javax.lang.model.util.Types getTypes() {
|
||||
return task.getTypes();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unit the wrapped snippet to class files.
|
||||
*/
|
||||
class CompileTask extends BaseTask {
|
||||
|
||||
private final Map<Unit, List<OutputMemoryJavaFileObject>> classObjs = new HashMap<>();
|
||||
|
||||
CompileTask(Collection<Unit> units) {
|
||||
super(units.stream(), new UnitSourceHandler(),
|
||||
"-Xlint:unchecked", "-proc:none");
|
||||
}
|
||||
|
||||
boolean compile() {
|
||||
fileManager.registerClassFileCreationListener(this::listenForNewClassFile);
|
||||
boolean result = task.call();
|
||||
fileManager.registerClassFileCreationListener(null);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
List<ClassInfo> classInfoList(Unit u) {
|
||||
List<OutputMemoryJavaFileObject> l = classObjs.get(u);
|
||||
if (l == null) return Collections.emptyList();
|
||||
return l.stream()
|
||||
.map(fo -> state.classTracker.classInfo(fo.getName(), fo.getBytes()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void listenForNewClassFile(OutputMemoryJavaFileObject jfo, JavaFileManager.Location location,
|
||||
String className, JavaFileObject.Kind kind, FileObject sibling) {
|
||||
//debug("listenForNewClassFile %s loc=%s kind=%s\n", className, location, kind);
|
||||
if (location == CLASS_OUTPUT) {
|
||||
state.debug(DBG_GEN, "Compiler generating class %s\n", className);
|
||||
Unit u = ((sibling instanceof SourceMemoryJavaFileObject)
|
||||
&& (((SourceMemoryJavaFileObject) sibling).getOrigin() instanceof Unit))
|
||||
? (Unit) ((SourceMemoryJavaFileObject) sibling).getOrigin()
|
||||
: null;
|
||||
classObjs.compute(u, (k, v) -> (v == null)? new ArrayList<>() : v)
|
||||
.add(jfo);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
CompilationUnitTree cuTree() {
|
||||
throw new UnsupportedOperationException("Not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
abstract class BaseTask {
|
||||
|
||||
final DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
|
||||
final JavacTaskImpl task;
|
||||
private DiagList diags = null;
|
||||
private final SourceHandler<?> sourceHandler;
|
||||
private final Context context = new Context();
|
||||
private Types types;
|
||||
private JavacMessages messages;
|
||||
private Trees trees;
|
||||
|
||||
private <T>BaseTask(Stream<T> inputs,
|
||||
//BiFunction<MemoryFileManager, T, JavaFileObject> sfoCreator,
|
||||
SourceHandler<T> sh,
|
||||
String... extraOptions) {
|
||||
this.sourceHandler = sh;
|
||||
List<String> options = Arrays.asList(extraOptions);
|
||||
Iterable<? extends JavaFileObject> compilationUnits = inputs
|
||||
.map(in -> sh.sourceToFileObject(fileManager, in))
|
||||
.collect(Collectors.toList());
|
||||
this.task = (JavacTaskImpl) ((JavacTool) compiler).getTask(null,
|
||||
fileManager, diagnostics, options, null,
|
||||
compilationUnits, context);
|
||||
}
|
||||
|
||||
abstract CompilationUnitTree cuTree();
|
||||
|
||||
Diag diag(Diagnostic<? extends JavaFileObject> diag) {
|
||||
return sourceHandler.diag(diag);
|
||||
}
|
||||
|
||||
Context getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
Types types() {
|
||||
if (types == null) {
|
||||
types = Types.instance(context);
|
||||
}
|
||||
return types;
|
||||
}
|
||||
|
||||
JavacMessages messages() {
|
||||
if (messages == null) {
|
||||
messages = JavacMessages.instance(context);
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
|
||||
Trees trees() {
|
||||
if (trees == null) {
|
||||
trees = Trees.instance(task);
|
||||
}
|
||||
return trees;
|
||||
}
|
||||
|
||||
// ------------------ diags functionality
|
||||
|
||||
DiagList getDiagnostics() {
|
||||
if (diags == null) {
|
||||
LinkedHashMap<String, Diag> diagMap = new LinkedHashMap<>();
|
||||
for (Diagnostic<? extends JavaFileObject> in : diagnostics.getDiagnostics()) {
|
||||
Diag d = diag(in);
|
||||
String uniqueKey = d.getCode() + ":" + d.getPosition() + ":" + d.getMessage(null);
|
||||
diagMap.put(uniqueKey, d);
|
||||
}
|
||||
diags = new DiagList(diagMap.values());
|
||||
}
|
||||
return diags;
|
||||
}
|
||||
|
||||
boolean hasErrors() {
|
||||
return getDiagnostics().hasErrors();
|
||||
}
|
||||
|
||||
String shortErrorMessage() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Diag diag : getDiagnostics()) {
|
||||
for (String line : diag.getMessage(null).split("\\r?\\n")) {
|
||||
if (!line.trim().startsWith("location:")) {
|
||||
sb.append(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
void debugPrintDiagnostics(String src) {
|
||||
for (Diag diag : getDiagnostics()) {
|
||||
state.debug(DBG_GEN, "ERROR --\n");
|
||||
for (String line : diag.getMessage(null).split("\\r?\\n")) {
|
||||
if (!line.trim().startsWith("location:")) {
|
||||
state.debug(DBG_GEN, "%s\n", line);
|
||||
}
|
||||
}
|
||||
int start = (int) diag.getStartPosition();
|
||||
int end = (int) diag.getEndPosition();
|
||||
if (src != null) {
|
||||
String[] srcLines = src.split("\\r?\\n");
|
||||
for (String line : srcLines) {
|
||||
state.debug(DBG_GEN, "%s\n", line);
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < start; ++i) {
|
||||
sb.append(' ');
|
||||
}
|
||||
sb.append('^');
|
||||
if (end > start) {
|
||||
for (int i = start + 1; i < end; ++i) {
|
||||
sb.append('-');
|
||||
}
|
||||
sb.append('^');
|
||||
}
|
||||
state.debug(DBG_GEN, "%s\n", sb.toString());
|
||||
}
|
||||
state.debug(DBG_GEN, "printDiagnostics start-pos = %d ==> %d -- wrap = %s\n",
|
||||
diag.getStartPosition(), start, this);
|
||||
state.debug(DBG_GEN, "Code: %s\n", diag.getCode());
|
||||
state.debug(DBG_GEN, "Pos: %d (%d - %d) -- %s\n", diag.getPosition(),
|
||||
diag.getStartPosition(), diag.getEndPosition(), diag.getMessage(null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jshell;
|
||||
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.IdentifierTree;
|
||||
import com.sun.source.tree.ImportTree;
|
||||
import com.sun.source.tree.MemberSelectTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.tree.PackageTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.source.tree.VariableTree;
|
||||
import com.sun.source.util.TreeScanner;
|
||||
import com.sun.tools.javac.util.DefinedBy;
|
||||
import com.sun.tools.javac.util.DefinedBy.Api;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import javax.lang.model.element.Name;
|
||||
|
||||
/**
|
||||
* Search a compiler API parse tree for dependencies.
|
||||
*/
|
||||
class TreeDependencyScanner extends TreeScanner<Void, Set<String>> {
|
||||
|
||||
private final Set<String> decl = new HashSet<>();
|
||||
private final Set<String> body = new HashSet<>();
|
||||
|
||||
public void scan(Tree node) {
|
||||
scan(node, decl);
|
||||
}
|
||||
|
||||
public Collection<String> declareReferences() {
|
||||
return decl;
|
||||
}
|
||||
|
||||
public Collection<String> bodyReferences() {
|
||||
return body;
|
||||
}
|
||||
|
||||
private void add(Set<String> p, Name name) {
|
||||
p.add(name.toString());
|
||||
}
|
||||
|
||||
// -- Differentiate declaration references from body references ---
|
||||
|
||||
@Override @DefinedBy(Api.COMPILER_TREE)
|
||||
public Void visitClass(ClassTree node, Set<String> p) {
|
||||
scan(node.getModifiers(), p);
|
||||
scan(node.getTypeParameters(), p);
|
||||
scan(node.getExtendsClause(), p);
|
||||
scan(node.getImplementsClause(), p);
|
||||
scan(node.getMembers(), body);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override @DefinedBy(Api.COMPILER_TREE)
|
||||
public Void visitMethod(MethodTree node, Set<String> p) {
|
||||
scan(node.getModifiers(), p);
|
||||
scan(node.getReturnType(), p);
|
||||
scan(node.getTypeParameters(), p);
|
||||
scan(node.getParameters(), p);
|
||||
scan(node.getReceiverParameter(), p);
|
||||
scan(node.getThrows(), p);
|
||||
scan(node.getBody(), body);
|
||||
scan(node.getDefaultValue(), body);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override @DefinedBy(Api.COMPILER_TREE)
|
||||
public Void visitVariable(VariableTree node, Set<String> p) {
|
||||
scan(node.getModifiers(), p);
|
||||
scan(node.getType(), p);
|
||||
scan(node.getNameExpression(), p);
|
||||
scan(node.getInitializer(), body);
|
||||
return null;
|
||||
}
|
||||
|
||||
// --- Ignore these ---
|
||||
|
||||
@Override @DefinedBy(Api.COMPILER_TREE)
|
||||
public Void visitPackage(PackageTree node, Set<String> p) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override @DefinedBy(Api.COMPILER_TREE)
|
||||
public Void visitImport(ImportTree node, Set<String> p) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// -- Actual Symbol names ---
|
||||
|
||||
@Override @DefinedBy(Api.COMPILER_TREE)
|
||||
public Void visitMemberSelect(MemberSelectTree node, Set<String> p) {
|
||||
add(p, node.getIdentifier());
|
||||
return super.visitMemberSelect(node, p);
|
||||
}
|
||||
|
||||
@Override @DefinedBy(Api.COMPILER_TREE)
|
||||
public Void visitIdentifier(IdentifierTree node, Set<String> p) {
|
||||
add(p, node.getName());
|
||||
return super.visitIdentifier(node, p);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,283 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jshell;
|
||||
|
||||
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.tree.ReturnTree;
|
||||
import com.sun.source.tree.StatementTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.source.tree.VariableTree;
|
||||
import com.sun.source.util.SourcePositions;
|
||||
import com.sun.source.util.TreePath;
|
||||
import com.sun.source.util.Trees;
|
||||
import com.sun.tools.javac.code.Type;
|
||||
import com.sun.tools.javac.code.Type.MethodType;
|
||||
import com.sun.tools.javac.code.Types;
|
||||
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
|
||||
import com.sun.tools.javac.util.JavacMessages;
|
||||
import com.sun.tools.javac.util.Name;
|
||||
import static jdk.jshell.Util.isDoIt;
|
||||
import jdk.jshell.Wrap.Range;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.function.BinaryOperator;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
|
||||
/**
|
||||
* Utilities for analyzing compiler API parse trees.
|
||||
* @author Robert Field
|
||||
*/
|
||||
|
||||
class TreeDissector {
|
||||
|
||||
private static final String OBJECT_TYPE = "Object";
|
||||
|
||||
static class ExpressionInfo {
|
||||
|
||||
boolean isNonVoid;
|
||||
String typeName;
|
||||
ExpressionTree tree;
|
||||
String signature;
|
||||
}
|
||||
|
||||
private final TaskFactory.BaseTask bt;
|
||||
private ClassTree firstClass;
|
||||
private SourcePositions theSourcePositions = null;
|
||||
|
||||
TreeDissector(TaskFactory.BaseTask bt) {
|
||||
this.bt = bt;
|
||||
}
|
||||
|
||||
|
||||
ClassTree firstClass() {
|
||||
if (firstClass == null) {
|
||||
firstClass = computeFirstClass();
|
||||
}
|
||||
return firstClass;
|
||||
}
|
||||
|
||||
CompilationUnitTree cuTree() {
|
||||
return bt.cuTree();
|
||||
}
|
||||
|
||||
Types types() {
|
||||
return bt.types();
|
||||
}
|
||||
|
||||
Trees trees() {
|
||||
return bt.trees();
|
||||
}
|
||||
|
||||
SourcePositions getSourcePositions() {
|
||||
if (theSourcePositions == null) {
|
||||
theSourcePositions = trees().getSourcePositions();
|
||||
}
|
||||
return theSourcePositions;
|
||||
}
|
||||
|
||||
int getStartPosition(Tree tree) {
|
||||
return (int) getSourcePositions().getStartPosition(cuTree(), tree);
|
||||
}
|
||||
|
||||
int getEndPosition(Tree tree) {
|
||||
return (int) getSourcePositions().getEndPosition(cuTree(), tree);
|
||||
}
|
||||
|
||||
Range treeToRange(Tree tree) {
|
||||
return new Range(getStartPosition(tree), getEndPosition(tree));
|
||||
}
|
||||
|
||||
Range treeListToRange(List<? extends Tree> treeList) {
|
||||
int start = Integer.MAX_VALUE;
|
||||
int end = -1;
|
||||
for (Tree t : treeList) {
|
||||
int tstart = getStartPosition(t);
|
||||
int tend = getEndPosition(t);
|
||||
if (tstart < start) {
|
||||
start = tstart;
|
||||
}
|
||||
if (tend > end) {
|
||||
end = tend;
|
||||
}
|
||||
}
|
||||
if (start == Integer.MAX_VALUE) {
|
||||
return null;
|
||||
}
|
||||
return new Range(start, end);
|
||||
}
|
||||
|
||||
Tree firstClassMember() {
|
||||
if (firstClass() != null) {
|
||||
//TODO: missing classes
|
||||
for (Tree mem : firstClass().getMembers()) {
|
||||
if (mem.getKind() == Tree.Kind.VARIABLE) {
|
||||
return mem;
|
||||
}
|
||||
if (mem.getKind() == Tree.Kind.METHOD) {
|
||||
MethodTree mt = (MethodTree) mem;
|
||||
if (!isDoIt(mt.getName()) && !mt.getName().toString().equals("<init>")) {
|
||||
return mt;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
StatementTree firstStatement() {
|
||||
if (firstClass() != null) {
|
||||
for (Tree mem : firstClass().getMembers()) {
|
||||
if (mem.getKind() == Tree.Kind.METHOD) {
|
||||
MethodTree mt = (MethodTree) mem;
|
||||
if (isDoIt(mt.getName())) {
|
||||
List<? extends StatementTree> stmts = mt.getBody().getStatements();
|
||||
if (!stmts.isEmpty()) {
|
||||
return stmts.get(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
VariableTree firstVariable() {
|
||||
if (firstClass() != null) {
|
||||
for (Tree mem : firstClass().getMembers()) {
|
||||
if (mem.getKind() == Tree.Kind.VARIABLE) {
|
||||
VariableTree vt = (VariableTree) mem;
|
||||
return vt;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private ClassTree computeFirstClass() {
|
||||
if (cuTree() == null) {
|
||||
return null;
|
||||
}
|
||||
for (Tree decl : cuTree().getTypeDecls()) {
|
||||
if (decl.getKind() == Tree.Kind.CLASS || decl.getKind() == Tree.Kind.INTERFACE) {
|
||||
return (ClassTree) decl;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
ExpressionInfo typeOfReturnStatement(JavacMessages messages, BinaryOperator<String> fullClassNameAndPackageToClass) {
|
||||
ExpressionInfo ei = new ExpressionInfo();
|
||||
Tree unitTree = firstStatement();
|
||||
if (unitTree instanceof ReturnTree) {
|
||||
ei.tree = ((ReturnTree) unitTree).getExpression();
|
||||
if (ei.tree != null) {
|
||||
TreePath viPath = trees().getPath(cuTree(), ei.tree);
|
||||
if (viPath != null) {
|
||||
TypeMirror tm = trees().getTypeMirror(viPath);
|
||||
if (tm != null) {
|
||||
Type type = (Type)tm;
|
||||
TypePrinter tp = new TypePrinter(messages, fullClassNameAndPackageToClass, type);
|
||||
ei.typeName = tp.visit(type, Locale.getDefault());
|
||||
switch (tm.getKind()) {
|
||||
case VOID:
|
||||
case NONE:
|
||||
case ERROR:
|
||||
case OTHER:
|
||||
break;
|
||||
case NULL:
|
||||
ei.isNonVoid = true;
|
||||
ei.typeName = OBJECT_TYPE;
|
||||
break;
|
||||
default: {
|
||||
ei.isNonVoid = true;
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ei;
|
||||
}
|
||||
|
||||
String typeOfMethod() {
|
||||
Tree unitTree = firstClassMember();
|
||||
if (unitTree instanceof JCMethodDecl) {
|
||||
JCMethodDecl mtree = (JCMethodDecl) unitTree;
|
||||
Type mt = types().erasure(mtree.type);
|
||||
if (mt instanceof MethodType) {
|
||||
return signature(types(), (MethodType) mt);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static String signature(Types types, MethodType mt) {
|
||||
TDSignatureGenerator sg = new TDSignatureGenerator(types);
|
||||
sg.assembleSig(mt);
|
||||
return sg.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Signature Generation
|
||||
*/
|
||||
private static class TDSignatureGenerator extends Types.SignatureGenerator {
|
||||
|
||||
/**
|
||||
* An output buffer for type signatures.
|
||||
*/
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
TDSignatureGenerator(Types types) {
|
||||
super(types);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void append(char ch) {
|
||||
sb.append(ch);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void append(byte[] ba) {
|
||||
sb.append(new String(ba));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void append(Name name) {
|
||||
sb.append(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jshell;
|
||||
|
||||
import java.util.Collection;
|
||||
import jdk.jshell.Key.TypeDeclKey;
|
||||
|
||||
/**
|
||||
* Snippet for a type definition (a class, interface, enum, or annotation
|
||||
* interface definition).
|
||||
* The Kind is {@link jdk.jshell.Snippet.Kind#TYPE_DECL}.
|
||||
* <p>
|
||||
* <code>TypeDeclSnippet</code> is immutable: an access to
|
||||
* any of its methods will always return the same result.
|
||||
* and thus is thread-safe.
|
||||
*/
|
||||
public class TypeDeclSnippet extends DeclarationSnippet {
|
||||
|
||||
TypeDeclSnippet(TypeDeclKey key, String userSource, Wrap guts,
|
||||
String unitName, SubKind subkind, Wrap corralled,
|
||||
Collection<String> declareReferences,
|
||||
Collection<String> bodyReferences) {
|
||||
super(key, userSource, guts, unitName, subkind, corralled, declareReferences, bodyReferences);
|
||||
}
|
||||
|
||||
/**** internal access ****/
|
||||
|
||||
@Override
|
||||
TypeDeclKey key() {
|
||||
return (TypeDeclKey) super.key();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jshell;
|
||||
|
||||
import static com.sun.tools.javac.code.Flags.COMPOUND;
|
||||
import static com.sun.tools.javac.code.Kinds.Kind.PCK;
|
||||
import com.sun.tools.javac.code.Printer;
|
||||
import com.sun.tools.javac.code.Symbol;
|
||||
import com.sun.tools.javac.code.Symbol.ClassSymbol;
|
||||
import com.sun.tools.javac.code.Symbol.PackageSymbol;
|
||||
import com.sun.tools.javac.code.Type;
|
||||
import com.sun.tools.javac.code.Type.ClassType;
|
||||
import com.sun.tools.javac.util.JavacMessages;
|
||||
import java.util.Locale;
|
||||
import java.util.function.BinaryOperator;
|
||||
|
||||
/**
|
||||
* Print types in source form.
|
||||
*/
|
||||
class TypePrinter extends Printer {
|
||||
|
||||
private final JavacMessages messages;
|
||||
private final BinaryOperator<String> fullClassNameAndPackageToClass;
|
||||
private final Type typeToPrint;
|
||||
|
||||
TypePrinter(JavacMessages messages, BinaryOperator<String> fullClassNameAndPackageToClass, Type typeToPrint) {
|
||||
this.messages = messages;
|
||||
this.fullClassNameAndPackageToClass = fullClassNameAndPackageToClass;
|
||||
this.typeToPrint = typeToPrint;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String localize(Locale locale, String key, Object... args) {
|
||||
return messages.getLocalizedString(locale, key, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String capturedVarId(Type.CapturedType t, Locale locale) {
|
||||
throw new InternalError("should never call this");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitCapturedType(Type.CapturedType t, Locale locale) {
|
||||
if (t == typeToPrint) {
|
||||
return visit(t.getUpperBound(), locale);
|
||||
} else {
|
||||
return visit(t.wildcard, locale);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitType(Type t, Locale locale) {
|
||||
String s = (t.tsym == null || t.tsym.name == null)
|
||||
? "Object" // none
|
||||
: t.tsym.name.toString();
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a class name into a (possibly localized) string. Anonymous
|
||||
* inner classes get converted into a localized string.
|
||||
*
|
||||
* @param t the type of the class whose name is to be rendered
|
||||
* @param longform if set, the class' fullname is displayed - if unset the
|
||||
* short name is chosen (w/o package)
|
||||
* @param locale the locale in which the string is to be rendered
|
||||
* @return localized string representation
|
||||
*/
|
||||
@Override
|
||||
protected String className(ClassType t, boolean longform, Locale locale) {
|
||||
Symbol sym = t.tsym;
|
||||
if (sym.name.length() == 0 && (sym.flags() & COMPOUND) != 0) {
|
||||
/***
|
||||
StringBuilder s = new StringBuilder(visit(t.supertype_field, locale));
|
||||
for (List<Type> is = t.interfaces_field; is.nonEmpty(); is = is.tail) {
|
||||
s.append('&');
|
||||
s.append(visit(is.head, locale));
|
||||
}
|
||||
return s.toString();
|
||||
***/
|
||||
return "Object";
|
||||
} else if (sym.name.length() == 0) {
|
||||
// Anonymous
|
||||
String s;
|
||||
ClassType norm = (ClassType) t.tsym.type;
|
||||
if (norm == null) {
|
||||
s = "object";
|
||||
} else if (norm.interfaces_field != null && norm.interfaces_field.nonEmpty()) {
|
||||
s = visit(norm.interfaces_field.head, locale);
|
||||
} else {
|
||||
s = visit(norm.supertype_field, locale);
|
||||
}
|
||||
return s;
|
||||
} else if (longform) {
|
||||
String pkg = "";
|
||||
for (Symbol psym = sym; psym != null; psym = psym.owner) {
|
||||
if (psym.kind == PCK) {
|
||||
pkg = psym.getQualifiedName().toString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return fullClassNameAndPackageToClass.apply(
|
||||
sym.getQualifiedName().toString(),
|
||||
pkg
|
||||
);
|
||||
} else {
|
||||
return sym.name.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitClassSymbol(ClassSymbol sym, Locale locale) {
|
||||
return sym.name.isEmpty()
|
||||
? sym.flatname.toString() // Anonymous
|
||||
: sym.fullname.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitPackageSymbol(PackageSymbol s, Locale locale) {
|
||||
return s.isUnnamed()
|
||||
? "" // Unnamed package
|
||||
: s.fullname.toString();
|
||||
}
|
||||
|
||||
}
|
||||
493
langtools/src/jdk.jshell/share/classes/jdk/jshell/Unit.java
Normal file
493
langtools/src/jdk.jshell/share/classes/jdk/jshell/Unit.java
Normal file
@ -0,0 +1,493 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jshell;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
import com.sun.jdi.ReferenceType;
|
||||
import jdk.jshell.Snippet.Kind;
|
||||
import jdk.jshell.Snippet.Status;
|
||||
import jdk.jshell.Snippet.SubKind;
|
||||
import jdk.jshell.TaskFactory.AnalyzeTask;
|
||||
import jdk.jshell.ClassTracker.ClassInfo;
|
||||
import jdk.jshell.TaskFactory.CompileTask;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_EVNT;
|
||||
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
|
||||
import static jdk.jshell.Snippet.Status.OVERWRITTEN;
|
||||
import static jdk.jshell.Snippet.Status.RECOVERABLE_DEFINED;
|
||||
import static jdk.jshell.Snippet.Status.RECOVERABLE_NOT_DEFINED;
|
||||
import static jdk.jshell.Snippet.Status.REJECTED;
|
||||
import static jdk.jshell.Snippet.Status.VALID;
|
||||
import static jdk.jshell.Util.expunge;
|
||||
|
||||
/**
|
||||
* Tracks the compilation and load of a new or updated snippet.
|
||||
* @author Robert Field
|
||||
*/
|
||||
final class Unit {
|
||||
|
||||
private final JShell state;
|
||||
private final Snippet si;
|
||||
private final Snippet siOld;
|
||||
private final boolean isDependency;
|
||||
private final boolean isNew;
|
||||
private final Snippet causalSnippet;
|
||||
private final DiagList generatedDiagnostics;
|
||||
|
||||
private int seq;
|
||||
private int seqInitial;
|
||||
private Wrap activeGuts;
|
||||
private Status status;
|
||||
private Status prevStatus;
|
||||
private boolean signatureChanged;
|
||||
private DiagList compilationDiagnostics;
|
||||
private DiagList recompilationDiagnostics = null;
|
||||
private List<String> unresolved;
|
||||
private SnippetEvent replaceOldEvent;
|
||||
private List<SnippetEvent> secondaryEvents;
|
||||
private boolean isAttemptingCorral;
|
||||
private List<ClassInfo> toRedefine;
|
||||
private boolean dependenciesNeeded;
|
||||
|
||||
Unit(JShell state, Snippet si, Snippet causalSnippet,
|
||||
DiagList generatedDiagnostics) {
|
||||
this.state = state;
|
||||
this.si = si;
|
||||
this.isDependency = causalSnippet != null;
|
||||
this.siOld = isDependency
|
||||
? si
|
||||
: state.maps.getSnippet(si.key());
|
||||
this.isNew = siOld == null;
|
||||
this.causalSnippet = causalSnippet;
|
||||
this.generatedDiagnostics = generatedDiagnostics;
|
||||
|
||||
this.seq = isNew? 0 : siOld.sequenceNumber();
|
||||
this.seqInitial = seq;
|
||||
this.prevStatus = (isNew || isDependency)
|
||||
? si.status()
|
||||
: siOld.status();
|
||||
si.setSequenceNumber(seq);
|
||||
}
|
||||
|
||||
// Drop entry
|
||||
Unit(JShell state, Snippet si) {
|
||||
this.state = state;
|
||||
this.si = si;
|
||||
this.siOld = null;
|
||||
this.isDependency = false;
|
||||
this.isNew = false;
|
||||
this.causalSnippet = null;
|
||||
this.generatedDiagnostics = new DiagList();
|
||||
this.prevStatus = si.status();
|
||||
si.setDropped();
|
||||
this.status = si.status();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return si.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return (o instanceof Unit)
|
||||
? si.equals(((Unit) o).si)
|
||||
: false;
|
||||
}
|
||||
|
||||
Snippet snippet() {
|
||||
return si;
|
||||
}
|
||||
|
||||
boolean isDependency() {
|
||||
return isDependency;
|
||||
}
|
||||
|
||||
boolean isNew() {
|
||||
return isNew;
|
||||
}
|
||||
|
||||
boolean isRedundant() {
|
||||
return !isNew && !isDependency() && !si.isExecutable() &&
|
||||
prevStatus.isDefined &&
|
||||
siOld.source().equals(si.source());
|
||||
}
|
||||
|
||||
void initialize(Collection<Unit> working) {
|
||||
isAttemptingCorral = false;
|
||||
dependenciesNeeded = false;
|
||||
toRedefine = null; // assure NPE if classToLoad not called
|
||||
activeGuts = si.guts();
|
||||
markOldDeclarationOverwritten();
|
||||
setWrap(working, working);
|
||||
}
|
||||
|
||||
void setWrap(Collection<Unit> except, Collection<Unit> plus) {
|
||||
si.setOuterWrap(isImport()
|
||||
? OuterWrap.wrapImport(si.source(), activeGuts)
|
||||
: state.eval.wrapInClass(si,
|
||||
except.stream().map(u -> u.snippet().key()).collect(toSet()),
|
||||
activeGuts,
|
||||
plus.stream().map(u -> u.snippet())
|
||||
.filter(sn -> sn != si)
|
||||
.collect(toList())));
|
||||
}
|
||||
|
||||
void setDiagnostics(AnalyzeTask ct) {
|
||||
setDiagnostics(ct.getDiagnostics().ofUnit(this));
|
||||
}
|
||||
|
||||
void setDiagnostics(DiagList diags) {
|
||||
compilationDiagnostics = diags;
|
||||
UnresolvedExtractor ue = new UnresolvedExtractor(diags);
|
||||
unresolved = ue.unresolved();
|
||||
state.debug(DBG_GEN, "++setCompilationInfo() %s\n%s\n-- diags: %s\n",
|
||||
si, si.outerWrap().wrapped(), diags);
|
||||
}
|
||||
|
||||
private boolean isRecoverable() {
|
||||
// Unit failed, use corralling if it is defined on this Snippet,
|
||||
// and either all the errors are resolution errors or this is a
|
||||
// redeclare of an existing method
|
||||
return compilationDiagnostics.hasErrors()
|
||||
&& si instanceof DeclarationSnippet
|
||||
&& (isDependency()
|
||||
|| (si.subKind() != SubKind.VAR_DECLARATION_WITH_INITIALIZER_SUBKIND
|
||||
&& compilationDiagnostics.hasResolutionErrorsAndNoOthers()));
|
||||
}
|
||||
|
||||
/**
|
||||
* If it meets the conditions for corralling, install the corralled wrap
|
||||
* @return true is the corralled wrap was installed
|
||||
*/
|
||||
boolean corralIfNeeded(Collection<Unit> working) {
|
||||
if (isRecoverable()
|
||||
&& si.corralled() != null) {
|
||||
activeGuts = si.corralled();
|
||||
setWrap(working, working);
|
||||
return isAttemptingCorral = true;
|
||||
}
|
||||
return isAttemptingCorral = false;
|
||||
}
|
||||
|
||||
void setCorralledDiagnostics(AnalyzeTask cct) {
|
||||
// set corralled diagnostics, but don't reset unresolved
|
||||
recompilationDiagnostics = cct.getDiagnostics().ofUnit(this);
|
||||
state.debug(DBG_GEN, "++recomp %s\n%s\n-- diags: %s\n",
|
||||
si, si.outerWrap().wrapped(), recompilationDiagnostics);
|
||||
}
|
||||
|
||||
boolean smashingErrorDiagnostics(CompileTask ct) {
|
||||
if (isDefined()) {
|
||||
// set corralled diagnostics, but don't reset unresolved
|
||||
DiagList dl = ct.getDiagnostics().ofUnit(this);
|
||||
if (dl.hasErrors()) {
|
||||
setDiagnostics(dl);
|
||||
status = RECOVERABLE_NOT_DEFINED;
|
||||
// overwrite orginal bytes
|
||||
state.debug(DBG_GEN, "++smashingErrorDiagnostics %s\n%s\n-- diags: %s\n",
|
||||
si, si.outerWrap().wrapped(), dl);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void setStatus() {
|
||||
if (!compilationDiagnostics.hasErrors()) {
|
||||
status = VALID;
|
||||
} else if (isRecoverable()) {
|
||||
if (isAttemptingCorral && !recompilationDiagnostics.hasErrors()) {
|
||||
status = RECOVERABLE_DEFINED;
|
||||
} else {
|
||||
status = RECOVERABLE_NOT_DEFINED;
|
||||
}
|
||||
} else {
|
||||
status = REJECTED;
|
||||
}
|
||||
checkForOverwrite();
|
||||
|
||||
state.debug(DBG_GEN, "setStatus() %s - status: %s\n",
|
||||
si, status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Must be called for each unit
|
||||
* @return
|
||||
*/
|
||||
boolean isDefined() {
|
||||
return status.isDefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the class information from the last compile.
|
||||
* Requires loading of returned list.
|
||||
* @return the list of classes to load
|
||||
*/
|
||||
Stream<ClassInfo> classesToLoad(List<ClassInfo> cil) {
|
||||
toRedefine = new ArrayList<>();
|
||||
List<ClassInfo> toLoad = new ArrayList<>();
|
||||
if (status.isDefined && !isImport()) {
|
||||
cil.stream().forEach(ci -> {
|
||||
if (!ci.isLoaded()) {
|
||||
if (ci.getReferenceTypeOrNull() == null) {
|
||||
toLoad.add(ci);
|
||||
ci.setLoaded();
|
||||
dependenciesNeeded = true;
|
||||
} else {
|
||||
toRedefine.add(ci);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return toLoad.stream();
|
||||
}
|
||||
|
||||
/**
|
||||
* Redefine classes needing redefine.
|
||||
* classesToLoad() must be called first.
|
||||
* @return true if all redefines succeeded (can be vacuously true)
|
||||
*/
|
||||
boolean doRedefines() {
|
||||
if (toRedefine.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
Map<ReferenceType, byte[]> mp = toRedefine.stream()
|
||||
.collect(toMap(ci -> ci.getReferenceTypeOrNull(), ci -> ci.getBytes()));
|
||||
if (state.executionControl().commandRedefine(mp)) {
|
||||
// success, mark as loaded
|
||||
toRedefine.stream().forEach(ci -> ci.setLoaded());
|
||||
return true;
|
||||
} else {
|
||||
// failed to redefine
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void markForReplacement() {
|
||||
// increment for replace class wrapper
|
||||
si.setSequenceNumber(++seq);
|
||||
}
|
||||
|
||||
private boolean isImport() {
|
||||
return si.kind() == Kind.IMPORT;
|
||||
}
|
||||
|
||||
private boolean sigChanged() {
|
||||
return (status.isDefined != prevStatus.isDefined)
|
||||
|| (seq != seqInitial && status.isDefined)
|
||||
|| signatureChanged;
|
||||
}
|
||||
|
||||
Stream<Unit> effectedDependents() {
|
||||
return sigChanged() || dependenciesNeeded || status == RECOVERABLE_NOT_DEFINED
|
||||
? dependents()
|
||||
: Stream.empty();
|
||||
}
|
||||
|
||||
Stream<Unit> dependents() {
|
||||
return state.maps.getDependents(si)
|
||||
.stream()
|
||||
.filter(xsi -> xsi != si && xsi.status().isActive)
|
||||
.map(xsi -> new Unit(state, xsi, si, new DiagList()));
|
||||
}
|
||||
|
||||
void finish() {
|
||||
recordCompilation();
|
||||
state.maps.installSnippet(si);
|
||||
}
|
||||
|
||||
private void markOldDeclarationOverwritten() {
|
||||
if (si != siOld && siOld != null && siOld.status().isActive) {
|
||||
// Mark the old declaraion as replaced
|
||||
replaceOldEvent = new SnippetEvent(siOld,
|
||||
siOld.status(), OVERWRITTEN,
|
||||
false, si, null, null);
|
||||
siOld.setOverwritten();
|
||||
}
|
||||
}
|
||||
|
||||
private DiagList computeDiagnostics() {
|
||||
DiagList diagnostics = new DiagList();
|
||||
DiagList diags = compilationDiagnostics;
|
||||
if (status == RECOVERABLE_DEFINED || status == RECOVERABLE_NOT_DEFINED) {
|
||||
UnresolvedExtractor ue = new UnresolvedExtractor(diags);
|
||||
diagnostics.addAll(ue.otherAll());
|
||||
} else {
|
||||
unresolved = Collections.emptyList();
|
||||
diagnostics.addAll(diags);
|
||||
}
|
||||
diagnostics.addAll(generatedDiagnostics);
|
||||
return diagnostics;
|
||||
}
|
||||
|
||||
private void recordCompilation() {
|
||||
state.maps.mapDependencies(si);
|
||||
DiagList diags = computeDiagnostics();
|
||||
si.setCompilationStatus(status, unresolved, diags);
|
||||
state.debug(DBG_GEN, "recordCompilation: %s -- status %s, unresolved %s\n",
|
||||
si, status, unresolved);
|
||||
}
|
||||
|
||||
private void checkForOverwrite() {
|
||||
secondaryEvents = new ArrayList<>();
|
||||
if (replaceOldEvent != null) secondaryEvents.add(replaceOldEvent);
|
||||
|
||||
// Defined methods can overwrite methods of other (equivalent) snippets
|
||||
if (si.kind() == Kind.METHOD && status.isDefined) {
|
||||
String oqpt = ((MethodSnippet) si).qualifiedParameterTypes();
|
||||
String nqpt = computeQualifiedParameterTypes(si);
|
||||
if (!nqpt.equals(oqpt)) {
|
||||
((MethodSnippet) si).setQualifiedParamaterTypes(nqpt);
|
||||
Status overwrittenStatus = overwriteMatchingMethod(si);
|
||||
if (overwrittenStatus != null) {
|
||||
prevStatus = overwrittenStatus;
|
||||
signatureChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if there is a method whose user-declared parameter types are
|
||||
// different (and thus has a different snippet) but whose compiled parameter
|
||||
// types are the same. if so, consider it an overwrite replacement.
|
||||
private Status overwriteMatchingMethod(Snippet si) {
|
||||
String qpt = ((MethodSnippet) si).qualifiedParameterTypes();
|
||||
|
||||
// Look through all methods for a method of the same name, with the
|
||||
// same computed qualified parameter types
|
||||
Status overwrittenStatus = null;
|
||||
for (MethodSnippet sn : state.methods()) {
|
||||
if (sn != null && sn != si && sn.status().isActive && sn.name().equals(si.name())) {
|
||||
if (qpt.equals(sn.qualifiedParameterTypes())) {
|
||||
overwrittenStatus = sn.status();
|
||||
SnippetEvent se = new SnippetEvent(
|
||||
sn, overwrittenStatus, OVERWRITTEN,
|
||||
false, si, null, null);
|
||||
sn.setOverwritten();
|
||||
secondaryEvents.add(se);
|
||||
state.debug(DBG_EVNT,
|
||||
"Overwrite event #%d -- key: %s before: %s status: %s sig: %b cause: %s\n",
|
||||
secondaryEvents.size(), se.snippet(), se.previousStatus(),
|
||||
se.status(), se.isSignatureChange(), se.causeSnippet());
|
||||
}
|
||||
}
|
||||
}
|
||||
return overwrittenStatus;
|
||||
}
|
||||
|
||||
private String computeQualifiedParameterTypes(Snippet si) {
|
||||
MethodSnippet msi = (MethodSnippet) si;
|
||||
String qpt;
|
||||
AnalyzeTask at = state.taskFactory.new AnalyzeTask(msi.outerWrap());
|
||||
String rawSig = new TreeDissector(at).typeOfMethod();
|
||||
String signature = expunge(rawSig);
|
||||
int paren = signature.lastIndexOf(')');
|
||||
if (paren < 0) {
|
||||
// Uncompilable snippet, punt with user parameter types
|
||||
qpt = msi.parameterTypes();
|
||||
} else {
|
||||
qpt = signature.substring(0, paren + 1);
|
||||
}
|
||||
return qpt;
|
||||
}
|
||||
|
||||
SnippetEvent event(String value, Exception exception) {
|
||||
boolean wasSignatureChanged = sigChanged();
|
||||
state.debug(DBG_EVNT, "Snippet: %s id: %s before: %s status: %s sig: %b cause: %s\n",
|
||||
si, si.id(), prevStatus, si.status(), wasSignatureChanged, causalSnippet);
|
||||
return new SnippetEvent(si, prevStatus, si.status(),
|
||||
wasSignatureChanged, causalSnippet, value, exception);
|
||||
}
|
||||
|
||||
List<SnippetEvent> secondaryEvents() {
|
||||
return secondaryEvents;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Unit(" + si.name() + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
* Separate out the unresolvedDependencies errors from both the other
|
||||
* corralling errors and the overall errors.
|
||||
*/
|
||||
private static class UnresolvedExtractor {
|
||||
|
||||
private static final String RESOLVE_ERROR_SYMBOL = "symbol:";
|
||||
private static final String RESOLVE_ERROR_LOCATION = "location:";
|
||||
|
||||
//TODO extract from tree instead -- note: internationalization
|
||||
private final Set<String> unresolved = new LinkedHashSet<>();
|
||||
private final DiagList otherErrors = new DiagList();
|
||||
private final DiagList otherAll = new DiagList();
|
||||
|
||||
UnresolvedExtractor(DiagList diags) {
|
||||
for (Diag diag : diags) {
|
||||
if (diag.isError()) {
|
||||
if (diag.isResolutionError()) {
|
||||
String m = diag.getMessage(null);
|
||||
int symPos = m.indexOf(RESOLVE_ERROR_SYMBOL);
|
||||
if (symPos >= 0) {
|
||||
m = m.substring(symPos + RESOLVE_ERROR_SYMBOL.length());
|
||||
int symLoc = m.indexOf(RESOLVE_ERROR_LOCATION);
|
||||
if (symLoc >= 0) {
|
||||
m = m.substring(0, symLoc);
|
||||
}
|
||||
m = m.trim();
|
||||
unresolved.add(m);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
otherErrors.add(diag);
|
||||
}
|
||||
otherAll.add(diag);
|
||||
}
|
||||
}
|
||||
|
||||
DiagList otherCorralledErrors() {
|
||||
return otherErrors;
|
||||
}
|
||||
|
||||
DiagList otherAll() {
|
||||
return otherAll;
|
||||
}
|
||||
|
||||
List<String> unresolved() {
|
||||
return new ArrayList<>(unresolved);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jshell;
|
||||
|
||||
/**
|
||||
* Exception reported on attempting to execute a
|
||||
* {@link jdk.jshell.Snippet.Status#RECOVERABLE_DEFINED RECOVERABLE_DEFINED}
|
||||
* method.
|
||||
* <p>
|
||||
* The stack can be queried by methods on <code>Exception</code>.
|
||||
* Note that in stack trace frames representing JShell Snippets,
|
||||
* <code>StackTraceElement.getFileName()</code> will return "#" followed by
|
||||
* the Snippet id and for snippets without a method name (for example an
|
||||
* expression) <code>StackTraceElement.getMethodName()</code> will be the
|
||||
* empty string.
|
||||
*/
|
||||
@SuppressWarnings("serial") // serialVersionUID intentionally omitted
|
||||
public class UnresolvedReferenceException extends Exception {
|
||||
|
||||
final MethodSnippet methodSnippet;
|
||||
|
||||
UnresolvedReferenceException(MethodSnippet methodSnippet, StackTraceElement[] stackElements) {
|
||||
super("Attempt to invoke method with unresolved references");
|
||||
this.methodSnippet = methodSnippet;
|
||||
this.setStackTrace(stackElements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the method Snippet which has the unresolved reference(s).
|
||||
* @return the <code>MethodSnippet</code> of the
|
||||
* {@link jdk.jshell.Snippet.Status#RECOVERABLE_DEFINED RECOVERABLE_DEFINED}
|
||||
* method.
|
||||
*/
|
||||
public MethodSnippet getMethodSnippet() {
|
||||
return methodSnippet;
|
||||
}
|
||||
}
|
||||
94
langtools/src/jdk.jshell/share/classes/jdk/jshell/Util.java
Normal file
94
langtools/src/jdk.jshell/share/classes/jdk/jshell/Util.java
Normal file
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.jshell;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
import javax.lang.model.element.Name;
|
||||
import static jdk.internal.jshell.remote.RemoteCodes.DOIT_METHOD_NAME;
|
||||
import static jdk.internal.jshell.remote.RemoteCodes.prefixPattern;
|
||||
|
||||
/**
|
||||
* Assorted shared utilities.
|
||||
* @author Robert Field
|
||||
*/
|
||||
class Util {
|
||||
|
||||
static final String REPL_CLASS_PREFIX = "$REPL";
|
||||
static final String REPL_DOESNOTMATTER_CLASS_NAME = REPL_CLASS_PREFIX+"00DOESNOTMATTER";
|
||||
|
||||
static boolean isDoIt(Name name) {
|
||||
return isDoIt(name.toString());
|
||||
}
|
||||
|
||||
static boolean isDoIt(String sname) {
|
||||
return sname.equals(DOIT_METHOD_NAME);
|
||||
}
|
||||
|
||||
static String expunge(String s) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (String comp : prefixPattern.split(s)) {
|
||||
sb.append(comp);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
static String asLetters(int i) {
|
||||
if (i == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
char buf[] = new char[33];
|
||||
int charPos = 32;
|
||||
|
||||
i = -i;
|
||||
while (i <= -26) {
|
||||
buf[charPos--] = (char) ('A'-(i % 26));
|
||||
i = i / 26;
|
||||
}
|
||||
buf[charPos] = (char) ('A'-i);
|
||||
|
||||
return new String(buf, charPos, (33 - charPos));
|
||||
}
|
||||
|
||||
|
||||
static String trimEnd(String s) {
|
||||
int last = s.length() - 1;
|
||||
int i = last;
|
||||
while (i >= 0 && Character.isWhitespace(s.charAt(i))) {
|
||||
--i;
|
||||
}
|
||||
if (i != last) {
|
||||
return s.substring(0, i + 1);
|
||||
} else {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
static <T> Stream<T> stream(Iterable<T> iterable) {
|
||||
return StreamSupport.stream(iterable.spliterator(), false);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jshell;
|
||||
|
||||
import java.util.Collection;
|
||||
import jdk.jshell.Key.VarKey;
|
||||
|
||||
/**
|
||||
* Snippet for a variable definition.
|
||||
* The Kind is {@link jdk.jshell.Snippet.Kind#VAR}.
|
||||
* <p>
|
||||
* <code>VarSnippet</code> is immutable: an access to
|
||||
* any of its methods will always return the same result.
|
||||
* and thus is thread-safe.
|
||||
* @jls 8.3: FieldDeclaration.
|
||||
*/
|
||||
public class VarSnippet extends DeclarationSnippet {
|
||||
|
||||
final String typeName;
|
||||
|
||||
VarSnippet(VarKey key, String userSource, Wrap guts,
|
||||
String name, SubKind subkind, String typeName,
|
||||
Collection<String> declareReferences) {
|
||||
super(key, userSource, guts, name, subkind, null, declareReferences, null);
|
||||
this.typeName = typeName;
|
||||
}
|
||||
|
||||
/**
|
||||
* A String representation of the type of the variable.
|
||||
* @return the variable type as a String.
|
||||
*/
|
||||
public String typeName() {
|
||||
return typeName;
|
||||
}
|
||||
}
|
||||
458
langtools/src/jdk.jshell/share/classes/jdk/jshell/Wrap.java
Normal file
458
langtools/src/jdk.jshell/share/classes/jdk/jshell/Wrap.java
Normal file
@ -0,0 +1,458 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.jshell;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import static jdk.internal.jshell.remote.RemoteCodes.DOIT_METHOD_NAME;
|
||||
|
||||
/**
|
||||
* Wrapping of source into Java methods, fields, etc. All but outer layer
|
||||
* wrapping with imports and class.
|
||||
*
|
||||
* @author Robert Field
|
||||
*/
|
||||
abstract class Wrap implements GeneralWrap {
|
||||
|
||||
private static Wrap methodWrap(String prefix, String source, String suffix) {
|
||||
Wrap wunit = new NoWrap(source);
|
||||
return new DoitMethodWrap(new CompoundWrap(prefix, wunit, suffix));
|
||||
}
|
||||
|
||||
public static Wrap methodWrap(String source) {
|
||||
return methodWrap("", source, semi(source) + " return null;\n");
|
||||
}
|
||||
|
||||
public static Wrap methodReturnWrap(String source) {
|
||||
return methodWrap("return ", source, semi(source));
|
||||
}
|
||||
|
||||
public static Wrap methodUnreachableSemiWrap(String source) {
|
||||
return methodWrap("", source, semi(source));
|
||||
}
|
||||
|
||||
public static Wrap methodUnreachableWrap(String source) {
|
||||
return methodWrap("", source, "");
|
||||
}
|
||||
|
||||
public static Wrap corralledMethod(String source, Range modRange, Range tpRange, Range typeRange, String name, Range paramRange, Range throwsRange, int id) {
|
||||
List<Object> l = new ArrayList<>();
|
||||
l.add(" public static\n ");
|
||||
if (!modRange.isEmpty()) {
|
||||
l.add(new RangeWrap(source, modRange));
|
||||
l.add(" ");
|
||||
}
|
||||
if (tpRange != null) {
|
||||
l.add("<");
|
||||
l.add(new RangeWrap(source, tpRange));
|
||||
l.add("> ");
|
||||
}
|
||||
l.add(new RangeWrap(source, typeRange));
|
||||
l.add(" " + name + "(\n ");
|
||||
if (paramRange != null) {
|
||||
l.add(new RangeWrap(source, paramRange));
|
||||
}
|
||||
l.add(") ");
|
||||
if (throwsRange != null) {
|
||||
l.add("throws ");
|
||||
l.add(new RangeWrap(source, throwsRange));
|
||||
}
|
||||
l.add(" {\n throw new jdk.internal.jshell.remote.RemoteResolutionException(" + id + ");\n}\n");
|
||||
return new CompoundWrap(l.toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param in
|
||||
* @param rname
|
||||
* @param rinit Initializer or null
|
||||
* @param rdecl Type name and name
|
||||
* @return
|
||||
*/
|
||||
public static Wrap varWrap(String source, Range rtype, String brackets, Range rname, Range rinit) {
|
||||
RangeWrap wname = new RangeWrap(source, rname);
|
||||
RangeWrap wtype = new RangeWrap(source, rtype);
|
||||
Wrap wVarDecl = new VarDeclareWrap(wtype, brackets, wname);
|
||||
Wrap wmeth;
|
||||
|
||||
if (rinit == null) {
|
||||
wmeth = new CompoundWrap(new NoWrap(" "), " return null;\n");
|
||||
} else {
|
||||
RangeWrap winit = new RangeWrap(source, rinit);
|
||||
// int x = y
|
||||
// int x_ = y; return x = x_;
|
||||
// decl + "_ = " + init ; + "return " + name + "=" + name + "_ ;"
|
||||
wmeth = new CompoundWrap(
|
||||
wtype, brackets + " ", wname, "_ =\n ", winit, semi(winit),
|
||||
" return ", wname, " = ", wname, "_;\n"
|
||||
);
|
||||
}
|
||||
Wrap wInitMeth = new DoitMethodWrap(wmeth);
|
||||
return new CompoundWrap(wVarDecl, wInitMeth);
|
||||
}
|
||||
|
||||
public static Wrap tempVarWrap(String source, String typename, String name) {
|
||||
RangeWrap winit = new NoWrap(source);
|
||||
// y
|
||||
// return $1 = y;
|
||||
// "return " + $1 + "=" + init ;
|
||||
Wrap wmeth = new CompoundWrap("return " + name + " =\n ", winit, semi(winit));
|
||||
Wrap wInitMeth = new DoitMethodWrap(wmeth);
|
||||
|
||||
String varDecl = " public static\n " + typename + " " + name + ";\n";
|
||||
return new CompoundWrap(varDecl, wInitMeth);
|
||||
}
|
||||
|
||||
public static Wrap importWrap(String source) {
|
||||
return new NoWrap(source);
|
||||
}
|
||||
|
||||
public static Wrap classMemberWrap(String source) {
|
||||
Wrap w = new NoWrap(source);
|
||||
return new CompoundWrap(" public static\n ", w);
|
||||
}
|
||||
|
||||
private static int countLines(String s) {
|
||||
return countLines(s, 0, s.length());
|
||||
}
|
||||
|
||||
private static int countLines(String s, int from, int toEx) {
|
||||
int cnt = 0;
|
||||
int idx = from;
|
||||
while ((idx = s.indexOf('\n', idx)) > 0) {
|
||||
if (idx >= toEx) break;
|
||||
++cnt;
|
||||
++idx;
|
||||
}
|
||||
return cnt;
|
||||
}
|
||||
|
||||
public static final class Range {
|
||||
final int begin;
|
||||
final int end;
|
||||
|
||||
Range(int begin, int end) {
|
||||
this.begin = begin;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
Range(String s) {
|
||||
this.begin = 0;
|
||||
this.end = s.length();
|
||||
}
|
||||
|
||||
String part(String s) {
|
||||
return s.substring(begin, end);
|
||||
}
|
||||
|
||||
int length() {
|
||||
return end - begin;
|
||||
}
|
||||
|
||||
boolean isEmpty() {
|
||||
return end == begin;
|
||||
}
|
||||
|
||||
void verify(String s) {
|
||||
if (begin < 0 || end <= begin || end > s.length()) {
|
||||
throw new InternalError("Bad Range: " + s + "[" + begin + "," + end + "]");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Range[" + begin + "," + end + "]";
|
||||
}
|
||||
}
|
||||
|
||||
public static class CompoundWrap extends Wrap {
|
||||
|
||||
final Object[] os;
|
||||
final String wrapped;
|
||||
final int snidxFirst;
|
||||
final int snidxLast;
|
||||
final int snlineFirst;
|
||||
final int snlineLast;
|
||||
|
||||
CompoundWrap(Object... os) {
|
||||
this.os = os;
|
||||
int sniFirst = Integer.MAX_VALUE;
|
||||
int sniLast = Integer.MIN_VALUE;
|
||||
int snlnFirst = Integer.MAX_VALUE;
|
||||
int snlnLast = Integer.MIN_VALUE;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Object o : os) {
|
||||
if (o instanceof String) {
|
||||
String s = (String) o;
|
||||
sb.append(s);
|
||||
} else if (o instanceof Wrap) {
|
||||
Wrap w = (Wrap) o;
|
||||
if (w.firstSnippetIndex() < sniFirst) {
|
||||
sniFirst = w.firstSnippetIndex();
|
||||
}
|
||||
if (w.lastSnippetIndex() > sniLast) {
|
||||
sniLast = w.lastSnippetIndex();
|
||||
}
|
||||
if (w.firstSnippetLine() < snlnFirst) {
|
||||
snlnFirst = w.firstSnippetLine();
|
||||
}
|
||||
if (w.lastSnippetLine() > snlnLast) {
|
||||
snlnLast = w.lastSnippetLine();
|
||||
}
|
||||
sb.append(w.wrapped());
|
||||
} else {
|
||||
throw new InternalError("Bad object in CommoundWrap: " + o);
|
||||
}
|
||||
}
|
||||
this.wrapped = sb.toString();
|
||||
this.snidxFirst = sniFirst;
|
||||
this.snidxLast = sniLast;
|
||||
this.snlineFirst = snlnFirst;
|
||||
this.snlineLast = snlnLast;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String wrapped() {
|
||||
return wrapped;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int snippetIndexToWrapIndex(int sni) {
|
||||
int before = 0;
|
||||
for (Object o : os) {
|
||||
if (o instanceof String) {
|
||||
String s = (String) o;
|
||||
before += s.length();
|
||||
} else if (o instanceof Wrap) {
|
||||
Wrap w = (Wrap) o;
|
||||
if (sni >= w.firstSnippetIndex() && sni <= w.lastSnippetIndex()) {
|
||||
return w.snippetIndexToWrapIndex(sni) + before;
|
||||
}
|
||||
before += w.wrapped().length();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int wrapIndexToSnippetIndex(int wi) {
|
||||
int before = 0;
|
||||
for (Object o : os) {
|
||||
if (o instanceof String) {
|
||||
String s = (String) o;
|
||||
before += s.length();
|
||||
} else if (o instanceof Wrap) {
|
||||
Wrap w = (Wrap) o;
|
||||
int len = w.wrapped().length();
|
||||
if ((wi - before) <= len) {
|
||||
//System.err.printf("Defer to wrap %s - wi: %d. before; %d -- %s >>> %s\n",
|
||||
// w, wi, before, w.debugPos(wi - before), w.wrapped());
|
||||
return w.wrapIndexToSnippetIndex(wi - before);
|
||||
}
|
||||
before += len;
|
||||
}
|
||||
}
|
||||
return lastSnippetIndex();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int firstSnippetIndex() {
|
||||
return snidxFirst;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lastSnippetIndex() {
|
||||
return snidxLast;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int snippetLineToWrapLine(int snline) {
|
||||
int before = 0;
|
||||
for (Object o : os) {
|
||||
if (o instanceof String) {
|
||||
String s = (String) o;
|
||||
before += countLines(s);
|
||||
} else if (o instanceof Wrap) {
|
||||
Wrap w = (Wrap) o;
|
||||
if (snline >= w.firstSnippetLine() && snline <= w.lastSnippetLine()) {
|
||||
return w.snippetLineToWrapLine(snline) + before;
|
||||
}
|
||||
before += countLines(w.wrapped());
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int wrapLineToSnippetLine(int wline) {
|
||||
int before = 0;
|
||||
for (Object o : os) {
|
||||
if (o instanceof String) {
|
||||
String s = (String) o;
|
||||
before += countLines(s);
|
||||
} else if (o instanceof Wrap) {
|
||||
Wrap w = (Wrap) o;
|
||||
int lns = countLines(w.wrapped());
|
||||
if ((wline - before) < lns) {
|
||||
return w.wrapLineToSnippetLine(wline - before);
|
||||
}
|
||||
before += lns;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int firstSnippetLine() {
|
||||
return snlineFirst;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lastSnippetLine() {
|
||||
return snlineLast;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private static class RangeWrap extends Wrap {
|
||||
|
||||
final Range range;
|
||||
final String wrapped;
|
||||
final int firstSnline;
|
||||
final int lastSnline;
|
||||
|
||||
RangeWrap(String snippetSource, Range usedWithinSnippet) {
|
||||
this.range = usedWithinSnippet;
|
||||
this.wrapped = usedWithinSnippet.part(snippetSource);
|
||||
usedWithinSnippet.verify(snippetSource);
|
||||
this.firstSnline = countLines(snippetSource, 0, range.begin);
|
||||
this.lastSnline = firstSnline + countLines(snippetSource, range.begin, range.end);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String wrapped() {
|
||||
return wrapped;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int snippetIndexToWrapIndex(int sni) {
|
||||
if (sni < range.begin) {
|
||||
return 0;
|
||||
}
|
||||
if (sni > range.end) {
|
||||
return range.length();
|
||||
}
|
||||
return sni - range.begin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int wrapIndexToSnippetIndex(int wi) {
|
||||
if (wi < 0) {
|
||||
return 0; // bad index
|
||||
}
|
||||
int max = range.length();
|
||||
if (wi > max) {
|
||||
wi = max;
|
||||
}
|
||||
return wi + range.begin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int firstSnippetIndex() {
|
||||
return range.begin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lastSnippetIndex() {
|
||||
return range.end;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int snippetLineToWrapLine(int snline) {
|
||||
if (snline < firstSnline) {
|
||||
return 0;
|
||||
}
|
||||
if (snline >= lastSnline) {
|
||||
return lastSnline - firstSnline;
|
||||
}
|
||||
return snline - firstSnline;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int wrapLineToSnippetLine(int wline) {
|
||||
if (wline < 0) {
|
||||
return 0; // bad index
|
||||
}
|
||||
int max = lastSnline - firstSnline;
|
||||
if (wline > max) {
|
||||
wline = max;
|
||||
}
|
||||
return wline + firstSnline;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int firstSnippetLine() {
|
||||
return firstSnline;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lastSnippetLine() {
|
||||
return lastSnline;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class NoWrap extends RangeWrap {
|
||||
|
||||
NoWrap(String unit) {
|
||||
super(unit, new Range(unit));
|
||||
}
|
||||
}
|
||||
|
||||
private static String semi(Wrap w) {
|
||||
return semi(w.wrapped());
|
||||
}
|
||||
|
||||
private static String semi(String s) {
|
||||
return ((s.endsWith(";")) ? "\n" : ((s.endsWith(";\n")) ? "" : ";\n"));
|
||||
}
|
||||
|
||||
private static class DoitMethodWrap extends CompoundWrap {
|
||||
|
||||
DoitMethodWrap(Wrap w) {
|
||||
super(" public static Object " + DOIT_METHOD_NAME + "() throws Throwable {\n"
|
||||
+ " ", w,
|
||||
" }\n");
|
||||
}
|
||||
}
|
||||
|
||||
private static class VarDeclareWrap extends CompoundWrap {
|
||||
|
||||
VarDeclareWrap(Wrap wtype, String brackets, Wrap wname) {
|
||||
super(" public static ", wtype, brackets + " ", wname, semi(wname));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides interfaces for creating tools, such as a Read-Eval-Print Loop (REPL),
|
||||
* which interactively evaluate "snippets" of Java programming language code.
|
||||
* Where a "snippet" is a single expression, statement, or declaration.
|
||||
* This functionality can be used to enhance tools such as IDEs or can be
|
||||
* stand-alone.
|
||||
* <p>
|
||||
* {@link jdk.jshell.JShell} is the central class. An instance of
|
||||
* <code>JShell</code> holds the evaluation state, which is both the current
|
||||
* set of source snippets and the execution state they have produced.
|
||||
* <p>
|
||||
* Each source snippet is represented by an instance of a subclass of
|
||||
* {@link jdk.jshell.Snippet}. For example, a statement is represented by an
|
||||
* instance of {@link jdk.jshell.StatementSnippet}, and a method declaration is
|
||||
* represented by an instance of {@link jdk.jshell.MethodSnippet}.
|
||||
* Snippets are created when {@link jdk.jshell.JShell#eval(java.lang.String)}
|
||||
* is invoked with an input which includes one or more snippets of code.
|
||||
* <p>
|
||||
* Any change to the compilation status of a snippet is reported with a
|
||||
* {@link jdk.jshell.SnippetEvent}. There are three major kinds of
|
||||
* changes to the status of a snippet: it can created with <code>eval</code>,
|
||||
* it can be dropped from the active source state with
|
||||
* {@link jdk.jshell.JShell#drop(jdk.jshell.PersistentSnippet)}, and it can have
|
||||
* its status updated as a result of a status change in another snippet.
|
||||
* For
|
||||
* example: given <code>js</code>, an instance of <code>JShell</code>, executing
|
||||
* <code>js.eval("int x = 5;")</code> will add the variable <code>x</code> to
|
||||
* the source state and will generate an event describing the creation of a
|
||||
* {@link jdk.jshell.VarSnippet} for <code>x</code>. Then executing
|
||||
* <code>js.eval("int timesx(int val) { return val * x; }")</code> will add
|
||||
* a method to the source state and will generate an event
|
||||
* describing the creation of a {@link jdk.jshell.MethodSnippet} for
|
||||
* <code>timesx</code>.
|
||||
* Assume that <code>varx</code> holds the snippet created by the first
|
||||
* call to <code>eval</code>, executing <code>js.drop(varx)</code> will
|
||||
* generate two events: one for changing the status of the
|
||||
* variable snippet to <code>DROPPED</code> and one for
|
||||
* updating the method snippet (which now has an unresolved reference to
|
||||
* <code>x</code>).
|
||||
* <p>
|
||||
* Of course, for any general application of the API, the input would not be
|
||||
* fixed strings, but would come from the user. Below is a very simplified
|
||||
* example of how the API might be used to implement a REPL.
|
||||
* <pre>
|
||||
* {@code
|
||||
* import java.io.ByteArrayInputStream;
|
||||
* import java.io.Console;
|
||||
* import java.util.List;
|
||||
* import jdk.jshell.*;
|
||||
* import jdk.jshell.Snippet.Status;
|
||||
*
|
||||
* class ExampleJShell {
|
||||
* public static void main(String[] args) {
|
||||
* Console console = System.console();
|
||||
* try (JShell js = JShell.create()) {
|
||||
* do {
|
||||
* System.out.print("Enter some Java code: ");
|
||||
* String input = console.readLine();
|
||||
* if (input == null) {
|
||||
* break;
|
||||
* }
|
||||
* List<SnippetEvent> events = js.eval(input);
|
||||
* for (SnippetEvent e : events) {
|
||||
* StringBuilder sb = new StringBuilder();
|
||||
* if (e.causeSnippet == null) {
|
||||
* // We have a snippet creation event
|
||||
* switch (e.status) {
|
||||
* case VALID:
|
||||
* sb.append("Successful ");
|
||||
* break;
|
||||
* case RECOVERABLE_DEFINED:
|
||||
* sb.append("With unresolved references ");
|
||||
* break;
|
||||
* case RECOVERABLE_NOT_DEFINED:
|
||||
* sb.append("Possibly reparable, failed ");
|
||||
* break;
|
||||
* case REJECTED:
|
||||
* sb.append("Failed ");
|
||||
* break;
|
||||
* }
|
||||
* if (e.previousStatus == Status.NONEXISTENT) {
|
||||
* sb.append("addition");
|
||||
* } else {
|
||||
* sb.append("modification");
|
||||
* }
|
||||
* sb.append(" of ");
|
||||
* sb.append(e.snippet.source());
|
||||
* System.out.println(sb);
|
||||
* if (e.value != null) {
|
||||
* System.out.printf("Value is: %s\n", e.value);
|
||||
* }
|
||||
* System.out.flush();
|
||||
* }
|
||||
* }
|
||||
* } while (true);
|
||||
* }
|
||||
* System.out.println("\nGoodbye");
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
* <p>
|
||||
* To register for status change events use
|
||||
* {@link jdk.jshell.JShell#onSnippetEvent(java.util.function.Consumer)}.
|
||||
* These events are only generated by <code>eval</code> and <code>drop</code>,
|
||||
* the return values of these methods are the list of events generated by that
|
||||
* call. So, as in the example above, events can be used without registering
|
||||
* to receive events.
|
||||
* <p>
|
||||
* If you experiment with this example, you will see that failing to terminate
|
||||
* a statement or variable declaration with a semi-colon will simply fail.
|
||||
* An unfinished entry (for example a desired multi-line method) will also just
|
||||
* fail after one line. The utilities in {@link jdk.jshell.SourceCodeAnalysis}
|
||||
* provide source boundary and completeness analysis to address cases like
|
||||
* those. <code>SourceCodeAnalysis</code> also provides suggested completions
|
||||
* of input, as might be used in tab-completion.
|
||||
*/
|
||||
|
||||
|
||||
package jdk.jshell;
|
||||
|
||||
@ -51,7 +51,8 @@ public class TestSimpleTag extends JavadocTester {
|
||||
"-tag", "regular:a:Regular Tag:",
|
||||
"-tag", "back-slash\\:tag\\\\:a:Back-Slash-Tag:",
|
||||
testSrc("C.java"));
|
||||
checkExit(Exit.FAILED); // TODO: investigate why failed
|
||||
// doclint fails because '\' is not allowed in tag name
|
||||
checkExit(Exit.FAILED);
|
||||
|
||||
checkOutput("C.html", true,
|
||||
"<span class=\"simpleTagLabel\">Todo:</span>",
|
||||
|
||||
65
langtools/test/jdk/jshell/AnalysisTest.java
Normal file
65
langtools/test/jdk/jshell/AnalysisTest.java
Normal file
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Test SourceCodeAnalysis
|
||||
* @build KullaTesting TestingInputStream
|
||||
* @run testng AnalysisTest
|
||||
*/
|
||||
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
@Test
|
||||
public class AnalysisTest extends KullaTesting {
|
||||
|
||||
public void testSource() {
|
||||
assertAnalyze("int x=3//test", "int x=3;//test", "", true);
|
||||
assertAnalyze("int x=3 ;//test", "int x=3 ;//test", "", true);
|
||||
assertAnalyze("int x=3 //;", "int x=3; //;", "", true);
|
||||
assertAnalyze("void m5() {} /// hgjghj", "void m5() {} /// hgjghj", "", true);
|
||||
assertAnalyze("int ff; int v // hi", "int ff;", " int v // hi", true);
|
||||
}
|
||||
|
||||
public void testSourceSlashStar() {
|
||||
assertAnalyze("/*zoo*/int x=3 /*test*/", "/*zoo*/int x=3; /*test*/", "", true);
|
||||
assertAnalyze("/*zoo*/int x=3 ;/*test*/", "/*zoo*/int x=3 ;/*test*/", "", true);
|
||||
assertAnalyze("int x=3 /*;*/", "int x=3; /*;*/", "", true);
|
||||
assertAnalyze("void m5() {} /*hgjghj*/", "void m5() {} /*hgjghj*/", "", true);
|
||||
assertAnalyze("int ff; int v /*hgjghj*/", "int ff;", " int v /*hgjghj*/", true);
|
||||
}
|
||||
|
||||
public void testIncomplete() {
|
||||
assertAnalyze("void m() { //erer", null, "void m() { //erer\n", false);
|
||||
assertAnalyze("int m=//", null, "int m=//\n", false);
|
||||
}
|
||||
|
||||
public void testExpression() {
|
||||
assertAnalyze("45//test", "45//test", "", true);
|
||||
assertAnalyze("45;//test", "45;//test", "", true);
|
||||
assertAnalyze("45//;", "45//;", "", true);
|
||||
assertAnalyze("/*zoo*/45/*test*/", "/*zoo*/45/*test*/", "", true);
|
||||
assertAnalyze("/*zoo*/45;/*test*/", "/*zoo*/45;/*test*/", "", true);
|
||||
assertAnalyze("45/*;*/", "45/*;*/", "", true);
|
||||
}
|
||||
}
|
||||
355
langtools/test/jdk/jshell/ClassMembersTest.java
Normal file
355
langtools/test/jdk/jshell/ClassMembersTest.java
Normal file
@ -0,0 +1,355 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Test access to members of user defined class.
|
||||
* @build KullaTesting TestingInputStream ExpectedDiagnostic
|
||||
* @run testng/timeout=600 ClassMembersTest
|
||||
*/
|
||||
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.tools.Diagnostic;
|
||||
|
||||
import jdk.jshell.SourceCodeAnalysis;
|
||||
import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class ClassMembersTest extends KullaTesting {
|
||||
|
||||
@Test(dataProvider = "memberTestCase")
|
||||
public void memberTest(AccessModifier accessModifier, CodeChunk codeChunk, Static isStaticMember, Static isStaticReference) {
|
||||
MemberTestCase testCase = new MemberTestCase(accessModifier, codeChunk, isStaticMember, isStaticReference);
|
||||
assertEval(testCase.generateSource());
|
||||
String expectedMessage = testCase.expectedMessage;
|
||||
if (testCase.codeChunk != CodeChunk.CONSTRUCTOR || testCase.isAccessible()) {
|
||||
assertEval("A a = new A();");
|
||||
}
|
||||
if (expectedMessage == null) {
|
||||
assertEval(testCase.useCodeChunk());
|
||||
} else {
|
||||
assertDeclareFail(testCase.useCodeChunk(), expectedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> parseCode(String input) {
|
||||
List<String> list = new ArrayList<>();
|
||||
SourceCodeAnalysis codeAnalysis = getAnalysis();
|
||||
String source = input;
|
||||
while (!source.trim().isEmpty()) {
|
||||
SourceCodeAnalysis.CompletionInfo info = codeAnalysis.analyzeCompletion(source);
|
||||
list.add(info.source);
|
||||
source = info.remaining;
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Test(dataProvider = "memberTestCase")
|
||||
public void extendsMemberTest(AccessModifier accessModifier, CodeChunk codeChunk, Static isStaticMember, Static isStaticReference) {
|
||||
MemberTestCase testCase = new ExtendsMemberTestCase(accessModifier, codeChunk, isStaticMember, isStaticReference);
|
||||
String input = testCase.generateSource();
|
||||
List<String> ss = parseCode(input);
|
||||
assertEval(ss.get(0));
|
||||
if (testCase.codeChunk != CodeChunk.CONSTRUCTOR || testCase.isAccessible()) {
|
||||
assertEval(ss.get(1));
|
||||
assertEval("B b = new B();");
|
||||
}
|
||||
String expectedMessage = testCase.expectedMessage;
|
||||
if (expectedMessage == null) {
|
||||
assertEval(testCase.useCodeChunk());
|
||||
} else {
|
||||
assertDeclareFail(testCase.useCodeChunk(), expectedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void interfaceTest() {
|
||||
String interfaceSource =
|
||||
"interface A {\n" +
|
||||
" default int defaultMethod() { return 1; }\n" +
|
||||
" static int staticMethod() { return 2; }\n" +
|
||||
" int method();\n" +
|
||||
" class Inner1 {}\n" +
|
||||
" static class Inner2 {}\n" +
|
||||
"}";
|
||||
assertEval(interfaceSource);
|
||||
assertEval("A.staticMethod();", "2");
|
||||
String classSource =
|
||||
"class B implements A {\n" +
|
||||
" public int method() { return 3; }\n" +
|
||||
"}";
|
||||
assertEval(classSource);
|
||||
assertEval("B b = new B();");
|
||||
assertEval("b.defaultMethod();", "1");
|
||||
assertDeclareFail("B.staticMethod();",
|
||||
new ExpectedDiagnostic("compiler.err.cant.resolve.location.args", 0, 14, 1, -1, -1, Diagnostic.Kind.ERROR));
|
||||
assertEval("b.method();", "3");
|
||||
assertEval("new A.Inner1();");
|
||||
assertEval("new A.Inner2();");
|
||||
assertEval("new B.Inner1();");
|
||||
assertEval("new B.Inner2();");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enumTest() {
|
||||
String enumSource =
|
||||
"enum E {A(\"s\");\n" +
|
||||
" private final String s;\n" +
|
||||
" private E(String s) { this.s = s; }\n" +
|
||||
" public String method() { return s; }\n" +
|
||||
" private String privateMethod() { return s; }\n" +
|
||||
" public static String staticMethod() { return staticPrivateMethod(); }\n" +
|
||||
" private static String staticPrivateMethod() { return \"a\"; }\n" +
|
||||
"}";
|
||||
assertEval(enumSource);
|
||||
assertEval("E a = E.A;", "A");
|
||||
assertDeclareFail("a.s;",
|
||||
new ExpectedDiagnostic("compiler.err.report.access", 0, 3, 1, -1, -1, Diagnostic.Kind.ERROR));
|
||||
assertDeclareFail("new E(\"q\");",
|
||||
new ExpectedDiagnostic("compiler.err.enum.cant.be.instantiated", 0, 10, 0, -1, -1, Diagnostic.Kind.ERROR));
|
||||
assertEval("a.method();", "\"s\"");
|
||||
assertDeclareFail("a.privateMethod();",
|
||||
new ExpectedDiagnostic("compiler.err.report.access", 0, 15, 1, -1, -1, Diagnostic.Kind.ERROR));
|
||||
assertEval("E.staticMethod();", "\"a\"");
|
||||
assertDeclareFail("a.staticPrivateMethod();",
|
||||
new ExpectedDiagnostic("compiler.err.report.access", 0, 21, 1, -1, -1, Diagnostic.Kind.ERROR));
|
||||
assertDeclareFail("E.method();",
|
||||
new ExpectedDiagnostic("compiler.err.non-static.cant.be.ref", 0, 8, 1, -1, -1, Diagnostic.Kind.ERROR));
|
||||
}
|
||||
|
||||
@Test(enabled = false) // TODO 8080354
|
||||
public void annotationTest() {
|
||||
assertEval("import java.lang.annotation.*;");
|
||||
for (RetentionPolicy policy : RetentionPolicy.values()) {
|
||||
String annotationSource =
|
||||
"@Retention(RetentionPolicy." + policy.toString() + ")\n" +
|
||||
"@interface A {}";
|
||||
assertEval(annotationSource);
|
||||
String classSource =
|
||||
"@A class C {\n" +
|
||||
" @A C() {}\n" +
|
||||
" @A void f() {}\n" +
|
||||
" @A int f;\n" +
|
||||
" @A class Inner {}\n" +
|
||||
"}";
|
||||
assertEval(classSource);
|
||||
String isRuntimeVisible = policy == RetentionPolicy.RUNTIME ? "true" : "false";
|
||||
assertEval("C.class.getAnnotationsByType(A.class).length > 0;", isRuntimeVisible);
|
||||
assertEval("C.class.getDeclaredConstructor().getAnnotationsByType(A.class).length > 0;", isRuntimeVisible);
|
||||
assertEval("C.class.getDeclaredMethod(\"f\").getAnnotationsByType(A.class).length > 0;", isRuntimeVisible);
|
||||
assertEval("C.class.getDeclaredField(\"f\").getAnnotationsByType(A.class).length > 0;", isRuntimeVisible);
|
||||
assertEval("C.Inner.class.getAnnotationsByType(A.class).length > 0;", isRuntimeVisible);
|
||||
}
|
||||
}
|
||||
|
||||
@DataProvider(name = "memberTestCase")
|
||||
public Object[][] memberTestCaseGenerator() {
|
||||
List<Object[]> list = new ArrayList<>();
|
||||
for (AccessModifier accessModifier : AccessModifier.values()) {
|
||||
for (Static isStaticMember : Static.values()) {
|
||||
for (Static isStaticReference : Static.values()) {
|
||||
for (CodeChunk codeChunk : CodeChunk.values()) {
|
||||
if (codeChunk == CodeChunk.CONSTRUCTOR && isStaticMember == Static.STATIC) {
|
||||
continue;
|
||||
}
|
||||
list.add(new Object[]{ accessModifier, codeChunk, isStaticMember, isStaticReference });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return list.toArray(new Object[list.size()][]);
|
||||
}
|
||||
|
||||
public static class ExtendsMemberTestCase extends MemberTestCase {
|
||||
|
||||
public ExtendsMemberTestCase(AccessModifier accessModifier, CodeChunk codeChunk, Static isStaticMember, Static isStaticReference) {
|
||||
super(accessModifier, codeChunk, isStaticMember, isStaticReference);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSourceTemplate() {
|
||||
return super.getSourceTemplate() + "\n"
|
||||
+ "class B extends A {}";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String errorMessage() {
|
||||
if (!isAccessible()) {
|
||||
if (codeChunk == CodeChunk.METHOD) {
|
||||
return "compiler.err.cant.resolve.location.args";
|
||||
}
|
||||
if (codeChunk == CodeChunk.CONSTRUCTOR) {
|
||||
return "compiler.err.cant.resolve.location";
|
||||
}
|
||||
}
|
||||
return super.errorMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String useCodeChunk() {
|
||||
return useCodeChunk("B");
|
||||
}
|
||||
}
|
||||
|
||||
public static class MemberTestCase {
|
||||
public final AccessModifier accessModifier;
|
||||
public final CodeChunk codeChunk;
|
||||
public final Static isStaticMember;
|
||||
public final Static isStaticReference;
|
||||
public final String expectedMessage;
|
||||
|
||||
public MemberTestCase(AccessModifier accessModifier, CodeChunk codeChunk, Static isStaticMember,
|
||||
Static isStaticReference) {
|
||||
this.accessModifier = accessModifier;
|
||||
this.codeChunk = codeChunk;
|
||||
this.isStaticMember = isStaticMember;
|
||||
this.isStaticReference = isStaticReference;
|
||||
this.expectedMessage = errorMessage();
|
||||
}
|
||||
|
||||
public String getSourceTemplate() {
|
||||
return "class A {\n" +
|
||||
" #MEMBER#\n" +
|
||||
"}";
|
||||
}
|
||||
|
||||
public boolean isAccessible() {
|
||||
return accessModifier != AccessModifier.PRIVATE;
|
||||
}
|
||||
|
||||
public String errorMessage() {
|
||||
if (!isAccessible()) {
|
||||
return "compiler.err.report.access";
|
||||
}
|
||||
if (codeChunk == CodeChunk.INNER_INTERFACE) {
|
||||
return "compiler.err.abstract.cant.be.instantiated";
|
||||
}
|
||||
if (isStaticMember == Static.STATIC) {
|
||||
if (isStaticReference == Static.NO && codeChunk == CodeChunk.INNER_CLASS) {
|
||||
return "compiler.err.qualified.new.of.static.class";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (isStaticReference == Static.STATIC) {
|
||||
if (codeChunk == CodeChunk.CONSTRUCTOR) {
|
||||
return null;
|
||||
}
|
||||
if (codeChunk == CodeChunk.INNER_CLASS) {
|
||||
return "compiler.err.encl.class.required";
|
||||
}
|
||||
return "compiler.err.non-static.cant.be.ref";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String generateSource() {
|
||||
return getSourceTemplate().replace("#MEMBER#", codeChunk.generateSource(accessModifier, isStaticMember));
|
||||
}
|
||||
|
||||
protected String useCodeChunk(String className) {
|
||||
String name = className.toLowerCase();
|
||||
switch (codeChunk) {
|
||||
case CONSTRUCTOR:
|
||||
return String.format("new %s();", className);
|
||||
case METHOD:
|
||||
if (isStaticReference == Static.STATIC) {
|
||||
return String.format("%s.method();", className);
|
||||
} else {
|
||||
return String.format("%s.method();", name);
|
||||
}
|
||||
case FIELD:
|
||||
if (isStaticReference == Static.STATIC) {
|
||||
return String.format("%s.field;", className);
|
||||
} else {
|
||||
return String.format("%s.field;", name);
|
||||
}
|
||||
case INNER_CLASS:
|
||||
if (isStaticReference == Static.STATIC) {
|
||||
return String.format("new %s.Inner();", className);
|
||||
} else {
|
||||
return String.format("%s.new Inner();", name);
|
||||
}
|
||||
case INNER_INTERFACE:
|
||||
return String.format("new %s.Inner();", className);
|
||||
default:
|
||||
throw new AssertionError("Unknown code chunk: " + this);
|
||||
}
|
||||
}
|
||||
|
||||
public String useCodeChunk() {
|
||||
return useCodeChunk("A");
|
||||
}
|
||||
}
|
||||
|
||||
public enum AccessModifier {
|
||||
PUBLIC("public"),
|
||||
PROTECTED("protected"),
|
||||
PACKAGE_PRIVATE(""),
|
||||
PRIVATE("private");
|
||||
|
||||
private final String modifier;
|
||||
|
||||
AccessModifier(String modifier) {
|
||||
this.modifier = modifier;
|
||||
}
|
||||
|
||||
public String getModifier() {
|
||||
return modifier;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Static {
|
||||
STATIC("static"), NO("");
|
||||
|
||||
private final String modifier;
|
||||
|
||||
Static(String modifier) {
|
||||
this.modifier = modifier;
|
||||
}
|
||||
|
||||
public String getModifier() {
|
||||
return modifier;
|
||||
}
|
||||
}
|
||||
|
||||
public enum CodeChunk {
|
||||
CONSTRUCTOR("#MODIFIER# A() {}"),
|
||||
METHOD("#MODIFIER# int method() { return 10; }"),
|
||||
FIELD("#MODIFIER# int field = 10;"),
|
||||
INNER_CLASS("#MODIFIER# class Inner {}"),
|
||||
INNER_INTERFACE("#MODIFIER# interface Inner {}");
|
||||
|
||||
private final String code;
|
||||
|
||||
CodeChunk(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String generateSource(AccessModifier accessModifier, Static isStatic) {
|
||||
return code.replace("#MODIFIER#", accessModifier.getModifier() + " " + isStatic.getModifier());
|
||||
}
|
||||
}
|
||||
}
|
||||
114
langtools/test/jdk/jshell/ClassPathTest.java
Normal file
114
langtools/test/jdk/jshell/ClassPathTest.java
Normal file
@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Tests for EvalState#addToClasspath
|
||||
* @library /tools/lib
|
||||
* @build KullaTesting TestingInputStream ToolBox Compiler
|
||||
* @run testng ClassPathTest
|
||||
*/
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
@Test
|
||||
public class ClassPathTest extends KullaTesting {
|
||||
|
||||
private final Compiler compiler = new Compiler();
|
||||
private final Path outDir = Paths.get("class_path_test");
|
||||
|
||||
public void testDirectory() {
|
||||
compiler.compile(outDir, "package pkg; public class TestDirectory { }");
|
||||
assertDeclareFail("import pkg.TestDirectory;", "compiler.err.doesnt.exist");
|
||||
assertDeclareFail("new pkg.TestDirectory();", "compiler.err.doesnt.exist");
|
||||
addToClasspath(compiler.getPath(outDir));
|
||||
assertEval("new pkg.TestDirectory();");
|
||||
}
|
||||
|
||||
public void testJar() {
|
||||
compiler.compile(outDir, "package pkg; public class TestJar { }");
|
||||
String jarName = "test.jar";
|
||||
compiler.jar(outDir, jarName, "pkg/TestJar.class");
|
||||
assertDeclareFail("import pkg.TestJar;", "compiler.err.doesnt.exist");
|
||||
assertDeclareFail("new pkg.TestJar();", "compiler.err.doesnt.exist");
|
||||
addToClasspath(compiler.getPath(outDir).resolve(jarName));
|
||||
assertEval("new pkg.TestJar();");
|
||||
}
|
||||
|
||||
public void testAmbiguousDirectory() {
|
||||
Path p1 = outDir.resolve("dir1");
|
||||
compiler.compile(p1,
|
||||
"package p; public class TestAmbiguous {\n" +
|
||||
" public String toString() {\n" +
|
||||
" return \"first\";" +
|
||||
" }\n" +
|
||||
"}");
|
||||
addToClasspath(compiler.getPath(p1));
|
||||
Path p2 = outDir.resolve("dir2");
|
||||
compiler.compile(p2,
|
||||
"package p; public class TestAmbiguous {\n" +
|
||||
" public String toString() {\n" +
|
||||
" return \"second\";" +
|
||||
" }\n" +
|
||||
"}");
|
||||
addToClasspath(compiler.getPath(p2));
|
||||
assertEval("new p.TestAmbiguous();", "first");
|
||||
}
|
||||
|
||||
public void testAmbiguousJar() {
|
||||
Path p1 = outDir.resolve("dir1");
|
||||
compiler.compile(p1,
|
||||
"package p; public class TestAmbiguous {\n" +
|
||||
" public String toString() {\n" +
|
||||
" return \"first\";" +
|
||||
" }\n" +
|
||||
"}");
|
||||
String jarName = "test.jar";
|
||||
compiler.jar(p1, jarName, "p/TestAmbiguous.class");
|
||||
addToClasspath(compiler.getPath(p1.resolve(jarName)));
|
||||
Path p2 = outDir.resolve("dir2");
|
||||
compiler.compile(p2,
|
||||
"package p; public class TestAmbiguous {\n" +
|
||||
" public String toString() {\n" +
|
||||
" return \"second\";" +
|
||||
" }\n" +
|
||||
"}");
|
||||
addToClasspath(compiler.getPath(p2));
|
||||
assertEval("new p.TestAmbiguous();", "first");
|
||||
}
|
||||
|
||||
public void testEmptyClassPath() {
|
||||
addToClasspath("");
|
||||
assertEval("new java.util.ArrayList<String>();");
|
||||
}
|
||||
|
||||
public void testUnknown() {
|
||||
addToClasspath(compiler.getPath(outDir.resolve("UNKNOWN")));
|
||||
assertDeclareFail("new Unknown();", "compiler.err.cant.resolve.location");
|
||||
addToClasspath(compiler.getPath(outDir.resolve("UNKNOWN.jar")));
|
||||
assertDeclareFail("new Unknown();", "compiler.err.cant.resolve.location");
|
||||
}
|
||||
}
|
||||
289
langtools/test/jdk/jshell/ClassesTest.java
Normal file
289
langtools/test/jdk/jshell/ClassesTest.java
Normal file
@ -0,0 +1,289 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Tests for EvaluationState.classes
|
||||
* @build KullaTesting TestingInputStream ExpectedDiagnostic
|
||||
* @run testng ClassesTest
|
||||
*/
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.tools.Diagnostic;
|
||||
|
||||
import jdk.jshell.Snippet;
|
||||
import jdk.jshell.TypeDeclSnippet;
|
||||
import jdk.jshell.VarSnippet;
|
||||
import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import jdk.jshell.Diag;
|
||||
import static jdk.jshell.Snippet.Status.VALID;
|
||||
import static jdk.jshell.Snippet.Status.RECOVERABLE_NOT_DEFINED;
|
||||
import static jdk.jshell.Snippet.Status.DROPPED;
|
||||
import static jdk.jshell.Snippet.Status.REJECTED;
|
||||
import static jdk.jshell.Snippet.SubKind.*;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
import static org.testng.Assert.assertTrue;
|
||||
import static jdk.jshell.Snippet.Status.OVERWRITTEN;
|
||||
|
||||
@Test
|
||||
public class ClassesTest extends KullaTesting {
|
||||
|
||||
public void noClasses() {
|
||||
assertNumberOfActiveClasses(0);
|
||||
}
|
||||
|
||||
public void testSignature1() {
|
||||
TypeDeclSnippet c1 = classKey(assertEval("class A extends B {}", added(RECOVERABLE_NOT_DEFINED)));
|
||||
assertTypeDeclSnippet(c1, "A", RECOVERABLE_NOT_DEFINED, CLASS_SUBKIND, 1, 0);
|
||||
TypeDeclSnippet c2 = classKey(assertEval("@interface A { Class<B> f() default B.class; }",
|
||||
ste(MAIN_SNIPPET, RECOVERABLE_NOT_DEFINED, RECOVERABLE_NOT_DEFINED, false, null),
|
||||
ste(c1, RECOVERABLE_NOT_DEFINED, OVERWRITTEN, false, MAIN_SNIPPET)));
|
||||
assertTypeDeclSnippet(c2, "A", RECOVERABLE_NOT_DEFINED, ANNOTATION_TYPE_SUBKIND, 1, 0);
|
||||
TypeDeclSnippet c3 = classKey(assertEval("enum A {; private A(B b) {} }",
|
||||
ste(MAIN_SNIPPET, RECOVERABLE_NOT_DEFINED, RECOVERABLE_NOT_DEFINED, false, null),
|
||||
ste(c2, RECOVERABLE_NOT_DEFINED, OVERWRITTEN, false, MAIN_SNIPPET)));
|
||||
assertTypeDeclSnippet(c3, "A", RECOVERABLE_NOT_DEFINED, ENUM_SUBKIND, 1, 0);
|
||||
TypeDeclSnippet c4 = classKey(assertEval("interface A extends B {}",
|
||||
ste(MAIN_SNIPPET, RECOVERABLE_NOT_DEFINED, RECOVERABLE_NOT_DEFINED, false, null),
|
||||
ste(c3, RECOVERABLE_NOT_DEFINED, OVERWRITTEN, false, MAIN_SNIPPET)));
|
||||
assertTypeDeclSnippet(c4, "A", RECOVERABLE_NOT_DEFINED, INTERFACE_SUBKIND, 1, 0);
|
||||
TypeDeclSnippet c5 = classKey(assertEval("class A { void f(B b) {} }",
|
||||
ste(MAIN_SNIPPET, RECOVERABLE_NOT_DEFINED, RECOVERABLE_NOT_DEFINED, false, null),
|
||||
ste(c4, RECOVERABLE_NOT_DEFINED, OVERWRITTEN, false, MAIN_SNIPPET)));
|
||||
assertTypeDeclSnippet(c5, "A", RECOVERABLE_NOT_DEFINED, CLASS_SUBKIND, 1, 0);
|
||||
}
|
||||
|
||||
public void testSignature2() {
|
||||
TypeDeclSnippet c1 = (TypeDeclSnippet) assertDeclareFail("class A { void f() { return g(); } }", "compiler.err.prob.found.req");
|
||||
assertTypeDeclSnippet(c1, "A", REJECTED, CLASS_SUBKIND, 0, 2);
|
||||
TypeDeclSnippet c2 = classKey(assertEval("class A { int f() { return g(); } }",
|
||||
ste(c1, REJECTED, RECOVERABLE_NOT_DEFINED, false, null)));
|
||||
assertTypeDeclSnippet(c2, "A", RECOVERABLE_NOT_DEFINED, CLASS_SUBKIND, 1, 0);
|
||||
assertDrop(c2,
|
||||
ste(c2, RECOVERABLE_NOT_DEFINED, DROPPED, false, null));
|
||||
}
|
||||
|
||||
public void classDeclaration() {
|
||||
assertEval("class A { }");
|
||||
assertClasses(clazz(KullaTesting.ClassType.CLASS, "A"));
|
||||
}
|
||||
|
||||
|
||||
public void interfaceDeclaration() {
|
||||
assertEval("interface A { }");
|
||||
assertClasses(clazz(KullaTesting.ClassType.INTERFACE, "A"));
|
||||
}
|
||||
|
||||
public void annotationDeclaration() {
|
||||
assertEval("@interface A { }");
|
||||
assertClasses(clazz(KullaTesting.ClassType.ANNOTATION, "A"));
|
||||
}
|
||||
|
||||
public void enumDeclaration() {
|
||||
assertEval("enum A { }");
|
||||
assertClasses(clazz(KullaTesting.ClassType.ENUM, "A"));
|
||||
}
|
||||
|
||||
public void classesDeclaration() {
|
||||
assertEval("interface A { }");
|
||||
assertEval("class B implements A { }");
|
||||
assertEval("interface C extends A { }");
|
||||
assertEval("enum D implements C { }");
|
||||
assertEval("@interface E { }");
|
||||
assertClasses(
|
||||
clazz(KullaTesting.ClassType.INTERFACE, "A"),
|
||||
clazz(KullaTesting.ClassType.CLASS, "B"),
|
||||
clazz(KullaTesting.ClassType.INTERFACE, "C"),
|
||||
clazz(KullaTesting.ClassType.ENUM, "D"),
|
||||
clazz(KullaTesting.ClassType.ANNOTATION, "E"));
|
||||
assertActiveKeys();
|
||||
}
|
||||
|
||||
public void classesRedeclaration1() {
|
||||
Snippet a = classKey(assertEval("class A { }"));
|
||||
Snippet b = classKey(assertEval("interface B { }"));
|
||||
assertClasses(clazz(KullaTesting.ClassType.CLASS, "A"), clazz(KullaTesting.ClassType.INTERFACE, "B"));
|
||||
assertActiveKeys();
|
||||
|
||||
assertEval("interface A { }",
|
||||
ste(MAIN_SNIPPET, VALID, VALID, true, null),
|
||||
ste(a, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
|
||||
assertClasses(clazz(KullaTesting.ClassType.INTERFACE, "A"),
|
||||
clazz(KullaTesting.ClassType.INTERFACE, "B"));
|
||||
assertActiveKeys();
|
||||
|
||||
assertEval("interface B { } //again",
|
||||
ste(MAIN_SNIPPET, VALID, VALID, false, null),
|
||||
ste(b, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
|
||||
assertClasses(clazz(KullaTesting.ClassType.INTERFACE, "A"),
|
||||
clazz(KullaTesting.ClassType.INTERFACE, "B"));
|
||||
assertActiveKeys();
|
||||
}
|
||||
|
||||
public void classesRedeclaration2() {
|
||||
assertEval("class A { }");
|
||||
assertClasses(clazz(KullaTesting.ClassType.CLASS, "A"));
|
||||
assertActiveKeys();
|
||||
|
||||
Snippet b = classKey(assertEval("class B extends A { }"));
|
||||
assertClasses(clazz(KullaTesting.ClassType.CLASS, "A"),
|
||||
clazz(KullaTesting.ClassType.CLASS, "B"));
|
||||
assertActiveKeys();
|
||||
|
||||
Snippet c = classKey(assertEval("class C extends B { }"));
|
||||
assertClasses(clazz(KullaTesting.ClassType.CLASS, "A"),
|
||||
clazz(KullaTesting.ClassType.CLASS, "B"), clazz(KullaTesting.ClassType.CLASS, "C"));
|
||||
assertActiveKeys();
|
||||
|
||||
assertEval("interface B { }",
|
||||
DiagCheck.DIAG_OK,
|
||||
DiagCheck.DIAG_ERROR,
|
||||
ste(MAIN_SNIPPET, VALID, VALID, true, null),
|
||||
ste(b, VALID, OVERWRITTEN, false, MAIN_SNIPPET),
|
||||
ste(c, VALID, RECOVERABLE_NOT_DEFINED, true, MAIN_SNIPPET));
|
||||
assertClasses(clazz(KullaTesting.ClassType.CLASS, "A"),
|
||||
clazz(KullaTesting.ClassType.INTERFACE, "B"), clazz(KullaTesting.ClassType.CLASS, "C"));
|
||||
assertEval("new C();",
|
||||
DiagCheck.DIAG_ERROR,
|
||||
DiagCheck.DIAG_ERROR,
|
||||
added(REJECTED));
|
||||
assertActiveKeys();
|
||||
}
|
||||
|
||||
public void classesCyclic1() {
|
||||
Snippet b = classKey(assertEval("class B extends A { }",
|
||||
added(RECOVERABLE_NOT_DEFINED)));
|
||||
Snippet a = classKey(assertEval("class A extends B { }", DiagCheck.DIAG_IGNORE, DiagCheck.DIAG_IGNORE,
|
||||
added(RECOVERABLE_NOT_DEFINED),
|
||||
ste(b, RECOVERABLE_NOT_DEFINED, RECOVERABLE_NOT_DEFINED, false, MAIN_SNIPPET)));
|
||||
/***
|
||||
assertDeclareFail("class A extends B { }", "****",
|
||||
added(REJECTED),
|
||||
ste(b, RECOVERABLE_NOT_DEFINED, RECOVERABLE_NOT_DEFINED, false, MAIN_SNIPPET));
|
||||
***/
|
||||
// It is random which one it shows up in, but cyclic error should be there
|
||||
List<Diag> diagsA = getState().diagnostics(a);
|
||||
List<Diag> diagsB = getState().diagnostics(b);
|
||||
List<Diag> diags;
|
||||
if (diagsA.isEmpty()) {
|
||||
diags = diagsB;
|
||||
} else {
|
||||
diags = diagsA;
|
||||
assertTrue(diagsB.isEmpty());
|
||||
}
|
||||
assertEquals(diags.size(), 1, "Expected one error");
|
||||
assertEquals(diags.get(0).getCode(), "compiler.err.cyclic.inheritance", "Expected cyclic inheritance error");
|
||||
assertActiveKeys();
|
||||
}
|
||||
|
||||
public void classesCyclic2() {
|
||||
Snippet d = classKey(assertEval("class D extends E { }", added(RECOVERABLE_NOT_DEFINED)));
|
||||
assertEval("class E { D d; }",
|
||||
added(VALID),
|
||||
ste(d, RECOVERABLE_NOT_DEFINED, VALID, true, MAIN_SNIPPET));
|
||||
assertActiveKeys();
|
||||
}
|
||||
|
||||
public void classesCyclic3() {
|
||||
Snippet outer = classKey(assertEval("class Outer { class Inner extends Foo { } }",
|
||||
added(RECOVERABLE_NOT_DEFINED)));
|
||||
Snippet foo = classKey(assertEval("class Foo { } ",
|
||||
added(VALID),
|
||||
ste(outer, RECOVERABLE_NOT_DEFINED, VALID, true, MAIN_SNIPPET)));
|
||||
assertEval(" class Foo extends Outer { }",
|
||||
ste(MAIN_SNIPPET, VALID, VALID, true, null),
|
||||
ste(foo, VALID, OVERWRITTEN, false, MAIN_SNIPPET),
|
||||
ste(outer, VALID, VALID, true, MAIN_SNIPPET));
|
||||
assertActiveKeys();
|
||||
}
|
||||
|
||||
public void classesIgnoredModifiers() {
|
||||
assertDeclareWarn1("public interface A { }",
|
||||
new ExpectedDiagnostic("jdk.eval.warn.illegal.modifiers", 0, 6, 0, -1, -1, Diagnostic.Kind.WARNING));
|
||||
assertDeclareWarn1("static class B implements A { }",
|
||||
new ExpectedDiagnostic("jdk.eval.warn.illegal.modifiers", 0, 6, 0, -1, -1, Diagnostic.Kind.WARNING));
|
||||
assertDeclareWarn1("final interface C extends A { }",
|
||||
new ExpectedDiagnostic("jdk.eval.warn.illegal.modifiers", 0, 5, 0, -1, -1, Diagnostic.Kind.WARNING));
|
||||
assertDeclareWarn1("protected enum D implements C { }",
|
||||
new ExpectedDiagnostic("jdk.eval.warn.illegal.modifiers", 0, 9, 0, -1, -1, Diagnostic.Kind.WARNING));
|
||||
assertActiveKeys();
|
||||
}
|
||||
|
||||
public void ignoreModifierSpaceIssue() {
|
||||
assertEval("interface I { void f(); } ");
|
||||
// there should not be a space between 'I' and '{' to reproduce the failure
|
||||
assertEval("class C implements I{ public void f() {}}");
|
||||
assertClasses(clazz(KullaTesting.ClassType.CLASS, "C"), clazz(KullaTesting.ClassType.INTERFACE, "I"));
|
||||
assertActiveKeys();
|
||||
}
|
||||
|
||||
@DataProvider(name = "innerClasses")
|
||||
public Object[][] innerClasses() {
|
||||
List<Object[]> list = new ArrayList<>();
|
||||
for (ClassType outerClassType : ClassType.values()) {
|
||||
for (ClassType innerClassType : ClassType.values()) {
|
||||
list.add(new Object[]{outerClassType, innerClassType});
|
||||
}
|
||||
}
|
||||
return list.toArray(new Object[list.size()][]);
|
||||
}
|
||||
|
||||
@Test(dataProvider = "innerClasses")
|
||||
public void innerClasses(ClassType outerClassType, ClassType innerClassType) {
|
||||
String source =
|
||||
outerClassType + " A {" + (outerClassType == ClassType.ENUM ? ";" : "") +
|
||||
innerClassType + " B { }" +
|
||||
"}";
|
||||
assertEval(source);
|
||||
assertNumberOfActiveClasses(1);
|
||||
assertActiveKeys();
|
||||
}
|
||||
|
||||
public void testInnerClassesCrash() {
|
||||
Snippet a = classKey(assertEval("class A { class B extends A {} }"));
|
||||
Snippet a2 = classKey(assertEval("class A { interface I1 extends I2 {} interface I2 {} }",
|
||||
ste(MAIN_SNIPPET, VALID, VALID, false, null),
|
||||
ste(a, VALID, OVERWRITTEN, false, MAIN_SNIPPET)));
|
||||
assertEval("class A { A a = new A() {}; }",
|
||||
ste(MAIN_SNIPPET, VALID, VALID, true, null),
|
||||
ste(a2, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
|
||||
}
|
||||
|
||||
public void testInnerClassesCrash1() {
|
||||
assertEval("class A { class B extends A {} B getB() { return new B();} }");
|
||||
assertEquals(varKey(assertEval("A a = new A();")).name(), "a");
|
||||
VarSnippet variableKey = varKey(assertEval("a.getB();"));
|
||||
assertEquals(variableKey.typeName(), "A.B");
|
||||
}
|
||||
|
||||
public void testInnerClassesCrash2() {
|
||||
assertEval("class A { interface I1 extends I2 {} interface I2 {} I1 x; }");
|
||||
assertEquals(varKey(assertEval("A a = new A();")).name(), "a");
|
||||
VarSnippet variableKey = varKey(assertEval("a.x;"));
|
||||
assertEquals(variableKey.typeName(), "A.I1");
|
||||
}
|
||||
}
|
||||
170
langtools/test/jdk/jshell/CommandCompletionTest.java
Normal file
170
langtools/test/jdk/jshell/CommandCompletionTest.java
Normal file
@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Test Command Completion
|
||||
* @library /tools/lib
|
||||
* @build ReplToolTesting TestingInputStream Compiler ToolBox
|
||||
* @run testng CommandCompletionTest
|
||||
*/
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
@Test
|
||||
public class CommandCompletionTest extends ReplToolTesting {
|
||||
|
||||
public void testCommand() {
|
||||
assertCompletion("/f|", false, "/feedback ");
|
||||
assertCompletion("/deb|", false);
|
||||
assertCompletion("/feedback v|", false, "verbose");
|
||||
assertCompletion("/c|", false, "/classes ", "/classpath ");
|
||||
assertCompletion("/h|", false, "/help ", "/history ");
|
||||
assertCompletion("/feedback |", false,
|
||||
"?", "concise", "default", "normal", "off", "verbose");
|
||||
}
|
||||
|
||||
public void testList() {
|
||||
assertCompletion("/l|", false, "/list ");
|
||||
assertCompletion("/list |", false, "all");
|
||||
assertCompletion("/list q|", false);
|
||||
}
|
||||
|
||||
public void testDrop() {
|
||||
assertCompletion("/d|", false, "/drop ");
|
||||
|
||||
test(false, new String[] {"-nostartup"},
|
||||
a -> assertClass(a, "class cTest {}", "class", "cTest"),
|
||||
a -> assertMethod(a, "int mTest() { return 0; }", "()I", "mTest"),
|
||||
a -> assertVariable(a, "int", "fTest"),
|
||||
a -> assertCompletion(a, "/drop |", false, "1", "2", "3", "cTest", "fTest", "mTest"),
|
||||
a -> assertCompletion(a, "/drop f|", false, "fTest")
|
||||
);
|
||||
}
|
||||
|
||||
public void testEdit() {
|
||||
assertCompletion("/e|", false, "/edit ", "/exit ");
|
||||
assertCompletion("/ed|", false, "/edit ");
|
||||
|
||||
test(false, new String[]{"-nostartup"},
|
||||
a -> assertClass(a, "class cTest {}", "class", "cTest"),
|
||||
a -> assertMethod(a, "int mTest() { return 0; }", "()I", "mTest"),
|
||||
a -> assertVariable(a, "int", "fTest"),
|
||||
a -> assertCompletion(a, "/edit |", false, "1", "2", "3", "cTest", "fTest", "mTest"),
|
||||
a -> assertCompletion(a, "/edit f|", false, "fTest")
|
||||
);
|
||||
}
|
||||
|
||||
public void testOpen() throws IOException {
|
||||
Compiler compiler = new Compiler();
|
||||
assertCompletion("/o|", false, "/open ");
|
||||
List<String> p1 = listFiles(Paths.get(""));
|
||||
FileSystems.getDefault().getRootDirectories().forEach(s -> p1.add(s.toString()));
|
||||
Collections.sort(p1);
|
||||
assertCompletion("/open |", false, p1.toArray(new String[p1.size()]));
|
||||
Path classDir = compiler.getClassDir();
|
||||
List<String> p2 = listFiles(classDir);
|
||||
assertCompletion("/open " + classDir + "/|", false, p2.toArray(new String[p2.size()]));
|
||||
}
|
||||
|
||||
public void testSave() throws IOException {
|
||||
Compiler compiler = new Compiler();
|
||||
assertCompletion("/s|", false, "/save ", "/savestart ", "/seteditor ", "/setstart ");
|
||||
List<String> p1 = listFiles(Paths.get(""));
|
||||
Collections.addAll(p1, "all ", "history ");
|
||||
FileSystems.getDefault().getRootDirectories().forEach(s -> p1.add(s.toString()));
|
||||
Collections.sort(p1);
|
||||
assertCompletion("/save |", false, p1.toArray(new String[p1.size()]));
|
||||
Path classDir = compiler.getClassDir();
|
||||
List<String> p2 = listFiles(classDir);
|
||||
assertCompletion("/save " + classDir + "/|",
|
||||
false, p2.toArray(new String[p2.size()]));
|
||||
assertCompletion("/save all " + classDir + "/|",
|
||||
false, p2.toArray(new String[p2.size()]));
|
||||
}
|
||||
|
||||
public void testClassPath() throws IOException {
|
||||
assertCompletion("/classp|", false, "/classpath ");
|
||||
Compiler compiler = new Compiler();
|
||||
Path outDir = compiler.getPath("testClasspathCompletion");
|
||||
Files.createDirectories(outDir);
|
||||
Files.createDirectories(outDir.resolve("dir"));
|
||||
createIfNeeded(outDir.resolve("test.jar"));
|
||||
createIfNeeded(outDir.resolve("test.zip"));
|
||||
compiler.compile(outDir, "package pkg; public class A { public String toString() { return \"A\"; } }");
|
||||
String jarName = "test.jar";
|
||||
compiler.jar(outDir, jarName, "pkg/A.class");
|
||||
compiler.getPath(outDir).resolve(jarName);
|
||||
List<String> paths = listFiles(outDir, CLASSPATH_FILTER);
|
||||
assertCompletion("/classpath " + outDir + "/|", false, paths.toArray(new String[paths.size()]));
|
||||
}
|
||||
|
||||
public void testUserHome() throws IOException {
|
||||
List<String> completions;
|
||||
Path home = Paths.get(System.getProperty("user.home"));
|
||||
try (Stream<Path> content = Files.list(home)) {
|
||||
completions = content.filter(CLASSPATH_FILTER)
|
||||
.map(file -> file.getFileName().toString() + (Files.isDirectory(file) ? "/" : ""))
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
assertCompletion("/classpath ~/|", false, completions.toArray(new String[completions.size()]));
|
||||
}
|
||||
|
||||
private void createIfNeeded(Path file) throws IOException {
|
||||
if (!Files.exists(file))
|
||||
Files.createFile(file);
|
||||
}
|
||||
private List<String> listFiles(Path path) throws IOException {
|
||||
return listFiles(path, ACCEPT_ALL);
|
||||
}
|
||||
|
||||
private List<String> listFiles(Path path, Predicate<? super Path> filter) throws IOException {
|
||||
try (Stream<Path> stream = Files.list(path)) {
|
||||
return stream.filter(filter)
|
||||
.map(p -> p.getFileName().toString() + (Files.isDirectory(p) ? "/" : ""))
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
private static final Predicate<? super Path> ACCEPT_ALL =
|
||||
(file) -> !file.endsWith(".") && !file.endsWith("..");
|
||||
|
||||
private static final Predicate<? super Path> CLASSPATH_FILTER =
|
||||
(file) -> ACCEPT_ALL.test(file) &&
|
||||
(Files.isDirectory(file) ||
|
||||
file.getFileName().toString().endsWith(".jar") ||
|
||||
file.getFileName().toString().endsWith(".zip"));
|
||||
}
|
||||
99
langtools/test/jdk/jshell/Compiler.java
Normal file
99
langtools/test/jdk/jshell/Compiler.java
Normal file
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
public class Compiler {
|
||||
|
||||
private final ToolBox tb = new ToolBox();
|
||||
|
||||
public Path getClassDir() {
|
||||
String classes = ToolBox.testClasses;
|
||||
if (classes == null) {
|
||||
return Paths.get("build");
|
||||
} else {
|
||||
return Paths.get(classes);
|
||||
}
|
||||
}
|
||||
|
||||
public Path getPath(String path) {
|
||||
return getPath(Paths.get(path));
|
||||
}
|
||||
|
||||
public Path getPath(Path path) {
|
||||
return getClassDir().resolve(path);
|
||||
}
|
||||
|
||||
public void compile(String...sources) {
|
||||
compile(Paths.get("."), sources);
|
||||
}
|
||||
|
||||
public void compile(Path directory, String...sources) {
|
||||
Path classDir = getClassDir();
|
||||
tb.new JavacTask()
|
||||
.options("-d", classDir.resolve(directory).toString())
|
||||
.sources(sources)
|
||||
.run();
|
||||
}
|
||||
|
||||
public void jar(String jarName, String...files) {
|
||||
jar(Paths.get("."), jarName, files);
|
||||
}
|
||||
|
||||
public void jar(Path directory, String jarName, String...files) {
|
||||
Manifest manifest = new Manifest();
|
||||
manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
|
||||
Path classDirPath = getClassDir();
|
||||
Path baseDir = classDirPath.resolve(directory);
|
||||
Path jarPath = baseDir.resolve(jarName);
|
||||
tb.new JarTask(jarPath.toString())
|
||||
.manifest(manifest)
|
||||
.baseDir(baseDir.toString())
|
||||
.files(files).run();
|
||||
}
|
||||
|
||||
public void writeToFile(Path path, String...sources) {
|
||||
try {
|
||||
if (path.getParent() != null) {
|
||||
Files.createDirectories(path.getParent());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
try (BufferedWriter writer = Files.newBufferedWriter(path)) {
|
||||
for (String source : sources) {
|
||||
writer.append(source);
|
||||
writer.append('\n');
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
265
langtools/test/jdk/jshell/CompletenessStressTest.java
Normal file
265
langtools/test/jdk/jshell/CompletenessStressTest.java
Normal file
@ -0,0 +1,265 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
import javax.tools.JavaCompiler;
|
||||
import javax.tools.JavaFileObject;
|
||||
import javax.tools.StandardJavaFileManager;
|
||||
import javax.tools.ToolProvider;
|
||||
|
||||
import com.sun.source.tree.BlockTree;
|
||||
import com.sun.source.tree.BreakTree;
|
||||
import com.sun.source.tree.CaseTree;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.tree.ContinueTree;
|
||||
import com.sun.source.tree.DoWhileLoopTree;
|
||||
import com.sun.source.tree.ExpressionStatementTree;
|
||||
import com.sun.source.tree.ForLoopTree;
|
||||
import com.sun.source.tree.IfTree;
|
||||
import com.sun.source.tree.ImportTree;
|
||||
import com.sun.source.tree.LabeledStatementTree;
|
||||
import com.sun.source.tree.LineMap;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.tree.ReturnTree;
|
||||
import com.sun.source.tree.StatementTree;
|
||||
import com.sun.source.tree.SwitchTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.source.tree.WhileLoopTree;
|
||||
import com.sun.source.util.SourcePositions;
|
||||
import com.sun.source.util.Trees;
|
||||
import com.sun.tools.javac.api.JavacTaskImpl;
|
||||
|
||||
import jdk.jshell.SourceCodeAnalysis;
|
||||
|
||||
import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static java.lang.Integer.max;
|
||||
import static java.lang.Integer.min;
|
||||
import static jdk.jshell.SourceCodeAnalysis.Completeness.*;
|
||||
|
||||
public class CompletenessStressTest extends KullaTesting {
|
||||
public final static String JDK_ROOT_SRC_PROP = "jdk.root.src";
|
||||
public final static String JDK_ROOT_SRC;
|
||||
|
||||
static {
|
||||
JDK_ROOT_SRC = System.getProperty(JDK_ROOT_SRC_PROP);
|
||||
}
|
||||
|
||||
public File getSourceFile(String fileName) {
|
||||
for (File dir : getDirectoriesToTest()) {
|
||||
File file = new File(dir, fileName);
|
||||
if (file.exists()) {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
throw new AssertionError("File not found: " + fileName);
|
||||
}
|
||||
|
||||
public File[] getDirectoriesToTest() {
|
||||
return new File[]{
|
||||
new File(JDK_ROOT_SRC, "nashorn/src"),
|
||||
new File(JDK_ROOT_SRC, "langtools/src"),
|
||||
new File(JDK_ROOT_SRC, "jaxp/src"),
|
||||
new File(JDK_ROOT_SRC, "jaxws/src"),
|
||||
new File(JDK_ROOT_SRC, "jdk/src"),
|
||||
new File(JDK_ROOT_SRC, "corba/src")
|
||||
};
|
||||
}
|
||||
|
||||
@DataProvider(name = "crawler")
|
||||
public Object[][] dataProvider() throws IOException {
|
||||
File[] srcDirs = getDirectoriesToTest();
|
||||
List<String[]> list = new ArrayList<>();
|
||||
for (File srcDir : srcDirs) {
|
||||
String srcDirName = srcDir.getAbsolutePath();
|
||||
// this is just to obtain pretty test names for testng tests
|
||||
List<String[]> a = Files.walk(Paths.get(srcDirName))
|
||||
.map(Path::toFile)
|
||||
.map(File::getAbsolutePath)
|
||||
.filter(n -> n.endsWith(".java"))
|
||||
.map(n -> n.replace(srcDirName, ""))
|
||||
.map(n -> new String[]{n})
|
||||
.collect(Collectors.toList());
|
||||
if (a.isEmpty()) {
|
||||
throw new AssertionError("Java sources have not been found in directory: " + srcDirName);
|
||||
}
|
||||
list.addAll(a);
|
||||
}
|
||||
return list.toArray(new String[list.size()][]);
|
||||
}
|
||||
|
||||
@Test(dataProvider = "crawler")
|
||||
public void testFile(String fileName) throws IOException {
|
||||
File file = getSourceFile(fileName);
|
||||
final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
|
||||
final StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
|
||||
boolean success = true;
|
||||
StringWriter writer = new StringWriter();
|
||||
writer.write("Testing : " + file.toString() + "\n");
|
||||
String text = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
|
||||
Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjects(file);
|
||||
JavacTaskImpl task = (JavacTaskImpl) compiler.getTask(null, fileManager, null, null, null, compilationUnits);
|
||||
Iterable<? extends CompilationUnitTree> asts = task.parse();
|
||||
Trees trees = Trees.instance(task);
|
||||
SourcePositions sp = trees.getSourcePositions();
|
||||
|
||||
for (CompilationUnitTree cut : asts) {
|
||||
for (ImportTree imp : cut.getImports()) {
|
||||
success &= testStatement(writer, sp, text, cut, imp);
|
||||
}
|
||||
for (Tree decl : cut.getTypeDecls()) {
|
||||
success &= testStatement(writer, sp, text, cut, decl);
|
||||
if (decl instanceof ClassTree) {
|
||||
ClassTree ct = (ClassTree) decl;
|
||||
for (Tree mem : ct.getMembers()) {
|
||||
if (mem instanceof MethodTree) {
|
||||
MethodTree mt = (MethodTree) mem;
|
||||
BlockTree bt = mt.getBody();
|
||||
// No abstract methods or constructors
|
||||
if (bt != null && mt.getReturnType() != null) {
|
||||
// The modifiers synchronized, abstract, and default are not allowed on
|
||||
// top-level declarations and are errors.
|
||||
Set<Modifier> modifier = mt.getModifiers().getFlags();
|
||||
if (!modifier.contains(Modifier.ABSTRACT)
|
||||
&& !modifier.contains(Modifier.SYNCHRONIZED)
|
||||
&& !modifier.contains(Modifier.DEFAULT)) {
|
||||
success &= testStatement(writer, sp, text, cut, mt);
|
||||
}
|
||||
testBlock(writer, sp, text, cut, bt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fileManager.close();
|
||||
if (!success) {
|
||||
throw new AssertionError(writer.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isLegal(StatementTree st) {
|
||||
return !(st instanceof ReturnTree) &&
|
||||
!(st instanceof ContinueTree) && !(st instanceof BreakTree);
|
||||
}
|
||||
|
||||
private boolean testBranch(StringWriter writer, SourcePositions sp, String text, CompilationUnitTree cut, StatementTree statementTree) {
|
||||
if (statementTree instanceof BlockTree) {
|
||||
return testBlock(writer, sp, text, cut, (BlockTree) statementTree);
|
||||
} else if (isLegal(statementTree)) {
|
||||
return testStatement(writer, sp, text, cut, statementTree);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean testBlock(StringWriter writer, SourcePositions sp, String text, CompilationUnitTree cut, BlockTree blockTree) {
|
||||
boolean success = true;
|
||||
for (StatementTree st : blockTree.getStatements()) {
|
||||
if (isLegal(st)) {
|
||||
success &= testStatement(writer, sp, text, cut, st);
|
||||
}
|
||||
if (st instanceof IfTree) {
|
||||
IfTree ifTree = (IfTree) st;
|
||||
success &= testBranch(writer, sp, text, cut, ifTree.getThenStatement());
|
||||
success &= testBranch(writer, sp, text, cut, ifTree.getElseStatement());
|
||||
} else if (st instanceof WhileLoopTree) {
|
||||
WhileLoopTree whileLoopTree = (WhileLoopTree) st;
|
||||
success &= testBranch(writer, sp, text, cut, whileLoopTree.getStatement());
|
||||
} else if (st instanceof DoWhileLoopTree) {
|
||||
DoWhileLoopTree doWhileLoopTree = (DoWhileLoopTree) st;
|
||||
success &= testBranch(writer, sp, text, cut, doWhileLoopTree.getStatement());
|
||||
} else if (st instanceof ForLoopTree) {
|
||||
ForLoopTree forLoopTree = (ForLoopTree) st;
|
||||
success &= testBranch(writer, sp, text, cut, forLoopTree.getStatement());
|
||||
} else if (st instanceof LabeledStatementTree) {
|
||||
LabeledStatementTree labelTree = (LabeledStatementTree) st;
|
||||
success &= testBranch(writer, sp, text, cut, labelTree.getStatement());
|
||||
} else if (st instanceof SwitchTree) {
|
||||
SwitchTree switchTree = (SwitchTree) st;
|
||||
for (CaseTree caseTree : switchTree.getCases()) {
|
||||
for (StatementTree statementTree : caseTree.getStatements()) {
|
||||
success &= testBranch(writer, sp, text, cut, statementTree);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
private boolean testStatement(StringWriter writer, SourcePositions sp, String text, CompilationUnitTree cut, Tree statement) {
|
||||
if (statement == null) {
|
||||
return true;
|
||||
}
|
||||
int start = (int) sp.getStartPosition(cut, statement);
|
||||
int end = (int) sp.getEndPosition(cut, statement);
|
||||
char ch = text.charAt(end - 1);
|
||||
SourceCodeAnalysis.Completeness expected = COMPLETE;
|
||||
LineMap lineMap = cut.getLineMap();
|
||||
int row = (int) lineMap.getLineNumber(start);
|
||||
int column = (int) lineMap.getColumnNumber(start);
|
||||
switch (ch) {
|
||||
case ',':
|
||||
case ';':
|
||||
expected = (statement instanceof ExpressionStatementTree)
|
||||
? COMPLETE
|
||||
: COMPLETE_WITH_SEMI;
|
||||
--end;
|
||||
break;
|
||||
case '}':
|
||||
break;
|
||||
default:
|
||||
writer.write(String.format("Unexpected end: row %d, column %d: '%c' -- %s\n",
|
||||
row, column, ch, text.substring(start, end)));
|
||||
return true;
|
||||
}
|
||||
String unit = text.substring(start, end);
|
||||
SourceCodeAnalysis.CompletionInfo ci = getAnalysis().analyzeCompletion(unit);
|
||||
if (ci.completeness != expected) {
|
||||
if (expected == COMPLETE_WITH_SEMI && (ci.completeness == CONSIDERED_INCOMPLETE || ci.completeness == EMPTY)) {
|
||||
writer.write(String.format("Empty statement: row %d, column %d: -- %s\n",
|
||||
start, end, unit));
|
||||
} else {
|
||||
String oops = unit.substring(max(0, ci.unitEndPos - 10), ci.unitEndPos) + "|||" +
|
||||
unit.substring(ci.unitEndPos, min(unit.length(), ci.unitEndPos + 10));
|
||||
writer.write(String.format("Expected %s got %s: '%s' row %d, column %d: -- %s\n",
|
||||
expected, ci.completeness, oops, row, column, unit));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
291
langtools/test/jdk/jshell/CompletenessTest.java
Normal file
291
langtools/test/jdk/jshell/CompletenessTest.java
Normal file
@ -0,0 +1,291 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Test SourceCodeAnalysis
|
||||
* @build KullaTesting TestingInputStream
|
||||
* @run testng CompletenessTest
|
||||
*/
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.testng.annotations.Test;
|
||||
import jdk.jshell.SourceCodeAnalysis.Completeness;
|
||||
|
||||
import static jdk.jshell.SourceCodeAnalysis.Completeness.*;
|
||||
|
||||
@Test
|
||||
public class CompletenessTest extends KullaTesting {
|
||||
|
||||
// Add complete units that end with semicolon to complete_with_semi (without
|
||||
// the semicolon). Both cases will be tested.
|
||||
static final String[] complete = new String[] {
|
||||
"{ x= 4; }",
|
||||
"int mm(int x) {kll}",
|
||||
"if (t) { ddd; }",
|
||||
"for (int i = 0; i < lines.length(); ++i) { foo }",
|
||||
"while (ct == null) { switch (current.kind) { case EOF: { } } }",
|
||||
"if (match.kind == BRACES && (prevCT.kind == ARROW || prevCT.kind == NEW_MIDDLE)) { new CT(UNMATCHED, current, \"Unmatched \" + unmatched); }",
|
||||
"enum TK { EOF(TokenKind.EOF, 0), NEW_MIDDLE(XEXPR1|XTERM); }",
|
||||
"List<T> f() { return null; }",
|
||||
"List<?> f() { return null; }",
|
||||
"List<? extends Object> f() { return null; }",
|
||||
"Map<? extends Object, ? super Object> f() { return null; }",
|
||||
"class C { int z; }",
|
||||
"synchronized (r) { f(); }",
|
||||
"try { } catch (Exception ex) { }",
|
||||
"try { } catch (Exception ex) { } finally { }",
|
||||
"try { } finally { }",
|
||||
"try (java.util.zip.ZipFile zf = new java.util.zip.ZipFile(zipFileName)) { }",
|
||||
"foo: while (true) { printf(\"Innn\"); break foo; }",
|
||||
";",
|
||||
};
|
||||
|
||||
static final String[] expression = new String[] {
|
||||
"test",
|
||||
"x + y",
|
||||
"x + y ++",
|
||||
"p = 9",
|
||||
"match(BRACKETS, TokenKind.LBRACKET)",
|
||||
"new C()",
|
||||
"new C() { public String toString() { return \"Hi\"; } }",
|
||||
"new int[]",
|
||||
"new int[] {1, 2,3}",
|
||||
"new Foo() {}",
|
||||
"i >= 0 && Character.isWhitespace(s.charAt(i))",
|
||||
};
|
||||
|
||||
static final String[] complete_with_semi = new String[] {
|
||||
"int mm",
|
||||
"if (t) ddd",
|
||||
"int p = 9",
|
||||
"int p",
|
||||
"Deque<Token> stack = new ArrayDeque<>()",
|
||||
"final Deque<Token> stack = new ArrayDeque<>()",
|
||||
"java.util.Scanner input = new java.util.Scanner(System.in)",
|
||||
"java.util.Scanner input = new java.util.Scanner(System.in) { }",
|
||||
"int j = -i",
|
||||
"String[] a = { \"AAA\" }",
|
||||
"assert true",
|
||||
"int path[]",
|
||||
"int path[][]",
|
||||
"int path[][] = new int[22][]",
|
||||
"int path[] = new int[22]",
|
||||
"int path[] = new int[] {1, 2, 3}",
|
||||
"int[] path",
|
||||
"int path[] = new int[22]",
|
||||
"int path[][] = new int[22][]",
|
||||
"for (Object o : a) System.out.println(\"Yep\")",
|
||||
"while (os == null) System.out.println(\"Yep\")",
|
||||
"do f(); while (t)",
|
||||
"if (os == null) System.out.println(\"Yep\")",
|
||||
"if (t) if (!t) System.out.println(123)",
|
||||
"for (int i = 0; i < 10; ++i) if (i < 5) System.out.println(i); else break",
|
||||
"for (int i = 0; i < 10; ++i) if (i < 5) System.out.println(i); else continue",
|
||||
"for (int i = 0; i < 10; ++i) if (i < 5) System.out.println(i); else return",
|
||||
"throw ex",
|
||||
"C c = new C()",
|
||||
"java.util.zip.ZipFile zf = new java.util.zip.ZipFile(zipFileName)",
|
||||
"BufferedReader br = new BufferedReader(new FileReader(path))",
|
||||
"bar: g()",
|
||||
"baz: while (true) if (t()) printf('-'); else break baz",
|
||||
};
|
||||
|
||||
static final String[] considered_incomplete = new String[] {
|
||||
"if (t)",
|
||||
"if (t) { } else",
|
||||
"if (t) if (!t)",
|
||||
"if (match.kind == BRACES && (prevCT.kind == ARROW || prevCT.kind == NEW_MIDDLE))",
|
||||
"for (int i = 0; i < 10; ++i)",
|
||||
"while (os == null)",
|
||||
};
|
||||
|
||||
static final String[] definitely_incomplete = new String[] {
|
||||
"int mm(",
|
||||
"int mm(int x",
|
||||
"int mm(int x)",
|
||||
"int mm(int x) {",
|
||||
"int mm(int x) {kll",
|
||||
"if",
|
||||
"if (",
|
||||
"if (t",
|
||||
"if (t) {",
|
||||
"if (t) { ddd",
|
||||
"if (t) { ddd;",
|
||||
"if (t) if (",
|
||||
"if (stack.isEmpty()) {",
|
||||
"if (match.kind == BRACES && (prevCT.kind == ARROW || prevCT.kind == NEW_MIDDLE)) {",
|
||||
"if (match.kind == BRACES && (prevCT.kind == ARROW || prevCT.kind == NEW_MIDDLE)) { new CT(UNMATCHED, current, \"Unmatched \" + unmatched);",
|
||||
"x +",
|
||||
"int",
|
||||
"for (int i = 0; i < lines.length(); ++i) {",
|
||||
"new",
|
||||
"new C(",
|
||||
"new int[",
|
||||
"new int[] {1, 2,3",
|
||||
"new int[] {",
|
||||
"while (ct == null) {",
|
||||
"while (ct == null) { switch (current.kind) {",
|
||||
"while (ct == null) { switch (current.kind) { case EOF: {",
|
||||
"while (ct == null) { switch (current.kind) { case EOF: { } }",
|
||||
"enum TK {",
|
||||
"enum TK { EOF(TokenKind.EOF, 0),",
|
||||
"enum TK { EOF(TokenKind.EOF, 0), NEW_MIDDLE(XEXPR1|XTERM)",
|
||||
"enum TK { EOF(TokenKind.EOF, 0), NEW_MIDDLE(XEXPR1|XTERM); ",
|
||||
};
|
||||
|
||||
static final String[] unknown = new String[] {
|
||||
"new ;"
|
||||
};
|
||||
|
||||
static final Map<Completeness, String[]> statusToCases = new HashMap<>();
|
||||
static {
|
||||
statusToCases.put(COMPLETE, complete);
|
||||
statusToCases.put(COMPLETE_WITH_SEMI, complete_with_semi);
|
||||
statusToCases.put(CONSIDERED_INCOMPLETE, considered_incomplete);
|
||||
statusToCases.put(DEFINITELY_INCOMPLETE, definitely_incomplete);
|
||||
}
|
||||
|
||||
private void assertStatus(String input, Completeness status, String source) {
|
||||
String augSrc;
|
||||
switch (status) {
|
||||
case COMPLETE_WITH_SEMI:
|
||||
augSrc = source + ";";
|
||||
break;
|
||||
|
||||
case DEFINITELY_INCOMPLETE:
|
||||
case CONSIDERED_INCOMPLETE:
|
||||
augSrc = null;
|
||||
break;
|
||||
|
||||
case EMPTY:
|
||||
case COMPLETE:
|
||||
case UNKNOWN:
|
||||
augSrc = source;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
assertAnalyze(input, status, augSrc);
|
||||
}
|
||||
|
||||
private void assertStatus(String[] ins, Completeness status) {
|
||||
for (String input : ins) {
|
||||
assertStatus(input, status, input);
|
||||
}
|
||||
}
|
||||
|
||||
public void test_complete() {
|
||||
assertStatus(complete, COMPLETE);
|
||||
}
|
||||
|
||||
public void test_expression() {
|
||||
assertStatus(expression, COMPLETE);
|
||||
}
|
||||
|
||||
public void test_complete_with_semi() {
|
||||
assertStatus(complete_with_semi, COMPLETE_WITH_SEMI);
|
||||
}
|
||||
|
||||
public void test_considered_incomplete() {
|
||||
assertStatus(considered_incomplete, CONSIDERED_INCOMPLETE);
|
||||
}
|
||||
|
||||
public void test_definitely_incomplete() {
|
||||
assertStatus(definitely_incomplete, DEFINITELY_INCOMPLETE);
|
||||
}
|
||||
|
||||
public void test_unknown() {
|
||||
assertStatus(definitely_incomplete, DEFINITELY_INCOMPLETE);
|
||||
}
|
||||
|
||||
public void testCompleted_complete_with_semi() {
|
||||
for (String in : complete_with_semi) {
|
||||
String input = in + ";";
|
||||
assertStatus(input, COMPLETE, input);
|
||||
}
|
||||
}
|
||||
|
||||
public void testCompleted_expression_with_semi() {
|
||||
for (String in : expression) {
|
||||
String input = in + ";";
|
||||
assertStatus(input, COMPLETE, input);
|
||||
}
|
||||
}
|
||||
|
||||
public void testCompleted_considered_incomplete() {
|
||||
for (String in : considered_incomplete) {
|
||||
String input = in + ";";
|
||||
assertStatus(input, COMPLETE, input);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertSourceByStatus(String first) {
|
||||
for (Map.Entry<Completeness, String[]> e : statusToCases.entrySet()) {
|
||||
for (String in : e.getValue()) {
|
||||
String input = first + in;
|
||||
assertAnalyze(input, COMPLETE, first, in, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testCompleteSource_complete() {
|
||||
for (String input : complete) {
|
||||
assertSourceByStatus(input);
|
||||
}
|
||||
}
|
||||
|
||||
public void testCompleteSource_complete_with_semi() {
|
||||
for (String in : complete_with_semi) {
|
||||
String input = in + ";";
|
||||
assertSourceByStatus(input);
|
||||
}
|
||||
}
|
||||
|
||||
public void testCompleteSource_expression() {
|
||||
for (String in : expression) {
|
||||
String input = in + ";";
|
||||
assertSourceByStatus(input);
|
||||
}
|
||||
}
|
||||
|
||||
public void testCompleteSource_considered_incomplete() {
|
||||
for (String in : considered_incomplete) {
|
||||
String input = in + ";";
|
||||
assertSourceByStatus(input);
|
||||
}
|
||||
}
|
||||
|
||||
public void testTrailingSlash() {
|
||||
assertStatus("\"abc\\", UNKNOWN, "\"abc\\");
|
||||
}
|
||||
|
||||
public void testMiscSource() {
|
||||
assertStatus("if (t) if ", DEFINITELY_INCOMPLETE, "if (t) if"); //Bug
|
||||
assertStatus("int m() {} dfd", COMPLETE, "int m() {}");
|
||||
assertStatus("int p = ", DEFINITELY_INCOMPLETE, "int p ="); //Bug
|
||||
}
|
||||
}
|
||||
524
langtools/test/jdk/jshell/CompletionSuggestionTest.java
Normal file
524
langtools/test/jdk/jshell/CompletionSuggestionTest.java
Normal file
@ -0,0 +1,524 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Test Completion
|
||||
* @library /tools/lib
|
||||
* @build KullaTesting TestingInputStream ToolBox Compiler
|
||||
* @run testng CompletionSuggestionTest
|
||||
*/
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
|
||||
import jdk.jshell.Snippet;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static jdk.jshell.Snippet.Status.VALID;
|
||||
import static jdk.jshell.Snippet.Status.OVERWRITTEN;
|
||||
|
||||
@Test
|
||||
public class CompletionSuggestionTest extends KullaTesting {
|
||||
|
||||
public void testMemberExpr() {
|
||||
assertEval("class Test { static void test() { } }");
|
||||
assertCompletion("Test.t|", "test()");
|
||||
assertEval("Test ccTestInstance = new Test();");
|
||||
assertCompletion("ccTestInstance.t|", "toString()");
|
||||
assertCompletion(" ccTe|", "ccTestInstance");
|
||||
assertCompletion("String value = ccTestInstance.to|", "toString()");
|
||||
assertCompletion("java.util.Coll|", "Collection", "Collections");
|
||||
assertCompletion("String.cla|", "class");
|
||||
assertCompletion("boolean.cla|", "class");
|
||||
assertCompletion("byte.cla|", "class");
|
||||
assertCompletion("short.cla|", "class");
|
||||
assertCompletion("char.cla|", "class");
|
||||
assertCompletion("int.cla|", "class");
|
||||
assertCompletion("float.cla|", "class");
|
||||
assertCompletion("long.cla|", "class");
|
||||
assertCompletion("double.cla|", "class");
|
||||
assertCompletion("void.cla|", "class");
|
||||
assertCompletion("Object[].|", "class");
|
||||
assertCompletion("int[].|", "class");
|
||||
assertEval("Object[] ao = null;");
|
||||
assertCompletion("int i = ao.|", "length");
|
||||
assertEval("int[] ai = null;");
|
||||
assertCompletion("int i = ai.|", "length");
|
||||
assertCompletionIncludesExcludes("\"\".|",
|
||||
new HashSet<>(Collections.emptyList()),
|
||||
new HashSet<>(Arrays.asList("String(")));
|
||||
assertEval("double d = 0;");
|
||||
assertEval("void m() {}");
|
||||
assertCompletionIncludesExcludes("d.|",
|
||||
new HashSet<>(Collections.emptyList()),
|
||||
new HashSet<>(Arrays.asList("class")));
|
||||
assertCompletionIncludesExcludes("m().|",
|
||||
new HashSet<>(Collections.emptyList()),
|
||||
new HashSet<>(Arrays.asList("class")));
|
||||
assertEval("class C {class D {} static class E {} enum F {} interface H {} void method() {} int number;}");
|
||||
assertCompletionIncludesExcludes("C.|",
|
||||
new HashSet<>(Arrays.asList("D", "E", "F", "H", "class")),
|
||||
new HashSet<>(Arrays.asList("method()", "number")));
|
||||
assertCompletionIncludesExcludes("new C().|",
|
||||
new HashSet<>(Arrays.asList("method()", "number")),
|
||||
new HashSet<>(Arrays.asList("D", "E", "F", "H", "class")));
|
||||
assertCompletionIncludesExcludes("new C() {}.|",
|
||||
new HashSet<>(Arrays.asList("method()", "number")),
|
||||
new HashSet<>(Arrays.asList("D", "E", "F", "H", "class")));
|
||||
}
|
||||
|
||||
public void testStartOfExpression() {
|
||||
assertEval("int ccTest = 0;");
|
||||
assertCompletion("System.err.println(cc|", "ccTest");
|
||||
assertCompletion("for (int i = cc|", "ccTest");
|
||||
}
|
||||
|
||||
public void testParameter() {
|
||||
assertCompletion("class C{void method(int num){num|", "num");
|
||||
}
|
||||
|
||||
public void testPrimitive() {
|
||||
Set<String> primitives = new HashSet<>(Arrays.asList("boolean", "char", "byte", "short", "int", "long", "float", "double"));
|
||||
Set<String> onlyVoid = new HashSet<>(Collections.singletonList("void"));
|
||||
Set<String> primitivesOrVoid = new HashSet<>(primitives);
|
||||
primitivesOrVoid.addAll(onlyVoid);
|
||||
|
||||
assertCompletionIncludesExcludes("|",
|
||||
primitivesOrVoid,
|
||||
new HashSet<>(Collections.emptyList()));
|
||||
assertCompletionIncludesExcludes("int num = |",
|
||||
primitivesOrVoid,
|
||||
new HashSet<>(Collections.emptyList()));
|
||||
assertCompletionIncludesExcludes("num = |",
|
||||
primitivesOrVoid,
|
||||
new HashSet<>(Collections.emptyList()));
|
||||
assertCompletionIncludesExcludes("class C{void m() {|",
|
||||
primitivesOrVoid,
|
||||
new HashSet<>(Collections.emptyList()));
|
||||
assertCompletionIncludesExcludes("void method(|",
|
||||
primitives,
|
||||
onlyVoid);
|
||||
assertCompletionIncludesExcludes("void method(int num, |",
|
||||
primitives,
|
||||
onlyVoid);
|
||||
assertCompletion("new java.util.ArrayList<doub|");
|
||||
assertCompletion("class A extends doubl|");
|
||||
assertCompletion("class A implements doubl|");
|
||||
assertCompletion("interface A extends doubl|");
|
||||
assertCompletion("enum A implements doubl|");
|
||||
assertCompletion("class A<T extends doubl|");
|
||||
}
|
||||
|
||||
public void testEmpty() {
|
||||
assertCompletionIncludesExcludes("|",
|
||||
new HashSet<>(Arrays.asList("Object", "Void")),
|
||||
new HashSet<>(Arrays.asList("$REPL00DOESNOTMATTER")));
|
||||
assertCompletionIncludesExcludes("V|",
|
||||
new HashSet<>(Collections.singletonList("Void")),
|
||||
new HashSet<>(Collections.singletonList("Object")));
|
||||
assertCompletionIncludesExcludes("{ |",
|
||||
new HashSet<>(Arrays.asList("Object", "Void")),
|
||||
new HashSet<>(Arrays.asList("$REPL00DOESNOTMATTER")));
|
||||
}
|
||||
|
||||
public void testSmartCompletion() {
|
||||
assertEval("int ccTest1 = 0;");
|
||||
assertEval("int ccTest2 = 0;");
|
||||
assertEval("String ccTest3 = null;");
|
||||
assertEval("void method(int i, String str) { }");
|
||||
assertEval("void method(String str, int i) { }");
|
||||
assertEval("java.util.List<String> list = null;");
|
||||
assertCompletion("int ccTest4 = |", true, "ccTest1", "ccTest2");
|
||||
assertCompletion("ccTest2 = |", true, "ccTest1", "ccTest2");
|
||||
assertCompletion("int ccTest4 = ccTe|", "ccTest1", "ccTest2", "ccTest3");
|
||||
assertCompletion("int ccTest4 = ccTest3.len|", true, "length()");
|
||||
assertCompletion("method(|", true, "ccTest1", "ccTest2", "ccTest3");
|
||||
assertCompletion("method(0, |", true, "ccTest3");
|
||||
assertCompletion("list.add(|", true, "ccTest1", "ccTest2", "ccTest3");
|
||||
assertCompletion("list.add(0, |", true, "ccTest3");
|
||||
assertCompletion("new String(|", true, "ccTest3");
|
||||
assertCompletion("new String(new char[0], |", true, "ccTest1", "ccTest2");
|
||||
assertCompletionIncludesExcludes("new jav|", new HashSet<>(Arrays.asList("java", "javax")), Collections.emptySet());
|
||||
assertCompletion("Class<String> clazz = String.c|", true, "class");
|
||||
|
||||
Snippet klass = classKey(assertEval("class Klass {void method(int n) {} private void method(String str) {}}"));
|
||||
assertCompletion("new Klass().method(|", true, "ccTest1", "ccTest2");
|
||||
Snippet klass2 = classKey(assertEval("class Klass {static void method(int n) {} void method(String str) {}}",
|
||||
ste(MAIN_SNIPPET, VALID, VALID, true, null),
|
||||
ste(klass, VALID, OVERWRITTEN, false, MAIN_SNIPPET)));
|
||||
assertCompletion("Klass.method(|", true, "ccTest1", "ccTest2");
|
||||
assertEval("class Klass {Klass(int n) {} private Klass(String str) {}}",
|
||||
ste(MAIN_SNIPPET, VALID, VALID, true, null),
|
||||
ste(klass2, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
|
||||
assertCompletion("new Klass(|", true, "ccTest1", "ccTest2");
|
||||
}
|
||||
|
||||
public void testSmartCompletionInOverriddenMethodInvocation() {
|
||||
assertEval("int ccTest1 = 0;");
|
||||
assertEval("int ccTest2 = 0;");
|
||||
assertEval("String ccTest3 = null;");
|
||||
assertCompletion("\"\".wait(|", true, "ccTest1", "ccTest2");
|
||||
assertEval("class Base {void method(int n) {}}");
|
||||
assertEval("class Extend extends Base {}");
|
||||
assertCompletion("new Extend().method(|", true, "ccTest1", "ccTest2");
|
||||
}
|
||||
|
||||
public void testSmartCompletionForBoxedType() {
|
||||
assertEval("int ccTest1 = 0;");
|
||||
assertEval("Integer ccTest2 = 0;");
|
||||
assertEval("Object ccTest3 = null;");
|
||||
assertEval("int method1(int n) {return n;}");
|
||||
assertEval("Integer method2(Integer n) {return n;}");
|
||||
assertEval("Object method3(Object o) {return o;}");
|
||||
assertCompletion("int ccTest4 = |", true, "ccTest1", "ccTest2", "method1(", "method2(");
|
||||
assertCompletion("Integer ccTest4 = |", true, "ccTest1", "ccTest2", "method1(", "method2(");
|
||||
assertCompletion("Object ccTest4 = |", true, "ccTest1", "ccTest2", "ccTest3", "method1(", "method2(", "method3(");
|
||||
assertCompletion("method1(|", true, "ccTest1", "ccTest2", "method1(", "method2(");
|
||||
assertCompletion("method2(|", true, "ccTest1", "ccTest2", "method1(", "method2(");
|
||||
assertCompletion("method3(|", true, "ccTest1", "ccTest2", "ccTest3", "method1(", "method2(", "method3(");
|
||||
}
|
||||
|
||||
public void testNewClass() {
|
||||
assertCompletion("String str = new Strin|", "String(", "StringBuffer(", "StringBuilder(", "StringIndexOutOfBoundsException(");
|
||||
assertCompletion("String str = new java.lang.Strin|", "String(", "StringBuffer(", "StringBuilder(", "StringIndexOutOfBoundsException(");
|
||||
assertCompletion("String str = new |", true, "String(");
|
||||
assertCompletion("String str = new java.lang.|", true, "String(");
|
||||
assertCompletion("throw new Strin|", true, "StringIndexOutOfBoundsException(");
|
||||
|
||||
assertEval("class A{class B{} class C {C(int n) {}} static class D {} interface I {}}");
|
||||
assertEval("A a;");
|
||||
assertCompletion("new A().new |", "B()", "C(");
|
||||
assertCompletion("a.new |", "B()", "C(");
|
||||
assertCompletion("new A.|", "D()");
|
||||
|
||||
assertEval("enum E{; class A {}}");
|
||||
assertEval("interface I{; class A {}}");
|
||||
assertCompletion("new E.|", "A()");
|
||||
assertCompletion("new I.|", "A()");
|
||||
assertCompletion("new String(I.A|", "A");
|
||||
}
|
||||
|
||||
public void testFullyQualified() {
|
||||
assertCompletion("Optional<String> opt = java.u|", "util");
|
||||
assertCompletionIncludesExcludes("Optional<Strings> opt = java.util.O|", new HashSet<>(Collections.singletonList("Optional")), Collections.emptySet());
|
||||
|
||||
assertEval("void method(java.util.Optional<String> opt) {}");
|
||||
assertCompletion("method(java.u|", "util");
|
||||
|
||||
assertCompletion("Object.notElement.|");
|
||||
assertCompletion("Object o = com.su|", "sun");
|
||||
}
|
||||
|
||||
public void testCheckAccessibility() {
|
||||
assertCompletion("java.util.regex.Pattern.co|", "compile(");
|
||||
}
|
||||
|
||||
public void testCompletePackages() {
|
||||
assertCompletion("java.u|", "util");
|
||||
assertCompletionIncludesExcludes("jav|", new HashSet<>(Arrays.asList("java", "javax")), Collections.emptySet());
|
||||
}
|
||||
|
||||
public void testImports() {
|
||||
assertCompletion("import java.u|", "util");
|
||||
assertCompletionIncludesExcludes("import jav|", new HashSet<>(Arrays.asList("java", "javax")), Collections.emptySet());
|
||||
assertCompletion("import static java.u|", "util");
|
||||
assertCompletionIncludesExcludes("import static jav|", new HashSet<>(Arrays.asList("java", "javax")), Collections.emptySet());
|
||||
assertCompletion("import static java.lang.Boolean.g|", "getBoolean");
|
||||
assertCompletion("import java.util.*|");
|
||||
assertCompletionIncludesExcludes("import java.lang.String.|",
|
||||
Collections.emptySet(),
|
||||
new HashSet<>(Arrays.asList("CASE_INSENSITIVE_ORDER", "copyValueOf", "format", "join", "valueOf", "class", "length")));
|
||||
assertCompletionIncludesExcludes("import static java.lang.String.|",
|
||||
new HashSet<>(Arrays.asList("CASE_INSENSITIVE_ORDER", "copyValueOf", "format", "join", "valueOf")),
|
||||
new HashSet<>(Arrays.asList("class", "length")));
|
||||
assertCompletionIncludesExcludes("import java.util.Map.|",
|
||||
new HashSet<>(Arrays.asList("Entry")),
|
||||
new HashSet<>(Arrays.asList("class")));
|
||||
}
|
||||
|
||||
public void testBrokenClassFile() throws Exception {
|
||||
Compiler compiler = new Compiler();
|
||||
Path testOutDir = Paths.get("CompletionTestBrokenClassFile");
|
||||
String input = "package test.inner; public class Test {}";
|
||||
compiler.compile(testOutDir, input);
|
||||
addToClasspath(compiler.getPath(testOutDir).resolve("test"));
|
||||
assertCompletion("import inner.|");
|
||||
}
|
||||
|
||||
public void testDocumentation() {
|
||||
assertDocumentation("System.getProperty(|",
|
||||
"java.lang.System.getProperty(java.lang.String arg0)",
|
||||
"java.lang.System.getProperty(java.lang.String arg0, java.lang.String arg1)");
|
||||
assertEval("char[] chars = null;");
|
||||
assertDocumentation("new String(chars, |",
|
||||
"java.lang.String(char[] arg0, int arg1, int arg2)");
|
||||
assertDocumentation("String.format(|",
|
||||
"java.lang.String.format(java.lang.String arg0, java.lang.Object... arg1)",
|
||||
"java.lang.String.format(java.util.Locale arg0, java.lang.String arg1, java.lang.Object... arg2)");
|
||||
assertDocumentation("\"\".getBytes(\"\"|", "java.lang.String.getBytes(int arg0, int arg1, byte[] arg2, int arg3)",
|
||||
"java.lang.String.getBytes(java.lang.String arg0)",
|
||||
"java.lang.String.getBytes(java.nio.charset.Charset arg0)");
|
||||
assertDocumentation("\"\".getBytes(\"\" |", "java.lang.String.getBytes(int arg0, int arg1, byte[] arg2, int arg3)",
|
||||
"java.lang.String.getBytes(java.lang.String arg0)",
|
||||
"java.lang.String.getBytes(java.nio.charset.Charset arg0)");
|
||||
}
|
||||
|
||||
public void testMethodsWithNoArguments() {
|
||||
assertDocumentation("System.out.println(|",
|
||||
"java.io.PrintStream.println()",
|
||||
"java.io.PrintStream.println(boolean arg0)",
|
||||
"java.io.PrintStream.println(char arg0)",
|
||||
"java.io.PrintStream.println(int arg0)",
|
||||
"java.io.PrintStream.println(long arg0)",
|
||||
"java.io.PrintStream.println(float arg0)",
|
||||
"java.io.PrintStream.println(double arg0)",
|
||||
"java.io.PrintStream.println(char[] arg0)",
|
||||
"java.io.PrintStream.println(java.lang.String arg0)",
|
||||
"java.io.PrintStream.println(java.lang.Object arg0)");
|
||||
}
|
||||
|
||||
public void testErroneous() {
|
||||
assertCompletion("Undefined.|");
|
||||
}
|
||||
|
||||
public void testClinit() {
|
||||
assertEval("enum E{;}");
|
||||
assertEval("class C{static{}}");
|
||||
assertCompletionIncludesExcludes("E.|", Collections.emptySet(), new HashSet<>(Collections.singletonList("<clinit>")));
|
||||
assertCompletionIncludesExcludes("C.|", Collections.emptySet(), new HashSet<>(Collections.singletonList("<clinit>")));
|
||||
}
|
||||
|
||||
public void testMethodHeaderContext() {
|
||||
assertCompletion("private void f(Runn|", "Runnable");
|
||||
assertCompletion("void f(Runn|", "Runnable");
|
||||
assertCompletion("void f(Object o1, Runn|", "Runnable");
|
||||
assertCompletion("void f(Object o1) throws Num|", true, "NumberFormatException");
|
||||
assertCompletion("void f(Object o1) throws java.lang.Num|", true, "NumberFormatException");
|
||||
assertEval("class HogeHoge {static class HogeHogeException extends Exception {}}");
|
||||
assertCompletion("void f(Object o1) throws Hoge|", "HogeHoge");
|
||||
assertCompletion("void f(Object o1) throws HogeHoge.|", true, "HogeHogeException");
|
||||
}
|
||||
|
||||
public void testTypeVariables() {
|
||||
assertCompletion("class A<TYPE> { public void test() { TY|", "TYPE");
|
||||
assertCompletion("class A<TYPE> { public static void test() { TY|");
|
||||
assertCompletion("class A<TYPE> { public <TYPE> void test() { TY|", "TYPE");
|
||||
assertCompletion("class A<TYPE> { public static <TYPE> void test() { TY|", "TYPE");
|
||||
}
|
||||
|
||||
public void testGeneric() {
|
||||
assertEval("import java.util.concurrent.*;");
|
||||
assertCompletion("java.util.List<Integ|", "Integer");
|
||||
assertCompletion("class A<TYPE extends Call|", "Callable");
|
||||
assertCompletion("class A<TYPE extends Callable<TY|", "TYPE");
|
||||
assertCompletion("<TYPE> void f(TY|", "TYPE");
|
||||
assertCompletion("class A<TYPE extends Callable<? sup|", "super");
|
||||
assertCompletion("class A<TYPE extends Callable<? super TY|", "TYPE");
|
||||
}
|
||||
|
||||
public void testFields() {
|
||||
assertEval("interface Interface { int field = 0; }");
|
||||
Snippet clazz = classKey(assertEval("class Clazz {" +
|
||||
"static int staticField = 0;" +
|
||||
"int field = 0;" +
|
||||
" }"));
|
||||
assertCompletion("Interface.fiel|", "field");
|
||||
assertCompletion("Clazz.staticFiel|", "staticField");
|
||||
assertCompletion("new Interface() {}.fiel|");
|
||||
assertCompletion("new Clazz().staticFiel|");
|
||||
assertCompletion("new Clazz().fiel|", "field");
|
||||
assertCompletion("new Clazz() {}.fiel|", "field");
|
||||
assertEval("class Clazz implements Interface {}",
|
||||
ste(MAIN_SNIPPET, VALID, VALID, true, null),
|
||||
ste(clazz, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
|
||||
assertCompletion("Clazz.fiel|", "field");
|
||||
assertCompletion("new Clazz().fiel|");
|
||||
assertCompletion("new Clazz() {}.fiel|");
|
||||
}
|
||||
|
||||
public void testMethods() {
|
||||
assertEval("interface Interface {" +
|
||||
"default int defaultMethod() { return 0; }" +
|
||||
"static int staticMethod() { return 0; }" +
|
||||
"}");
|
||||
Snippet clazz = classKey(assertEval("class Clazz {" +
|
||||
"static int staticMethod() { return 0; }" +
|
||||
"int method() { return 0; }" +
|
||||
"}"));
|
||||
assertCompletion("Interface.staticMeth|", "staticMethod()");
|
||||
assertCompletion("Clazz.staticMeth|", "staticMethod()");
|
||||
assertCompletion("new Interface() {}.defaultMe||", "defaultMethod()");
|
||||
assertCompletion("new Clazz().staticMeth|");
|
||||
assertCompletion("new Clazz().meth|", "method()");
|
||||
assertEval("class Clazz implements Interface {}",
|
||||
ste(MAIN_SNIPPET, VALID, VALID, true, null),
|
||||
ste(clazz, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
|
||||
assertCompletion("Clazz.staticMeth|");
|
||||
assertCompletion("new Clazz() {}.defaultM|", "defaultMethod()");
|
||||
}
|
||||
|
||||
@Test(enabled = false) // TODO 8129422
|
||||
public void testUncompletedDeclaration() {
|
||||
assertCompletion("class Clazz { Claz|", "Clazz");
|
||||
assertCompletion("class Clazz { class A extends Claz|", "Clazz");
|
||||
assertCompletion("class Clazz { Clazz clazz; Object o = cla|", "clazz");
|
||||
assertCompletion("class Clazz { static Clazz clazz; Object o = cla|", "clazz");
|
||||
assertCompletion("class Clazz { Clazz clazz; static Object o = cla|", true);
|
||||
assertCompletion("class Clazz { void method(Claz|", "Clazz");
|
||||
assertCompletion("class A { int method() { return 0; } int a = meth|", "method");
|
||||
assertCompletion("class A { int field = 0; int method() { return fiel|", "field");
|
||||
assertCompletion("class A { static int method() { return 0; } int a = meth|", "method");
|
||||
assertCompletion("class A { static int field = 0; int method() { return fiel|", "field");
|
||||
assertCompletion("class A { int method() { return 0; } static int a = meth|", true);
|
||||
assertCompletion("class A { int field = 0; static int method() { return fiel|", true);
|
||||
}
|
||||
|
||||
@Test(enabled = false) // TODO 8129421
|
||||
public void testClassDeclaration() {
|
||||
assertEval("interface Interface {}");
|
||||
assertCompletion("interface A extends Interf|", "Interface");
|
||||
assertCompletion("class A implements Interf|", "Interface");
|
||||
assertEval("class Clazz {}");
|
||||
assertCompletion("class A extends Claz|", "Clazz");
|
||||
assertCompletion("class A extends Clazz implements Interf|", "Interface");
|
||||
assertEval("interface Interface1 {}");
|
||||
assertCompletion("class A extends Clazz implements Interface, Interf|", "Interface", "Interface1");
|
||||
assertCompletion("interface A implements Claz|");
|
||||
assertCompletion("interface A implements Inter|");
|
||||
assertCompletion("class A implements Claz|", true);
|
||||
assertCompletion("class A extends Clazz implements Interface, Interf|", true, "Interface1");
|
||||
}
|
||||
|
||||
public void testDocumentationOfUserDefinedMethods() {
|
||||
assertEval("void f() {}");
|
||||
assertDocumentation("f(|", "f()");
|
||||
assertEval("void f(int a) {}");
|
||||
assertDocumentation("f(|", "f()", "f(int arg0)");
|
||||
assertEval("<T> void f(T... a) {}", DiagCheck.DIAG_WARNING, DiagCheck.DIAG_OK);
|
||||
assertDocumentation("f(|", "f()", "f(int arg0)", "f(T... arg0)");
|
||||
assertEval("class A {}");
|
||||
assertEval("void f(A a) {}");
|
||||
assertDocumentation("f(|", "f()", "f(int arg0)", "f(T... arg0)", "f(A arg0)");
|
||||
}
|
||||
|
||||
public void testDocumentationOfUserDefinedConstructors() {
|
||||
Snippet a = classKey(assertEval("class A {}"));
|
||||
assertDocumentation("new A(|", "A()");
|
||||
Snippet a2 = classKey(assertEval("class A { A() {} A(int a) {}}",
|
||||
ste(MAIN_SNIPPET, VALID, VALID, true, null),
|
||||
ste(a, VALID, OVERWRITTEN, false, MAIN_SNIPPET)));
|
||||
assertDocumentation("new A(|", "A()", "A(int arg0)");
|
||||
assertEval("class A<T> { A(T a) {} A(int a) {}}",
|
||||
ste(MAIN_SNIPPET, VALID, VALID, true, null),
|
||||
ste(a2, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
|
||||
assertDocumentation("new A(|", "A(T arg0)", "A(int arg0)");
|
||||
}
|
||||
|
||||
public void testDocumentationOfOverriddenMethods() {
|
||||
assertDocumentation("\"\".wait(|",
|
||||
"java.lang.Object.wait(long arg0)",
|
||||
"java.lang.Object.wait(long arg0, int arg1)",
|
||||
"java.lang.Object.wait()");
|
||||
assertEval("class Base {void method() {}}");
|
||||
Snippet e = classKey(assertEval("class Extend extends Base {}"));
|
||||
assertDocumentation("new Extend().method(|", "Base.method()");
|
||||
assertEval("class Extend extends Base {void method() {}}",
|
||||
ste(MAIN_SNIPPET, VALID, VALID, true, null),
|
||||
ste(e, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
|
||||
assertDocumentation("new Extend().method(|", "Extend.method()");
|
||||
}
|
||||
|
||||
public void testDocumentationOfInvisibleMethods() {
|
||||
assertDocumentation("Object.wait(|", "");
|
||||
assertDocumentation("\"\".indexOfSupplementary(|", "");
|
||||
Snippet a = classKey(assertEval("class A {void method() {}}"));
|
||||
assertDocumentation("A.method(|", "");
|
||||
assertEval("class A {private void method() {}}",
|
||||
ste(MAIN_SNIPPET, VALID, VALID, true, null),
|
||||
ste(a, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
|
||||
assertDocumentation("new A().method(|", "");
|
||||
}
|
||||
|
||||
public void testDocumentationOfInvisibleConstructors() {
|
||||
assertDocumentation("new Compiler(|", "");
|
||||
assertEval("class A { private A() {} }");
|
||||
assertDocumentation("new A(|", "");
|
||||
}
|
||||
|
||||
public void testDocumentationWithBoxing() {
|
||||
assertEval("int primitive = 0;");
|
||||
assertEval("Integer boxed = 0;");
|
||||
assertEval("Object object = null;");
|
||||
assertEval("void method(int n, Object o) { }");
|
||||
assertEval("void method(Object n, int o) { }");
|
||||
assertDocumentation("method(primitive,|",
|
||||
"method(int arg0, java.lang.Object arg1)",
|
||||
"method(java.lang.Object arg0, int arg1)");
|
||||
assertDocumentation("method(boxed,|",
|
||||
"method(int arg0, java.lang.Object arg1)",
|
||||
"method(java.lang.Object arg0, int arg1)");
|
||||
assertDocumentation("method(object,|",
|
||||
"method(java.lang.Object arg0, int arg1)");
|
||||
}
|
||||
|
||||
public void testVarArgs() {
|
||||
assertEval("int i = 0;");
|
||||
assertEval("class Foo1 { static void m(int... i) { } } ");
|
||||
assertCompletion("Foo1.m(|", true, "i");
|
||||
assertCompletion("Foo1.m(i, |", true, "i");
|
||||
assertCompletion("Foo1.m(i, i, |", true, "i");
|
||||
assertEval("class Foo2 { static void m(String s, int... i) { } } ");
|
||||
assertCompletion("Foo2.m(|", true);
|
||||
assertCompletion("Foo2.m(i, |", true);
|
||||
assertCompletion("Foo2.m(\"\", |", true, "i");
|
||||
assertCompletion("Foo2.m(\"\", i, |", true, "i");
|
||||
assertCompletion("Foo2.m(\"\", i, i, |", true, "i");
|
||||
assertEval("class Foo3 { Foo3(String s, int... i) { } } ");
|
||||
assertCompletion("new Foo3(|", true);
|
||||
assertCompletion("new Foo3(i, |", true);
|
||||
assertCompletion("new Foo3(\"\", |", true, "i");
|
||||
assertCompletion("new Foo3(\"\", i, |", true, "i");
|
||||
assertCompletion("new Foo3(\"\", i, i, |", true, "i");
|
||||
assertEval("int[] ia = null;");
|
||||
assertCompletion("Foo1.m(ia, |", true);
|
||||
assertEval("class Foo4 { static void m(int... i) { } static void m(int[] ia, String str) { } } ");
|
||||
assertEval("String str = null;");
|
||||
assertCompletion("Foo4.m(ia, |", true, "str");
|
||||
}
|
||||
|
||||
public void testConstructorAsMemberOf() {
|
||||
assertEval("class Baz<X> { Baz(X x) { } } ");
|
||||
assertEval("String str = null;");
|
||||
assertEval("Integer i = null;");
|
||||
assertCompletion("new Baz(|", true, "i", "str");
|
||||
assertCompletion("new Baz<String>(|", true, "str");
|
||||
assertCompletion("Baz<String> bz = new Baz<>(|", true, "str");
|
||||
assertEval("class Foo { static void m(String str) {} static void m(Baz<String> baz) {} }");
|
||||
assertCompletion("Foo.m(new Baz<>(|", true, "str");
|
||||
}
|
||||
}
|
||||
114
langtools/test/jdk/jshell/CustomEditor.java
Normal file
114
langtools/test/jdk/jshell/CustomEditor.java
Normal file
@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.net.Socket;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collections;
|
||||
|
||||
public class CustomEditor implements AutoCloseable {
|
||||
|
||||
public static final int SOURCE_CODE = 0;
|
||||
public static final int GET_SOURCE_CODE = 1;
|
||||
public static final int REMOVE_CODE = 2;
|
||||
|
||||
public static final int EXIT_CODE = -1;
|
||||
public static final int ACCEPT_CODE = -2;
|
||||
public static final int CANCEL_CODE = -3;
|
||||
|
||||
private final Socket socket;
|
||||
private final Path path;
|
||||
private final StringWriter writer;
|
||||
private final String source;
|
||||
|
||||
public CustomEditor(int port, String fileName) throws IOException {
|
||||
this.socket = new Socket((String) null, port);
|
||||
this.path = Paths.get(fileName);
|
||||
this.writer = new StringWriter();
|
||||
this.source = new String(Files.readAllBytes(path), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public void loop() throws IOException {
|
||||
DataInputStream input = new DataInputStream(new BufferedInputStream(socket.getInputStream()));
|
||||
DataOutputStream output = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));
|
||||
|
||||
while (true) {
|
||||
int cmd = input.readInt();
|
||||
switch (cmd) {
|
||||
case EXIT_CODE: {
|
||||
Files.write(path, Collections.singletonList(writer.toString()));
|
||||
return;
|
||||
}
|
||||
case GET_SOURCE_CODE: {
|
||||
byte[] bytes = source.getBytes(StandardCharsets.UTF_8);
|
||||
output.writeInt(bytes.length);
|
||||
output.write(bytes);
|
||||
output.flush();
|
||||
break;
|
||||
}
|
||||
case REMOVE_CODE: {
|
||||
// only for external editor
|
||||
Files.delete(path);
|
||||
break;
|
||||
}
|
||||
case CANCEL_CODE: {
|
||||
return;
|
||||
}
|
||||
case ACCEPT_CODE: {
|
||||
Files.write(path, Collections.singletonList(writer.toString()));
|
||||
break;
|
||||
}
|
||||
case SOURCE_CODE: {
|
||||
int length = input.readInt();
|
||||
byte[] bytes = new byte[length];
|
||||
input.readFully(bytes);
|
||||
writer.write(new String(bytes, StandardCharsets.UTF_8));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
if (args.length != 2) {
|
||||
System.err.println("Usage: port file");
|
||||
System.exit(1);
|
||||
}
|
||||
try (CustomEditor editor = new CustomEditor(Integer.parseInt(args[0]), args[1])) {
|
||||
editor.loop();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
socket.close();
|
||||
}
|
||||
}
|
||||
216
langtools/test/jdk/jshell/DropTest.java
Normal file
216
langtools/test/jdk/jshell/DropTest.java
Normal file
@ -0,0 +1,216 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Test of JShell#drop().
|
||||
* @build KullaTesting TestingInputStream
|
||||
* @run testng DropTest
|
||||
*/
|
||||
|
||||
import jdk.jshell.DeclarationSnippet;
|
||||
import jdk.jshell.PersistentSnippet;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static jdk.jshell.Snippet.Status.*;
|
||||
|
||||
@Test
|
||||
public class DropTest extends KullaTesting {
|
||||
|
||||
public void testDrop() {
|
||||
PersistentSnippet var = varKey(assertEval("int x;"));
|
||||
PersistentSnippet method = methodKey(assertEval("int mu() { return x * 4; }"));
|
||||
PersistentSnippet clazz = classKey(assertEval("class C { String v() { return \"#\" + mu(); } }"));
|
||||
assertDrop(var,
|
||||
ste(var, VALID, DROPPED, true, null),
|
||||
ste(method, VALID, RECOVERABLE_DEFINED, false, var));
|
||||
//assertDrop(method,
|
||||
// ste(method, RECOVERABLE_DEFINED, DROPPED, false, null),
|
||||
// ste(clazz, RECOVERABLE_NOT_DEFINED, RECOVERABLE_NOT_DEFINED, false, method));
|
||||
//assertDeclareFail("new C();", "compiler.err.cant.resolve.location");
|
||||
assertVariables();
|
||||
assertMethods();
|
||||
assertClasses();
|
||||
assertActiveKeys();
|
||||
|
||||
assertEval("int x = 10;", "10",
|
||||
ste(var, DROPPED, VALID, true, null),
|
||||
ste(method, RECOVERABLE_DEFINED, VALID, false, MAIN_SNIPPET));
|
||||
PersistentSnippet c0 = varKey(assertEval("C c0 = new C();"));
|
||||
assertEval("c0.v();", "\"#40\"");
|
||||
assertEval("C c = new C();");
|
||||
assertEval("c.v();", "\"#40\"");
|
||||
assertEval("int mu() { return x * 3; }",
|
||||
ste(MAIN_SNIPPET, VALID, VALID, false, null),
|
||||
ste(method, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
|
||||
assertEval("c.v();", "\"#30\"");
|
||||
assertEval("class C { String v() { return \"@\" + mu(); } }",
|
||||
ste(MAIN_SNIPPET, VALID, VALID, false, null),
|
||||
ste(clazz, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
|
||||
assertEval("c0.v();", "\"@30\"");
|
||||
assertDrop(c0,
|
||||
ste(c0, VALID, DROPPED, true, null));
|
||||
assertEval("c = new C();");
|
||||
assertEval("c.v();", "\"@30\"");
|
||||
|
||||
assertVariables();
|
||||
assertMethods();
|
||||
assertClasses();
|
||||
assertActiveKeys();
|
||||
}
|
||||
|
||||
@Test(enabled = false) // TODO 8081431
|
||||
public void testDropImport() {
|
||||
PersistentSnippet imp = importKey(assertEval("import java.util.*;"));
|
||||
PersistentSnippet decl = varKey(
|
||||
assertEval("List<Integer> list = Arrays.asList(1, 2, 3);", "[1, 2, 3]"));
|
||||
assertEval("list;", "[1, 2, 3]");
|
||||
assertDrop(imp,
|
||||
DiagCheck.DIAG_OK,
|
||||
DiagCheck.DIAG_ERROR,
|
||||
ste(imp, VALID, DROPPED, true, null),
|
||||
ste(decl, VALID, RECOVERABLE_NOT_DEFINED, true, imp));
|
||||
assertDeclareFail("list;", "compiler.err.cant.resolve.location");
|
||||
}
|
||||
|
||||
public void testDropVarToMethod() {
|
||||
PersistentSnippet x = varKey(assertEval("int x;"));
|
||||
DeclarationSnippet method = methodKey(assertEval("double mu() { return x * 4; }"));
|
||||
assertEval("x == 0;", "true");
|
||||
assertEval("mu() == 0.0;", "true");
|
||||
|
||||
assertDrop(x,
|
||||
ste(x, VALID, DROPPED, true, null),
|
||||
ste(method, VALID, RECOVERABLE_DEFINED, false, x));
|
||||
assertUnresolvedDependencies1(method, RECOVERABLE_DEFINED, "variable x");
|
||||
assertEvalUnresolvedException("mu();", "mu", 1, 0);
|
||||
|
||||
assertVariables();
|
||||
assertMethods();
|
||||
assertActiveKeys();
|
||||
}
|
||||
|
||||
public void testDropMethodToMethod() {
|
||||
PersistentSnippet a = methodKey(assertEval("double a() { return 2; }"));
|
||||
DeclarationSnippet b = methodKey(assertEval("double b() { return a() * 10; }"));
|
||||
assertEval("double c() { return b() * 3; }");
|
||||
DeclarationSnippet d = methodKey(assertEval("double d() { return c() + 1000; }"));
|
||||
assertEval("d();", "1060.0");
|
||||
assertDrop(a,
|
||||
ste(a, VALID, DROPPED, true, null),
|
||||
ste(b, VALID, RECOVERABLE_DEFINED, false, a));
|
||||
assertUnresolvedDependencies1(b, RECOVERABLE_DEFINED, "method a()");
|
||||
assertUnresolvedDependencies(d, 0);
|
||||
assertEvalUnresolvedException("d();", "b", 1, 0);
|
||||
assertMethods();
|
||||
assertActiveKeys();
|
||||
}
|
||||
|
||||
public void testDropClassToMethod() {
|
||||
PersistentSnippet c = classKey(assertEval("class C { int f() { return 7; } }"));
|
||||
DeclarationSnippet m = methodKey(assertEval("int m() { return new C().f(); }"));
|
||||
assertDrop(c,
|
||||
ste(c, VALID, DROPPED, true, null),
|
||||
ste(m, VALID, RECOVERABLE_DEFINED, false, c));
|
||||
assertUnresolvedDependencies1(m, RECOVERABLE_DEFINED, "class C");
|
||||
assertEvalUnresolvedException("m();", "m", 1, 0);
|
||||
assertActiveKeys();
|
||||
}
|
||||
|
||||
public void testDropVarToClass() {
|
||||
PersistentSnippet x = varKey(assertEval("int x;"));
|
||||
DeclarationSnippet a = classKey(assertEval("class A { double a = 4 * x; }"));
|
||||
assertDrop(x,
|
||||
DiagCheck.DIAG_OK,
|
||||
DiagCheck.DIAG_ERROR,
|
||||
ste(x, VALID, DROPPED, true, null),
|
||||
ste(a, VALID, RECOVERABLE_NOT_DEFINED, true, x));
|
||||
assertUnresolvedDependencies1(a, RECOVERABLE_NOT_DEFINED, "variable x");
|
||||
assertDeclareFail("new A().a;", "compiler.err.cant.resolve.location");
|
||||
assertVariables();
|
||||
assertActiveKeys();
|
||||
}
|
||||
|
||||
public void testDropMethodToClass() {
|
||||
PersistentSnippet x = methodKey(assertEval("int x() { return 0; }"));
|
||||
DeclarationSnippet a = classKey(assertEval("class A { double a = 4 * x(); }"));
|
||||
assertDrop(x,
|
||||
DiagCheck.DIAG_OK,
|
||||
DiagCheck.DIAG_ERROR,
|
||||
ste(x, VALID, DROPPED, true, null),
|
||||
ste(a, VALID, RECOVERABLE_NOT_DEFINED, true, x));
|
||||
assertUnresolvedDependencies1(a, RECOVERABLE_NOT_DEFINED, "method x()");
|
||||
assertDeclareFail("new A().a;", "compiler.err.cant.resolve.location");
|
||||
assertMethods();
|
||||
assertActiveKeys();
|
||||
}
|
||||
|
||||
public void testDropClassToClass() {
|
||||
PersistentSnippet a = classKey(assertEval("class A {}"));
|
||||
PersistentSnippet b = classKey(assertEval("class B extends A {}"));
|
||||
PersistentSnippet c = classKey(assertEval("class C extends B {}"));
|
||||
PersistentSnippet d = classKey(assertEval("class D extends C {}"));
|
||||
assertDrop(a,
|
||||
DiagCheck.DIAG_OK,
|
||||
DiagCheck.DIAG_ERROR,
|
||||
ste(a, VALID, DROPPED, true, null),
|
||||
ste(b, VALID, RECOVERABLE_NOT_DEFINED, true, a),
|
||||
ste(c, VALID, RECOVERABLE_NOT_DEFINED, true, b),
|
||||
ste(d, VALID, RECOVERABLE_NOT_DEFINED, true, c));
|
||||
assertUnresolvedDependencies1((DeclarationSnippet) b, RECOVERABLE_NOT_DEFINED, "class A");
|
||||
assertDrop(c,
|
||||
DiagCheck.DIAG_OK,
|
||||
DiagCheck.DIAG_ERROR,
|
||||
ste(c, RECOVERABLE_NOT_DEFINED, DROPPED, false, null),
|
||||
ste(d, RECOVERABLE_NOT_DEFINED, RECOVERABLE_NOT_DEFINED, false, c));
|
||||
assertEval("interface A {}", null, null,
|
||||
DiagCheck.DIAG_OK, DiagCheck.DIAG_ERROR,
|
||||
ste(a, DROPPED, VALID, true, null),
|
||||
ste(b, RECOVERABLE_NOT_DEFINED, RECOVERABLE_NOT_DEFINED, false, MAIN_SNIPPET));
|
||||
assertClasses();
|
||||
assertActiveKeys();
|
||||
}
|
||||
|
||||
public void testDropNoUpdate() {
|
||||
String as1 = "class A {}";
|
||||
String as2 = "class A extends java.util.ArrayList<Boolean> {}";
|
||||
PersistentSnippet a = classKey(assertEval(as1, added(VALID)));
|
||||
PersistentSnippet b = classKey(assertEval("class B extends A {}", added(VALID)));
|
||||
PersistentSnippet ax = classKey(assertEval(as2,
|
||||
ste(MAIN_SNIPPET, VALID, VALID, true, null),
|
||||
ste(a, VALID, OVERWRITTEN, false, MAIN_SNIPPET),
|
||||
ste(b, VALID, VALID, true, MAIN_SNIPPET)));
|
||||
ax = classKey(assertEval(as1,
|
||||
ste(MAIN_SNIPPET, VALID, VALID, true, null),
|
||||
ste(ax, VALID, OVERWRITTEN, false, MAIN_SNIPPET),
|
||||
ste(b, VALID, VALID, true, MAIN_SNIPPET)));
|
||||
assertDrop(b,
|
||||
ste(b, VALID, DROPPED, true, null));
|
||||
ax = classKey(assertEval(as2,
|
||||
ste(MAIN_SNIPPET, VALID, VALID, true, null),
|
||||
ste(ax, VALID, OVERWRITTEN, false, MAIN_SNIPPET)));
|
||||
assertEval(as1,
|
||||
ste(MAIN_SNIPPET, VALID, VALID, true, null),
|
||||
ste(ax, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
|
||||
}
|
||||
}
|
||||
233
langtools/test/jdk/jshell/EditorPadTest.java
Normal file
233
langtools/test/jdk/jshell/EditorPadTest.java
Normal file
@ -0,0 +1,233 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Testing built-in editor.
|
||||
* @ignore 8139872
|
||||
* @build ReplToolTesting EditorTestBase
|
||||
* @run testng EditorPadTest
|
||||
*/
|
||||
|
||||
import java.awt.AWTException;
|
||||
import java.awt.Component;
|
||||
import java.awt.Container;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Frame;
|
||||
import java.awt.Point;
|
||||
import java.awt.Robot;
|
||||
import java.awt.event.InputEvent;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTextArea;
|
||||
import javax.swing.JViewport;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import org.testng.annotations.AfterClass;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class EditorPadTest extends EditorTestBase {
|
||||
|
||||
private static final int DELAY = 500;
|
||||
|
||||
private static Robot robot;
|
||||
private static JFrame frame = null;
|
||||
private static JTextArea area = null;
|
||||
private static JButton cancel = null;
|
||||
private static JButton accept = null;
|
||||
private static JButton exit = null;
|
||||
|
||||
@BeforeClass
|
||||
public static void setUp() {
|
||||
try {
|
||||
robot = new Robot();
|
||||
robot.setAutoWaitForIdle(true);
|
||||
robot.setAutoDelay(DELAY);
|
||||
} catch (AWTException e) {
|
||||
throw new ExceptionInInitializerError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void shutdown() {
|
||||
executorShutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSource(String s) {
|
||||
SwingUtilities.invokeLater(() -> area.setText(s));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSource() {
|
||||
try {
|
||||
String[] s = new String[1];
|
||||
SwingUtilities.invokeAndWait(() -> s[0] = area.getText());
|
||||
return s[0];
|
||||
} catch (InvocationTargetException | InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept() {
|
||||
clickOn(accept);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exit() {
|
||||
clickOn(exit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
clickOn(cancel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdownEditor() {
|
||||
SwingUtilities.invokeLater(this::clearElements);
|
||||
waitForIdle();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShuttingDown() {
|
||||
testEditor(
|
||||
(a) -> assertEditOutput(a, "/e", "", this::shutdownEditor)
|
||||
);
|
||||
}
|
||||
|
||||
private void waitForIdle() {
|
||||
robot.waitForIdle();
|
||||
robot.delay(DELAY);
|
||||
}
|
||||
|
||||
private Future<?> task;
|
||||
@Override
|
||||
public void assertEdit(boolean after, String cmd,
|
||||
Consumer<String> checkInput, Consumer<String> checkOutput, Action action) {
|
||||
if (!after) {
|
||||
setCommandInput(cmd + "\n");
|
||||
task = getExecutor().submit(() -> {
|
||||
try {
|
||||
waitForIdle();
|
||||
SwingUtilities.invokeLater(this::seekElements);
|
||||
waitForIdle();
|
||||
checkInput.accept(getSource());
|
||||
action.accept();
|
||||
} catch (Throwable e) {
|
||||
shutdownEditor();
|
||||
if (e instanceof AssertionError) {
|
||||
throw (AssertionError) e;
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
task.get();
|
||||
waitForIdle();
|
||||
checkOutput.accept(getCommandOutput());
|
||||
} catch (ExecutionException e) {
|
||||
if (e.getCause() instanceof AssertionError) {
|
||||
throw (AssertionError) e.getCause();
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
shutdownEditor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void seekElements() {
|
||||
for (Frame f : Frame.getFrames()) {
|
||||
if (f.getTitle().contains("Edit Pad")) {
|
||||
frame = (JFrame) f;
|
||||
// workaround
|
||||
frame.setLocation(0, 0);
|
||||
Container root = frame.getContentPane();
|
||||
for (Component c : root.getComponents()) {
|
||||
if (c instanceof JScrollPane) {
|
||||
JScrollPane scrollPane = (JScrollPane) c;
|
||||
for (Component comp : scrollPane.getComponents()) {
|
||||
if (comp instanceof JViewport) {
|
||||
JViewport view = (JViewport) comp;
|
||||
area = (JTextArea) view.getComponent(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (c instanceof JPanel) {
|
||||
JPanel p = (JPanel) c;
|
||||
for (Component comp : p.getComponents()) {
|
||||
if (comp instanceof JButton) {
|
||||
JButton b = (JButton) comp;
|
||||
switch (b.getText()) {
|
||||
case "Cancel":
|
||||
cancel = b;
|
||||
break;
|
||||
case "Exit":
|
||||
exit = b;
|
||||
break;
|
||||
case "Accept":
|
||||
accept = b;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void clearElements() {
|
||||
if (frame != null) {
|
||||
frame.dispatchEvent(new WindowEvent(frame, WindowEvent.WINDOW_CLOSING));
|
||||
frame = null;
|
||||
}
|
||||
area = null;
|
||||
accept = null;
|
||||
cancel = null;
|
||||
exit = null;
|
||||
}
|
||||
|
||||
private void clickOn(JButton button) {
|
||||
waitForIdle();
|
||||
Point p = button.getLocationOnScreen();
|
||||
Dimension d = button.getSize();
|
||||
robot.mouseMove(p.x + d.width / 2, p.y + d.height / 2);
|
||||
robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
|
||||
robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
|
||||
}
|
||||
}
|
||||
273
langtools/test/jdk/jshell/EditorTestBase.java
Normal file
273
langtools/test/jdk/jshell/EditorTestBase.java
Normal file
@ -0,0 +1,273 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static org.testng.Assert.assertEquals;
|
||||
import static org.testng.Assert.assertTrue;
|
||||
|
||||
public abstract class EditorTestBase extends ReplToolTesting {
|
||||
|
||||
private static ExecutorService executor;
|
||||
|
||||
public abstract void writeSource(String s);
|
||||
public abstract String getSource();
|
||||
public abstract void accept();
|
||||
public abstract void exit();
|
||||
public abstract void cancel();
|
||||
public abstract void shutdownEditor();
|
||||
|
||||
public void testEditor(ReplTest... tests) {
|
||||
testEditor(false, new String[]{"-nostartup"}, tests);
|
||||
}
|
||||
|
||||
public void testEditor(boolean defaultStartup, String[] args, ReplTest... tests) {
|
||||
test(defaultStartup, args, tests);
|
||||
}
|
||||
|
||||
public abstract void assertEdit(boolean after, String cmd,
|
||||
Consumer<String> checkInput, Consumer<String> checkOutput, Action action);
|
||||
|
||||
public void assertEditInput(boolean after, String cmd, Consumer<String> checkInput, Action action) {
|
||||
assertEdit(after, cmd, checkInput, s -> {}, action);
|
||||
}
|
||||
|
||||
public void assertEditOutput(boolean after, String cmd, Consumer<String> checkOutput, Action action) {
|
||||
assertEdit(after, cmd, s -> {}, checkOutput, action);
|
||||
}
|
||||
|
||||
public void assertEditInput(boolean after, String cmd, String input, Action action) {
|
||||
assertEditInput(after, cmd, s -> assertEquals(s, input, "Input"), action);
|
||||
}
|
||||
|
||||
public void assertEditOutput(boolean after, String cmd, String output, Action action) {
|
||||
assertEditOutput(after, cmd, s -> assertEquals(s, output, "command"), action);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEditNegative() {
|
||||
for (String edit : new String[] {"/e", "/edit"}) {
|
||||
test(new String[]{"-nostartup"},
|
||||
a -> assertCommand(a, edit + " 1",
|
||||
"| No definition or id named 1 found. See /classes /methods /vars or /list\n"),
|
||||
a -> assertCommand(a, edit + " -1",
|
||||
"| No definition or id named -1 found. See /classes /methods /vars or /list\n"),
|
||||
a -> assertCommand(a, edit + " unknown",
|
||||
"| No definition or id named unknown found. See /classes /methods /vars or /list\n")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoNothing() {
|
||||
testEditor(
|
||||
a -> assertVariable(a, "int", "a", "0", "0"),
|
||||
a -> assertEditOutput(a, "/e 1", "", this::exit),
|
||||
a -> assertCommandCheckOutput(a, "/v", assertVariables())
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEditVariable1() {
|
||||
testEditor(
|
||||
a -> assertVariable(a, "int", "a", "0", "0"),
|
||||
a -> assertEditOutput(a, "/e 1", "| Modified variable a of type int with initial value 10\n", () -> {
|
||||
writeSource("\n\n\nint a = 10;\n\n\n");
|
||||
exit();
|
||||
loadVariable(true, "int", "a", "10", "10");
|
||||
}),
|
||||
a -> assertEditOutput(a, "/e 1", "| Modified variable a of type int with initial value 15\n", () -> {
|
||||
writeSource("int a = 15;");
|
||||
exit();
|
||||
loadVariable(true, "int", "a", "15", "15");
|
||||
}),
|
||||
a -> assertCommandCheckOutput(a, "/v", assertVariables())
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEditVariable2() {
|
||||
testEditor(
|
||||
a -> assertVariable(a, "int", "a", "0", "0"),
|
||||
a -> assertEditOutput(a, "/e 1", "| Added variable b of type int with initial value 10\n", () -> {
|
||||
writeSource("int b = 10;");
|
||||
exit();
|
||||
loadVariable(true, "int", "b", "10", "10");
|
||||
}),
|
||||
a -> assertEditOutput(a, "/e 1", "| Modified variable a of type int with initial value 15\n", () -> {
|
||||
writeSource("int a = 15;");
|
||||
exit();
|
||||
loadVariable(true, "int", "a", "15", "15");
|
||||
}),
|
||||
a -> assertCommandCheckOutput(a, "/v", assertVariables())
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEditClass1() {
|
||||
testEditor(
|
||||
a -> assertClass(a, "class A {}", "class", "A"),
|
||||
a -> assertEditOutput(a, "/e 1", "", () -> {
|
||||
writeSource("\n\n\nclass A {}\n\n\n");
|
||||
exit();
|
||||
loadClass(true, "class A {}", "class", "A");
|
||||
}),
|
||||
a -> assertEditOutput(a, "/e 1",
|
||||
"| Replaced enum A\n" +
|
||||
"| Update overwrote class A\n", () -> {
|
||||
writeSource("enum A {}");
|
||||
exit();
|
||||
loadClass(true, "enum A {}", "enum", "A");
|
||||
}),
|
||||
a -> assertCommandCheckOutput(a, "/c", assertClasses())
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEditClass2() {
|
||||
testEditor(
|
||||
a -> assertClass(a, "class A {}", "class", "A"),
|
||||
a -> assertEditOutput(a, "/e 1", "| Added class B\n", () -> {
|
||||
writeSource("class B { }");
|
||||
exit();
|
||||
loadClass(true, "class B {}", "class", "B");
|
||||
}),
|
||||
a -> assertEditOutput(a, "/e 1",
|
||||
"| Replaced enum A\n" +
|
||||
"| Update overwrote class A\n", () -> {
|
||||
writeSource("enum A {}");
|
||||
exit();
|
||||
loadClass(true, "enum A {}", "enum", "A");
|
||||
}),
|
||||
a -> assertCommandCheckOutput(a, "/c", assertClasses())
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEditMethod1() {
|
||||
testEditor(
|
||||
a -> assertMethod(a, "void f() {}", "()void", "f"),
|
||||
a -> assertEditOutput(a, "/e 1", "", () -> {
|
||||
writeSource("\n\n\nvoid f() {}\n\n\n");
|
||||
exit();
|
||||
loadMethod(true, "void f() {}", "()void", "f");
|
||||
}),
|
||||
a -> assertEditOutput(a, "/e 1",
|
||||
"| Replaced method f()\n" +
|
||||
"| Update overwrote method f()\n", () -> {
|
||||
writeSource("double f() { return 0; }");
|
||||
exit();
|
||||
loadMethod(true, "double f() { return 0; }", "()double", "f");
|
||||
}),
|
||||
a -> assertCommandCheckOutput(a, "/m", assertMethods())
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEditMethod2() {
|
||||
testEditor(
|
||||
a -> assertMethod(a, "void f() {}", "()void", "f"),
|
||||
a -> assertEditOutput(a, "/e 1", "| Added method g()\n", () -> {
|
||||
writeSource("void g() {}");
|
||||
exit();
|
||||
loadMethod(true, "void g() {}", "()void", "g");
|
||||
}),
|
||||
a -> assertEditOutput(a, "/e 1",
|
||||
"| Replaced method f()\n" +
|
||||
"| Update overwrote method f()\n", () -> {
|
||||
writeSource("double f() { return 0; }");
|
||||
exit();
|
||||
loadMethod(true, "double f() { return 0; }", "()double", "f");
|
||||
}),
|
||||
a -> assertCommandCheckOutput(a, "/m", assertMethods())
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoArguments() {
|
||||
testEditor(
|
||||
a -> assertVariable(a, "int", "a"),
|
||||
a -> assertMethod(a, "void f() {}", "()void", "f"),
|
||||
a -> assertClass(a, "class A {}", "class", "A"),
|
||||
a -> assertEditInput(a, "/e", s -> {
|
||||
String[] ss = s.split("\n");
|
||||
assertEquals(ss.length, 3, "Expected 3 lines: " + s);
|
||||
assertEquals(ss[0], "int a;");
|
||||
assertEquals(ss[1], "void f() {}");
|
||||
assertEquals(ss[2], "class A {}");
|
||||
}, this::exit)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartup() {
|
||||
testEditor(true, new String[0],
|
||||
a -> assertEditInput(a, "/e", s -> assertTrue(s.isEmpty(), "Checking of startup: " + s), this::cancel),
|
||||
a -> assertEditInput(a, "/e printf", assertStartsWith("void printf"), this::cancel));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCancel() {
|
||||
testEditor(
|
||||
a -> assertVariable(a, "int", "a"),
|
||||
a -> assertEditOutput(a, "/e a", "", () -> {
|
||||
writeSource("int b = 10");
|
||||
cancel();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAccept() {
|
||||
testEditor(
|
||||
a -> assertVariable(a, "int", "a"),
|
||||
a -> assertEditOutput(a, "/e a", "| Added variable b of type int with initial value 10\n", () -> {
|
||||
writeSource("int b = 10");
|
||||
accept();
|
||||
exit();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public static ExecutorService getExecutor() {
|
||||
if (executor == null) {
|
||||
executor = Executors.newSingleThreadExecutor();
|
||||
}
|
||||
return executor;
|
||||
}
|
||||
|
||||
public static void executorShutdown() {
|
||||
if (executor != null) {
|
||||
executor.shutdown();
|
||||
executor = null;
|
||||
}
|
||||
}
|
||||
|
||||
interface Action {
|
||||
void accept();
|
||||
}
|
||||
}
|
||||
59
langtools/test/jdk/jshell/EmptyTest.java
Normal file
59
langtools/test/jdk/jshell/EmptyTest.java
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary null test
|
||||
* @build KullaTesting TestingInputStream
|
||||
* @run testng EmptyTest
|
||||
*/
|
||||
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
@Test
|
||||
public class EmptyTest extends KullaTesting {
|
||||
|
||||
public void testEmpty() {
|
||||
assertEvalEmpty("");
|
||||
}
|
||||
|
||||
public void testSpace() {
|
||||
assertEvalEmpty(" ");
|
||||
}
|
||||
|
||||
public void testSemicolon() {
|
||||
assertEval(";", "");
|
||||
}
|
||||
|
||||
public void testSlashStarComment() {
|
||||
assertEvalEmpty("/*test*/");
|
||||
}
|
||||
|
||||
public void testSlashStarCommentSemicolon() {
|
||||
assertEval("/*test*/;", "");
|
||||
}
|
||||
|
||||
public void testSlashComment() {
|
||||
assertEvalEmpty("// test");
|
||||
}
|
||||
}
|
||||
162
langtools/test/jdk/jshell/ErrorTranslationTest.java
Normal file
162
langtools/test/jdk/jshell/ErrorTranslationTest.java
Normal file
@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Tests for shell error translation
|
||||
* @library /tools/lib
|
||||
* @build KullaTesting TestingInputStream ExpectedDiagnostic ToolBox Compiler
|
||||
* @run testng ErrorTranslationTest
|
||||
*/
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.tools.Diagnostic;
|
||||
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static org.testng.Assert.assertEquals;
|
||||
import static org.testng.Assert.assertTrue;
|
||||
|
||||
@Test
|
||||
public class ErrorTranslationTest extends ReplToolTesting {
|
||||
|
||||
@Test(enabled = false) // TODO 8080353
|
||||
public void testErrors() {
|
||||
test(
|
||||
a -> assertDiagnostic(a, "abstract void f();", newExpectedDiagnostic(0, 8, 0, -1, -1, Diagnostic.Kind.ERROR)),
|
||||
a -> assertDiagnostic(a, "native void f();", newExpectedDiagnostic(0, 6, 0, -1, -1, Diagnostic.Kind.ERROR)),
|
||||
a -> assertDiagnostic(a, "static void f();", newExpectedDiagnostic(0, 16, 0, -1, -1, Diagnostic.Kind.ERROR)),
|
||||
a -> assertDiagnostic(a, "synchronized void f();", newExpectedDiagnostic(0, 12, 0, -1, -1, Diagnostic.Kind.ERROR)),
|
||||
a -> assertDiagnostic(a, "default void f();", newExpectedDiagnostic(0, 7, 0, -1, -1, Diagnostic.Kind.ERROR))
|
||||
);
|
||||
}
|
||||
|
||||
public void testWarnings() {
|
||||
List<ReplTest> list = new ArrayList<>();
|
||||
ExpectedDiagnostic[] diagnostics = new ExpectedDiagnostic[]{
|
||||
newExpectedDiagnostic(0, 6, 0, -1, -1, Diagnostic.Kind.WARNING),
|
||||
newExpectedDiagnostic(0, 9, 0, -1, -1, Diagnostic.Kind.WARNING),
|
||||
newExpectedDiagnostic(0, 7, 0, -1, -1, Diagnostic.Kind.WARNING),
|
||||
newExpectedDiagnostic(0, 6, 0, -1, -1, Diagnostic.Kind.WARNING),
|
||||
newExpectedDiagnostic(0, 5, 0, -1, -1, Diagnostic.Kind.WARNING)};
|
||||
String[] mods = {"public", "protected", "private", "static", "final"};
|
||||
for (int i = 0; i < mods.length; ++i) {
|
||||
for (String code : new String[] {"class A {}", "void f() {}", "int a;"}) {
|
||||
final int finalI = i;
|
||||
list.add(a -> assertDiagnostic(a, mods[finalI] + " " + code, diagnostics[finalI]));
|
||||
}
|
||||
}
|
||||
test(list.toArray(new ReplTest[list.size()]));
|
||||
}
|
||||
|
||||
@Test(enabled = false) // TODO 8132147
|
||||
public void stressTest() {
|
||||
Compiler compiler = new Compiler();
|
||||
Path oome = compiler.getPath("OOME.repl");
|
||||
Path soe = compiler.getPath("SOE.repl");
|
||||
compiler.writeToFile(oome,
|
||||
"List<byte[]> list = new ArrayList<>();\n",
|
||||
"while (true) {\n" +
|
||||
" list.add(new byte[1000000]);\n" +
|
||||
"}");
|
||||
compiler.writeToFile(soe,
|
||||
"void f() { f(); }",
|
||||
"f();");
|
||||
List<ReplTest> tests = new ArrayList<>();
|
||||
for (int i = 0; i < 25; ++i) {
|
||||
tests.add(a -> assertCommandCheckOutput(a, "/o " + soe.toString(),
|
||||
assertStartsWith("| java.lang.StackOverflowError thrown")));
|
||||
tests.add(a -> assertCommandCheckOutput(a, "/o " + oome.toString(),
|
||||
assertStartsWith("| java.lang.OutOfMemoryError thrown: Java heap space")));
|
||||
}
|
||||
test(tests.toArray(new ReplTest[tests.size()]));
|
||||
}
|
||||
|
||||
private ExpectedDiagnostic newExpectedDiagnostic(long startPosition, long endPosition, long position,
|
||||
long lineNumber, long columnNumber, Diagnostic.Kind kind) {
|
||||
return new ExpectedDiagnostic("", startPosition, endPosition, position, lineNumber, columnNumber, kind);
|
||||
}
|
||||
|
||||
private void assertDiagnostic(boolean after, String cmd, ExpectedDiagnostic expectedDiagnostic) {
|
||||
assertCommandCheckOutput(after, cmd, assertDiagnostic(cmd, expectedDiagnostic));
|
||||
}
|
||||
|
||||
private Consumer<String> assertDiagnostic(String expectedSource, ExpectedDiagnostic expectedDiagnostic) {
|
||||
int start = (int) expectedDiagnostic.getStartPosition();
|
||||
int end = (int) expectedDiagnostic.getEndPosition();
|
||||
String expectedMarkingLine = createMarkingLine(start, end);
|
||||
return s -> {
|
||||
String[] lines = s.split("\n");
|
||||
if (lines.length <= 3) {
|
||||
throw new AssertionError("Not enough lines: " + s);
|
||||
}
|
||||
String kind = getKind(expectedDiagnostic.getKind());
|
||||
assertEquals(lines[0], kind);
|
||||
String source;
|
||||
String markingLine;
|
||||
switch (expectedDiagnostic.getKind()) {
|
||||
case ERROR:
|
||||
case WARNING:
|
||||
source = lines[2];
|
||||
markingLine = lines[3];
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unsupported diagnostic kind: " + expectedDiagnostic.getKind());
|
||||
}
|
||||
assertTrue(source.endsWith(expectedSource), "Expected: " + expectedSource + ", found: " + source);
|
||||
assertEquals(markingLine, expectedMarkingLine, "Input: " + expectedSource + ", marking line: ");
|
||||
};
|
||||
}
|
||||
|
||||
private String createMarkingLine(int start, int end) {
|
||||
assertTrue(end >= start, String.format("End position %d is less than start position %d", end, start));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("| ");
|
||||
for (int i = 0; i < start; ++i) {
|
||||
sb.append(' ');
|
||||
}
|
||||
sb.append('^');
|
||||
for (int i = 1; i < end - start - 1; ++i) {
|
||||
sb.append('-');
|
||||
}
|
||||
if (start < end) {
|
||||
sb.append('^');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String getKind(Diagnostic.Kind kind) {
|
||||
switch (kind) {
|
||||
case WARNING:
|
||||
return "| Warning:";
|
||||
case ERROR:
|
||||
return "| Error:";
|
||||
default:
|
||||
throw new AssertionError("Unsupported kind: " + kind);
|
||||
}
|
||||
}
|
||||
}
|
||||
244
langtools/test/jdk/jshell/ExceptionsTest.java
Normal file
244
langtools/test/jdk/jshell/ExceptionsTest.java
Normal file
@ -0,0 +1,244 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Tests for exceptions
|
||||
* @build KullaTesting TestingInputStream
|
||||
* @run testng ExceptionsTest
|
||||
*/
|
||||
|
||||
import jdk.jshell.SnippetEvent;
|
||||
import jdk.jshell.EvalException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
|
||||
import jdk.jshell.Snippet;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static org.testng.Assert.*;
|
||||
|
||||
@Test
|
||||
public class ExceptionsTest extends KullaTesting {
|
||||
|
||||
public void throwUncheckedException() {
|
||||
String message = "error_message";
|
||||
SnippetEvent cr = assertEvalException("throw new RuntimeException(\"" + message + "\");");
|
||||
assertExceptionMatch(cr,
|
||||
new ExceptionInfo(RuntimeException.class, message,
|
||||
newStackTraceElement("", "", cr.snippet(), 1)));
|
||||
}
|
||||
|
||||
public void throwCheckedException() {
|
||||
String message = "error_message";
|
||||
SnippetEvent cr = assertEvalException("throw new Exception(\"" + message + "\");");
|
||||
assertExceptionMatch(cr,
|
||||
new ExceptionInfo(Exception.class, message,
|
||||
newStackTraceElement("", "", cr.snippet(), 1)));
|
||||
}
|
||||
|
||||
public void throwFromStaticMethodOfClass() {
|
||||
String message = "error_message";
|
||||
Snippet s1 = methodKey(assertEval("void f() { throw new RuntimeException(\"" + message + "\"); }"));
|
||||
Snippet s2 = classKey(assertEval("class A { static void g() { f(); } }"));
|
||||
SnippetEvent cr3 = assertEvalException("A.g();");
|
||||
assertExceptionMatch(cr3,
|
||||
new ExceptionInfo(RuntimeException.class, message,
|
||||
newStackTraceElement("", "f", s1, 1),
|
||||
newStackTraceElement("A", "g", s2, 1),
|
||||
newStackTraceElement("", "", cr3.snippet(), 1)));
|
||||
}
|
||||
|
||||
public void throwFromStaticMethodOfInterface() {
|
||||
String message = "error_message";
|
||||
Snippet s1 = methodKey(assertEval("void f() { throw new RuntimeException(\"" + message + "\"); }"));
|
||||
Snippet s2 = classKey(assertEval("interface A { static void g() { f(); } }"));
|
||||
SnippetEvent cr3 = assertEvalException("A.g();");
|
||||
assertExceptionMatch(cr3,
|
||||
new ExceptionInfo(RuntimeException.class, message,
|
||||
newStackTraceElement("", "f", s1, 1),
|
||||
newStackTraceElement("A", "g", s2, 1),
|
||||
newStackTraceElement("", "", cr3.snippet(), 1)));
|
||||
}
|
||||
|
||||
public void throwFromConstructor() {
|
||||
String message = "error_message";
|
||||
Snippet s1 = methodKey(assertEval("void f() { throw new RuntimeException(\"" + message + "\"); }"));
|
||||
Snippet s2 = classKey(assertEval("class A { A() { f(); } }"));
|
||||
SnippetEvent cr3 = assertEvalException("new A();");
|
||||
assertExceptionMatch(cr3,
|
||||
new ExceptionInfo(RuntimeException.class, message,
|
||||
newStackTraceElement("", "f", s1, 1),
|
||||
newStackTraceElement("A", "<init>", s2, 1),
|
||||
newStackTraceElement("", "", cr3.snippet(), 1)));
|
||||
}
|
||||
|
||||
public void throwFromDefaultMethodOfInterface() {
|
||||
String message = "error_message";
|
||||
Snippet s1 = methodKey(assertEval("void f() { throw new RuntimeException(\"" + message + "\"); }"));
|
||||
Snippet s2 = classKey(assertEval("interface A { default void g() { f(); } }"));
|
||||
SnippetEvent cr3 = assertEvalException("new A() { }.g();");
|
||||
assertExceptionMatch(cr3,
|
||||
new ExceptionInfo(RuntimeException.class, message,
|
||||
newStackTraceElement("", "f", s1, 1),
|
||||
newStackTraceElement("A", "g", s2, 1),
|
||||
newStackTraceElement("", "", cr3.snippet(), 1)));
|
||||
}
|
||||
|
||||
public void throwFromLambda() {
|
||||
String message = "lambda";
|
||||
Snippet s1 = varKey(assertEval(
|
||||
"Runnable run = () -> {\n" +
|
||||
" throw new RuntimeException(\"" + message + "\");\n" +
|
||||
"};"
|
||||
));
|
||||
SnippetEvent cr2 = assertEvalException("run.run();");
|
||||
assertExceptionMatch(cr2,
|
||||
new ExceptionInfo(RuntimeException.class, message,
|
||||
newStackTraceElement("", "lambda$", s1, 2),
|
||||
newStackTraceElement("", "", cr2.snippet(), 1)));
|
||||
}
|
||||
|
||||
public void throwFromAnonymousClass() {
|
||||
String message = "anonymous";
|
||||
Snippet s1 = varKey(assertEval(
|
||||
"Runnable run = new Runnable() {\n" +
|
||||
" public void run() {\n"+
|
||||
" throw new RuntimeException(\"" + message + "\");\n" +
|
||||
" }\n" +
|
||||
"};"
|
||||
));
|
||||
SnippetEvent cr2 = assertEvalException("run.run();");
|
||||
assertExceptionMatch(cr2,
|
||||
new ExceptionInfo(RuntimeException.class, message,
|
||||
newStackTraceElement("1", "run", s1, 3),
|
||||
newStackTraceElement("", "", cr2.snippet(), 1)));
|
||||
}
|
||||
|
||||
public void throwFromLocalClass() {
|
||||
String message = "local";
|
||||
Snippet s1 = methodKey(assertEval(
|
||||
"void f() {\n" +
|
||||
" class A {\n" +
|
||||
" void f() {\n"+
|
||||
" throw new RuntimeException(\"" + message + "\");\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" new A().f();\n" +
|
||||
"}"
|
||||
));
|
||||
SnippetEvent cr2 = assertEvalException("f();");
|
||||
assertExceptionMatch(cr2,
|
||||
new ExceptionInfo(RuntimeException.class, message,
|
||||
newStackTraceElement("1A", "f", s1, 4),
|
||||
newStackTraceElement("", "f", s1, 7),
|
||||
newStackTraceElement("", "", cr2.snippet(), 1)));
|
||||
}
|
||||
|
||||
@Test(enabled = false) // TODO 8129427
|
||||
public void outOfMemory() {
|
||||
assertEval("import java.util.*;");
|
||||
assertEval("List<byte[]> list = new ArrayList<>();");
|
||||
assertExecuteException("while (true) { list.add(new byte[10000]); }", OutOfMemoryError.class);
|
||||
}
|
||||
|
||||
public void stackOverflow() {
|
||||
assertEval("void f() { f(); }");
|
||||
assertExecuteException("f();", StackOverflowError.class);
|
||||
}
|
||||
|
||||
private StackTraceElement newStackTraceElement(String className, String methodName, Snippet key, int lineNumber) {
|
||||
return new StackTraceElement(className, methodName, "#" + key.id(), lineNumber);
|
||||
}
|
||||
|
||||
private static class ExceptionInfo {
|
||||
public final Class<? extends Throwable> exception;
|
||||
public final String message;
|
||||
public final StackTraceElement[] stackTraceElements;
|
||||
|
||||
public ExceptionInfo(Class<? extends Throwable> exception, String message, StackTraceElement...stackTraceElements) {
|
||||
this.exception = exception;
|
||||
this.message = message;
|
||||
this.stackTraceElements = stackTraceElements.length == 0 ? null : stackTraceElements;
|
||||
}
|
||||
}
|
||||
|
||||
private void assertExecuteException(String input, Class<? extends Throwable> exception) {
|
||||
assertExceptionMatch(assertEvalException(input), new ExceptionInfo(exception, null));
|
||||
}
|
||||
|
||||
private void assertExceptionMatch(SnippetEvent cr, ExceptionInfo exceptionInfo) {
|
||||
assertNotNull(cr.exception(), "Expected exception was not thrown: " + exceptionInfo.exception);
|
||||
if (cr.exception() instanceof EvalException) {
|
||||
EvalException ex = (EvalException) cr.exception();
|
||||
String actualException = ex.getExceptionClassName();
|
||||
String expectedException = exceptionInfo.exception.getCanonicalName();
|
||||
String stackTrace = getStackTrace(ex);
|
||||
String source = cr.snippet().source();
|
||||
assertEquals(actualException, expectedException,
|
||||
String.format("Given \"%s\" expected exception: %s, got: %s%nStack trace:%n%s",
|
||||
source, expectedException, actualException, stackTrace));
|
||||
if (exceptionInfo.message != null) {
|
||||
assertEquals(ex.getMessage(), exceptionInfo.message,
|
||||
String.format("Given \"%s\" expected message: %s, got: %s",
|
||||
source, exceptionInfo.message, ex.getMessage()));
|
||||
}
|
||||
if (exceptionInfo.stackTraceElements != null) {
|
||||
assertStackTrace(ex.getStackTrace(), exceptionInfo.stackTraceElements,
|
||||
String.format("Given \"%s\"%nStack trace:%n%s%n",
|
||||
source, stackTrace));
|
||||
}
|
||||
} else {
|
||||
fail("Unexpected execution exceptionInfo: " + cr.exception());
|
||||
}
|
||||
}
|
||||
|
||||
private void assertStackTrace(StackTraceElement[] actual, StackTraceElement[] expected, String message) {
|
||||
if (actual != expected) {
|
||||
if (actual == null || expected == null) {
|
||||
fail(message);
|
||||
} else {
|
||||
assertEquals(actual.length, expected.length, message + " : arrays do not have the same size");
|
||||
for (int i = 0; i < actual.length; ++i) {
|
||||
StackTraceElement actualElement = actual[i];
|
||||
StackTraceElement expectedElement = expected[i];
|
||||
assertEquals(actualElement.getClassName(), expectedElement.getClassName(), message + " : class names");
|
||||
String expectedMethodName = expectedElement.getMethodName();
|
||||
if (expectedMethodName.startsWith("lambda$")) {
|
||||
assertTrue(actualElement.getMethodName().startsWith("lambda$"), message + " : method names");
|
||||
} else {
|
||||
assertEquals(actualElement.getMethodName(), expectedElement.getMethodName(), message + " : method names");
|
||||
}
|
||||
assertEquals(actualElement.getFileName(), expectedElement.getFileName(), message + " : file names");
|
||||
assertEquals(actualElement.getLineNumber(), expectedElement.getLineNumber(), message + " : line numbers");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getStackTrace(EvalException ex) {
|
||||
StringWriter st = new StringWriter();
|
||||
ex.printStackTrace(new PrintWriter(st));
|
||||
return st.toString();
|
||||
}
|
||||
}
|
||||
94
langtools/test/jdk/jshell/ExpectedDiagnostic.java
Normal file
94
langtools/test/jdk/jshell/ExpectedDiagnostic.java
Normal file
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import javax.tools.Diagnostic;
|
||||
|
||||
import jdk.jshell.Diag;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
public class ExpectedDiagnostic {
|
||||
|
||||
private final String code;
|
||||
private final long startPosition;
|
||||
private final long endPosition;
|
||||
private final long position;
|
||||
private final long lineNumber;
|
||||
private final long columnNumber;
|
||||
private final Diagnostic.Kind kind;
|
||||
|
||||
public ExpectedDiagnostic(
|
||||
String code, long startPosition, long endPosition,
|
||||
long position, long lineNumber, long columnNumber,
|
||||
Diagnostic.Kind kind) {
|
||||
this.code = code;
|
||||
this.startPosition = startPosition;
|
||||
this.endPosition = endPosition;
|
||||
this.position = position;
|
||||
this.lineNumber = lineNumber;
|
||||
this.columnNumber = columnNumber;
|
||||
this.kind = kind;
|
||||
}
|
||||
|
||||
public long getStartPosition() {
|
||||
return startPosition;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public long getEndPosition() {
|
||||
return endPosition;
|
||||
}
|
||||
|
||||
public long getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public long getLineNumber() {
|
||||
return lineNumber;
|
||||
}
|
||||
|
||||
public long getColumnNumber() {
|
||||
return columnNumber;
|
||||
}
|
||||
|
||||
public Diagnostic.Kind getKind() {
|
||||
return kind;
|
||||
}
|
||||
|
||||
public void assertDiagnostic(Diag diagnostic) {
|
||||
String code = diagnostic.getCode();
|
||||
assertEquals(code, this.code, "Expected error: " + this.code + ", got: " + code);
|
||||
assertEquals(diagnostic.isError(), kind == Diagnostic.Kind.ERROR);
|
||||
if (startPosition != -1) {
|
||||
assertEquals(diagnostic.getStartPosition(), startPosition, "Start position");
|
||||
}
|
||||
if (endPosition != -1) {
|
||||
assertEquals(diagnostic.getEndPosition(), endPosition, "End position");
|
||||
}
|
||||
if (position != -1) {
|
||||
assertEquals(diagnostic.getPosition(), position, "Position");
|
||||
}
|
||||
}
|
||||
}
|
||||
225
langtools/test/jdk/jshell/ExternalEditorTest.java
Normal file
225
langtools/test/jdk/jshell/ExternalEditorTest.java
Normal file
@ -0,0 +1,225 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Testing external editor.
|
||||
* @bug 8080843
|
||||
* @ignore 8080843
|
||||
* @build ReplToolTesting CustomEditor EditorTestBase
|
||||
* @run testng ExternalEditorTest
|
||||
*/
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.testng.annotations.AfterClass;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static org.testng.Assert.fail;
|
||||
|
||||
public class ExternalEditorTest extends EditorTestBase {
|
||||
|
||||
private static Path executionScript;
|
||||
private static ServerSocket listener;
|
||||
|
||||
private DataInputStream inputStream;
|
||||
private DataOutputStream outputStream;
|
||||
|
||||
@Override
|
||||
public void writeSource(String s) {
|
||||
try {
|
||||
outputStream.writeInt(CustomEditor.SOURCE_CODE);
|
||||
byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
|
||||
outputStream.writeInt(bytes.length);
|
||||
outputStream.write(bytes);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSource() {
|
||||
try {
|
||||
outputStream.writeInt(CustomEditor.GET_SOURCE_CODE);
|
||||
int length = inputStream.readInt();
|
||||
byte[] bytes = new byte[length];
|
||||
inputStream.readFully(bytes);
|
||||
return new String(bytes, StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendCode(int code) {
|
||||
try {
|
||||
outputStream.writeInt(code);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept() {
|
||||
sendCode(CustomEditor.ACCEPT_CODE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exit() {
|
||||
sendCode(CustomEditor.EXIT_CODE);
|
||||
inputStream = null;
|
||||
outputStream = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
sendCode(CustomEditor.CANCEL_CODE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testEditor(boolean defaultStartup, String[] args, ReplTest... tests) {
|
||||
ReplTest[] t = new ReplTest[tests.length + 1];
|
||||
t[0] = a -> assertCommandCheckOutput(a, "/seteditor " + executionScript,
|
||||
assertStartsWith("| Editor set to: " + executionScript));
|
||||
System.arraycopy(tests, 0, t, 1, tests.length);
|
||||
super.testEditor(defaultStartup, args, t);
|
||||
}
|
||||
|
||||
private static boolean isWindows() {
|
||||
return System.getProperty("os.name").startsWith("Windows");
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void setUp() throws IOException {
|
||||
listener = new ServerSocket(0);
|
||||
listener.setSoTimeout(30000);
|
||||
int localPort = listener.getLocalPort();
|
||||
|
||||
executionScript = Paths.get(isWindows() ? "editor.bat" : "editor.sh").toAbsolutePath();
|
||||
Path java = Paths.get(System.getProperty("java.home")).resolve("bin").resolve("java");
|
||||
try (BufferedWriter writer = Files.newBufferedWriter(executionScript)) {
|
||||
if(!isWindows()) {
|
||||
writer.append(java.toString()).append(" ")
|
||||
.append(" -cp ").append(System.getProperty("java.class.path"))
|
||||
.append(" CustomEditor ").append(Integer.toString(localPort)).append(" $@");
|
||||
executionScript.toFile().setExecutable(true);
|
||||
} else {
|
||||
writer.append(java.toString()).append(" ")
|
||||
.append(" -cp ").append(System.getProperty("java.class.path"))
|
||||
.append(" CustomEditor ").append(Integer.toString(localPort)).append(" %*");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Future<?> task;
|
||||
@Override
|
||||
public void assertEdit(boolean after, String cmd,
|
||||
Consumer<String> checkInput, Consumer<String> checkOutput, Action action) {
|
||||
if (!after) {
|
||||
setCommandInput(cmd + "\n");
|
||||
task = getExecutor().submit(() -> {
|
||||
try (Socket socket = listener.accept()) {
|
||||
inputStream = new DataInputStream(socket.getInputStream());
|
||||
outputStream = new DataOutputStream(socket.getOutputStream());
|
||||
checkInput.accept(getSource());
|
||||
action.accept();
|
||||
} catch (SocketTimeoutException e) {
|
||||
fail("Socket timeout exception.\n Output: " + getCommandOutput() +
|
||||
"\n, error: " + getCommandErrorOutput());
|
||||
} catch (Throwable e) {
|
||||
shutdownEditor();
|
||||
if (e instanceof AssertionError) {
|
||||
throw (AssertionError) e;
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
task.get();
|
||||
checkOutput.accept(getCommandOutput());
|
||||
} catch (ExecutionException e) {
|
||||
if (e.getCause() instanceof AssertionError) {
|
||||
throw (AssertionError) e.getCause();
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdownEditor() {
|
||||
if (outputStream != null) {
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setUnknownEditor() {
|
||||
test(
|
||||
a -> assertCommand(a, "/seteditor", "| /seteditor requires a path argument\n"),
|
||||
a -> assertCommand(a, "/seteditor UNKNOWN", "| Editor set to: UNKNOWN\n"),
|
||||
a -> assertCommand(a, "int a;", null),
|
||||
a -> assertCommand(a, "/e 1",
|
||||
"| Edit Error: process IO failure: Cannot run program \"UNKNOWN\": error=2, No such file or directory\n")
|
||||
);
|
||||
}
|
||||
|
||||
@Test(enabled = false)
|
||||
public void testRemoveTempFile() {
|
||||
test(new String[]{"-nostartup"},
|
||||
a -> assertCommandCheckOutput(a, "/seteditor " + executionScript,
|
||||
assertStartsWith("| Editor set to: " + executionScript)),
|
||||
a -> assertVariable(a, "int", "a", "0", "0"),
|
||||
a -> assertEditOutput(a, "/e 1", assertStartsWith("| Edit Error: Failure read edit file:"), () -> {
|
||||
sendCode(CustomEditor.REMOVE_CODE);
|
||||
exit();
|
||||
}),
|
||||
a -> assertCommandCheckOutput(a, "/v", assertVariables())
|
||||
);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void shutdown() throws IOException {
|
||||
executorShutdown();
|
||||
if (listener != null) {
|
||||
listener.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
245
langtools/test/jdk/jshell/HistoryTest.java
Normal file
245
langtools/test/jdk/jshell/HistoryTest.java
Normal file
@ -0,0 +1,245 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Test Completion
|
||||
* @build HistoryTest
|
||||
* @run testng HistoryTest
|
||||
*/
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.prefs.AbstractPreferences;
|
||||
import java.util.prefs.BackingStoreException;
|
||||
import jdk.internal.jline.console.history.MemoryHistory;
|
||||
|
||||
import jdk.jshell.JShell;
|
||||
import jdk.jshell.SourceCodeAnalysis;
|
||||
import jdk.jshell.SourceCodeAnalysis.CompletionInfo;
|
||||
import org.testng.annotations.Test;
|
||||
import jdk.internal.jshell.tool.EditingHistory;
|
||||
|
||||
import static org.testng.Assert.*;
|
||||
|
||||
@Test
|
||||
public class HistoryTest {
|
||||
|
||||
public void testHistory() {
|
||||
JShell eval = JShell.builder()
|
||||
.in(new ByteArrayInputStream(new byte[0]))
|
||||
.out(new PrintStream(new ByteArrayOutputStream()))
|
||||
.err(new PrintStream(new ByteArrayOutputStream()))
|
||||
.build();
|
||||
SourceCodeAnalysis analysis = eval.sourceCodeAnalysis();
|
||||
MemoryPreferences prefs = new MemoryPreferences(null, "");
|
||||
EditingHistory history = new EditingHistory(prefs) {
|
||||
@Override protected CompletionInfo analyzeCompletion(String input) {
|
||||
return analysis.analyzeCompletion(input);
|
||||
}
|
||||
};
|
||||
history.add("void test() {");
|
||||
history.add(" System.err.println(1);");
|
||||
history.add("}");
|
||||
history.add("/exit");
|
||||
|
||||
previousAndAssert(history, "/exit");
|
||||
|
||||
history.previous(); history.previous(); history.previous();
|
||||
|
||||
history.add("void test() { /*changed*/");
|
||||
|
||||
previousAndAssert(history, "}");
|
||||
previousAndAssert(history, " System.err.println(1);");
|
||||
previousAndAssert(history, "void test() {");
|
||||
|
||||
assertFalse(history.previous());
|
||||
|
||||
nextAndAssert(history, " System.err.println(1);");
|
||||
nextAndAssert(history, "}");
|
||||
nextAndAssert(history, "");
|
||||
|
||||
history.add(" System.err.println(2);");
|
||||
history.add("} /*changed*/");
|
||||
|
||||
assertEquals(history.size(), 7);
|
||||
|
||||
history.save();
|
||||
|
||||
history = new EditingHistory(prefs) {
|
||||
@Override protected CompletionInfo analyzeCompletion(String input) {
|
||||
return analysis.analyzeCompletion(input);
|
||||
}
|
||||
};
|
||||
|
||||
previousSnippetAndAssert(history, "void test() { /*changed*/");
|
||||
previousSnippetAndAssert(history, "/exit");
|
||||
previousSnippetAndAssert(history, "void test() {");
|
||||
|
||||
assertFalse(history.previousSnippet());
|
||||
|
||||
nextSnippetAndAssert(history, "/exit");
|
||||
nextSnippetAndAssert(history, "void test() { /*changed*/");
|
||||
nextSnippetAndAssert(history, "");
|
||||
|
||||
assertFalse(history.nextSnippet());
|
||||
|
||||
history.add("{");
|
||||
history.add("}");
|
||||
|
||||
history.save();
|
||||
|
||||
history = new EditingHistory(prefs) {
|
||||
@Override protected CompletionInfo analyzeCompletion(String input) {
|
||||
return analysis.analyzeCompletion(input);
|
||||
}
|
||||
};
|
||||
|
||||
previousSnippetAndAssert(history, "{");
|
||||
previousSnippetAndAssert(history, "void test() { /*changed*/");
|
||||
previousSnippetAndAssert(history, "/exit");
|
||||
previousSnippetAndAssert(history, "void test() {");
|
||||
|
||||
while (history.next());
|
||||
|
||||
history.add("/*current1*/");
|
||||
history.add("/*current2*/");
|
||||
history.add("/*current3*/");
|
||||
|
||||
assertEquals(history.currentSessionEntries(), Arrays.asList("/*current1*/", "/*current2*/", "/*current3*/"));
|
||||
|
||||
history.remove(0);
|
||||
|
||||
assertEquals(history.currentSessionEntries(), Arrays.asList("/*current1*/", "/*current2*/", "/*current3*/"));
|
||||
|
||||
while (history.size() > 2)
|
||||
history.remove(0);
|
||||
|
||||
assertEquals(history.currentSessionEntries(), Arrays.asList("/*current2*/", "/*current3*/"));
|
||||
|
||||
for (int i = 0; i < MemoryHistory.DEFAULT_MAX_SIZE * 2; i++) {
|
||||
history.add("/exit");
|
||||
}
|
||||
|
||||
history.add("void test() { /*after full*/");
|
||||
history.add(" System.err.println(1);");
|
||||
history.add("}");
|
||||
|
||||
previousSnippetAndAssert(history, "void test() { /*after full*/");
|
||||
}
|
||||
|
||||
public void testSaveOneHistory() {
|
||||
JShell eval = JShell.builder()
|
||||
.in(new ByteArrayInputStream(new byte[0]))
|
||||
.out(new PrintStream(new ByteArrayOutputStream()))
|
||||
.err(new PrintStream(new ByteArrayOutputStream()))
|
||||
.build();
|
||||
SourceCodeAnalysis analysis = eval.sourceCodeAnalysis();
|
||||
MemoryPreferences prefs = new MemoryPreferences(null, "");
|
||||
EditingHistory history = new EditingHistory(prefs) {
|
||||
@Override protected CompletionInfo analyzeCompletion(String input) {
|
||||
return analysis.analyzeCompletion(input);
|
||||
}
|
||||
};
|
||||
|
||||
history.add("first");
|
||||
history.save();
|
||||
}
|
||||
|
||||
private void previousAndAssert(EditingHistory history, String expected) {
|
||||
assertTrue(history.previous());
|
||||
assertEquals(history.current().toString(), expected);
|
||||
}
|
||||
|
||||
private void nextAndAssert(EditingHistory history, String expected) {
|
||||
assertTrue(history.next());
|
||||
assertEquals(history.current().toString(), expected);
|
||||
}
|
||||
|
||||
private void previousSnippetAndAssert(EditingHistory history, String expected) {
|
||||
assertTrue(history.previousSnippet());
|
||||
assertEquals(history.current().toString(), expected);
|
||||
}
|
||||
|
||||
private void nextSnippetAndAssert(EditingHistory history, String expected) {
|
||||
assertTrue(history.nextSnippet());
|
||||
assertEquals(history.current().toString(), expected);
|
||||
}
|
||||
|
||||
private static final class MemoryPreferences extends AbstractPreferences {
|
||||
|
||||
private final Map<String, String> key2Value = new HashMap<>();
|
||||
private final Map<String, MemoryPreferences> key2SubNode = new HashMap<>();
|
||||
|
||||
public MemoryPreferences(AbstractPreferences parent, String name) {
|
||||
super(parent, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void putSpi(String key, String value) {
|
||||
key2Value.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSpi(String key) {
|
||||
return key2Value.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeSpi(String key) {
|
||||
key2Value.remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeNodeSpi() throws BackingStoreException {
|
||||
((MemoryPreferences) parent()).key2SubNode.remove(name());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] keysSpi() throws BackingStoreException {
|
||||
return key2Value.keySet().toArray(new String[key2Value.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] childrenNamesSpi() throws BackingStoreException {
|
||||
return key2SubNode.keySet().toArray(new String[key2SubNode.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractPreferences childSpi(String name) {
|
||||
return key2SubNode.computeIfAbsent(name, n -> new MemoryPreferences(this, n));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void syncSpi() throws BackingStoreException {}
|
||||
|
||||
@Override
|
||||
protected void flushSpi() throws BackingStoreException {}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user