8156101: JShell SPI: Provide a pluggable execution control SPI

Reviewed-by: jlahoda
This commit is contained in:
Robert Field 2016-05-21 22:32:08 -07:00
parent 8c88656e09
commit 5361169fbd
30 changed files with 1835 additions and 683 deletions

View File

@ -23,24 +23,60 @@
package jdk.internal.jshell.debug;
import java.io.PrintStream;
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
/**
* This class is used to externally control output messages for debugging the
* implementation of the JShell API.
* <p>
* This is not part of the SPI, not API.
*/
public class InternalDebugControl {
public static final int DBG_GEN = 0b0000001;
public static final int DBG_FMGR = 0b0000010;
/**
* This is a static only class; The constructor should never be called.
*/
private InternalDebugControl() {
}
/**
* General debugging.
*/
public static final int DBG_GEN = 0b0000001;
/**
* File manager debuging.
*/
public static final int DBG_FMGR = 0b0000010;
/**
* Completion analysis debugging.
*/
public static final int DBG_COMPA = 0b0000100;
public static final int DBG_DEP = 0b0001000;
public static final int DBG_EVNT = 0b0010000;
/**
* Dependency debugging.
*/
public static final int DBG_DEP = 0b0001000;
/**
* Event debugging.
*/
public static final int DBG_EVNT = 0b0010000;
private static Map<JShell, Integer> debugMap = null;
/**
* Sets which debug flags are enabled for a given JShell instance. The flags
* are or'ed bits as defined in {@code DBG_*}.
*
* @param state the JShell instance
* @param flags the or'ed debug bits
*/
public static void setDebugFlags(JShell state, int flags) {
if (debugMap == null) {
debugMap = new HashMap<>();
@ -48,7 +84,14 @@ public class InternalDebugControl {
debugMap.put(state, flags);
}
public static boolean debugEnabled(JShell state, int flag) {
/**
* Tests if any of the specified debug flags are enabled.
*
* @param state the JShell instance
* @param flag the {@code DBG_*} bits to check
* @return true if any of the flags are enabled
*/
public static boolean isDebugEnabled(JShell state, int flag) {
if (debugMap == null) {
return false;
}
@ -58,4 +101,34 @@ public class InternalDebugControl {
}
return (flags & flag) != 0;
}
/**
* Displays debug info if the specified debug flags are enabled.
*
* @param state the current JShell instance
* @param err the {@code PrintStream} to report on
* @param flags {@code DBG_*} flag bits to check
* @param format format string for the output
* @param args args for the format string
*/
public static void debug(JShell state, PrintStream err, int flags, String format, Object... args) {
if (isDebugEnabled(state, flags)) {
err.printf(format, args);
}
}
/**
* Displays a fatal exception as debug info.
*
* @param state the current JShell instance
* @param err the {@code PrintStream} to report on
* @param ex the fatal Exception
* @param where additional context
*/
public static void debug(JShell state, PrintStream err, Exception ex, String where) {
if (isDebugEnabled(state, 0xFFFFFFFF)) {
err.printf("Fatal error: %s: %s\n", where, ex.getMessage());
ex.printStackTrace(err);
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -22,35 +22,43 @@
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.internal.jshell.jdi;
package jdk.jshell;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Objects;
import com.sun.jdi.ReferenceType;
import java.util.List;
/**
* Tracks the state of a class through compilation and loading in the remote
* environment.
*
* @author Robert Field
* Tracks the state of a class.
*/
class ClassTracker {
private final JShell state;
private final JDIEnv jdiEnv;
private final HashMap<String, ClassInfo> map;
ClassTracker(JShell state) {
this.state = state;
ClassTracker(JDIEnv jdiEnv) {
this.jdiEnv = jdiEnv;
this.map = new HashMap<>();
}
/**
* Associates a class name, class bytes, and ReferenceType.
*/
class ClassInfo {
// The name of the class -- always set
private final String className;
// The corresponding compiled class bytes when a load or redefine
// is started. May not be the loaded bytes. May be null.
private byte[] bytes;
// The class bytes successfully loaded/redefined into the remote VM.
private byte[] loadedBytes;
// The corresponding JDI ReferenceType. Used by redefineClasses and
// acts as indicator of successful load (null if not loaded).
private ReferenceType rt;
private ClassInfo(String className) {
@ -61,33 +69,44 @@ class ClassTracker {
return className;
}
byte[] getLoadedBytes() {
return loadedBytes;
}
byte[] getBytes() {
return bytes;
}
void setBytes(byte[] bytes) {
this.bytes = bytes;
private void setBytes(byte[] potentialBytes) {
this.bytes = potentialBytes;
}
void setLoaded() {
// The class has been successful loaded redefined. The class bytes
// sent are now actually loaded.
void markLoaded() {
loadedBytes = bytes;
}
boolean isLoaded() {
return Arrays.equals(loadedBytes, bytes);
}
// Ask JDI for the ReferenceType, null if not loaded.
ReferenceType getReferenceTypeOrNull() {
if (rt == null) {
rt = state.executionControl().nameToRef(className);
rt = nameToRef(className);
}
return rt;
}
private ReferenceType nameToRef(String name) {
List<ReferenceType> rtl = jdiEnv.vm().classesByName(name);
if (rtl.size() != 1) {
return null;
}
return rtl.get(0);
}
@Override
public boolean equals(Object o) {
return o instanceof ClassInfo &&
((ClassInfo) o).className.equals(className);
return o instanceof ClassInfo
&& ((ClassInfo) o).className.equals(className);
}
@Override
@ -96,13 +115,15 @@ class ClassTracker {
}
}
// Map a class name to the current compiled class bytes.
ClassInfo classInfo(String className, byte[] bytes) {
ClassInfo ci = map.computeIfAbsent(className, k -> new ClassInfo(k));
ClassInfo ci = get(className);
ci.setBytes(bytes);
return ci;
}
// Lookup the ClassInfo by class name, create if it does not exist.
ClassInfo get(String className) {
return map.get(className);
return map.computeIfAbsent(className, k -> new ClassInfo(k));
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1998, 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -32,7 +32,7 @@
*/
package jdk.jshell;
package jdk.internal.jshell.jdi;
import com.sun.jdi.*;
import com.sun.jdi.connect.*;
@ -40,7 +40,6 @@ import com.sun.jdi.connect.*;
import java.util.*;
import java.util.Map.Entry;
import java.io.*;
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
/**
@ -54,8 +53,7 @@ class JDIConnection {
private Process process = null;
private int outputCompleteCount = 0;
private final JShell proc;
private final JDIEnv env;
private final JDIExecutionControl ec;
private final Connector connector;
private final Map<String, com.sun.jdi.connect.Connector.Argument> connectorArgs;
private final int traceFlags;
@ -103,9 +101,9 @@ class JDIConnection {
return arguments;
}
JDIConnection(JDIEnv env, String connectorName, Map<String, String> argumentName2Value, int traceFlags, JShell proc) {
this.env = env;
this.proc = proc;
JDIConnection(JDIExecutionControl ec, String connectorName, Map<String, String> argumentName2Value, int traceFlags) {
this.ec = ec;
this.connector = findConnector(connectorName);
if (connector == null) {
@ -260,8 +258,8 @@ class JDIConnection {
try {
dumpStream(inStream, pStream);
} catch (IOException ex) {
proc.debug(ex, "Failed reading output");
env.shutdown();
ec.debug(ex, "Failed reading output");
ec.jdiEnv.shutdown();
} finally {
notifyOutputComplete();
}
@ -287,8 +285,8 @@ class JDIConnection {
outStream.flush();
}
} catch (IOException ex) {
proc.debug(ex, "Failed reading output");
env.shutdown();
ec.debug(ex, "Failed reading output");
ec.jdiEnv.shutdown();
}
}
};
@ -302,9 +300,9 @@ class JDIConnection {
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);
displayRemoteOutput(process.getErrorStream(), ec.execEnv.userErr());
displayRemoteOutput(process.getInputStream(), ec.execEnv.userOut());
readRemoteInput(process.getOutputStream(), ec.execEnv.userIn());
return new_vm;
} catch (Exception ex) {
reportLaunchFail(ex, "launch");
@ -330,7 +328,7 @@ class JDIConnection {
ListeningConnector listener = (ListeningConnector)connector;
try {
String retAddress = listener.startListening(connectorArgs);
proc.debug(DBG_GEN, "Listening at address: " + retAddress);
ec.debug(DBG_GEN, "Listening at address: " + retAddress);
vm = listener.accept(connectorArgs);
listener.stopListening(connectorArgs);
return vm;

View File

@ -23,7 +23,7 @@
* questions.
*/
package jdk.jshell;
package jdk.internal.jshell.jdi;
import java.util.Map;
@ -37,14 +37,14 @@ import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
class JDIEnv {
private JDIConnection connection;
private final JShell state;
private final JDIExecutionControl ec;
JDIEnv(JShell state) {
this.state = state;
JDIEnv(JDIExecutionControl ec) {
this.ec = ec;
}
void init(String connectorName, Map<String, String> argumentName2Value, boolean openNow, int flags) {
connection = new JDIConnection(this, connectorName, argumentName2Value, flags, state);
connection = new JDIConnection(ec, connectorName, argumentName2Value, flags);
if (!connection.isLaunch() || openNow) {
connection.open();
}
@ -66,14 +66,14 @@ class JDIEnv {
// 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);
ec.debug(DBG_GEN, null, "disposeVM threw: " + e);
}
}
if (state != null) { // If state has been set-up
if (ec.execEnv.state() != null) { // If state has been set-up
try {
state.closeDown();
ec.execEnv.closeDown();
} catch (Throwable e) {
state.debug(DBG_GEN, null, "state().closeDown() threw: " + e);
ec.debug(DBG_GEN, null, "state().closeDown() threw: " + e);
}
}
}

View File

@ -23,7 +23,7 @@
* questions.
*/
package jdk.jshell;
package jdk.internal.jshell.jdi;
import com.sun.jdi.*;
import com.sun.jdi.event.*;

View File

@ -0,0 +1,595 @@
/*
* 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.jdi;
import static jdk.internal.jshell.remote.RemoteCodes.*;
import java.io.DataInputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import com.sun.jdi.*;
import java.io.EOFException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static java.util.stream.Collectors.toList;
import jdk.jshell.JShellException;
import jdk.jshell.spi.ExecutionControl;
import jdk.jshell.spi.ExecutionEnv;
import jdk.internal.jshell.jdi.ClassTracker.ClassInfo;
import static java.util.stream.Collectors.toMap;
import jdk.internal.jshell.debug.InternalDebugControl;
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
/**
* Controls the remote execution environment.
* Interfaces to the JShell-core by implementing ExecutionControl SPI.
* Interfaces to RemoteAgent over a socket and via JDI.
* Launches a remote process.
*/
public class JDIExecutionControl implements ExecutionControl {
ExecutionEnv execEnv;
JDIEnv jdiEnv;
private ClassTracker tracker;
private JDIEventHandler handler;
private Socket socket;
private ObjectInputStream remoteIn;
private ObjectOutputStream remoteOut;
private String remoteVMOptions;
/**
* Initializes the launching JDI execution engine. Initialize JDI and use it
* to launch the remote JVM. Set-up control and result communications socket
* to the remote execution environment. This socket also transports the
* input/output channels.
*
* @param execEnv the execution environment provided by the JShell-core
* @throws IOException
*/
@Override
public void start(ExecutionEnv execEnv) throws IOException {
this.execEnv = execEnv;
this.jdiEnv = new JDIEnv(this);
this.tracker = new ClassTracker(jdiEnv);
StringBuilder sb = new StringBuilder();
execEnv.extraRemoteVMOptions().stream()
.forEach(s -> {
sb.append(" ");
sb.append(s);
});
this.remoteVMOptions = sb.toString();
try (ServerSocket listener = new ServerSocket(0)) {
// timeout after 60 seconds
listener.setSoTimeout(60000);
int port = listener.getLocalPort();
jdiGo(port);
this.socket = listener.accept();
// out before in -- match remote creation so we don't hang
this.remoteOut = new ObjectOutputStream(socket.getOutputStream());
PipeInputStream commandIn = new PipeInputStream();
new DemultiplexInput(socket.getInputStream(), commandIn, execEnv.userOut(), execEnv.userErr()).start();
this.remoteIn = new ObjectInputStream(commandIn);
}
}
/**
* Closes the execution engine. Send an exit command to the remote agent.
* Shuts down the JDI connection. Should this close the socket?
*/
@Override
public void close() {
try {
if (remoteOut != null) {
remoteOut.writeInt(CMD_EXIT);
remoteOut.flush();
}
JDIConnection c = jdiEnv.connection();
if (c != null) {
c.disposeVM();
}
} catch (IOException ex) {
debug(DBG_GEN, "Exception on JDI exit: %s\n", ex);
}
}
/**
* Loads the list of classes specified. Sends a load command to the remote
* agent with pairs of classname/bytes.
*
* @param classes the names of the wrapper classes to loaded
* @return true if all classes loaded successfully
*/
@Override
public boolean load(Collection<String> classes) {
try {
// Create corresponding ClassInfo instances to track the classes.
// Each ClassInfo has the current class bytes associated with it.
List<ClassInfo> infos = withBytes(classes);
// Send a load command to the remote agent.
remoteOut.writeInt(CMD_LOAD);
remoteOut.writeInt(classes.size());
for (ClassInfo ci : infos) {
remoteOut.writeUTF(ci.getClassName());
remoteOut.writeObject(ci.getBytes());
}
remoteOut.flush();
// Retrieve and report results from the remote agent.
boolean result = readAndReportResult();
// For each class that now has a JDI ReferenceType, mark the bytes
// as loaded.
infos.stream()
.filter(ci -> ci.getReferenceTypeOrNull() != null)
.forEach(ci -> ci.markLoaded());
return result;
} catch (IOException ex) {
debug(DBG_GEN, "IOException on remote load operation: %s\n", ex);
return false;
}
}
/**
* Invoke the doit method on the specified class.
*
* @param classname name of the wrapper class whose doit should be invoked
* @return return the result value of the doit
* @throws JShellException if a user exception was thrown (EvalException) or
* an unresolved reference was encountered (UnresolvedReferenceException)
*/
@Override
public String invoke(String classname, String methodname) throws JShellException {
try {
synchronized (STOP_LOCK) {
userCodeRunning = true;
}
// Send the invoke command to the remote agent.
remoteOut.writeInt(CMD_INVOKE);
remoteOut.writeUTF(classname);
remoteOut.writeUTF(methodname);
remoteOut.flush();
// Retrieve and report results from the remote agent.
if (readAndReportExecutionResult()) {
String result = remoteIn.readUTF();
return result;
}
} catch (IOException | RuntimeException ex) {
if (!jdiEnv.connection().isRunning()) {
// The JDI connection is no longer live, shutdown.
jdiEnv.shutdown();
} else {
debug(DBG_GEN, "Exception on remote invoke: %s\n", ex);
return "Execution failure: " + ex.getMessage();
}
} finally {
synchronized (STOP_LOCK) {
userCodeRunning = false;
}
}
return "";
}
/**
* Retrieves the value of a JShell variable.
*
* @param classname name of the wrapper class holding the variable
* @param varname name of the variable
* @return the value as a String
*/
@Override
public String varValue(String classname, String varname) {
try {
// Send the variable-value command to the remote agent.
remoteOut.writeInt(CMD_VARVALUE);
remoteOut.writeUTF(classname);
remoteOut.writeUTF(varname);
remoteOut.flush();
// Retrieve and report results from the remote agent.
if (readAndReportResult()) {
String result = remoteIn.readUTF();
return result;
}
} catch (EOFException ex) {
jdiEnv.shutdown();
} catch (IOException ex) {
debug(DBG_GEN, "Exception on remote var value: %s\n", ex);
return "Execution failure: " + ex.getMessage();
}
return "";
}
/**
* Adds a path to the remote classpath.
*
* @param cp the additional path element
* @return true if succesful
*/
@Override
public boolean addToClasspath(String cp) {
try {
// Send the classpath addition command to the remote agent.
remoteOut.writeInt(CMD_CLASSPATH);
remoteOut.writeUTF(cp);
remoteOut.flush();
// Retrieve and report results from the remote agent.
return readAndReportResult();
} catch (IOException ex) {
throw new InternalError("Classpath addition failed: " + cp, ex);
}
}
/**
* Redefine the specified classes. Where 'redefine' is, as in JDI and JVMTI,
* an in-place replacement of the classes (preserving class identity) --
* that is, existing references to the class do not need to be recompiled.
* This implementation uses JDI redefineClasses. It will be unsuccessful if
* the signature of the class has changed (see the JDI spec). The
* JShell-core is designed to adapt to unsuccessful redefine.
*
* @param classes the names of the classes to redefine
* @return true if all the classes were redefined
*/
@Override
public boolean redefine(Collection<String> classes) {
try {
// Create corresponding ClassInfo instances to track the classes.
// Each ClassInfo has the current class bytes associated with it.
List<ClassInfo> infos = withBytes(classes);
// Convert to the JDI ReferenceType to class bytes map form needed
// by JDI.
Map<ReferenceType, byte[]> rmp = infos.stream()
.collect(toMap(
ci -> ci.getReferenceTypeOrNull(),
ci -> ci.getBytes()));
// Attempt redefine. Throws exceptions on failure.
jdiEnv.vm().redefineClasses(rmp);
// Successful: mark the bytes as loaded.
infos.stream()
.forEach(ci -> ci.markLoaded());
return true;
} catch (UnsupportedOperationException ex) {
// A form of class transformation not supported by JDI
return false;
} catch (Exception ex) {
debug(DBG_GEN, "Exception on JDI redefine: %s\n", ex);
return false;
}
}
/**
* Converts a collection of class names into ClassInfo instances associated
* with the most recently compiled class bytes.
*
* @param classes names of the classes
* @return a list of corresponding ClassInfo instances
*/
private List<ClassInfo> withBytes(Collection<String> classes) {
return classes.stream()
.map(cn -> tracker.classInfo(cn, execEnv.getClassBytes(cn)))
.collect(toList());
}
/**
* Reports the status of the named class. UNKNOWN if not loaded. CURRENT if
* the most recent successfully loaded/redefined bytes match the current
* compiled bytes.
*
* @param classname the name of the class to test
* @return the status
*/
@Override
public ClassStatus getClassStatus(String classname) {
ClassInfo ci = tracker.get(classname);
if (ci.getReferenceTypeOrNull() == null) {
// If the class does not have a JDI ReferenceType it has not been loaded
return ClassStatus.UNKNOWN;
}
// Compare successfully loaded with last compiled bytes.
return (Arrays.equals(execEnv.getClassBytes(classname), ci.getLoadedBytes()))
? ClassStatus.CURRENT
: ClassStatus.NOT_CURRENT;
}
/**
* Reports results from a remote agent command that does not expect
* exceptions.
*
* @return true if successful
* @throws IOException if the connection has dropped
*/
private boolean readAndReportResult() throws IOException {
int ok = remoteIn.readInt();
switch (ok) {
case RESULT_SUCCESS:
return true;
case RESULT_FAIL: {
String ex = remoteIn.readUTF();
debug(DBG_GEN, "Exception on remote operation: %s\n", ex);
return false;
}
default: {
debug(DBG_GEN, "Bad remote result code: %s\n", ok);
return false;
}
}
}
/**
* Reports results from a remote agent command that expects runtime
* exceptions.
*
* @return true if successful
* @throws IOException if the connection has dropped
* @throws EvalException if a user exception was encountered on invoke
* @throws UnresolvedReferenceException if an unresolved reference was
* encountered
*/
private boolean readAndReportExecutionResult() throws IOException, JShellException {
int ok = remoteIn.readInt();
switch (ok) {
case RESULT_SUCCESS:
return true;
case RESULT_FAIL: {
// An internal error has occurred.
String ex = remoteIn.readUTF();
return false;
}
case RESULT_EXCEPTION: {
// A user exception was encountered.
String exceptionClassName = remoteIn.readUTF();
String message = remoteIn.readUTF();
StackTraceElement[] elems = readStackTrace();
throw execEnv.createEvalException(message, exceptionClassName, elems);
}
case RESULT_CORRALLED: {
// An unresolved reference was encountered.
int id = remoteIn.readInt();
StackTraceElement[] elems = readStackTrace();
throw execEnv.createUnresolvedReferenceException(id, elems);
}
case RESULT_KILLED: {
// Execution was aborted by the stop()
debug(DBG_GEN, "Killed.");
return false;
}
default: {
debug(DBG_GEN, "Bad remote result code: %s\n", ok);
return false;
}
}
}
private StackTraceElement[] readStackTrace() throws IOException {
int elemCount = remoteIn.readInt();
StackTraceElement[] elems = new StackTraceElement[elemCount];
for (int i = 0; i < elemCount; ++i) {
String className = remoteIn.readUTF();
String methodName = remoteIn.readUTF();
String fileName = remoteIn.readUTF();
int line = remoteIn.readInt();
elems[i] = new StackTraceElement(className, methodName, fileName, line);
}
return elems;
}
/**
* Launch the remote agent as a JDI connection.
*
* @param port the socket port for (non-JDI) commands
*/
private void jdiGo(int port) {
//MessageOutput.textResources = ResourceBundle.getBundle("impl.TTYResources",
// Locale.getDefault());
// Set-up for a fresh launch of a remote agent with any user-specified VM options.
String connectorName = "com.sun.jdi.CommandLineLaunch";
Map<String, String> argumentName2Value = new HashMap<>();
argumentName2Value.put("main", "jdk.internal.jshell.remote.RemoteAgent " + port);
argumentName2Value.put("options", remoteVMOptions);
boolean launchImmediately = true;
int traceFlags = 0;// VirtualMachine.TRACE_SENDS | VirtualMachine.TRACE_EVENTS;
// Launch.
jdiEnv.init(connectorName, argumentName2Value, launchImmediately, traceFlags);
if (jdiEnv.connection().isOpen() && jdiEnv.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(jdiEnv);
}
}
private final Object STOP_LOCK = new Object();
private boolean userCodeRunning = false;
/**
* Interrupt a running invoke.
*/
@Override
public void stop() {
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();
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) {
debug(DBG_GEN, "Exception on remote stop: %s\n", ex);
} finally {
vm.resume();
}
}
}
void debug(int flags, String format, Object... args) {
InternalDebugControl.debug(execEnv.state(), execEnv.userErr(), flags, format, args);
}
void debug(Exception ex, String where) {
InternalDebugControl.debug(execEnv.state(), execEnv.userErr(), ex, where);
}
private final class DemultiplexInput extends Thread {
private final DataInputStream delegate;
private final PipeInputStream command;
private final PrintStream out;
private final PrintStream err;
public DemultiplexInput(InputStream input,
PipeInputStream command,
PrintStream out,
PrintStream err) {
super("output reader");
this.delegate = new DataInputStream(input);
this.command = command;
this.out = out;
this.err = err;
}
public void run() {
try {
while (true) {
int nameLen = delegate.read();
if (nameLen == (-1))
break;
byte[] name = new byte[nameLen];
DemultiplexInput.this.delegate.readFully(name);
int dataLen = delegate.read();
byte[] data = new byte[dataLen];
DemultiplexInput.this.delegate.readFully(data);
switch (new String(name, "UTF-8")) {
case "err":
err.write(data);
break;
case "out":
out.write(data);
break;
case "command":
for (byte b : data) {
command.write(Byte.toUnsignedInt(b));
}
break;
}
}
} catch (IOException ex) {
debug(ex, "Failed reading output");
} finally {
command.close();
}
}
}
public static final class PipeInputStream extends InputStream {
public static final int INITIAL_SIZE = 128;
private int[] buffer = new int[INITIAL_SIZE];
private int start;
private int end;
private boolean closed;
@Override
public synchronized int read() {
while (start == end) {
if (closed) {
return -1;
}
try {
wait();
} catch (InterruptedException ex) {
//ignore
}
}
try {
return buffer[start];
} finally {
start = (start + 1) % buffer.length;
}
}
public synchronized void write(int b) {
if (closed)
throw new IllegalStateException("Already closed.");
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();
}
@Override
public synchronized void close() {
closed = true;
notifyAll();
}
}
}

View File

@ -23,7 +23,7 @@
* questions.
*/
package jdk.jshell;
package jdk.internal.jshell.jdi;
/**
* Internal exception when Java Debug Interface VirtualMacine is not connected.

View File

@ -24,6 +24,7 @@
*/
package jdk.internal.jshell.remote;
import jdk.jshell.spi.SPIResolutionException;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
@ -111,10 +112,11 @@ class RemoteAgent {
out.flush();
break;
}
String methodName = in.readUTF();
Method doitMethod;
try {
this.getClass().getModule().addExports(RemoteResolutionException.class.getPackage().getName(), klass.getModule());
doitMethod = klass.getDeclaredMethod(DOIT_METHOD_NAME, new Class<?>[0]);
this.getClass().getModule().addExports(SPIResolutionException.class.getPackage().getName(), klass.getModule());
doitMethod = klass.getDeclaredMethod(methodName, new Class<?>[0]);
doitMethod.setAccessible(true);
Object res;
try {
@ -138,9 +140,9 @@ class RemoteAgent {
} catch (InvocationTargetException ex) {
Throwable cause = ex.getCause();
StackTraceElement[] elems = cause.getStackTrace();
if (cause instanceof RemoteResolutionException) {
if (cause instanceof SPIResolutionException) {
out.writeInt(RESULT_CORRALLED);
out.writeInt(((RemoteResolutionException) cause).id);
out.writeInt(((SPIResolutionException) cause).id());
} else {
out.writeInt(RESULT_EXCEPTION);
out.writeUTF(cause.getClass().getName());
@ -254,27 +256,14 @@ class RemoteAgent {
if (value == null) {
return "null";
} else if (value instanceof String) {
return "\"" + expunge((String)value) + "\"";
return "\"" + (String)value + "\"";
} else if (value instanceof Character) {
return "'" + value + "'";
} else {
return expunge(value.toString());
return value.toString();
}
}
/**
* Expunge internal info from string
* @param s string to process
* @return string the display, JShell package and wrapper class names removed
*/
static String expunge(String s) {
StringBuilder sb = new StringBuilder();
for (String comp : PREFIX_PATTERN.split(s)) {
sb.append(comp);
}
return sb.toString();
}
private static final class MultiplexingOutputStream extends OutputStream {
private static final int PACKET_SIZE = 127;

View File

@ -25,8 +25,6 @@
package jdk.internal.jshell.remote;
import java.util.regex.Pattern;
/**
* Communication constants shared between the main process and the remote
* execution process
@ -46,13 +44,4 @@ public class RemoteCodes {
public static final int RESULT_EXCEPTION = 102;
public static final int RESULT_CORRALLED = 103;
public static final int RESULT_KILLED = 104;
// String constants
public static final String REPL_PACKAGE = "REPL";
public static final String REPL_CLASS_PREFIX = "$JShell$";
public static final String DOIT_METHOD_NAME = "do_it$";
public static final Pattern PREFIX_PATTERN = Pattern.compile(
"(" + REPL_PACKAGE + "\\.)?" +
"(?<class>" + Pattern.quote(REPL_CLASS_PREFIX) +
"\\w+" + ")" + "[\\$\\.]?");
}

View File

@ -101,8 +101,13 @@ import jdk.internal.jshell.tool.Feedback.FormatResolve;
import jdk.internal.jshell.tool.Feedback.FormatUnresolved;
import jdk.internal.jshell.tool.Feedback.FormatWhen;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
import static jdk.jshell.Snippet.SubKind.VAR_VALUE_SUBKIND;
import static java.util.stream.Collectors.toMap;
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_COMPA;
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_DEP;
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_EVNT;
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_FMGR;
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
/**
* Command line REPL tool for Java using the JShell API.
@ -1344,7 +1349,7 @@ public class JShellTool implements MessageHandler {
boolean cmdDebug(String arg) {
if (arg.isEmpty()) {
debug = !debug;
InternalDebugControl.setDebugFlags(state, debug ? InternalDebugControl.DBG_GEN : 0);
InternalDebugControl.setDebugFlags(state, debug ? DBG_GEN : 0);
fluff("Debugging %s", debug ? "on" : "off");
} else {
int flags = 0;
@ -1360,23 +1365,23 @@ public class JShellTool implements MessageHandler {
fluff("REPL tool debugging on");
break;
case 'g':
flags |= InternalDebugControl.DBG_GEN;
flags |= DBG_GEN;
fluff("General debugging on");
break;
case 'f':
flags |= InternalDebugControl.DBG_FMGR;
flags |= DBG_FMGR;
fluff("File manager debugging on");
break;
case 'c':
flags |= InternalDebugControl.DBG_COMPA;
flags |= DBG_COMPA;
fluff("Completion analysis debugging on");
break;
case 'd':
flags |= InternalDebugControl.DBG_DEP;
flags |= DBG_DEP;
fluff("Dependency debugging on");
break;
case 'e':
flags |= InternalDebugControl.DBG_EVNT;
flags |= DBG_EVNT;
fluff("Event debugging on");
break;
default:

View File

@ -49,6 +49,8 @@ import static com.sun.tools.javac.code.Flags.STATIC;
import static com.sun.tools.javac.code.Flags.INTERFACE;
import static com.sun.tools.javac.code.Flags.ENUM;
import static com.sun.tools.javac.code.Flags.PUBLIC;
import com.sun.tools.javac.util.Name;
import jdk.jshell.spi.SPIResolutionException;
/**
* Produce a corralled version of the Wrap for a snippet.
@ -129,16 +131,22 @@ class Corraller extends Pretty {
super.visitClassDef(tree);
}
// Build a compiler tree for an exception throwing block, e.g.:
// {
// throw new jdk.jshell.spi.SPIResolutionException(9);
// }
private JCBlock resolutionExceptionBlock() {
if (resolutionExceptionBlock == null) {
JCExpression expClass
= make.Select(make.Select(make.Select(make.Select(
make.Ident(names.fromString("jdk")),
names.fromString("internal")),
names.fromString("jshell")),
names.fromString("remote")),
names.fromString("RemoteResolutionException")
);
JCExpression expClass = null;
// Split the exception class name at dots
for (String id : SPIResolutionException.class.getName().split("\\.")) {
Name nm = names.fromString(id);
if (expClass == null) {
expClass = make.Ident(nm);
} else {
expClass = make.Select(expClass, nm);
}
}
JCNewClass exp = make.NewClass(null,
null, expClass, List.of(make.Literal(keyIndex)), null);
resolutionExceptionBlock = make.Block(0L, List.<JCStatement>of(

View File

@ -49,7 +49,6 @@ 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.Key.TypeDeclKey;
@ -64,9 +63,9 @@ 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.PREFIX_PATTERN;
import static jdk.jshell.Util.DOIT_METHOD_NAME;
import static jdk.jshell.Util.PREFIX_PATTERN;
import static jdk.jshell.Util.expunge;
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;
@ -456,7 +455,7 @@ class Eval {
if (si.status().isDefined) {
if (si.isExecutable()) {
try {
value = state.executionControl().commandInvoke(si.classFullName());
value = state.executionControl().invoke(si.classFullName(), DOIT_METHOD_NAME);
value = si.subKind().hasValue()
? expunge(value)
: "";
@ -578,7 +577,7 @@ class Eval {
// load all new classes
load(legit.stream()
.flatMap(u -> u.classesToLoad(ct.classInfoList(u.snippet().outerWrap())))
.flatMap(u -> u.classesToLoad(ct.classList(u.snippet().outerWrap())))
.collect(toSet()));
// attempt to redefine the remaining classes
List<Unit> toReplace = legit.stream()
@ -613,9 +612,13 @@ class Eval {
}
}
private void load(Set<ClassInfo> cil) {
if (!cil.isEmpty()) {
state.executionControl().commandLoad(cil);
/**
* If there are classes to load, loads by calling the execution engine.
* @param classnames names of the classes to load.
*/
private void load(Collection<String> classnames) {
if (!classnames.isEmpty()) {
state.executionControl().load(classnames);
}
}

View File

@ -1,434 +0,0 @@
/*
* 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.DataInputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import com.sun.jdi.*;
import java.io.EOFException;
import java.util.Collection;
import java.util.HashMap;
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;
private final String remoteVMOptions;
ExecutionControl(JDIEnv env, SnippetMaps maps, JShell proc, List<String> extraRemoteVMOptions) {
this.env = env;
this.maps = maps;
this.proc = proc;
StringBuilder sb = new StringBuilder();
extraRemoteVMOptions.stream()
.forEach(s -> {
sb.append(" ");
sb.append(s);
});
this.remoteVMOptions = sb.toString();
}
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());
PipeInputStream commandIn = new PipeInputStream();
new DemultiplexInput(socket.getInputStream(), commandIn, proc.out, proc.err).start();
in = new ObjectInputStream(commandIn);
}
}
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(Collection<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 JShellException {
try {
synchronized (STOP_LOCK) {
userCodeRunning = true;
}
out.writeInt(CMD_INVOKE);
out.writeUTF(classname);
out.flush();
if (readAndReportExecutionResult()) {
String result = in.readUTF();
return result;
}
} catch (IOException | RuntimeException ex) {
if (!env.connection().isRunning()) {
env.shutdown();
} else {
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, JShellException {
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.getSnippetDeadOrAlive(id);
throw new UnresolvedReferenceException((DeclarationSnippet) 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 connectorName = "com.sun.jdi.CommandLineLaunch";
Map<String, String> argumentName2Value = new HashMap<>();
argumentName2Value.put("main", "jdk.internal.jshell.remote.RemoteAgent " + port);
argumentName2Value.put("options", remoteVMOptions);
boolean launchImmediately = true;
int traceFlags = 0;// VirtualMachine.TRACE_SENDS | VirtualMachine.TRACE_EVENTS;
env.init(connectorName, argumentName2Value, 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();
}
}
}
private final class DemultiplexInput extends Thread {
private final DataInputStream delegate;
private final PipeInputStream command;
private final PrintStream out;
private final PrintStream err;
public DemultiplexInput(InputStream input,
PipeInputStream command,
PrintStream out,
PrintStream err) {
super("output reader");
this.delegate = new DataInputStream(input);
this.command = command;
this.out = out;
this.err = err;
}
public void run() {
try {
while (true) {
int nameLen = delegate.read();
if (nameLen == (-1))
break;
byte[] name = new byte[nameLen];
DemultiplexInput.this.delegate.readFully(name);
int dataLen = delegate.read();
byte[] data = new byte[dataLen];
DemultiplexInput.this.delegate.readFully(data);
switch (new String(name, "UTF-8")) {
case "err":
err.write(data);
break;
case "out":
out.write(data);
break;
case "command":
for (byte b : data) {
command.write(Byte.toUnsignedInt(b));
}
break;
}
}
} catch (IOException ex) {
proc.debug(ex, "Failed reading output");
} finally {
command.close();
}
}
}
public static final class PipeInputStream extends InputStream {
public static final int INITIAL_SIZE = 128;
private int[] buffer = new int[INITIAL_SIZE];
private int start;
private int end;
private boolean closed;
@Override
public synchronized int read() {
while (start == end) {
if (closed) {
return -1;
}
try {
wait();
} catch (InterruptedException ex) {
//ignore
}
}
try {
return buffer[start];
} finally {
start = (start + 1) % buffer.length;
}
}
public synchronized void write(int b) {
if (closed)
throw new IllegalStateException("Already closed.");
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();
}
@Override
public synchronized void close() {
closed = true;
notifyAll();
}
}
}

View File

@ -25,6 +25,7 @@
package jdk.jshell;
import jdk.jshell.spi.ExecutionControl;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.PrintStream;
@ -47,6 +48,8 @@ import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toList;
import static jdk.jshell.Util.expunge;
import jdk.jshell.Snippet.Status;
import jdk.internal.jshell.jdi.JDIExecutionControl;
import jdk.jshell.spi.ExecutionEnv;
/**
* The JShell evaluation state engine. This is the central class in the JShell
@ -71,7 +74,6 @@ import jdk.jshell.Snippet.Status;
* <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 {
@ -86,16 +88,17 @@ public class JShell implements AutoCloseable {
final Supplier<String> tempVariableNameGenerator;
final BiFunction<Snippet, Integer, String> idGenerator;
final List<String> extraRemoteVMOptions;
final ExecutionControl executionControl;
private int nextKeyIndex = 1;
final Eval eval;
final ClassTracker classTracker;
private final Map<String, byte[]> classnameToBytes = new HashMap<>();
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 boolean executionControlLaunched = false;
private SourceCodeAnalysisImpl sourceCodeAnalysis = null;
private static final String L10N_RB_NAME = "jdk.jshell.resources.l10n";
@ -108,13 +111,15 @@ public class JShell implements AutoCloseable {
this.tempVariableNameGenerator = b.tempVariableNameGenerator;
this.idGenerator = b.idGenerator;
this.extraRemoteVMOptions = b.extraRemoteVMOptions;
this.executionControl = b.executionControl==null
? new JDIExecutionControl()
: b.executionControl;
this.maps = new SnippetMaps(this);
this.keyMap = new KeyMap(this);
this.outerMap = new OuterWrapMap(this);
this.taskFactory = new TaskFactory(this);
this.eval = new Eval(this);
this.classTracker = new ClassTracker(this);
}
/**
@ -143,22 +148,23 @@ public class JShell implements AutoCloseable {
Supplier<String> tempVariableNameGenerator = null;
BiFunction<Snippet, Integer, String> idGenerator = null;
List<String> extraRemoteVMOptions = new ArrayList<>();
ExecutionControl executionControl;
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
* Sets the input for the running evaluation (it's {@code System.in}). Note:
* applications that use {@code System.in} for snippet or other
* user input cannot use {@code System.in} 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>.
* -- {@code new ByteArrayInputStream(new byte[0])}.
*
* @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).
* @param in the {@code InputStream} to be channelled to
* {@code System.in} in the remote execution process
* @return the {@code Builder} instance (for use in chained
* initialization)
*/
public Builder in(InputStream in) {
this.in = in;
@ -166,16 +172,16 @@ public class JShell implements AutoCloseable {
}
/**
* Output for the running evaluation (it's <code>System.out</code>).
* Sets the output for the running evaluation (it's {@code System.out}).
* The controlling process and
* the remote process can share <code>System.out</code>.
* the remote process can share {@code System.out}.
* <p>
* The default, if this is not set, is <code>System.out</code>.
* The default, if this is not set, is {@code System.out}.
*
* @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).
* @param out the {@code PrintStream} to be channelled to
* {@code System.out} in the remote execution process
* @return the {@code Builder} instance (for use in chained
* initialization)
*/
public Builder out(PrintStream out) {
this.out = out;
@ -183,16 +189,16 @@ public class JShell implements AutoCloseable {
}
/**
* 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>.
* Sets the error output for the running evaluation (it's
* {@code System.err}). The controlling process and the remote
* process can share {@code System.err}.
* <p>
* The default, if this is not set, is <code>System.err</code>.
* The default, if this is not set, is {@code System.err}.
*
* @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).
* @param err the {@code PrintStream} to be channelled to
* {@code System.err} in the remote execution process
* @return the {@code Builder} instance (for use in chained
* initialization)
*/
public Builder err(PrintStream err) {
this.err = err;
@ -200,7 +206,7 @@ public class JShell implements AutoCloseable {
}
/**
* Set a generator of temp variable names for
* Sets a generator of temp variable names for
* {@link jdk.jshell.VarSnippet} of
* {@link jdk.jshell.Snippet.SubKind#TEMP_VAR_EXPRESSION_SUBKIND}.
* <p>
@ -221,9 +227,9 @@ public class JShell implements AutoCloseable {
* 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).
* variable name string or <code>null</code>
* @return the {@code Builder} instance (for use in chained
* initialization)
*/
public Builder tempVariableNameGenerator(Supplier<String> generator) {
this.tempVariableNameGenerator = generator;
@ -231,7 +237,7 @@ public class JShell implements AutoCloseable {
}
/**
* Set the generator of identifying names for Snippets.
* Sets the generator of identifying names for Snippets.
* <p>
* Do not use this method unless you have explicit need for it.
* <p>
@ -258,9 +264,9 @@ public class JShell implements AutoCloseable {
* 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).
* string or <code>null</code>
* @return the {@code Builder} instance (for use in chained
* initialization)
*/
public Builder idGenerator(BiFunction<Snippet, Integer, String> generator) {
this.idGenerator = generator;
@ -268,11 +274,11 @@ public class JShell implements AutoCloseable {
}
/**
* Set additional VM options for launching the VM.
* Sets additional VM options for launching the VM.
*
* @param options The options for the remote VM.
* @return the <code>Builder</code> instance (for use in chained
* initialization).
* @param options The options for the remote VM
* @return the {@code Builder} instance (for use in chained
* initialization)
*/
public Builder remoteVMOptions(String... options) {
this.extraRemoteVMOptions.addAll(Arrays.asList(options));
@ -280,11 +286,24 @@ public class JShell implements AutoCloseable {
}
/**
* Build a JShell state engine. This is the entry-point to all JShell
* Sets the custom engine for execution. Snippet execution will be
* provided by the specified {@link ExecutionControl} instance.
*
* @param execEngine the execution engine
* @return the {@code Builder} instance (for use in chained
* initialization)
*/
public Builder executionEngine(ExecutionControl execEngine) {
this.executionControl = execEngine;
return this;
}
/**
* Builds 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.
* @return the state engine
*/
public JShell build() {
return new JShell(this);
@ -406,7 +425,7 @@ public class JShell implements AutoCloseable {
*/
public void addToClasspath(String path) {
taskFactory.addToClasspath(path); // Compiler
executionControl().commandAddToClasspath(path); // Runtime
executionControl().addToClasspath(path); // Runtime
if (sourceCodeAnalysis != null) {
sourceCodeAnalysis.classpathChanged();
}
@ -427,7 +446,7 @@ public class JShell implements AutoCloseable {
*/
public void stop() {
if (executionControl != null)
executionControl.commandStop();
executionControl.stop();
}
/**
@ -438,7 +457,7 @@ public class JShell implements AutoCloseable {
public void close() {
if (!closed) {
closeDown();
executionControl().commandExit();
executionControl().close();
}
}
@ -580,7 +599,7 @@ public class JShell implements AutoCloseable {
throw new IllegalArgumentException(
messageFormat("jshell.exc.var.not.valid", snippet, snippet.status()));
}
String value = executionControl().commandVarValue(snippet.classFullName(), snippet.name());
String value = executionControl().varValue(snippet.classFullName(), snippet.name());
return expunge(value);
}
@ -633,35 +652,86 @@ public class JShell implements AutoCloseable {
}
}
// --- private / package-private implementation support ---
/**
* Provide the environment for a execution engine.
*/
class ExecutionEnvImpl implements ExecutionEnv {
@Override
public InputStream userIn() {
return in;
}
@Override
public PrintStream userOut() {
return out;
}
@Override
public PrintStream userErr() {
return err;
}
@Override
public JShell state() {
return JShell.this;
}
@Override
public List<String> extraRemoteVMOptions() {
return extraRemoteVMOptions;
}
@Override
public byte[] getClassBytes(String classname) {
return classnameToBytes.get(classname);
}
@Override
public EvalException createEvalException(String message, String exceptionClass, StackTraceElement[] stackElements) {
return new EvalException(message, exceptionClass, stackElements);
}
@Override
public UnresolvedReferenceException createUnresolvedReferenceException(int id, StackTraceElement[] stackElements) {
DeclarationSnippet sn = (DeclarationSnippet) maps.getSnippetDeadOrAlive(id);
return new UnresolvedReferenceException(sn, stackElements);
}
@Override
public void closeDown() {
JShell.this.closeDown();
}
}
// --- private / package-private implementation support ---
ExecutionControl executionControl() {
if (executionControl == null) {
this.executionControl = new ExecutionControl(new JDIEnv(this), maps, this, extraRemoteVMOptions);
if (!executionControlLaunched) {
try {
executionControl.launch();
executionControlLaunched = true;
executionControl.start(new ExecutionEnvImpl());
} catch (Throwable ex) {
throw new InternalError("Launching JDI execution engine threw: " + ex.getMessage(), ex);
throw new InternalError("Launching execution engine threw: " + ex.getMessage(), ex);
}
}
return executionControl;
}
void setClassnameToBytes(String classname, byte[] bytes) {
classnameToBytes.put(classname, bytes);
}
void debug(int flags, String format, Object... args) {
if (InternalDebugControl.debugEnabled(this, flags)) {
err.printf(format, args);
}
InternalDebugControl.debug(this, err, flags, 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);
}
InternalDebugControl.debug(this, err, ex, where);
}
/**
* Generate the next key index, indicating a unique snippet signature.
*
* @return the next key index
*/
int nextKeyIndex() {

View File

@ -28,9 +28,11 @@ package jdk.jshell;
import java.util.Locale;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
import jdk.internal.jshell.remote.RemoteCodes;
import static jdk.jshell.Util.*;
import static jdk.internal.jshell.remote.RemoteCodes.REPL_PACKAGE;
import static jdk.jshell.Util.PARSED_LOCALE;
import static jdk.jshell.Util.REPL_CLASS_PREFIX;
import static jdk.jshell.Util.REPL_DOESNOTMATTER_CLASS_NAME;
import static jdk.jshell.Util.REPL_PACKAGE;
import static jdk.jshell.Util.expunge;
/**
*
@ -163,7 +165,7 @@ class OuterWrap implements GeneralWrap {
}
for (String line : diag.getMessage(PARSED_LOCALE).split("\\r?\\n")) {
if (line.trim().startsWith("location:")) {
if (!line.contains(RemoteCodes.REPL_CLASS_PREFIX)) {
if (!line.contains(REPL_CLASS_PREFIX)) {
// Resolution error must occur within a REPL class or it is not resolvable
return false;
}

View File

@ -35,8 +35,8 @@ import java.util.Set;
import java.util.regex.Matcher;
import java.util.stream.Collectors;
import jdk.jshell.Wrap.CompoundWrap;
import static jdk.internal.jshell.remote.RemoteCodes.PREFIX_PATTERN;
import static jdk.internal.jshell.remote.RemoteCodes.REPL_CLASS_PREFIX;
import static jdk.jshell.Util.PREFIX_PATTERN;
import static jdk.jshell.Util.REPL_CLASS_PREFIX;
import static jdk.jshell.Util.REPL_DOESNOTMATTER_CLASS_NAME;
import static jdk.jshell.Util.asLetters;

View File

@ -38,9 +38,9 @@ import java.util.regex.Matcher;
import java.util.stream.Stream;
import static java.util.stream.Collectors.toList;
import static jdk.internal.jshell.remote.RemoteCodes.PREFIX_PATTERN;
import static jdk.jshell.Util.PREFIX_PATTERN;
import static jdk.jshell.Util.REPL_PACKAGE;
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_DEP;
import static jdk.internal.jshell.remote.RemoteCodes.REPL_PACKAGE;
/**
* Maintain relationships between the significant entities: Snippets,

View File

@ -104,7 +104,6 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
@ -130,6 +129,7 @@ import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
import static jdk.jshell.Util.REPL_DOESNOTMATTER_CLASS_NAME;
import static java.util.stream.Collectors.joining;
/**
* The concrete implementation of SourceCodeAnalysis.

View File

@ -60,7 +60,6 @@ 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;
import java.lang.Runtime.Version;
/**
@ -278,13 +277,19 @@ class TaskFactory {
return result;
}
List<ClassInfo> classInfoList(OuterWrap w) {
// Returns the list of classes generated during this compile.
// Stores the mapping between class name and current compiled bytes.
List<String> classList(OuterWrap w) {
List<OutputMemoryJavaFileObject> l = classObjs.get(w);
if (l == null) return Collections.emptyList();
return l.stream()
.map(fo -> state.classTracker.classInfo(fo.getName(), fo.getBytes()))
.collect(Collectors.toList());
if (l == null) {
return Collections.emptyList();
}
List<String> list = new ArrayList<>();
for (OutputMemoryJavaFileObject fo : l) {
state.setClassnameToBytes(fo.getName(), fo.getBytes());
list.add(fo.getName());
}
return list;
}
private void listenForNewClassFile(OutputMemoryJavaFileObject jfo, JavaFileManager.Location location,

View File

@ -30,18 +30,14 @@ 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;
@ -79,7 +75,7 @@ final class Unit {
private SnippetEvent replaceOldEvent;
private List<SnippetEvent> secondaryEvents;
private boolean isAttemptingCorral;
private List<ClassInfo> toRedefine;
private List<String> toRedefine;
private boolean dependenciesNeeded;
Unit(JShell state, Snippet si, Snippet causalSnippet,
@ -260,10 +256,6 @@ final class Unit {
si, status);
}
/**
* Must be called for each unit
* @return
*/
boolean isDefined() {
return status.isDefined;
}
@ -273,21 +265,28 @@ final class Unit {
* Requires loading of returned list.
* @return the list of classes to load
*/
Stream<ClassInfo> classesToLoad(List<ClassInfo> cil) {
Stream<String> classesToLoad(List<String> classnames) {
toRedefine = new ArrayList<>();
List<ClassInfo> toLoad = new ArrayList<>();
List<String> toLoad = new ArrayList<>();
if (status.isDefined && !isImport()) {
cil.stream().forEach(ci -> {
if (!ci.isLoaded()) {
if (ci.getReferenceTypeOrNull() == null) {
toLoad.add(ci);
ci.setLoaded();
// Classes should only be loaded/redefined if the compile left them
// in a defined state. Imports do not have code and are not loaded.
for (String cn : classnames) {
switch (state.executionControl().getClassStatus(cn)) {
case UNKNOWN:
// If not loaded, add to the list of classes to load.
toLoad.add(cn);
dependenciesNeeded = true;
} else {
toRedefine.add(ci);
}
break;
case NOT_CURRENT:
// If loaded but out of date, add to the list of classes to attempt redefine.
toRedefine.add(cn);
break;
case CURRENT:
// Loaded and current, so nothing to do
break;
}
});
}
}
return toLoad.stream();
}
@ -298,19 +297,9 @@ final class Unit {
* @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;
}
return toRedefine.isEmpty()
? true
: state.executionControl().redefine(toRedefine);
}
void markForReplacement() {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -29,20 +29,40 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.regex.Pattern;
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.PREFIX_PATTERN;
import static jdk.internal.jshell.remote.RemoteCodes.REPL_CLASS_PREFIX;
/**
* Assorted shared utilities.
* @author Robert Field
* Assorted shared utilities and constants.
*/
class Util {
static final String REPL_DOESNOTMATTER_CLASS_NAME = REPL_CLASS_PREFIX+"DOESNOTMATTER";
/**
* The package name of all wrapper classes.
*/
static final String REPL_PACKAGE = "REPL";
/**
* The prefix for all wrapper class names.
*/
static final String REPL_CLASS_PREFIX = "$JShell$";
/**
* The name of the invoke method.
*/
static final String DOIT_METHOD_NAME = "do_it$";
/**
* A pattern matching the full or simple class name of a wrapper class.
*/
static final Pattern PREFIX_PATTERN = Pattern.compile(
"(" + REPL_PACKAGE + "\\.)?"
+ "(?<class>" + Pattern.quote(REPL_CLASS_PREFIX)
+ "\\w+" + ")" + "[\\$\\.]?");
static final String REPL_DOESNOTMATTER_CLASS_NAME = REPL_CLASS_PREFIX + "DOESNOTMATTER";
static final Locale PARSED_LOCALE = Locale.ROOT;

View File

@ -27,7 +27,7 @@ package jdk.jshell;
import java.util.Arrays;
import static java.util.stream.Collectors.joining;
import static jdk.internal.jshell.remote.RemoteCodes.DOIT_METHOD_NAME;
import static jdk.jshell.Util.DOIT_METHOD_NAME;
/**
* Wrapping of source into Java methods, fields, etc. All but outer layer

View File

@ -0,0 +1,154 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute 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.spi;
import java.util.Collection;
import jdk.jshell.JShellException;
/**
* This interface specifies the functionality that must provided to implement
* a pluggable JShell execution engine.
* <p>
* The audience for this Service Provider Interface is engineers
* wishing to implement their own version of the execution engine in support
* of the JShell API. This is NOT a part of the JShell API.
* <p>
* A Snippet is compiled into code wrapped in a 'wrapper class'. The execution
* engine is used by the core JShell implementation to load and, for
* executable Snippets, execute the Snippet.
* <p>
* Methods defined in this interface should only be called by the core JShell
* implementation.
* <p>
* To install an instance of ExecutionControl, it is passed to
* {@link jdk.jshell.JShell.Builder#executionEngine(jdk.jshell.spi.ExecutionControl) }.
*/
public interface ExecutionControl {
/**
* Represents the current status of a class in the execution engine.
*/
public enum ClassStatus {
/**
* Class is not known to the execution engine (not loaded).
*/
UNKNOWN,
/**
* Class is loaded, but the loaded/redefined bytes do not match those
* returned by {@link ExecutionEnv#getClassBytes(java.lang.String) }.
*/
NOT_CURRENT,
/**
* Class is loaded and loaded/redefined bytes match those
* returned by {@link ExecutionEnv#getClassBytes(java.lang.String) }.
*/
CURRENT
};
/**
* Initializes the instance. No methods in this interface can be called
* before this.
*
* @param env the execution environment information provided by JShell
* @throws Exception if the instance is unable to initialize
*/
void start(ExecutionEnv env) throws Exception;
/**
* Shuts down this execution engine. Implementation should free all
* resources held by this execution engine.
* <p>
* No calls to methods on this interface should be made after close.
*/
void close();
/**
* Adds the path to the execution class path.
*
* @param path the path to add
* @return true if successful
*/
boolean addToClasspath(String path);
/**
* Invokes an executable Snippet by calling a method on the specified
* wrapper class. The method must have no arguments and return String.
*
* @param classname the class whose method should be invoked
* @param methodname the name of method to invoke
* @return the result of the execution or null if no result
* @throws JShellException if a user exception if thrown,
* {@link jdk.jshell.EvalException EvalException} will be thrown; if an
* unresolved reference is encountered,
* {@link jdk.jshell.UnresolvedReferenceException UnresolvedReferenceException}
* will be thrown
*/
String invoke(String classname, String methodname) throws JShellException;
/**
* Attempts to load new classes. Class bytes are retrieved from
* {@link ExecutionEnv#getClassBytes(java.lang.String) }
*
* @param classes list of class names to load
* @return true if load succeeded
*/
boolean load(Collection<String> classes);
/**
* Attempts to redefine previously loaded classes. Class bytes are retrieved
* from {@link ExecutionEnv#getClassBytes(java.lang.String) }
*
* @param classes list of class names to redefine
* @return true if redefine succeeded
*/
boolean redefine(Collection<String> classes);
/**
* Queries if the class is loaded and the class bytes are current.
*
* @param classname name of the wrapper class to query
* @return {@code UNKNOWN} if the class is not loaded; {@code CURRENT} if
* the loaded/redefined bytes are equal to the most recent bytes for this
* wrapper class; otherwise {@code NOT_CURRENT}
*/
ClassStatus getClassStatus(String classname);
/**
* Interrupt a running invoke.
*/
void stop();
/**
* Returns the value of a variable.
*
* @param classname the name of the wrapper class of the variable
* @param varname the name of the variable
* @return the value of the variable
*/
String varValue(String classname, String varname);
}

View File

@ -0,0 +1,127 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute 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.spi;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.List;
import jdk.jshell.EvalException;
import jdk.jshell.JShell;
import jdk.jshell.UnresolvedReferenceException;
/**
* Functionality made available to a pluggable JShell execution engine. It is
* provided to the execution engine by the core JShell implementation calling
* {@link ExecutionControl#start(jdk.jshell.spi.ExecutionEnv) }.
* <p>
* This interface is designed to provide the access to core JShell functionality
* needed to implement ExecutionControl.
*
* @see ExecutionControl
*/
public interface ExecutionEnv {
/**
* Returns the user's input stream.
*
* @return the user's input stream
*/
InputStream userIn();
/**
* Returns the user's output stream.
*
* @return the user's output stream
*/
PrintStream userOut();
/**
* Returns the user's error stream.
*
* @return the user's error stream
*/
PrintStream userErr();
/**
* @return the JShell instance
*/
JShell state();
/**
* Returns the additional VM options to be used when launching the remote
* JVM. This is advice to the execution engine.
* <p>
* Note: an execution engine need not launch a remote JVM.
*
* @return the additional options with which to launch the remote JVM
*/
List<String> extraRemoteVMOptions();
/**
* Retrieves the class file bytes for the specified wrapper class.
*
* @param className the name of the wrapper class
* @return the current class file bytes as a byte array
*/
byte[] getClassBytes(String className);
/**
* Creates an {@code EvalException} corresponding to a user exception. An
* user exception thrown during
* {@link ExecutionControl#invoke(java.lang.String, java.lang.String) }
* should be converted to an {@code EvalException} using this method.
*
* @param message the exception message to use (from the user exception)
* @param exceptionClass the class name of the user exception
* @param stackElements the stack trace elements to install
* @return a user API EvalException for the user exception
*/
EvalException createEvalException(String message, String exceptionClass,
StackTraceElement[] stackElements);
/**
* Creates an {@code UnresolvedReferenceException} for the Snippet identifed
* by the specified identifier. An {@link SPIResolutionException} thrown
* during {@link ExecutionControl#invoke(java.lang.String, java.lang.String) }
* should be converted to an {@code UnresolvedReferenceException} using
* this method.
* <p>
* The identifier is an internal id, different from the id in the API. This
* internal id is returned by {@link SPIResolutionException#id()}.
*
* @param id the internal integer identifier
* @param stackElements the stack trace elements to install
* @return an {@code UnresolvedReferenceException} for the unresolved
* reference
*/
UnresolvedReferenceException createUnresolvedReferenceException(int id,
StackTraceElement[] stackElements);
/**
* Reports that the execution engine has shutdown.
*/
void closeDown();
}

View File

@ -0,0 +1,69 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute 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.spi;
/**
* The construction and throw of this exception is embedded in code generated by
* the JShell core implementation in such a way that, upon executing a
* {@link jdk.jshell.Snippet.Status#RECOVERABLE_DEFINED RECOVERABLE_DEFINED}
* user method, this exception is thrown.
* <p>
* This exception is seen by the execution engine, but not seen by
* the end user nor through the JShell API.
*
* @see ExecutionEnv#createUnresolvedReferenceException(int,
* java.lang.StackTraceElement[])
*/
@SuppressWarnings("serial") // serialVersionUID intentionally omitted
public class SPIResolutionException extends RuntimeException {
private final int id;
/**
* Constructs an SPI layer exception indicating that a
* {@code DeclarationSnippet} with unresolved references has been
* encountered. 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 SPIResolutionException(int id) {
super("resolution exception");
this.id = id;
}
/**
* Retrieves the internal identifer of the unresolved identifer.
*
* @return the internal identifer
* @see ExecutionEnv#createUnresolvedReferenceException(int,
* java.lang.StackTraceElement[])
*/
public int id() {
return id;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -23,27 +23,17 @@
* 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
* Provides support for alternate implementations of the JShell execution
* engine. The JShell core tracks and compiles Snippets then sends them
* (represented in a wrapper class) to the execution engine for loading,
* and in the case of executable Snippets, execution. The JShell
* implementation includes a default execution engine (currently a remote
* process which is JDI controlled). By implementing the
* {@link ExecutionControl} interface and installing it with
* {@link jdk.jshell.JShell.Builder#executionEngine(jdk.jshell.spi.ExecutionControl) }
* other execution engines can be used.
* <p>
* This is not a part of the JShell API.
*/
@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;
}
}
package jdk.jshell.spi;

View File

@ -32,4 +32,5 @@ module jdk.jshell {
requires jdk.jdi;
exports jdk.jshell;
exports jdk.jshell.spi;
}

View File

@ -0,0 +1,150 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8156101
* @summary Tests for ExecutionControl SPI
* @build KullaTesting LocalExecutionControl
* @run testng ExecutionControlTest
*/
import javax.tools.Diagnostic;
import jdk.jshell.VarSnippet;
import org.testng.annotations.Test;
import static jdk.jshell.Snippet.Status.VALID;
import static jdk.jshell.Snippet.SubKind.*;
import static org.testng.Assert.assertEquals;
import org.testng.annotations.BeforeMethod;
@Test
public class ExecutionControlTest extends KullaTesting {
@BeforeMethod
@Override
public void setUp() {
setUp(new LocalExecutionControl());
}
public void verifyLocal() throws ClassNotFoundException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
System.setProperty("LOCAL_CHECK", "TBD");
assertEquals(System.getProperty("LOCAL_CHECK"), "TBD");
assertEval("System.setProperty(\"LOCAL_CHECK\", \"local\")");
assertEquals(System.getProperty("LOCAL_CHECK"), "local");
}
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();
}
@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();");
}
public void variables() {
VarSnippet snx = varKey(assertEval("int x = 10;"));
VarSnippet sny = varKey(assertEval("String y = \"hi\";"));
VarSnippet snz = varKey(assertEval("long z;"));
assertVariables(variable("int", "x"), variable("String", "y"), variable("long", "z"));
assertVarValue(snx, "10");
assertVarValue(sny, "\"hi\"");
assertVarValue(snz, "0");
assertActiveKeys();
}
public void methodOverload() {
assertEval("int m() { return 1; }");
assertEval("int m(int x) { return 2; }");
assertEval("int m(String s) { return 3; }");
assertEval("int m(int x, int y) { return 4; }");
assertEval("int m(int x, String z) { return 5; }");
assertEval("int m(int x, String z, long g) { return 6; }");
assertMethods(
method("()int", "m"),
method("(int)int", "m"),
method("(String)int", "m"),
method("(int,int)int", "m"),
method("(int,String)int", "m"),
method("(int,String,long)int", "m")
);
assertEval("m();", "1");
assertEval("m(3);", "2");
assertEval("m(\"hi\");", "3");
assertEval("m(7, 8);", "4");
assertEval("m(7, \"eight\");", "5");
assertEval("m(7, \"eight\", 9L);", "6");
assertActiveKeys();
}
public void testExprSanity() {
assertEval("int x = 3;", "3");
assertEval("int y = 4;", "4");
assertEval("x + y;", "7");
assertActiveKeys();
}
public void testImportOnDemand() {
assertImportKeyMatch("import java.util.*;", "java.util.*", TYPE_IMPORT_ON_DEMAND_SUBKIND, added(VALID));
assertEval("List<Integer> list = new ArrayList<>();");
assertEval("list.add(45);");
assertEval("list.size();", "1");
}
}

View File

@ -72,6 +72,7 @@ import jdk.jshell.Diag;
import static jdk.jshell.Snippet.Status.*;
import static org.testng.Assert.*;
import static jdk.jshell.Snippet.SubKind.METHOD_SUBKIND;
import jdk.jshell.spi.ExecutionControl;
public class KullaTesting {
@ -166,6 +167,21 @@ public class KullaTesting {
classpath = new ArrayList<>();
}
public void setUp(ExecutionControl ec) {
inStream = new TestingInputStream();
outStream = new ByteArrayOutputStream();
errStream = new ByteArrayOutputStream();
state = JShell.builder()
.executionEngine(ec)
.in(inStream)
.out(new PrintStream(outStream))
.err(new PrintStream(errStream))
.build();
allSnippets = new LinkedHashSet<>();
idToSnippet = new LinkedHashMap<>();
classpath = new ArrayList<>();
}
@AfterMethod
public void tearDown() {
if (state != null) state.close();

View File

@ -0,0 +1,312 @@
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute 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.
*/
import jdk.jshell.spi.ExecutionControl;
import jdk.jshell.spi.ExecutionEnv;
import jdk.jshell.spi.SPIResolutionException;
import jdk.jshell.EvalException;
import jdk.jshell.UnresolvedReferenceException;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.CodeSource;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicReference;
/**
* An implementation of ExecutionControl which executes in the same JVM as the
* JShell core.
*
* @author Grigory Ptashko
*/
class LocalExecutionControl implements ExecutionControl {
private class REPLClassLoader extends URLClassLoader {
REPLClassLoader() {
super(new URL[0]);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
debug("findClass %s\n", name);
byte[] b = execEnv.getClassBytes(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);
}
}
private ExecutionEnv execEnv;
private final Object STOP_LOCK = new Object();
private boolean userCodeRunning = false;
private REPLClassLoader loader = new REPLClassLoader();
private final Map<String, Class<?>> klasses = new TreeMap<>();
private final Map<String, byte[]> classBytes = new HashMap<>();
private ThreadGroup execThreadGroup;
@Override
public void start(ExecutionEnv execEnv) throws Exception {
this.execEnv = execEnv;
debug("Process-local code snippets execution control started");
}
@Override
public void close() {
}
@Override
public boolean load(Collection<String> classes) {
try {
loadLocal(classes);
return true;
} catch (ClassNotFoundException | ClassCastException ex) {
debug(ex, "Exception on load operation");
}
return false;
}
@Override
public String invoke(String classname, String methodname) throws EvalException, UnresolvedReferenceException {
try {
synchronized (STOP_LOCK) {
userCodeRunning = true;
}
// Invoke executable entry point in loaded code
Class<?> klass = klasses.get(classname);
if (klass == null) {
debug("Invoke failure: no such class loaded %s\n", classname);
return "";
}
Method doitMethod;
try {
this.getClass().getModule().addReads(klass.getModule());
this.getClass().getModule().addExports(SPIResolutionException.class.getPackage()
.getName(), klass.getModule());
doitMethod = klass.getDeclaredMethod(methodname, new Class<?>[0]);
doitMethod.setAccessible(true);
execThreadGroup = new ThreadGroup("JShell process local execution");
AtomicReference<InvocationTargetException> iteEx = new AtomicReference<>();
AtomicReference<IllegalAccessException> iaeEx = new AtomicReference<>();
AtomicReference<NoSuchMethodException> nmeEx = new AtomicReference<>();
AtomicReference<Boolean> stopped = new AtomicReference<>(false);
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
if (e instanceof InvocationTargetException) {
if (e.getCause() instanceof ThreadDeath) {
stopped.set(true);
} else {
iteEx.set((InvocationTargetException)e);
}
} else if (e instanceof IllegalAccessException) {
iaeEx.set((IllegalAccessException)e);
} else if (e instanceof NoSuchMethodException) {
nmeEx.set((NoSuchMethodException)e);
} else if (e instanceof ThreadDeath) {
stopped.set(true);
}
});
final Object[] res = new Object[1];
Thread snippetThread = new Thread(execThreadGroup, () -> {
try {
res[0] = doitMethod.invoke(null, new Object[0]);
} catch (InvocationTargetException e) {
if (e.getCause() instanceof ThreadDeath) {
stopped.set(true);
} else {
iteEx.set(e);
}
} catch (IllegalAccessException e) {
iaeEx.set(e);
} catch (ThreadDeath e) {
stopped.set(true);
}
});
snippetThread.start();
Thread[] threadList = new Thread[execThreadGroup.activeCount()];
execThreadGroup.enumerate(threadList);
for (Thread thread : threadList) {
thread.join();
}
if (stopped.get()) {
debug("Killed.");
return "";
}
if (iteEx.get() != null) {
throw iteEx.get();
} else if (nmeEx.get() != null) {
throw nmeEx.get();
} else if (iaeEx.get() != null) {
throw iaeEx.get();
}
return valueString(res[0]);
} catch (InvocationTargetException ex) {
Throwable cause = ex.getCause();
StackTraceElement[] elems = cause.getStackTrace();
if (cause instanceof SPIResolutionException) {
int id = ((SPIResolutionException)cause).id();
throw execEnv.createUnresolvedReferenceException(id, elems);
} else {
throw execEnv.createEvalException(cause.getMessage() == null ?
"<none>" : cause.getMessage(), cause.getClass().getName(), elems);
}
} catch (NoSuchMethodException | IllegalAccessException | InterruptedException ex) {
debug(ex, "Invoke failure");
}
} finally {
synchronized (STOP_LOCK) {
userCodeRunning = false;
}
}
return "";
}
@Override
@SuppressWarnings("deprecation")
public void stop() {
synchronized (STOP_LOCK) {
if (!userCodeRunning)
return;
if (execThreadGroup == null) {
debug("Process-local code snippets thread group is null. Aborting stop.");
return;
}
execThreadGroup.stop();
}
}
@Override
public String varValue(String classname, String varname) {
Class<?> klass = klasses.get(classname);
if (klass == null) {
debug("Var value failure: no such class loaded %s\n", classname);
return "";
}
try {
this.getClass().getModule().addReads(klass.getModule());
Field var = klass.getDeclaredField(varname);
var.setAccessible(true);
Object res = var.get(null);
return valueString(res);
} catch (Exception ex) {
debug("Var value failure: no such field %s.%s\n", classname, varname);
}
return "";
}
@Override
public boolean addToClasspath(String cp) {
// Append to the claspath
for (String path : cp.split(File.pathSeparator)) {
try {
loader.addURL(new File(path).toURI().toURL());
} catch (MalformedURLException e) {
throw new InternalError("Classpath addition failed: " + cp, e);
}
}
return true;
}
@Override
public boolean redefine(Collection<String> classes) {
return false;
}
@Override
public ClassStatus getClassStatus(String classname) {
if (!classBytes.containsKey(classname)) {
return ClassStatus.UNKNOWN;
} else if (!Arrays.equals(classBytes.get(classname), execEnv.getClassBytes(classname))) {
return ClassStatus.NOT_CURRENT;
} else {
return ClassStatus.CURRENT;
}
}
private void loadLocal(Collection<String> classes) throws ClassNotFoundException {
for (String className : classes) {
Class<?> klass = loader.loadClass(className);
klasses.put(className, klass);
classBytes.put(className, execEnv.getClassBytes(className));
klass.getDeclaredMethods();
}
}
private void debug(String format, Object... args) {
//debug(execEnv.state(), execEnv.userErr(), flags, format, args);
}
private void debug(Exception ex, String where) {
//debug(execEnv.state(), execEnv.userErr(), ex, where);
}
private static String valueString(Object value) {
if (value == null) {
return "null";
} else if (value instanceof String) {
return "\"" + (String)value + "\"";
} else if (value instanceof Character) {
return "'" + value + "'";
} else {
return value.toString();
}
}
}