blob: a2c70a1bdaa8d5a68c192600e875525a91d6a11a [file] [log] [blame]
/*
* Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute 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.execution;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import jdk.jshell.JShellException;
import jdk.jshell.spi.ExecutionControl;
import static jdk.jshell.execution.ExecutionControlForwarder.NULL_MARKER;
import static jdk.jshell.execution.RemoteCodes.*;
/**
* An implementation of the {@link jdk.jshell.spi.ExecutionControl}
* execution engine SPI which streams requests to a remote agent where
* execution takes place.
*
* @author Robert Field
* @since 9
*/
public class StreamingExecutionControl implements ExecutionControl {
private final ObjectOutput out;
private final ObjectInput in;
/**
* Creates an instance.
*
* @param out the output for commands
* @param in the input for command responses
*/
public StreamingExecutionControl(ObjectOutput out, ObjectInput in) {
this.out = out;
this.in = in;
}
@Override
public void load(ClassBytecodes[] cbcs)
throws ClassInstallException, NotImplementedException, EngineTerminationException {
try {
// Send a load command to the remote agent.
writeCommand(CMD_LOAD);
out.writeObject(cbcs);
out.flush();
// Retrieve and report results from the remote agent.
readAndReportClassInstallResult();
} catch (IOException ex) {
throw new EngineTerminationException("Exception writing remote load: " + ex);
}
}
@Override
public void redefine(ClassBytecodes[] cbcs)
throws ClassInstallException, NotImplementedException, EngineTerminationException {
try {
// Send a load command to the remote agent.
writeCommand(CMD_REDEFINE);
out.writeObject(cbcs);
out.flush();
// Retrieve and report results from the remote agent.
readAndReportClassInstallResult();
} catch (IOException ex) {
throw new EngineTerminationException("Exception writing remote redefine: " + ex);
}
}
@Override
public String invoke(String classname, String methodname)
throws RunException, EngineTerminationException, InternalException {
try {
// Send the invoke command to the remote agent.
writeCommand(CMD_INVOKE);
out.writeUTF(classname);
out.writeUTF(methodname);
out.flush();
// Retrieve and report results from the remote agent.
readAndReportExecutionResult();
String result = in.readUTF();
return result;
} catch (IOException ex) {
throw new EngineTerminationException("Exception writing remote invoke: " + ex);
}
}
@Override
public String varValue(String classname, String varname)
throws RunException, EngineTerminationException, InternalException {
try {
// Send the variable-value command to the remote agent.
writeCommand(CMD_VAR_VALUE);
out.writeUTF(classname);
out.writeUTF(varname);
out.flush();
// Retrieve and report results from the remote agent.
readAndReportExecutionResult();
String result = in.readUTF();
return result;
} catch (IOException ex) {
throw new EngineTerminationException("Exception writing remote varValue: " + ex);
}
}
@Override
public void addToClasspath(String path)
throws EngineTerminationException, InternalException {
try {
// Send the classpath addition command to the remote agent.
writeCommand(CMD_ADD_CLASSPATH);
out.writeUTF(path);
out.flush();
// Retrieve and report results from the remote agent.
readAndReportClassSimpleResult();
} catch (IOException ex) {
throw new EngineTerminationException("Exception writing remote add to classpath: " + ex);
}
}
@Override
public void stop()
throws EngineTerminationException, InternalException {
try {
// Send the variable-value command to the remote agent.
writeCommand(CMD_STOP);
out.flush();
} catch (IOException ex) {
throw new EngineTerminationException("Exception writing remote stop: " + ex);
}
}
@Override
public Object extensionCommand(String command, Object arg)
throws RunException, EngineTerminationException, InternalException {
try {
writeCommand(command);
out.writeObject(arg);
out.flush();
// Retrieve and report results from the remote agent.
readAndReportExecutionResult();
Object result = in.readObject();
return result;
} catch (IOException | ClassNotFoundException ex) {
throw new EngineTerminationException("Exception transmitting remote extensionCommand: "
+ command + " -- " + ex);
}
}
/**
* Closes the execution engine. Send an exit command to the remote agent.
*/
@Override
public void close() {
try {
writeCommand(CMD_CLOSE);
out.flush();
} catch (IOException ex) {
// ignore;
}
}
private void writeCommand(String cmd) throws IOException {
out.writeInt(COMMAND_PREFIX);
out.writeUTF(cmd);
}
/**
* Read a UTF or a null encoded as a null marker.
* @return a string or null
* @throws IOException passed through from readUTF()
*/
private String readNullOrUTF() throws IOException {
String s = in.readUTF();
return s.equals(NULL_MARKER) ? null : s;
}
/**
* Reports results from a remote agent command that does not expect
* exceptions.
*/
private void readAndReportClassSimpleResult() throws EngineTerminationException, InternalException {
try {
int status = in.readInt();
switch (status) {
case RESULT_SUCCESS:
return;
case RESULT_NOT_IMPLEMENTED: {
String message = in.readUTF();
throw new NotImplementedException(message);
}
case RESULT_INTERNAL_PROBLEM: {
String message = in.readUTF();
throw new InternalException(message);
}
case RESULT_TERMINATED: {
String message = in.readUTF();
throw new EngineTerminationException(message);
}
default: {
throw new EngineTerminationException("Bad remote result code: " + status);
}
}
} catch (IOException ex) {
throw new EngineTerminationException(ex.toString());
}
}
/**
* Reports results from a remote agent command that does not expect
* exceptions.
*/
private void readAndReportClassInstallResult() throws ClassInstallException,
NotImplementedException, EngineTerminationException {
try {
int status = in.readInt();
switch (status) {
case RESULT_SUCCESS:
return;
case RESULT_NOT_IMPLEMENTED: {
String message = in.readUTF();
throw new NotImplementedException(message);
}
case RESULT_CLASS_INSTALL_EXCEPTION: {
String message = in.readUTF();
boolean[] loaded = (boolean[]) in.readObject();
throw new ClassInstallException(message, loaded);
}
case RESULT_TERMINATED: {
String message = in.readUTF();
throw new EngineTerminationException(message);
}
default: {
throw new EngineTerminationException("Bad remote result code: " + status);
}
}
} catch (IOException | ClassNotFoundException ex) {
throw new EngineTerminationException(ex.toString());
}
}
/**
* Reports results from a remote agent command that expects runtime
* exceptions.
*
* @return true if successful
* @throws IOException if the connection has dropped
* @throws JShellException {@link jdk.jshell.EvalException}, if a user
* exception was encountered on invoke;
* {@link jdk.jshell.UnresolvedReferenceException}, if an unresolved
* reference was encountered
* @throws java.lang.ClassNotFoundException
*/
private void readAndReportExecutionResult() throws RunException,
EngineTerminationException, InternalException {
try {
int status = in.readInt();
switch (status) {
case RESULT_SUCCESS:
return;
case RESULT_NOT_IMPLEMENTED: {
String message = in.readUTF();
throw new NotImplementedException(message);
}
case RESULT_USER_EXCEPTION: {
// A user exception was encountered. Handle pre JDK 11 back-ends
throw readUserException();
}
case RESULT_CORRALLED: {
// An unresolved reference was encountered.
throw readResolutionException();
}
case RESULT_USER_EXCEPTION_CHAINED: {
// A user exception was encountered -- transmit chained.
in.readInt(); // always RESULT_USER_EXCEPTION
UserException result = readUserException();
RunException caused = result;
// Loop through the chained causes (if any) building a chained exception
loop: while (true) {
RunException ex;
int cstatus = in.readInt();
switch (cstatus) {
case RESULT_USER_EXCEPTION: {
// A user exception was the proximal cause.
ex = readUserException();
break;
}
case RESULT_CORRALLED: {
// An unresolved reference was the underlying cause.
ex = readResolutionException();
break;
}
case RESULT_SUCCESS: {
// End of chained exceptions
break loop;
}
default: {
throw new EngineTerminationException("Bad chained remote result code: " + cstatus);
}
}
caused.initCause(ex);
caused = ex;
}
caused.initCause(null); // root cause has no cause
throw result;
}
case RESULT_STOPPED: {
// Execution was aborted by the stop()
throw new StoppedException();
}
case RESULT_INTERNAL_PROBLEM: {
// An internal error has occurred.
String message = in.readUTF();
throw new InternalException(message);
}
case RESULT_TERMINATED: {
String message = in.readUTF();
throw new EngineTerminationException(message);
}
default: {
throw new EngineTerminationException("Bad remote result code: " + status);
}
}
} catch (IOException | ClassNotFoundException ex) {
ex.printStackTrace();
throw new EngineTerminationException(ex.toString());
}
}
private UserException readUserException() throws IOException, ClassNotFoundException {
String message = readNullOrUTF();
String exceptionClassName = in.readUTF();
StackTraceElement[] elems = (StackTraceElement[]) in.readObject();
return new UserException(message, exceptionClassName, elems);
}
private ResolutionException readResolutionException() throws IOException, ClassNotFoundException {
int id = in.readInt();
StackTraceElement[] elems = (StackTraceElement[]) in.readObject();
return new ResolutionException(id, elems);
}
}