| /* | |
| * Copyright (c) 2016, 2025, 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. | |
| * <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. | |
| * | |
| * @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. | |
| * | |
| * @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. | |
| * | |
| * @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. | |
| * | |
| * @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. | |
| * | |
| * @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 | |
| * @throws InternalException an internal problem occurred | |
| */ | |
| void stop() | |
| throws EngineTerminationException, InternalException; | |
| /** | |
| * 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. | |
| * <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 | |
| * {@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 | |
| * {@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. | |
| */ | |
| public static final class ClassBytecodes implements Serializable { | |
| private static final long serialVersionUID = 0xC1A55B47EC0DE5L; | |
| /** @serial */ | |
| private final String name; | |
| /** @serial */ | |
| private final byte[] 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. | |
| * | |
| * @return the bytecodes | |
| */ | |
| public byte[] bytecodes() { | |
| return bytecodes; | |
| } | |
| /** | |
| * The class name. | |
| * | |
| * @return the class name | |
| */ | |
| public String name() { | |
| return name; | |
| } | |
| } | |
| /** | |
| * The abstract base of all {@code ExecutionControl} exceptions. | |
| */ | |
| public abstract static class ExecutionControlException extends Exception { | |
| private static final long serialVersionUID = 1L; | |
| public ExecutionControlException(String message) { | |
| super(message); | |
| } | |
| } | |
| /** | |
| * 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. | |
| */ | |
| public static class NotImplementedException extends InternalException { | |
| private static final long serialVersionUID = 1L; | |
| public NotImplementedException(String message) { | |
| super(message); | |
| } | |
| } | |
| /** | |
| * 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. | |
| */ | |
| public static class ClassInstallException extends ExecutionControlException { | |
| private static final long serialVersionUID = 1L; | |
| /** @serial */ | |
| 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. | |
| * @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. | |
| */ | |
| public abstract static class RunException extends ExecutionControlException { | |
| private static final long serialVersionUID = 1L; | |
| private RunException(String message) { | |
| super(message); | |
| } | |
| } | |
| /** | |
| * A 'normal' user exception occurred. | |
| */ | |
| public static class UserException extends RunException { | |
| private static final long serialVersionUID = 1L; | |
| /** @serial */ | |
| private final String causeExceptionClass; | |
| @SuppressWarnings("this-escape") | |
| public UserException(String message, String causeExceptionClass, StackTraceElement[] stackElements) { | |
| super(message); | |
| this.causeExceptionClass = causeExceptionClass; | |
| this.setStackTrace(stackElements); | |
| } | |
| /** | |
| * 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 {@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; | |
| /** @serial */ | |
| private final int id; | |
| /** | |
| * 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 | |
| */ | |
| @SuppressWarnings("this-escape") | |
| public ResolutionException(int id, StackTraceElement[] stackElements) { | |
| super("resolution exception: " + id); | |
| this.id = id; | |
| this.setStackTrace(stackElements); | |
| } | |
| /** | |
| * Retrieves the internal identifier of the unresolved identifier. | |
| * | |
| * @return the internal identifier | |
| */ | |
| public int id() { | |
| return id; | |
| } | |
| } | |
| /** | |
| * 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()"); | |
| } | |
| } | |
| } |