This commit is contained in:
Lana Steuck 2015-10-21 18:40:01 -07:00
commit 574f2d4b2d
136 changed files with 25452 additions and 233 deletions

View File

@ -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

View File

@ -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}"/>

View 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)

View File

@ -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" />

View File

@ -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" />

View File

@ -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}

View File

@ -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 . -->

View File

@ -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>

View 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);
}
}
}

View File

@ -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;

View File

@ -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.

View File

@ -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.

View File

@ -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()));
}
}

View File

@ -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));

View File

@ -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.

View File

@ -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);

View File

@ -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) {

View File

@ -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);

View File

@ -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);
}

View File

@ -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;

View File

@ -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;

View File

@ -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) {

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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 + "[\\$\\.]?");
}

View File

@ -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;
}
}

View File

@ -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));
}
}
}

View File

@ -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);
}
}

View File

@ -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 {}
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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
}
}

View File

@ -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)

View File

@ -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);
}
}

View File

@ -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;
}
}
}
}

View File

@ -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";
}
}

View 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");
}
}

View 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;
}
}

View File

@ -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);
}
}

View 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));
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}
}
}

View File

@ -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();
}
}

View File

@ -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)));
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View 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>&amp;&amp; snippet.kind() == Kind.VARIABLE</code>
* and cast to <code>VarSnippet</code>.
* @return the active declared variables.
* @throws IllegalStateException if this JShell instance is closed.
*/
public List<VarSnippet> variables() throws IllegalStateException {
return snippets().stream()
.filter(sn -> status(sn).isActive && sn.kind() == Snippet.Kind.VAR)
.map(sn -> (VarSnippet) sn)
.collect(collectingAndThen(toList(), Collections::unmodifiableList));
}
/**
* Returns the active method snippets.
* This convenience method is equivalent to <code>snippets()</code> filtered for
* {@link jdk.jshell.Snippet.Status#isActive status(snippet).isActive}
* <code>&amp;&amp; snippet.kind() == Kind.METHOD</code>
* and cast to MethodSnippet.
* @return the active declared methods.
* @throws IllegalStateException if this JShell instance is closed.
*/
public List<MethodSnippet> methods() throws IllegalStateException {
return snippets().stream()
.filter(sn -> status(sn).isActive && sn.kind() == Snippet.Kind.METHOD)
.map(sn -> (MethodSnippet)sn)
.collect(collectingAndThen(toList(), Collections::unmodifiableList));
}
/**
* Returns the active type declaration (class, interface, annotation type, and enum) snippets.
* This convenience method is equivalent to <code>snippets()</code> filtered for
* {@link jdk.jshell.Snippet.Status#isActive status(snippet).isActive}
* <code>&amp;&amp; snippet.kind() == Kind.TYPE_DECL</code>
* and cast to TypeDeclSnippet.
* @return the active declared type declarations.
* @throws IllegalStateException if this JShell instance is closed.
*/
public List<TypeDeclSnippet> types() throws IllegalStateException {
return snippets().stream()
.filter(sn -> status(sn).isActive && sn.kind() == Snippet.Kind.TYPE_DECL)
.map(sn -> (TypeDeclSnippet) sn)
.collect(collectingAndThen(toList(), Collections::unmodifiableList));
}
/**
* Return the status of the snippet.
* This is updated either because of an explicit <code>eval()</code> call or
* an automatic update triggered by a dependency.
* @param snippet the <code>Snippet</code> to look up
* @return the status corresponding to this snippet
* @throws IllegalStateException if this <code>JShell</code> instance is closed.
* @throws IllegalArgumentException if the snippet is not associated with
* this <code>JShell</code> instance.
*/
public Status status(Snippet snippet) {
return checkValidSnippet(snippet).status();
}
/**
* Return the diagnostics of the most recent evaluation of the snippet.
* The evaluation can either because of an explicit <code>eval()</code> call or
* an automatic update triggered by a dependency.
* @param snippet the <code>Snippet</code> to look up
* @return the diagnostics corresponding to this snippet. This does not
* include unresolvedDependencies references reported in <code>unresolvedDependencies()</code>.
* @throws IllegalStateException if this <code>JShell</code> instance is closed.
* @throws IllegalArgumentException if the snippet is not associated with
* this <code>JShell</code> instance.
*/
public List<Diag> diagnostics(Snippet snippet) {
return Collections.unmodifiableList(checkValidSnippet(snippet).diagnostics());
}
/**
* For {@link jdk.jshell.Snippet.Status#RECOVERABLE_DEFINED RECOVERABLE_DEFINED} or
* {@link jdk.jshell.Snippet.Status#RECOVERABLE_NOT_DEFINED RECOVERABLE_NOT_DEFINED}
* declarations, the names of current unresolved dependencies for
* the snippet.
* The returned value of this method, for a given method may change when an
* <code>eval()</code> or <code>drop()</code> of another snippet causes
* an update of a dependency.
* @param snippet the declaration <code>Snippet</code> to look up
* @return the list of symbol names that are currently unresolvedDependencies.
* @throws IllegalStateException if this <code>JShell</code> instance is closed.
* @throws IllegalArgumentException if the snippet is not associated with
* this <code>JShell</code> instance.
*/
public List<String> unresolvedDependencies(DeclarationSnippet snippet) {
return Collections.unmodifiableList(checkValidSnippet(snippet).unresolved());
}
/**
* Get the current value of a variable.
* @param snippet the variable Snippet whose value is queried.
* @return the current value of the variable referenced by snippet.
* @throws IllegalStateException if this <code>JShell</code> instance is closed.
* @throws IllegalArgumentException if the snippet is not associated with
* this <code>JShell</code> instance.
* @throws IllegalArgumentException if the variable's status is anything but
* {@link jdk.jshell.Snippet.Status#VALID}.
*/
public String varValue(VarSnippet snippet) throws IllegalStateException {
checkIfAlive();
checkValidSnippet(snippet);
if (snippet.status() != Status.VALID) {
throw new IllegalArgumentException("Snippet parameter of varValue() '" +
snippet + "' must be VALID, it is: " + snippet.status());
}
String value = executionControl().commandVarValue(maps.classFullName(snippet), snippet.name());
return expunge(value);
}
/**
* Register a callback to be called when the Status of a snippet changes.
* Each call adds a new subscription.
* @param listener Action to perform when the Status changes.
* @return A token which can be used to {@linkplain JShell#unsubscribe unsubscribe} this subscription.
* @throws IllegalStateException if this <code>JShell</code> instance is closed.
*/
public Subscription onSnippetEvent(Consumer<SnippetEvent> listener)
throws IllegalStateException {
return onX(keyStatusListeners, listener);
}
/**
* Register a callback to be called when this JShell instance terminates.
* This occurs either because the client process has ended (e.g. called System.exit(0))
* or the connection has been shutdown, as by close().
* Each call adds a new subscription.
* @param listener Action to perform when the state terminates.
* @return A token which can be used to {@linkplain JShell#unsubscribe unsubscribe} this subscription.
* @throws IllegalStateException if this JShell instance is closed
*/
public Subscription onShutdown(Consumer<JShell> listener)
throws IllegalStateException {
return onX(shutdownListeners, listener);
}
/**
* Cancel a callback subscription.
* @param token The token corresponding to the subscription to be unsubscribed.
*/
public void unsubscribe(Subscription token) {
synchronized (this) {
token.remover.accept(token);
}
}
/**
* Subscription is a token for referring to subscriptions so they can
* be {@linkplain JShell#unsubscribe unsubscribed}.
*/
public class Subscription {
Consumer<Subscription> remover;
Subscription(Consumer<Subscription> remover) {
this.remover = remover;
}
}
// --- private / package-private implementation support ---
ExecutionControl executionControl() {
if (executionControl == null) {
this.executionControl = new ExecutionControl(new JDIEnv(this), maps, this);
try {
executionControl.launch();
} catch (IOException ex) {
throw new InternalError("Launching JDI execution engine threw: " + ex.getMessage(), ex);
}
}
return executionControl;
}
void debug(int flags, String format, Object... args) {
if (InternalDebugControl.debugEnabled(this, flags)) {
err.printf(format, args);
}
}
void debug(Exception ex, String where) {
if (InternalDebugControl.debugEnabled(this, 0xFFFFFFFF)) {
err.printf("Fatal error: %s: %s\n", where, ex.getMessage());
ex.printStackTrace(err);
}
}
/**
* Generate the next key index, indicating a unique snippet signature.
* @return the next key index
*/
int nextKeyIndex() {
return nextKeyIndex++;
}
private synchronized <T> Subscription onX(Map<Subscription, Consumer<T>> map, Consumer<T> listener)
throws IllegalStateException {
Objects.requireNonNull(listener);
checkIfAlive();
Subscription token = new Subscription(map::remove);
map.put(token, listener);
return token;
}
private synchronized void notifyKeyStatusEvent(SnippetEvent event) {
keyStatusListeners.values().forEach(l -> l.accept(event));
}
private synchronized void notifyShutdownEvent(JShell state) {
shutdownListeners.values().forEach(l -> l.accept(state));
}
void closeDown() {
if (!closed) {
// Send only once
closed = true;
notifyShutdownEvent(this);
}
}
/**
* Check if this JShell has been closed
* @throws IllegalStateException if it is closed
*/
private void checkIfAlive() throws IllegalStateException {
if (closed) {
throw new IllegalStateException("JShell (" + this + ") has been closed.");
}
}
/**
* Check a Snippet parameter coming from the API user
* @param sn the Snippet to check
* @throws NullPointerException if Snippet parameter is null
* @throws IllegalArgumentException if Snippet is not from this JShell
* @return the input Snippet (for chained calls)
*/
private Snippet checkValidSnippet(Snippet sn) {
if (sn == null) {
throw new NullPointerException("Snippet must not be null");
} else {
if (sn.key().state() != this) {
throw new IllegalArgumentException("Snippet not from this JShell");
}
return sn;
}
}
}

View 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(); }
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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&nbsp;and&nbsp;Settings\UncleBob\src\share\classes"</code>,
* a valid result would be a file object representing the file
* <code>"C:\Documents&nbsp;and&nbsp;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
}
}

View File

@ -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;
}
}

View 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() + ")";
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View 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();
}
}

View File

@ -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) +
")";
}
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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);
}
}

View File

@ -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));
}
}
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View 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);
}
}
}

View File

@ -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;
}
}

View 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);
}
}

View File

@ -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;
}
}

View 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));
}
}
}

View File

@ -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&lt;SnippetEvent&gt; 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;

View File

@ -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>",

View 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);
}
}

View 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());
}
}
}

View 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");
}
}

View 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");
}
}

View 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"));
}

View 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);
}
}
}

View 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;
}
}

View 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
}
}

View 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");
}
}

View 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();
}
}

View 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));
}
}

View 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);
}
}

View 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();
}
}

View 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");
}
}

View 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);
}
}
}

View 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();
}
}

View 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");
}
}
}

View 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();
}
}
}

View 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