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 */
25package jdk.jshell.spi;
26
27import java.io.Serializable;
28import java.util.Collections;
29import java.util.HashMap;
30import java.util.Map;
31import java.util.ServiceLoader;
32import 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 */
51public 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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
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     */
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     */
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         */
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         */
323        public byte[] bytecodes() {
324            return bytecodes;
325        }
326
327        /**
328         * The class name.
329         *
330         * @return the class name
331         */
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
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
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
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
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
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         */
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
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
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         */
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         */
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         */
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
497        public StoppedException() {
498            super("stopped by stop()");
499        }
500    }
501
502}
503