/*
* Copyright ( c ) 2015 , 2017 , 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 jdk.jshell.spi.ExecutionControl;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.PrintStream;
import java.net.InetAddress;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Objects;
import java.util.ResourceBundle;
import java.util.function .BiFunction;
import java.util.function .Consumer;
import java.util.function .Function ;
import java.util.function .Supplier;
import java.util.stream.Stream;
import javax.tools.StandardJavaFileManager;
import jdk.internal.jshell.debug.InternalDebugControl;
import jdk.jshell.Snippet.Status;
import jdk.jshell.spi.ExecutionControl.EngineTerminationException;
import jdk.jshell.spi.ExecutionControl.ExecutionControlException;
import jdk.jshell.spi.ExecutionControlProvider;
import jdk.jshell.spi.ExecutionEnv;
import static jdk.jshell.Util.expunge;
/**
* The JShell evaluation state engine . This is the central class in the JShell
* API . A { @ code 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 . Snippet ) drop ( Snippet ) } and
* { @ link jdk . jshell . JShell # addToClasspath ( java . lang . String ) addToClasspath ( String ) } .
* The majority of methods query the state .
* A { @ code 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 ( ) } .
* < p >
* An instance of { @ code JShell } is created with
* { @ code JShell . create ( ) } .
* < p >
* This class is not thread safe , except as noted , all access should be through
* a single thread .
*
* @ author Robert Field
* @ since 9
*/
public class JShell implements AutoCloseable {
final SnippetMaps maps;
final KeyMap keyMap;
final OuterWrapMap outerMap;
final TaskFactory taskFactory;
final InputStream in;
final PrintStream out;
final PrintStream err;
final Supplier<String> tempVariableNameGenerator;
final BiFunction<Snippet, Integer, String> idGenerator;
final List<String> extraRemoteVMOptions;
final List<String> extraCompilerOptions;
final Function <StandardJavaFileManager, StandardJavaFileManager> fileManagerMapping;
private int nextKeyIndex = 1 ;
final Eval eval;
final ClassTracker classTracker;
private final Map<Subscription, Consumer<JShell>> shutdownListeners = new HashMap<>();
private final Map<Subscription, Consumer<SnippetEvent>> keyStatusListeners = new HashMap<>();
private boolean closed = false ;
private final ExecutionControl executionControl;
private SourceCodeAnalysisImpl sourceCodeAnalysis = null ;
private static final String L10N_RB_NAME = "jdk.jshell.resources.l10n" ;
private static ResourceBundle outputRB = null ;
JShell(Builder b) throws IllegalStateException {
this .in = b.in;
this .out = b.out;
this .err = b.err;
this .tempVariableNameGenerator = b.tempVariableNameGenerator;
this .idGenerator = b.idGenerator;
this .extraRemoteVMOptions = b.extraRemoteVMOptions;
this .extraCompilerOptions = b.extraCompilerOptions;
this .fileManagerMapping = b.fileManagerMapping;
try {
if (b.executionControlProvider != null ) {
executionControl = b.executionControlProvider.generate(new ExecutionEnvImpl(),
b.executionControlParameters == null
? b.executionControlProvider.defaultParameters()
: b.executionControlParameters);
} else {
String loopback = InetAddress.getLoopbackAddress().getHostAddress();
String spec = b.executionControlSpec == null
? "failover:0(jdi:hostname(" + loopback + ")),"
+ "1(jdi:launch(true)), 2(jdi)"
: b.executionControlSpec;
executionControl = ExecutionControl.generate(new ExecutionEnvImpl(), spec);
}
} catch (Throwable ex) {
throw new IllegalStateException("Launching JShell execution engine threw: " + ex.getMessage(), ex);
}
this .maps = new SnippetMaps(this );
this .keyMap = new KeyMap(this );
this .outerMap = new OuterWrapMap(this );
this .taskFactory = new TaskFactory(this );
this .eval = new Eval(this );
this .classTracker = new ClassTracker();
}
/**
* Builder for { @ code JShell } instances .
* Create custom instances of { @ code JShell } by using the setter
* methods on this class . After zero or more of these , use the
* { @ link # build ( ) } method to create a { @ code JShell } instance .
* These can all be chained . For example , setting the remote output and
* error streams :
* < pre >
* { @ code
* JShell myShell =
* JShell . builder ( )
* . out ( myOutStream )
* . err ( myErrStream )
* . build ( ) ; } < / pre >
* If no special set - up is needed , just use
* { @ code JShell . builder ( ) . build ( ) } or the short - cut equivalent
* { @ code JShell . create ( ) } .
*/
public static class Builder {
InputStream in = new ByteArrayInputStream(new byte [0 ]);
PrintStream out = System.out;
PrintStream err = System.err;
Supplier<String> tempVariableNameGenerator = null ;
BiFunction<Snippet, Integer, String> idGenerator = null ;
List<String> extraRemoteVMOptions = new ArrayList<>();
List<String> extraCompilerOptions = new ArrayList<>();
ExecutionControlProvider executionControlProvider;
Map<String,String> executionControlParameters;
String executionControlSpec;
Function <StandardJavaFileManager, StandardJavaFileManager> fileManagerMapping;
Builder() { }
/**
* Sets the input for the running evaluation ( it ' s { @ code System . in } ) . Note :
* applications that use { @ code System . in } for snippet or other
* user input cannot use { @ code System . in } as the input stream for
* the remote process .
* < p >
* The { @ code read } method of the { @ code InputStream } may throw the { @ link InterruptedIOException }
* to signal the user canceled the input . The currently running snippet will be automatically
* { @ link JShell # stop ( ) stopped } .
* < p >
* The default , if this is not set , is to provide an empty input stream
* - - { @ code new ByteArrayInputStream ( new byte [ 0 ] ) } .
*
* @ param in the { @ code InputStream } to be channelled to
* { @ code System . in } in the remote execution process
* @ return the { @ code Builder } instance ( for use in chained
* initialization )
*/
public Builder in(InputStream in) {
this .in = in;
return this ;
}
/**
* Sets the output for the running evaluation ( it ' s { @ code System . out } ) .
* The controlling process and
* the remote process can share { @ code System . out } .
* < p >
* The default , if this is not set , is { @ code System . out } .
*
* @ param out the { @ code PrintStream } to be channelled to
* { @ code System . out } in the remote execution process
* @ return the { @ code Builder } instance ( for use in chained
* initialization )
*/
public Builder out(PrintStream out) {
this .out = out;
return this ;
}
/**
* Sets the error output for the running evaluation ( it ' s
* { @ code System . err } ) . The controlling process and the remote
* process can share { @ code System . err } .
* < p >
* The default , if this is not set , is { @ code System . err } .
*
* @ param err the { @ code PrintStream } to be channelled to
* { @ code System . err } in the remote execution process
* @ return the { @ code Builder } instance ( for use in chained
* initialization )
*/
public Builder err(PrintStream err) {
this .err = err;
return this ;
}
/**
* Sets a generator of temp variable names for
* { @ link jdk . jshell . VarSnippet } of
* { @ link jdk . jshell . Snippet . SubKind # TEMP_VAR_EXPRESSION_SUBKIND } .
* < p >
* Do not use this method unless you have explicit need for it .
* < p >
* The generator will be used for newly created VarSnippet
* instances . The name of a variable is queried with
* { @ link jdk . jshell . VarSnippet # name ( ) } .
* < p >
* The callback is sent during the processing of the snippet , the
* JShell state is not stable . No calls whatsoever on the
* { @ code JShell } instance may be made from the callback .
* < p >
* The generated name must be unique within active snippets .
* < p >
* The default behavior ( if this is not set or { @ code generator }
* is null ) is to generate the name as a sequential number with a
* prefixing dollar sign ( " $ " ) .
*
* @ param generator the { @ code Supplier } to generate the temporary
* variable name string or { @ code null }
* @ return the { @ code Builder } instance ( for use in chained
* initialization )
*/
public Builder tempVariableNameGenerator(Supplier<String> generator) {
this .tempVariableNameGenerator = generator;
return this ;
}
/**
* Sets the generator of identifying names for Snippets .
* < p >
* Do not use this method unless you have explicit need for it .
* < p >
* 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
* { @ code StackTraceElement . getFileName ( ) } for a
* { @ link jdk . jshell . EvalException } and
* { @ link jdk . jshell . UnresolvedReferenceException } .
* < p >
* 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 .
* < p >
* 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 { @ code JShell } instance may be made from the
* callback , except to
* { @ link # status ( jdk . jshell . Snippet ) status ( Snippet ) } .
* < p >
* The default behavior ( if this is not set or { @ code generator }
* is null ) is to generate the id as the integer converted to a string .
*
* @ param generator the { @ code BiFunction } to generate the id
* string or { @ code null }
* @ return the { @ code Builder } instance ( for use in chained
* initialization )
*/
public Builder idGenerator(BiFunction<Snippet, Integer, String> generator) {
this .idGenerator = generator;
return this ;
}
/**
* Sets additional VM options for launching the VM .
*
* @ param options The options for the remote VM
* @ return the { @ code Builder } instance ( for use in chained
* initialization )
*/
public Builder remoteVMOptions(String... options) {
this .extraRemoteVMOptions.addAll(Arrays.asList(options));
return this ;
}
/**
* Adds compiler options . These additional options will be used on
* parsing , analysis , and code generation calls to the compiler .
* Options which interfere with results are not supported and have
* undefined effects on JShell ' s operation .
*
* @ param options the addition options for compiler invocations
* @ return the { @ code Builder } instance ( for use in chained
* initialization )
*/
public Builder compilerOptions(String... options) {
this .extraCompilerOptions.addAll(Arrays.asList(options));
return this ;
}
/**
* Sets the custom engine for execution . Snippet execution will be
* provided by the { @ link ExecutionControl } instance selected by the
* specified execution control spec .
* Use , at most , one of these overloaded { @ code executionEngine } builder
* methods .
*
* @ param executionControlSpec the execution control spec ,
* which is documented in the { @ link jdk . jshell . spi }
* package documentation .
* @ return the { @ code Builder } instance ( for use in chained
* initialization )
*/
public Builder executionEngine(String executionControlSpec) {
this .executionControlSpec = executionControlSpec;
return this ;
}
/**
* Sets the custom engine for execution . Snippet execution will be
* provided by the specified { @ link ExecutionControl } instance .
* Use , at most , one of these overloaded { @ code executionEngine } builder
* methods .
*
* @ param executionControlProvider the provider to supply the execution
* engine
* @ param executionControlParameters the parameters to the provider , or
* { @ code null } for default parameters
* @ return the { @ code Builder } instance ( for use in chained
* initialization )
*/
public Builder executionEngine(ExecutionControlProvider executionControlProvider,
Map<String,String> executionControlParameters) {
this .executionControlProvider = executionControlProvider;
this .executionControlParameters = executionControlParameters;
return this ;
}
/**
* Configure the { @ code FileManager } to be used by compilation and
* source analysis .
* If not set or passed null , the compiler ' s standard file manager will
* be used ( identity mapping ) .
* For use in special applications where the compiler ' s normal file
* handling needs to be overridden . See the file manager APIs for more
* information .
* The file manager input enables forwarding file managers , if this
* is not needed , the incoming file manager can be ignored ( constant
* function ) .
*
* @ param mapping a function that given the compiler ' s standard file
* manager , returns a file manager to use
* @ return the { @ code Builder } instance ( for use in chained
* initialization )
*/
public Builder fileManager(Function <StandardJavaFileManager, StandardJavaFileManager> mapping) {
this .fileManagerMapping = mapping;
return this ;
}
/**
* Builds a JShell state engine . This is the entry - point to all JShell
* functionality . This creates a remote process for execution . It is
* thus important to close the returned instance .
*
* @ throws IllegalStateException if the { @ code JShell } instance could not be created .
* @ return the state engine
*/
public JShell build() throws IllegalStateException {
return new JShell(this );
}
}
// --- public API ---
/**
* Create a new JShell state engine .
* That is , create an instance of { @ code JShell } .
* < p >
* Equivalent to { @ link JShell # builder ( ) JShell . builder ( ) } { @ link JShell . Builder # build ( ) . build ( ) } .
* @ throws IllegalStateException if the { @ code JShell } instance could not be created .
* @ return an instance of { @ code JShell } .
*/
public static JShell create() throws IllegalStateException {
return builder().build();
}
/**
* Factory method for { @ code JShell . Builder } which , in - turn , is used
* for creating instances of { @ code JShell } .
* Create a default instance of { @ code JShell } with
* { @ code JShell . builder ( ) . build ( ) } . For more construction options
* see { @ link jdk . jshell . JShell . Builder } .
* @ return an instance of { @ code Builder } .
* @ see jdk . jshell . JShell . Builder
*/
public static Builder builder() {
return new Builder();
}
/**
* Access to source code analysis functionality .
* An instance of { @ code JShell } will always return the same
* { @ code SourceCodeAnalysis } instance from
* { @ code 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 .
* < p >
* 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 ) } .
* < p >
* 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 .
* < p >
* If execution environment is out of process , as is the default case , then
* if the evaluated code
* causes the execution environment to terminate , this { @ code 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 { @ code JShell } instance is closed .
* @ see SourceCodeAnalysis # analyzeCompletion ( String )
* @ see JShell # onShutdown ( java . util . function . Consumer )
*/
public List<SnippetEvent> eval(String input) throws IllegalStateException {
SourceCodeAnalysisImpl a = sourceCodeAnalysis;
if (a != null ) {
a.suspendIndexing();
}
try {
checkIfAlive();
List<SnippetEvent> events = eval.eval(input);
events.forEach(this ::notifyKeyStatusEvent);
return Collections.unmodifiableList(events);
} finally {
if (a != null ) {
a.resumeIndexing();
}
}
}
/**
* Remove a declaration from the state . That is , if the snippet is an
* { @ linkplain jdk . jshell . Snippet . Status # isActive ( ) active }
* { @ linkplain jdk . jshell . PersistentSnippet persistent } snippet , remove the
* snippet and update the JShell evaluation state accordingly .
* For all active snippets , change the { @ linkplain # status status } to
* { @ link jdk . jshell . Snippet . Status # DROPPED DROPPED } .
* @ param snippet The snippet to remove
* @ return The list of events from updating declarations dependent on the
* dropped snippet .
* @ throws IllegalStateException if this { @ code JShell } instance is closed .
* @ throws IllegalArgumentException if the snippet is not associated with
* this { @ code JShell } instance .
*/
public List<SnippetEvent> drop(Snippet snippet) throws IllegalStateException {
checkIfAlive();
checkValidSnippet(snippet);
List<SnippetEvent> events = eval.drop(snippet);
events.forEach(this ::notifyKeyStatusEvent);
return Collections.unmodifiableList(events);
}
/**
* The specified path is added to the end of the classpath used in eval ( ) .
* Note that the unnamed package is not accessible from the package in which
* { @ link JShell # eval ( String ) } code is placed .
* @ param path the path to add to the classpath .
* @ throws IllegalStateException if this { @ code JShell } instance is closed .
*/
public void addToClasspath(String path) {
checkIfAlive();
// Compiler
taskFactory.addToClasspath(path);
// Runtime
try {
executionControl().addToClasspath(path);
} catch (ExecutionControlException ex) {
debug(ex, "on addToClasspath(" + path + ")" );
}
if (sourceCodeAnalysis != null ) {
sourceCodeAnalysis.classpathChanged();
}
}
/**
* Attempt to stop currently running evaluation . When called while
* the { @ link # eval ( java . lang . String ) } method is running and the
* user ' s code being executed , an attempt will be made to stop user ' s code .
* Note that typically this method needs to be called from a different thread
* than the one running the { @ code eval } method .
* < p >
* If the { @ link # eval ( java . lang . String ) } method is not running , does nothing .
* < p >
* 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 ) {
try {
executionControl.stop();
} catch (ExecutionControlException ex) {
debug(ex, "on stop()" );
}
}
}
/**
* Close this state engine . Frees resources . Should be called when this
* state engine is no longer needed .
*/
@Override
public void close() {
closeDown();
}
/**
* Return all snippets .
* @ return the snippets for all current snippets in id order .
*/
public Stream<Snippet> snippets() {
return maps.snippetList().stream();
}
/**
* Returns the active variable snippets .
* This convenience method is equivalent to { @ code snippets ( ) } filtered for
* { @ link jdk . jshell . Snippet . Status # isActive ( ) status ( snippet ) . isActive ( ) }
* { @ code & & snippet . kind ( ) = = Kind . VARIABLE }
* and cast to { @ code VarSnippet } .
* @ return the active declared variables .
*/
public Stream<VarSnippet> variables() {
return snippets()
.filter(sn -> status(sn).isActive() && sn.kind() == Snippet.Kind.VAR )
.map(sn -> (VarSnippet) sn);
}
/**
* Returns the active method snippets .
* This convenience method is equivalent to { @ code snippets ( ) } filtered for
* { @ link jdk . jshell . Snippet . Status # isActive ( ) status ( snippet ) . isActive ( ) }
* { @ code & & snippet . kind ( ) = = Kind . METHOD }
* and cast to MethodSnippet .
* @ return the active declared methods .
*/
public Stream<MethodSnippet> methods() {
return snippets()
.filter(sn -> status(sn).isActive() && sn.kind() == Snippet.Kind.METHOD)
.map(sn -> (MethodSnippet)sn);
}
/**
* Returns the active type declaration ( class , interface , annotation type , and enum ) snippets .
* This convenience method is equivalent to { @ code snippets ( ) } filtered for
* { @ link jdk . jshell . Snippet . Status # isActive ( ) status ( snippet ) . isActive ( ) }
* { @ code & & snippet . kind ( ) = = Kind . TYPE_DECL }
* and cast to TypeDeclSnippet .
* @ return the active declared type declarations .
*/
public Stream<TypeDeclSnippet> types() {
return snippets()
.filter(sn -> status(sn).isActive() && sn.kind() == Snippet.Kind.TYPE_DECL)
.map(sn -> (TypeDeclSnippet) sn);
}
/**
* Returns the active import snippets .
* This convenience method is equivalent to { @ code snippets ( ) } filtered for
* { @ link jdk . jshell . Snippet . Status # isActive ( ) status ( snippet ) . isActive ( ) }
* { @ code & & snippet . kind ( ) = = Kind . IMPORT }
* and cast to ImportSnippet .
* @ return the active declared import declarations .
*/
public Stream<ImportSnippet> imports() {
return snippets()
.filter(sn -> status(sn).isActive() && sn.kind() == Snippet.Kind.IMPORT )
.map(sn -> (ImportSnippet) sn);
}
/**
* Return the status of the snippet .
* This is updated either because of an explicit { @ code eval ( ) } call or
* an automatic update triggered by a dependency .
* @ param snippet the { @ code Snippet } to look up
* @ return the status corresponding to this snippet
* @ throws IllegalStateException if this { @ code JShell } instance is closed .
* @ throws IllegalArgumentException if the snippet is not associated with
* this { @ code 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 { @ code eval ( ) } call or
* an automatic update triggered by a dependency .
* @ param snippet the { @ code Snippet } to look up
* @ return the diagnostics corresponding to this snippet . This does not
* include unresolvedDependencies references reported in { @ code unresolvedDependencies ( ) } .
* @ throws IllegalStateException if this { @ code JShell } instance is closed .
* @ throws IllegalArgumentException if the snippet is not associated with
* this { @ code JShell } instance .
*/
public Stream<Diag> diagnostics(Snippet snippet) {
return checkValidSnippet(snippet).diagnostics().stream();
}
/**
* For { @ link jdk . jshell . Snippet . Status # RECOVERABLE_DEFINED RECOVERABLE_DEFINED } or
* { @ link jdk . jshell . Snippet . Status # RECOVERABLE_NOT_DEFINED RECOVERABLE_NOT_DEFINED }
* declarations , the names of current unresolved dependencies for
* the snippet .
* The returned value of this method , for a given method may change when an
* { @ code eval ( ) } or { @ code drop ( ) } of another snippet causes
* an update of a dependency .
* @ param snippet the declaration { @ code Snippet } to look up
* @ return a stream of symbol names that are currently unresolvedDependencies .
* @ throws IllegalStateException if this { @ code JShell } instance is closed .
* @ throws IllegalArgumentException if the snippet is not associated with
* this { @ code JShell } instance .
*/
public Stream<String> unresolvedDependencies(DeclarationSnippet snippet) {
return checkValidSnippet(snippet).unresolved().stream();
}
/**
* Get the current value of a variable .
* @ param snippet the variable Snippet whose value is queried .
* @ return the current value of the variable referenced by snippet .
* @ throws IllegalStateException if this { @ code JShell } instance is closed .
* @ throws IllegalArgumentException if the snippet is not associated with
* this { @ code 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(
messageFormat("jshell.exc.var.not.valid" , snippet, snippet.status()));
}
String value;
try {
value = executionControl().varValue(snippet.classFullName(), snippet.name());
} catch (EngineTerminationException ex) {
throw new IllegalStateException(ex.getMessage());
} catch (ExecutionControlException ex) {
debug(ex, "In varValue()" );
return "[" + ex.getMessage() + "]" ;
}
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 { @ code JShell } instance is closed .
*/
public Subscription onSnippetEvent(Consumer<SnippetEvent> listener)
throws IllegalStateException {
return onX(keyStatusListeners, listener);
}
/**
* Register a callback to be called when this JShell instance terminates .
* This occurs either because the client process has ended ( e . g . called System . exit ( 0 ) )
* or the connection has been shutdown , as by close ( ) .
* Each call adds a new subscription .
* @ param listener Action to perform when the state terminates .
* @ return A token which can be used to { @ linkplain JShell # unsubscribe unsubscribe } this subscription .
* @ throws IllegalStateException if this JShell instance is closed
*/
public Subscription onShutdown(Consumer<JShell> listener)
throws IllegalStateException {
return onX(shutdownListeners, listener);
}
/**
* Cancel a callback subscription .
* @ param token The token corresponding to the subscription to be unsubscribed .
*/
public void unsubscribe(Subscription token) {
synchronized (this ) {
token.remover.accept(token);
}
}
/**
* Subscription is a token for referring to subscriptions so they can
* be { @ linkplain JShell # unsubscribe unsubscribed } .
*/
public class Subscription {
Consumer<Subscription> remover;
Subscription(Consumer<Subscription> remover) {
this .remover = remover;
}
}
/**
* Provide the environment for a execution engine .
*/
class ExecutionEnvImpl implements ExecutionEnv {
@Override
public InputStream userIn() {
return in;
}
@Override
public PrintStream userOut() {
return out;
}
@Override
public PrintStream userErr() {
return err;
}
@Override
public List<String> extraRemoteVMOptions() {
return extraRemoteVMOptions;
}
@Override
public void closeDown() {
JShell.this .closeDown();
}
}
// --- private / package-private implementation support ---
ExecutionControl executionControl() {
return executionControl;
}
void debug(int flags, String format, Object... args) {
InternalDebugControl.debug(this , err, flags, format, args);
}
void debug(Throwable ex, String where) {
InternalDebugControl.debug(this , err, ex, where);
}
/**
* Generate the next key index , indicating a unique snippet signature .
*
* @ return the next key index
*/
int nextKeyIndex() {
return nextKeyIndex++;
}
private synchronized <T> Subscription onX(Map<Subscription, Consumer<T>> map, Consumer<T> listener)
throws IllegalStateException {
Objects.requireNonNull(listener);
checkIfAlive();
Subscription token = new Subscription(map::remove);
map.put(token, listener);
return token;
}
private synchronized void notifyKeyStatusEvent(SnippetEvent event) {
keyStatusListeners.values().forEach(l -> l.accept(event));
}
private synchronized void notifyShutdownEvent(JShell state) {
shutdownListeners.values().forEach(l -> l.accept(state));
}
void closeDown() {
if (!closed) {
// Send only once
closed = true ;
try {
notifyShutdownEvent(this );
} catch (Throwable thr) {
// Don't care about dying exceptions
}
try {
executionControl().close();
} catch (Throwable ex) {
// don't care about exceptions on close
}
if (sourceCodeAnalysis != null ) {
sourceCodeAnalysis.close();
}
InternalDebugControl.release(this );
}
}
/**
* Check if this JShell has been closed
* @ throws IllegalStateException if it is closed
*/
void checkIfAlive() throws IllegalStateException {
if (closed) {
throw new IllegalStateException(messageFormat("jshell.exc.closed" , this ));
}
}
/**
* Check a Snippet parameter coming from the API user
* @ param sn the Snippet to check
* @ throws NullPointerException if Snippet parameter is null
* @ throws IllegalArgumentException if Snippet is not from this JShell
* @ return the input Snippet ( for chained calls )
*/
private Snippet checkValidSnippet(Snippet sn) {
if (sn == null ) {
throw new NullPointerException(messageFormat("jshell.exc.null" ));
} else {
if (sn.key().state() != this || sn.id() == Snippet.UNASSOCIATED_ID) {
throw new IllegalArgumentException(messageFormat("jshell.exc.alien" , sn.toString()));
}
return sn;
}
}
/**
* Format using resource bundle look - up using MessageFormat
*
* @ param key the resource key
* @ param args
*/
String messageFormat(String key, Object... args) {
if (outputRB == null ) {
try {
outputRB = ResourceBundle.getBundle(L10N_RB_NAME);
} catch (MissingResourceException mre) {
throw new InternalError("Cannot find ResourceBundle: " + L10N_RB_NAME);
}
}
String s;
try {
s = outputRB.getString(key);
} catch (MissingResourceException mre) {
throw new InternalError("Missing resource: " + key + " in " + L10N_RB_NAME);
}
return MessageFormat.format(s, args);
}
}
Messung V0.5 in Prozent C=92 H=92 G=91
¤ Dauer der Verarbeitung: 0.32 Sekunden
¤
*© Formatika GbR, Deutschland