/*
* 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.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
This interface specifies the functionality that must provided to implement a
pluggable JShell execution engine.
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.
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.
Methods defined in this interface should only be called by the core JShell
implementation.
Since: 9
/**
* 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.
* <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.
*
* @since 9
*/
public interface ExecutionControl extends AutoCloseable {
Attempts to load new classes.
Params: - cbcs – the class name and bytecodes to load
Throws: - ClassInstallException – exception occurred loading the classes,
some or all were not loaded
- NotImplementedException – if not implemented
- EngineTerminationException – the execution engine has terminated
/**
* Attempts to load new classes.
*
* @param cbcs the class name and bytecodes to load
* @throws ClassInstallException exception occurred loading the classes,
* some or all were not loaded
* @throws NotImplementedException if not implemented
* @throws EngineTerminationException the execution engine has terminated
*/
void load(ClassBytecodes[] cbcs)
throws ClassInstallException, NotImplementedException, EngineTerminationException;
Attempts to redefine previously loaded classes.
Params: - cbcs – the class name and bytecodes to redefine
Throws: - ClassInstallException – exception occurred redefining the classes,
some or all were not redefined
- NotImplementedException – if not implemented
- EngineTerminationException – the execution engine has terminated
/**
* Attempts to redefine previously loaded classes.
*
* @param cbcs the class name and bytecodes to redefine
* @throws ClassInstallException exception occurred redefining the classes,
* some or all were not redefined
* @throws NotImplementedException if not implemented
* @throws EngineTerminationException the execution engine has terminated
*/
void redefine(ClassBytecodes[] cbcs)
throws ClassInstallException, NotImplementedException, EngineTerminationException;
Invokes an executable Snippet by calling a method on the specified
wrapper class. The method must have no arguments and return String.
Params: - className – the class whose method should be invoked
- methodName – the name of method to invoke
Throws: - UserException – the invoke raised a user exception
- ResolutionException – the invoke attempted to directly or
indirectly invoke an unresolved snippet
- StoppedException – if the
invoke()
was canceled by stop
- EngineTerminationException – the execution engine has terminated
- InternalException – an internal problem occurred
Returns: the result of the execution or null if no result
/**
* 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 UserException the invoke raised a user exception
* @throws ResolutionException the invoke attempted to directly or
* indirectly invoke an unresolved snippet
* @throws StoppedException if the {@code invoke()} was canceled by
* {@link ExecutionControl#stop}
* @throws EngineTerminationException the execution engine has terminated
* @throws InternalException an internal problem occurred
*/
String invoke(String className, String methodName)
throws RunException, EngineTerminationException, InternalException;
Returns the value of a variable.
Params: - className – the name of the wrapper class of the variable
- varName – the name of the variable
Throws: - UserException – formatting the value raised a user exception
- ResolutionException – formatting the value attempted to directly or
indirectly invoke an unresolved snippet
- StoppedException – if the formatting the value was canceled by
stop
- EngineTerminationException – the execution engine has terminated
- InternalException – an internal problem occurred
Returns: the value of the variable
/**
* 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
* @throws UserException formatting the value raised a user exception
* @throws ResolutionException formatting the value attempted to directly or
* indirectly invoke an unresolved snippet
* @throws StoppedException if the formatting the value was canceled by
* {@link ExecutionControl#stop}
* @throws EngineTerminationException the execution engine has terminated
* @throws InternalException an internal problem occurred
*/
String varValue(String className, String varName)
throws RunException, EngineTerminationException, InternalException;
Adds the path to the execution class path.
Params: - path – the path to add
Throws: - EngineTerminationException – the execution engine has terminated
- InternalException – an internal problem occurred
/**
* Adds the path to the execution class path.
*
* @param path the path to add
* @throws EngineTerminationException the execution engine has terminated
* @throws InternalException an internal problem occurred
*/
void addToClasspath(String path)
throws EngineTerminationException, InternalException;
Interrupts a running invoke.
Throws: - EngineTerminationException – the execution engine has terminated
- InternalException – an internal problem occurred
/**
* Interrupts a running invoke.
*
* @throws EngineTerminationException the execution engine has terminated
* @throws InternalException an internal problem occurred
*/
void stop()
throws EngineTerminationException, InternalException;
Run a non-standard command (or a standard command from a newer version).
Params: - command – the non-standard command
- arg – the commands argument
Throws: - UserException – the command raised a user exception
- ResolutionException – the command attempted to directly or
indirectly invoke an unresolved snippet
- StoppedException – if the command was canceled by
stop
- EngineTerminationException – the execution engine has terminated
- NotImplementedException – if not implemented
- InternalException – an internal problem occurred
Returns: the commands return value
/**
* Run a non-standard command (or a standard command from a newer version).
*
* @param command the non-standard command
* @param arg the commands argument
* @return the commands return value
* @throws UserException the command raised a user exception
* @throws ResolutionException the command attempted to directly or
* indirectly invoke an unresolved snippet
* @throws StoppedException if the command was canceled by
* {@link ExecutionControl#stop}
* @throws EngineTerminationException the execution engine has terminated
* @throws NotImplementedException if not implemented
* @throws InternalException an internal problem occurred
*/
Object extensionCommand(String command, Object arg)
throws RunException, EngineTerminationException, InternalException;
Shuts down this execution engine. Implementation should free all
resources held by this execution engine.
No calls to methods on this interface should be made after close.
/**
* 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.
*/
@Override
void close();
Search for a provider, then create and return the ExecutionControl
instance. Params: - env – the execution environment (provided by JShell)
- name – the name of provider
- parameters – the parameter map.
Throws: - Throwable – an exception that occurred attempting to find or create
the execution engine.
- IllegalArgumentException – if no ExecutionControlProvider has the specified
name
and parameters
.
Returns: the execution engine
/**
* Search for a provider, then create and return the
* {@code ExecutionControl} instance.
*
* @param env the execution environment (provided by JShell)
* @param name the name of provider
* @param parameters the parameter map.
* @return the execution engine
* @throws Throwable an exception that occurred attempting to find or create
* the execution engine.
* @throws IllegalArgumentException if no ExecutionControlProvider has the
* specified {@code name} and {@code parameters}.
*/
static ExecutionControl generate(ExecutionEnv env, String name, Map<String, String> parameters)
throws Throwable {
Set<String> keys = parameters == null
? Collections.emptySet()
: parameters.keySet();
for (ExecutionControlProvider p : ServiceLoader.load(ExecutionControlProvider.class)) {
if (p.name().equals(name)
&& p.defaultParameters().keySet().containsAll(keys)) {
return p.generate(env, parameters);
}
}
throw new IllegalArgumentException("No ExecutionControlProvider with name '"
+ name + "' and parameter keys: " + keys.toString());
}
Search for a provider, then create and return the ExecutionControl
instance. Params: - env – the execution environment (provided by JShell)
- spec – the
ExecutionControl
spec, which is described in the documentation of this package documentation.
Throws: - Throwable – an exception that occurred attempting to find or create
the execution engine.
- IllegalArgumentException – if no ExecutionControlProvider has the specified
name
and parameters
. - IllegalArgumentException – if
spec
is malformed
Returns: the execution engine
/**
* Search for a provider, then create and return the
* {@code ExecutionControl} instance.
*
* @param env the execution environment (provided by JShell)
* @param spec the {@code ExecutionControl} spec, which is described in
* the documentation of this
* {@linkplain jdk.jshell.spi package documentation}.
* @return the execution engine
* @throws Throwable an exception that occurred attempting to find or create
* the execution engine.
* @throws IllegalArgumentException if no ExecutionControlProvider has the
* specified {@code name} and {@code parameters}.
* @throws IllegalArgumentException if {@code spec} is malformed
*/
static ExecutionControl generate(ExecutionEnv env, String spec)
throws Throwable {
class SpecReader {
int len = spec.length();
int i = -1;
char ch;
SpecReader() {
next();
}
boolean more() {
return i < len;
}
char current() {
return ch;
}
final boolean next() {
++i;
if (i < len) {
ch = spec.charAt(i);
return true;
}
i = len;
return false;
}
void skipWhite() {
while (more() && Character.isWhitespace(ch)) {
next();
}
}
String readId() {
skipWhite();
StringBuilder sb = new StringBuilder();
while (more() && Character.isJavaIdentifierPart(ch)) {
sb.append(ch);
next();
}
skipWhite();
String id = sb.toString();
if (id.isEmpty()) {
throw new IllegalArgumentException("Expected identifier in " + spec);
}
return id;
}
void expect(char exp) {
skipWhite();
if (!more() || ch != exp) {
throw new IllegalArgumentException("Expected '" + exp + "' in " + spec);
}
next();
skipWhite();
}
String readValue() {
expect('(');
int parenDepth = 1;
StringBuilder sb = new StringBuilder();
while (more()) {
if (ch == ')') {
--parenDepth;
if (parenDepth == 0) {
break;
}
} else if (ch == '(') {
++parenDepth;
}
sb.append(ch);
next();
}
expect(')');
return sb.toString();
}
}
Map<String, String> parameters = new HashMap<>();
SpecReader sr = new SpecReader();
String name = sr.readId();
if (sr.more()) {
sr.expect(':');
while (sr.more()) {
String key = sr.readId();
String value = sr.readValue();
parameters.put(key, value);
if (sr.more()) {
sr.expect(',');
}
}
}
return generate(env, name, parameters);
}
Bundles class name with class bytecodes.
/**
* Bundles class name with class bytecodes.
*/
public static final class ClassBytecodes implements Serializable {
private static final long serialVersionUID = 0xC1A55B47EC0DE5L;
private final String name;
private final byte[] bytecodes;
Creates a name/bytecode pair.
Params: - name – the class name
- bytecodes – the class bytecodes
/**
* Creates a name/bytecode pair.
* @param name the class name
* @param bytecodes the class bytecodes
*/
public ClassBytecodes(String name, byte[] bytecodes) {
this.name = name;
this.bytecodes = bytecodes;
}
The bytecodes for the class.
Returns: the bytecodes
/**
* The bytecodes for the class.
*
* @return the bytecodes
*/
public byte[] bytecodes() {
return bytecodes;
}
The class name.
Returns: the class name
/**
* The class name.
*
* @return the class name
*/
public String name() {
return name;
}
}
The abstract base of all ExecutionControl
exceptions. /**
* The abstract base of all {@code ExecutionControl} exceptions.
*/
public static abstract class ExecutionControlException extends Exception {
private static final long serialVersionUID = 1L;
public ExecutionControlException(String message) {
super(message);
}
}
Unbidden execution engine termination has occurred.
/**
* Unbidden execution engine termination has occurred.
*/
public static class EngineTerminationException extends ExecutionControlException {
private static final long serialVersionUID = 1L;
public EngineTerminationException(String message) {
super(message);
}
}
The command is not implemented.
/**
* The command is not implemented.
*/
public static class NotImplementedException extends InternalException {
private static final long serialVersionUID = 1L;
public NotImplementedException(String message) {
super(message);
}
}
An internal problem has occurred.
/**
* An internal problem has occurred.
*/
public static class InternalException extends ExecutionControlException {
private static final long serialVersionUID = 1L;
public InternalException(String message) {
super(message);
}
}
A class install (load or redefine) encountered a problem.
/**
* A class install (load or redefine) encountered a problem.
*/
public static class ClassInstallException extends ExecutionControlException {
private static final long serialVersionUID = 1L;
private final boolean[] installed;
public ClassInstallException(String message, boolean[] installed) {
super(message);
this.installed = installed;
}
Indicates which of the passed classes were successfully
loaded/redefined.
Returns: a one-to-one array with the ClassBytecodes
array -- true
if installed
/**
* Indicates which of the passed classes were successfully
* loaded/redefined.
* @return a one-to-one array with the {@link ClassBytecodes}{@code[]}
* array -- {@code true} if installed
*/
public boolean[] installed() {
return installed;
}
}
The abstract base of of exceptions specific to running user code.
/**
* The abstract base of of exceptions specific to running user code.
*/
public static abstract class RunException extends ExecutionControlException {
private static final long serialVersionUID = 1L;
private RunException(String message) {
super(message);
}
}
A 'normal' user exception occurred.
/**
* A 'normal' user exception occurred.
*/
public static class UserException extends RunException {
private static final long serialVersionUID = 1L;
private final String causeExceptionClass;
public UserException(String message, String causeExceptionClass, StackTraceElement[] stackElements) {
super(message);
this.causeExceptionClass = causeExceptionClass;
this.setStackTrace(stackElements);
}
Returns the class of the user exception.
Returns: the name of the user exception class
/**
* Returns the class of the user exception.
* @return the name of the user exception class
*/
public String causeExceptionClass() {
return causeExceptionClass;
}
}
An exception indicating that a DeclarationSnippet
with unresolved references has been encountered. Contrast this with the initiating SPIResolutionException
(a RuntimeException
) which is embedded in generated corralled code. Also, contrast this with UnresolvedReferenceException
the high-level exception (with DeclarationSnippet
reference) provided in the main API.
/**
* An exception indicating that a {@code DeclarationSnippet} with unresolved
* references has been encountered.
* <p>
* Contrast this with the initiating {@link SPIResolutionException}
* (a {@code RuntimeException}) which is embedded in generated corralled
* code. Also, contrast this with
* {@link jdk.jshell.UnresolvedReferenceException} the high-level
* exception (with {@code DeclarationSnippet} reference) provided in the
* main API.
*/
public static class ResolutionException extends RunException {
private static final long serialVersionUID = 1L;
private final int id;
Constructs an exception indicating that a DeclarationSnippet
with unresolved references has been encountered. Params: - id – An internal identifier of the specific method
- stackElements – the stack trace
/**
* Constructs an exception indicating that a {@code DeclarationSnippet}
* with unresolved references has been encountered.
*
* @param id An internal identifier of the specific method
* @param stackElements the stack trace
*/
public ResolutionException(int id, StackTraceElement[] stackElements) {
super("resolution exception: " + id);
this.id = id;
this.setStackTrace(stackElements);
}
Retrieves the internal identifier of the unresolved identifier.
Returns: the internal identifier
/**
* Retrieves the internal identifier of the unresolved identifier.
*
* @return the internal identifier
*/
public int id() {
return id;
}
}
An exception indicating that an ExecutionControl.invoke(String, String)
(or theoretically a ExecutionControl.varValue(String, String)
) has been interrupted by a ExecutionControl.stop()
. /**
* An exception indicating that an
* {@link ExecutionControl#invoke(java.lang.String, java.lang.String) }
* (or theoretically a
* {@link ExecutionControl#varValue(java.lang.String, java.lang.String) })
* has been interrupted by a {@link ExecutionControl#stop() }.
*/
public static class StoppedException extends RunException {
private static final long serialVersionUID = 1L;
public StoppedException() {
super("stopped by stop()");
}
}
}