/*
* Copyright (c) 2015, 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;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Supplier;
import jdk.internal.jshell.debug.InternalDebugControl;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toList;
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_EVNT;
import static jdk.jshell.Util.expunge;
import jdk.jshell.Snippet.Status;
/**
* The JShell evaluation state engine. This is the central class in the JShell
* API. A JShell
instance holds the evolving compilation and
* execution state. The state is changed with the instance methods
* {@link jdk.jshell.JShell#eval(java.lang.String) eval(String)},
* {@link jdk.jshell.JShell#drop(jdk.jshell.PersistentSnippet) drop(PersistentSnippet)} and
* {@link jdk.jshell.JShell#addToClasspath(java.lang.String) addToClasspath(String)}.
* The majority of methods query the state.
* A JShell
instance also allows registering for events with
* {@link jdk.jshell.JShell#onSnippetEvent(java.util.function.Consumer) onSnippetEvent(Consumer)}
* and {@link jdk.jshell.JShell#onShutdown(java.util.function.Consumer) onShutdown(Consumer)}, which
* are unregistered with
* {@link jdk.jshell.JShell#unsubscribe(jdk.jshell.JShell.Subscription) unsubscribe(Subscription)}.
* Access to the source analysis utilities is via
* {@link jdk.jshell.JShell#sourceCodeAnalysis()}.
* When complete the instance should be closed to free resources --
* {@link jdk.jshell.JShell#close()}.
*
* An instance of JShell
is created with
* JShell.create()
.
*
* This class is not thread safe, except as noted, all access should be through
* a single thread.
* @see jdk.jshell
* @author Robert Field
*/
public class JShell implements AutoCloseable {
final SnippetMaps maps;
final KeyMap keyMap;
final TaskFactory taskFactory;
final InputStream in;
final PrintStream out;
final PrintStream err;
final Supplier
* The default, if this is not set, is to provide an empty input stream
* --
* The default, if this is not set, is
* The default, if this is not set, is
* Do not use this method unless you have explicit need for it.
*
* The generator will be used for newly created VarSnippet
* instances. The name of a variable is queried with
* {@link jdk.jshell.VarSnippet#name()}.
*
* The callback is sent during the processing of the snippet, the
* JShell state is not stable. No calls whatsoever on the
*
* The generated name must be unique within active snippets.
*
* The default behavior (if this is not set or
* Do not use this method unless you have explicit need for it.
*
* The generator will be used for newly created Snippet instances. The
* identifying name (id) is accessed with
* {@link jdk.jshell.Snippet#id()} and can be seen in the
*
* The inputs to the generator are the {@link jdk.jshell.Snippet} and an
* integer. The integer will be the same for two Snippets which would
* overwrite one-another, but otherwise is unique.
*
* The callback is sent during the processing of the snippet and the
* Snippet and the state as a whole are not stable. No calls to change
* system state (including Snippet state) should be made. Queries of
* Snippet may be made except to {@link jdk.jshell.Snippet#id()}. No
* calls on the
* The default behavior (if this is not set or
* Equivalent to {@link JShell#builder() JShell.builder()}{@link JShell.Builder#build() .build()}.
* @return an instance of
* For imports, the import is added. Classes, interfaces. methods,
* and variables are defined. The initializer of variables, statements,
* and expressions are executed.
* The modifiers public, protected, private, static, and final are not
* allowed on op-level declarations and are ignored with a warning.
* Synchronized, native, abstract, and default top-level methods are not
* allowed and are errors.
* If a previous definition of a declaration is overwritten then there will
* be an event showing its status changed to OVERWRITTEN, this will not
* occur for dropped, rejected, or already overwritten declarations.
*
* The execution environment is out of process. If the evaluated code
* causes the execution environment to terminate, this
* If the {@link #eval(java.lang.String) } method is not running, does nothing.
*
* The attempt to stop the user's code may fail in some case, which may include
* when the execution is blocked on an I/O operation, or when the user's code is
* catching the {@link ThreadDeath} exception.
*/
public void stop() {
if (executionControl != null)
executionControl.commandStop();
}
/**
* Close this state engine. Frees resources. Should be called when this
* state engine is no longer needed.
*/
@Override
public void close() {
if (!closed) {
closeDown();
executionControl().commandExit();
}
}
/**
* Return all snippets.
* @return the snippets for all current snippets in id order.
* @throws IllegalStateException if this JShell instance is closed.
*/
public ListJShell
instances.
* Create custom instances of JShell
by using the setter
* methods on this class. After zero or more of these, use the
* {@link #build()} method to create a JShell
instance.
* These can all be chained. For example, setting the remote output and
* error streams:
*
*
* If no special set-up is needed, just use
*
* JShell myShell =
* JShell.builder()
* .out(myOutStream)
* .err(myErrStream)
* .build();
JShell.builder().build()
or the short-cut equivalent
* JShell.create()
.
*/
public static class Builder {
InputStream in = new ByteArrayInputStream(new byte[0]);
PrintStream out = System.out;
PrintStream err = System.err;
SupplierSystem.in
). Note:
* applications that use System.in
for snippet or other
* user input cannot use System.in
as the input stream for
* the remote process.
* new ByteArrayInputStream(new byte[0])
.
*
* @param in the InputStream
to be channelled to
* System.in
in the remote execution process.
* @return the Builder
instance (for use in chained
* initialization).
*/
public Builder in(InputStream in) {
this.in = in;
return this;
}
/**
* Output for the running evaluation (it's System.out
).
* The controlling process and
* the remote process can share System.out
.
* System.out
.
*
* @param out the PrintStream
to be channelled to
* System.out
in the remote execution process.
* @return the Builder
instance (for use in chained
* initialization).
*/
public Builder out(PrintStream out) {
this.out = out;
return this;
}
/**
* Error output for the running evaluation (it's
* System.err
). The controlling process and the remote
* process can share System.err
.
* System.err
.
*
* @param err the PrintStream
to be channelled to
* System.err
in the remote execution process.
* @return the Builder
instance (for use in chained
* initialization).
*/
public Builder err(PrintStream err) {
this.err = err;
return this;
}
/**
* Set a generator of temp variable names for
* {@link jdk.jshell.VarSnippet} of
* {@link jdk.jshell.Snippet.SubKind#TEMP_VAR_EXPRESSION_SUBKIND}.
* JShell
instance may be made from the callback.
* generator
* is null) is to generate the name as a sequential number with a
* prefixing dollar sign ("$").
*
* @param generator the Supplier
to generate the temporary
* variable name string or null
.
* @return the Builder
instance (for use in chained
* initialization).
*/
public Builder tempVariableNameGenerator(SupplierStackTraceElement.getFileName()
for a
* {@link jdk.jshell.EvalException} and
* {@link jdk.jshell.UnresolvedReferenceException}.
* JShell
instance may be made from the
* callback, except to
* {@link #status(jdk.jshell.Snippet) status(Snippet)}.
* generator
* is null) is to generate the id as the integer converted to a string.
*
* @param generator the BiFunction
to generate the id
* string or null
.
* @return the Builder
instance (for use in chained
* initialization).
*/
public Builder idGenerator(BiFunctionJShell
.
* JShell
.
*/
public static JShell create() {
return builder().build();
}
/**
* Factory method for JShell.Builder
which, in-turn, is used
* for creating instances of JShell
.
* Create a default instance of JShell
with
* JShell.builder().build()
. For more construction options
* see {@link jdk.jshell.JShell.Builder}.
* @return an instance of Builder
.
* @see jdk.jshell.JShell.Builder
*/
public static Builder builder() {
return new Builder();
}
/**
* Access to source code analysis functionality.
* An instance of JShell
will always return the same
* SourceCodeAnalysis
instance from
* sourceCodeAnalysis()
.
* @return an instance of {@link SourceCodeAnalysis SourceCodeAnalysis}
* which can be used for source analysis such as completion detection and
* completion suggestions.
*/
public SourceCodeAnalysis sourceCodeAnalysis() {
if (sourceCodeAnalysis == null) {
sourceCodeAnalysis = new SourceCodeAnalysisImpl(this);
}
return sourceCodeAnalysis;
}
/**
* Evaluate the input String, including definition and/or execution, if
* applicable. The input is checked for errors, unless the errors can be
* deferred (as is the case with some unresolvedDependencies references),
* errors will abort evaluation. The input should be
* exactly one complete snippet of source code, that is, one expression,
* statement, variable declaration, method declaration, class declaration,
* or import.
* To break arbitrary input into individual complete snippets, use
* {@link SourceCodeAnalysis#analyzeCompletion(String)}.
* JShell
* instance will be closed but the calling process and VM remain valid.
* @param input The input String to evaluate
* @return the list of events directly or indirectly caused by this evaluation.
* @throws IllegalStateException if this JShell
instance is closed.
* @see SourceCodeAnalysis#analyzeCompletion(String)
* @see JShell#onShutdown(java.util.function.Consumer)
*/
public ListJShell
instance is closed.
* @throws IllegalArgumentException if the snippet is not associated with
* this JShell
instance.
*/
public Listsnippets()
filtered for
* {@link jdk.jshell.Snippet.Status#isActive status(snippet).isActive}
* && snippet.kind() == Kind.VARIABLE
* and cast to VarSnippet
.
* @return the active declared variables.
* @throws IllegalStateException if this JShell instance is closed.
*/
public Listsnippets()
filtered for
* {@link jdk.jshell.Snippet.Status#isActive status(snippet).isActive}
* && snippet.kind() == Kind.METHOD
* and cast to MethodSnippet.
* @return the active declared methods.
* @throws IllegalStateException if this JShell instance is closed.
*/
public Listsnippets()
filtered for
* {@link jdk.jshell.Snippet.Status#isActive status(snippet).isActive}
* && snippet.kind() == Kind.TYPE_DECL
* and cast to TypeDeclSnippet.
* @return the active declared type declarations.
* @throws IllegalStateException if this JShell instance is closed.
*/
public Listsnippets()
filtered for
* {@link jdk.jshell.Snippet.Status#isActive status(snippet).isActive}
* && snippet.kind() == Kind.IMPORT
* and cast to ImportSnippet.
* @return the active declared import declarations.
* @throws IllegalStateException if this JShell instance is closed.
*/
public Listeval()
call or
* an automatic update triggered by a dependency.
* @param snippet the Snippet
to look up
* @return the status corresponding to this snippet
* @throws IllegalStateException if this JShell
instance is closed.
* @throws IllegalArgumentException if the snippet is not associated with
* this JShell
instance.
*/
public Status status(Snippet snippet) {
return checkValidSnippet(snippet).status();
}
/**
* Return the diagnostics of the most recent evaluation of the snippet.
* The evaluation can either because of an explicit eval()
call or
* an automatic update triggered by a dependency.
* @param snippet the Snippet
to look up
* @return the diagnostics corresponding to this snippet. This does not
* include unresolvedDependencies references reported in unresolvedDependencies()
.
* @throws IllegalStateException if this JShell
instance is closed.
* @throws IllegalArgumentException if the snippet is not associated with
* this JShell
instance.
*/
public Listeval()
or drop()
of another snippet causes
* an update of a dependency.
* @param snippet the declaration Snippet
to look up
* @return the list of symbol names that are currently unresolvedDependencies.
* @throws IllegalStateException if this JShell
instance is closed.
* @throws IllegalArgumentException if the snippet is not associated with
* this JShell
instance.
*/
public ListJShell
instance is closed.
* @throws IllegalArgumentException if the snippet is not associated with
* this JShell
instance.
* @throws IllegalArgumentException if the variable's status is anything but
* {@link jdk.jshell.Snippet.Status#VALID}.
*/
public String varValue(VarSnippet snippet) throws IllegalStateException {
checkIfAlive();
checkValidSnippet(snippet);
if (snippet.status() != Status.VALID) {
throw new IllegalArgumentException("Snippet parameter of varValue() '" +
snippet + "' must be VALID, it is: " + snippet.status());
}
String value = executionControl().commandVarValue(maps.classFullName(snippet), snippet.name());
return expunge(value);
}
/**
* Register a callback to be called when the Status of a snippet changes.
* Each call adds a new subscription.
* @param listener Action to perform when the Status changes.
* @return A token which can be used to {@linkplain JShell#unsubscribe unsubscribe} this subscription.
* @throws IllegalStateException if this JShell
instance is closed.
*/
public Subscription onSnippetEvent(Consumer