1 /*
2  * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 package jdk.jshell.spi;
26 
27 import java.io.Serializable;
28 import java.util.Collections;
29 import java.util.HashMap;
30 import java.util.Map;
31 import java.util.ServiceLoader;
32 import java.util.Set;
33 
34 /**
35  * This interface specifies the functionality that must provided to implement a
36  * pluggable JShell execution engine.
37  * <p>
38  * The audience for this Service Provider Interface is engineers wishing to
39  * implement their own version of the execution engine in support of the JShell
40  * API.
41  * <p>
42  * A Snippet is compiled into code wrapped in a 'wrapper class'. The execution
43  * engine is used by the core JShell implementation to load and, for executable
44  * Snippets, execute the Snippet.
45  * <p>
46  * Methods defined in this interface should only be called by the core JShell
47  * implementation.
48  *
49  * @since 9
50  */
51 public interface ExecutionControl extends AutoCloseable {
52 
53     /**
54      * Attempts to load new classes.
55      *
56      * @param cbcs the class name and bytecodes to load
57      * @throws ClassInstallException exception occurred loading the classes,
58      * some or all were not loaded
59      * @throws NotImplementedException if not implemented
60      * @throws EngineTerminationException the execution engine has terminated
61      */
load(ClassBytecodes[] cbcs)62     void load(ClassBytecodes[] cbcs)
63             throws ClassInstallException, NotImplementedException, EngineTerminationException;
64 
65     /**
66      * Attempts to redefine previously loaded classes.
67      *
68      * @param cbcs the class name and bytecodes to redefine
69      * @throws ClassInstallException exception occurred redefining the classes,
70      * some or all were not redefined
71      * @throws NotImplementedException if not implemented
72      * @throws EngineTerminationException the execution engine has terminated
73      */
redefine(ClassBytecodes[] cbcs)74     void redefine(ClassBytecodes[] cbcs)
75             throws ClassInstallException, NotImplementedException, EngineTerminationException;
76 
77     /**
78      * Invokes an executable Snippet by calling a method on the specified
79      * wrapper class. The method must have no arguments and return String.
80      *
81      * @param className the class whose method should be invoked
82      * @param methodName the name of method to invoke
83      * @return the result of the execution or null if no result
84      * @throws UserException the invoke raised a user exception
85      * @throws ResolutionException the invoke attempted to directly or
86      * indirectly invoke an unresolved snippet
87      * @throws StoppedException if the {@code invoke()} was canceled by
88      * {@link ExecutionControl#stop}
89      * @throws EngineTerminationException the execution engine has terminated
90      * @throws InternalException an internal problem occurred
91      */
invoke(String className, String methodName)92     String invoke(String className, String methodName)
93             throws RunException, EngineTerminationException, InternalException;
94 
95     /**
96      * Returns the value of a variable.
97      *
98      * @param className the name of the wrapper class of the variable
99      * @param varName the name of the variable
100      * @return the value of the variable
101      * @throws UserException formatting the value raised a user exception
102      * @throws ResolutionException formatting the value attempted to directly or
103      * indirectly invoke an unresolved snippet
104      * @throws StoppedException if the formatting the value was canceled by
105      * {@link ExecutionControl#stop}
106      * @throws EngineTerminationException the execution engine has terminated
107      * @throws InternalException an internal problem occurred
108      */
varValue(String className, String varName)109     String varValue(String className, String varName)
110             throws RunException, EngineTerminationException, InternalException;
111 
112     /**
113      * Adds the path to the execution class path.
114      *
115      * @param path the path to add
116      * @throws EngineTerminationException the execution engine has terminated
117      * @throws InternalException an internal problem occurred
118      */
addToClasspath(String path)119     void addToClasspath(String path)
120             throws EngineTerminationException, InternalException;
121 
122     /**
123      * Interrupts a running invoke.
124      *
125      * @throws EngineTerminationException the execution engine has terminated
126      * @throws InternalException an internal problem occurred
127      */
stop()128     void stop()
129             throws EngineTerminationException, InternalException;
130 
131     /**
132      * Run a non-standard command (or a standard command from a newer version).
133      *
134      * @param command the non-standard command
135      * @param arg the commands argument
136      * @return the commands return value
137      * @throws UserException the command raised a user exception
138      * @throws ResolutionException the command attempted to directly or
139      * indirectly invoke an unresolved snippet
140      * @throws StoppedException if the command was canceled by
141      * {@link ExecutionControl#stop}
142      * @throws EngineTerminationException the execution engine has terminated
143      * @throws NotImplementedException if not implemented
144      * @throws InternalException an internal problem occurred
145      */
extensionCommand(String command, Object arg)146     Object extensionCommand(String command, Object arg)
147             throws RunException, EngineTerminationException, InternalException;
148 
149     /**
150      * Shuts down this execution engine. Implementation should free all
151      * resources held by this execution engine.
152      * <p>
153      * No calls to methods on this interface should be made after close.
154      */
155     @Override
close()156     void close();
157 
158     /**
159      * Search for a provider, then create and return the
160      * {@code ExecutionControl} instance.
161      *
162      * @param env the execution environment (provided by JShell)
163      * @param name the name of provider
164      * @param parameters the parameter map.
165      * @return the execution engine
166      * @throws Throwable an exception that occurred attempting to find or create
167      * the execution engine.
168      * @throws IllegalArgumentException if no ExecutionControlProvider has the
169      * specified {@code name} and {@code parameters}.
170      */
generate(ExecutionEnv env, String name, Map<String, String> parameters)171     static ExecutionControl generate(ExecutionEnv env, String name, Map<String, String> parameters)
172             throws Throwable {
173         Set<String> keys = parameters == null
174                 ? Collections.emptySet()
175                 : parameters.keySet();
176         for (ExecutionControlProvider p : ServiceLoader.load(ExecutionControlProvider.class)) {
177             if (p.name().equals(name)
178                 && p.defaultParameters().keySet().containsAll(keys)) {
179                 return p.generate(env, parameters);
180             }
181         }
182         throw new IllegalArgumentException("No ExecutionControlProvider with name '"
183                 + name + "' and parameter keys: " + keys.toString());
184     }
185 
186     /**
187      * Search for a provider, then create and return the
188      * {@code ExecutionControl} instance.
189      *
190      * @param env the execution environment (provided by JShell)
191      * @param spec the {@code ExecutionControl} spec, which is described in
192      * the documentation of this
193      * {@linkplain jdk.jshell.spi package documentation}.
194      * @return the execution engine
195      * @throws Throwable an exception that occurred attempting to find or create
196      * the execution engine.
197      * @throws IllegalArgumentException if no ExecutionControlProvider has the
198      * specified {@code name} and {@code parameters}.
199      * @throws IllegalArgumentException if {@code spec} is malformed
200      */
generate(ExecutionEnv env, String spec)201     static ExecutionControl generate(ExecutionEnv env, String spec)
202             throws Throwable {
203         class SpecReader {
204 
205             int len = spec.length();
206             int i = -1;
207 
208             char ch;
209 
210             SpecReader() {
211                 next();
212             }
213 
214             boolean more() {
215                 return i < len;
216             }
217 
218             char current() {
219                 return ch;
220             }
221 
222             final boolean next() {
223                 ++i;
224                 if (i < len) {
225                     ch = spec.charAt(i);
226                     return true;
227                 }
228                 i = len;
229                 return false;
230             }
231 
232             void skipWhite() {
233                 while (more() && Character.isWhitespace(ch)) {
234                     next();
235                 }
236             }
237 
238             String readId() {
239                 skipWhite();
240                 StringBuilder sb = new StringBuilder();
241                 while (more() && Character.isJavaIdentifierPart(ch)) {
242                     sb.append(ch);
243                     next();
244                 }
245                 skipWhite();
246                 String id = sb.toString();
247                 if (id.isEmpty()) {
248                     throw new IllegalArgumentException("Expected identifier in " + spec);
249                 }
250                 return id;
251             }
252 
253             void expect(char exp) {
254                 skipWhite();
255                 if (!more() || ch != exp) {
256                     throw new IllegalArgumentException("Expected '" + exp + "' in " + spec);
257                 }
258                 next();
259                 skipWhite();
260             }
261 
262             String readValue() {
263                 expect('(');
264                 int parenDepth = 1;
265                 StringBuilder sb = new StringBuilder();
266                 while (more()) {
267                     if (ch == ')') {
268                         --parenDepth;
269                         if (parenDepth == 0) {
270                             break;
271                         }
272                     } else if (ch == '(') {
273                         ++parenDepth;
274                     }
275                     sb.append(ch);
276                     next();
277                 }
278                 expect(')');
279                 return sb.toString();
280             }
281         }
282         Map<String, String> parameters = new HashMap<>();
283         SpecReader sr = new SpecReader();
284         String name = sr.readId();
285         if (sr.more()) {
286             sr.expect(':');
287             while (sr.more()) {
288                 String key = sr.readId();
289                 String value = sr.readValue();
290                 parameters.put(key, value);
291                 if (sr.more()) {
292                     sr.expect(',');
293                 }
294             }
295         }
296         return generate(env, name, parameters);
297     }
298 
299     /**
300      * Bundles class name with class bytecodes.
301      */
302     public static final class ClassBytecodes implements Serializable {
303 
304         private static final long serialVersionUID = 0xC1A55B47EC0DE5L;
305         private final String name;
306         private final byte[] bytecodes;
307 
308         /**
309          * Creates a name/bytecode pair.
310          * @param name the class name
311          * @param bytecodes the class bytecodes
312          */
ClassBytecodes(String name, byte[] bytecodes)313         public ClassBytecodes(String name, byte[] bytecodes) {
314             this.name = name;
315             this.bytecodes = bytecodes;
316         }
317 
318         /**
319          * The bytecodes for the class.
320          *
321          * @return the bytecodes
322          */
bytecodes()323         public byte[] bytecodes() {
324             return bytecodes;
325         }
326 
327         /**
328          * The class name.
329          *
330          * @return the class name
331          */
name()332         public String name() {
333             return name;
334         }
335     }
336 
337     /**
338      * The abstract base of all {@code ExecutionControl} exceptions.
339      */
340     public static abstract class ExecutionControlException extends Exception {
341 
342         private static final long serialVersionUID = 1L;
343 
ExecutionControlException(String message)344         public ExecutionControlException(String message) {
345             super(message);
346         }
347     }
348 
349     /**
350      * Unbidden execution engine termination has occurred.
351      */
352     public static class EngineTerminationException extends ExecutionControlException {
353 
354         private static final long serialVersionUID = 1L;
355 
EngineTerminationException(String message)356         public EngineTerminationException(String message) {
357             super(message);
358         }
359     }
360 
361     /**
362      * The command is not implemented.
363      */
364     public static class NotImplementedException extends InternalException {
365 
366         private static final long serialVersionUID = 1L;
367 
NotImplementedException(String message)368         public NotImplementedException(String message) {
369             super(message);
370         }
371     }
372 
373     /**
374      * An internal problem has occurred.
375      */
376     public static class InternalException extends ExecutionControlException {
377 
378         private static final long serialVersionUID = 1L;
379 
InternalException(String message)380         public InternalException(String message) {
381             super(message);
382         }
383     }
384 
385     /**
386      * A class install (load or redefine) encountered a problem.
387      */
388     public static class ClassInstallException extends ExecutionControlException {
389 
390         private static final long serialVersionUID = 1L;
391 
392         private final boolean[] installed;
393 
ClassInstallException(String message, boolean[] installed)394         public ClassInstallException(String message, boolean[] installed) {
395             super(message);
396             this.installed = installed;
397         }
398 
399         /**
400          * Indicates which of the passed classes were successfully
401          * loaded/redefined.
402          * @return a one-to-one array with the {@link ClassBytecodes}{@code[]}
403          * array -- {@code true} if installed
404          */
installed()405         public boolean[] installed() {
406             return installed;
407         }
408     }
409 
410     /**
411      * The abstract base of of exceptions specific to running user code.
412      */
413     public static abstract class RunException extends ExecutionControlException {
414 
415         private static final long serialVersionUID = 1L;
416 
RunException(String message)417         private RunException(String message) {
418             super(message);
419         }
420     }
421 
422     /**
423      * A 'normal' user exception occurred.
424      */
425     public static class UserException extends RunException {
426 
427         private static final long serialVersionUID = 1L;
428 
429         private final String causeExceptionClass;
430 
UserException(String message, String causeExceptionClass, StackTraceElement[] stackElements)431         public UserException(String message, String causeExceptionClass, StackTraceElement[] stackElements) {
432             super(message);
433             this.causeExceptionClass = causeExceptionClass;
434             this.setStackTrace(stackElements);
435         }
436 
437         /**
438          * Returns the class of the user exception.
439          * @return the name of the user exception class
440          */
causeExceptionClass()441         public String causeExceptionClass() {
442             return causeExceptionClass;
443         }
444     }
445 
446     /**
447      * An exception indicating that a {@code DeclarationSnippet} with unresolved
448      * references has been encountered.
449      * <p>
450      * Contrast this with the initiating {@link SPIResolutionException}
451      * (a {@code RuntimeException}) which is embedded in generated corralled
452      * code.  Also, contrast this with
453      * {@link jdk.jshell.UnresolvedReferenceException} the high-level
454      * exception (with {@code DeclarationSnippet} reference) provided in the
455      * main API.
456      */
457     public static class ResolutionException extends RunException {
458 
459         private static final long serialVersionUID = 1L;
460 
461         private final int id;
462 
463         /**
464          * Constructs an exception indicating that a {@code DeclarationSnippet}
465          * with unresolved references has been encountered.
466          *
467          * @param id An internal identifier of the specific method
468          * @param stackElements the stack trace
469          */
ResolutionException(int id, StackTraceElement[] stackElements)470         public ResolutionException(int id, StackTraceElement[] stackElements) {
471             super("resolution exception: " + id);
472             this.id = id;
473             this.setStackTrace(stackElements);
474         }
475 
476         /**
477          * Retrieves the internal identifier of the unresolved identifier.
478          *
479          * @return the internal identifier
480          */
id()481         public int id() {
482             return id;
483         }
484     }
485 
486     /**
487      * An exception indicating that an
488      * {@link ExecutionControl#invoke(java.lang.String, java.lang.String) }
489      * (or theoretically a
490      * {@link ExecutionControl#varValue(java.lang.String, java.lang.String) })
491      * has been interrupted by a {@link ExecutionControl#stop() }.
492      */
493     public static class StoppedException extends RunException {
494 
495         private static final long serialVersionUID = 1L;
496 
StoppedException()497         public StoppedException() {
498             super("stopped by stop()");
499         }
500     }
501 
502 }
503