/*
* 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.spi.ExecutionControl;
import jdk.jshell.spi.ExecutionControl.ClassBytecodes;
import jdk.jshell.spi.ExecutionControl.ClassInstallException;
import jdk.jshell.spi.ExecutionControl.EngineTerminationException;
import jdk.jshell.spi.ExecutionControl.InternalException;
import jdk.jshell.spi.ExecutionControl.NotImplementedException;
import jdk.jshell.spi.ExecutionControl.ResolutionException;
import jdk.jshell.spi.ExecutionControl.StoppedException;
import jdk.jshell.spi.ExecutionControl.UserException;
import static jdk.jshell.execution.RemoteCodes.*;
Forwards commands from the input to the specified ExecutionControl
instance, then responses back on the output. /**
* Forwards commands from the input to the specified {@link ExecutionControl}
* instance, then responses back on the output.
*/
class ExecutionControlForwarder {
Represent null in a streamed UTF string. Vanishingly improbable string to
occur in a user string.
/**
* Represent null in a streamed UTF string. Vanishingly improbable string to
* occur in a user string.
*/
static final String NULL_MARKER = "\u0002*\u03C0*NULL*\u03C0*\u0003";
Maximum number of characters for writeUTF(). Byte maximum is 65535, at
maximum three bytes per character that is 65535 / 3 == 21845. Minus one
for safety.
/**
* Maximum number of characters for writeUTF(). Byte maximum is 65535, at
* maximum three bytes per character that is 65535 / 3 == 21845. Minus one
* for safety.
*/
private static final int MAX_UTF_CHARS = 21844;
private final ExecutionControl ec;
private final ObjectInput in;
private final ObjectOutput out;
ExecutionControlForwarder(ExecutionControl ec, ObjectInput in, ObjectOutput out) {
this.ec = ec;
this.in = in;
this.out = out;
}
private boolean writeSuccess() throws IOException {
writeStatus(RESULT_SUCCESS);
flush();
return true;
}
private boolean writeSuccessAndResult(String result) throws IOException {
writeStatus(RESULT_SUCCESS);
writeUTF(result);
flush();
return true;
}
private boolean writeSuccessAndResult(Object result) throws IOException {
writeStatus(RESULT_SUCCESS);
writeObject(result);
flush();
return true;
}
private void writeStatus(int status) throws IOException {
out.writeInt(status);
}
private void writeObject(Object o) throws IOException {
out.writeObject(o);
}
private void writeInt(int i) throws IOException {
out.writeInt(i);
}
private void writeNullOrUTF(String s) throws IOException {
writeUTF(s == null ? NULL_MARKER : s);
}
private void writeUTF(String s) throws IOException {
if (s == null) {
s = "";
} else if (s.length() > MAX_UTF_CHARS) {
// Truncate extremely long strings to prevent writeUTF from crashing the VM
s = s.substring(0, MAX_UTF_CHARS);
}
out.writeUTF(s);
}
private void flush() throws IOException {
out.flush();
}
private boolean processCommand() throws IOException {
try {
int prefix = in.readInt();
if (prefix != COMMAND_PREFIX) {
throw new EngineTerminationException("Invalid command prefix: " + prefix);
}
String cmd = in.readUTF();
switch (cmd) {
case CMD_LOAD: {
// Load a generated class file over the wire
ClassBytecodes[] cbcs = (ClassBytecodes[]) in.readObject();
ec.load(cbcs);
return writeSuccess();
}
case CMD_REDEFINE: {
// Load a generated class file over the wire
ClassBytecodes[] cbcs = (ClassBytecodes[]) in.readObject();
ec.redefine(cbcs);
return writeSuccess();
}
case CMD_INVOKE: {
// Invoke executable entry point in loaded code
String className = in.readUTF();
String methodName = in.readUTF();
String res = ec.invoke(className, methodName);
return writeSuccessAndResult(res);
}
case CMD_VAR_VALUE: {
// Retrieve a variable value
String className = in.readUTF();
String varName = in.readUTF();
String res = ec.varValue(className, varName);
return writeSuccessAndResult(res);
}
case CMD_ADD_CLASSPATH: {
// Append to the claspath
String cp = in.readUTF();
ec.addToClasspath(cp);
return writeSuccess();
}
case CMD_STOP: {
// Stop the current execution
try {
ec.stop();
} catch (Throwable ex) {
// JShell-core not waiting for a result, ignore
}
return true;
}
case CMD_CLOSE: {
// Terminate this process
try {
ec.close();
} catch (Throwable ex) {
// JShell-core not waiting for a result, ignore
}
return true;
}
default: {
Object arg = in.readObject();
Object res = ec.extensionCommand(cmd, arg);
return writeSuccessAndResult(res);
}
}
} catch (IOException ex) {
// handled by the outer level
throw ex;
} catch (EngineTerminationException ex) {
writeStatus(RESULT_TERMINATED);
writeUTF(ex.getMessage());
flush();
return false;
} catch (NotImplementedException ex) {
writeStatus(RESULT_NOT_IMPLEMENTED);
writeUTF(ex.getMessage());
flush();
return true;
} catch (InternalException ex) {
writeInternalException(ex);
flush();
return true;
} catch (ClassInstallException ex) {
writeStatus(RESULT_CLASS_INSTALL_EXCEPTION);
writeUTF(ex.getMessage());
writeObject(ex.installed());
flush();
return true;
} catch (UserException ex) {
writeStatus(RESULT_USER_EXCEPTION_CHAINED);
for (Throwable e = ex; e != null; ) {
if (e instanceof UserException) {
writeUserException((UserException) e);
e = e.getCause();
} else if (e instanceof ResolutionException) {
writeResolutionException((ResolutionException) e);
e = null;
} else {
writeInternalException(e);
e = null;
}
}
writeStatus(RESULT_SUCCESS);
flush();
return true;
} catch (ResolutionException ex) {
writeResolutionException(ex);
flush();
return true;
} catch (StoppedException ex) {
writeStatus(RESULT_STOPPED);
flush();
return true;
} catch (Throwable ex) {
// Unexpected exception, have something in the message
writeStatus(RESULT_TERMINATED);
String msg = ex.getMessage();
writeUTF(msg == null? ex.toString() : msg);
flush();
return false;
}
}
void writeInternalException(Throwable ex) throws IOException {
writeStatus(RESULT_INTERNAL_PROBLEM);
writeUTF(ex.getMessage());
}
void writeUserException(UserException ex) throws IOException {
writeStatus(RESULT_USER_EXCEPTION);
writeNullOrUTF(ex.getMessage());
writeUTF(ex.causeExceptionClass());
writeObject(ex.getStackTrace());
}
void writeResolutionException(ResolutionException ex) throws IOException {
writeStatus(RESULT_CORRALLED);
writeInt(ex.id());
writeObject(ex.getStackTrace());
}
void commandLoop() {
try {
while (processCommand()) {
// condition is loop action
}
} catch (IOException ex) {
// drop out of loop
}
}
}