1 /* 2 * Copyright (c) 2015, 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 26 package jdk.jshell; 27 28 import jdk.jshell.spi.ExecutionControl; 29 import java.io.ByteArrayInputStream; 30 import java.io.InputStream; 31 import java.io.PrintStream; 32 import java.text.MessageFormat; 33 import java.util.ArrayList; 34 import java.util.Arrays; 35 import java.util.Collections; 36 import java.util.HashMap; 37 import java.util.List; 38 import java.util.Map; 39 import java.util.MissingResourceException; 40 import java.util.Objects; 41 import java.util.ResourceBundle; 42 import java.util.function.BiFunction; 43 import java.util.function.Consumer; 44 45 import java.util.function.Supplier; 46 import jdk.internal.jshell.debug.InternalDebugControl; 47 import jdk.jshell.Snippet.Status; 48 import jdk.jshell.execution.JDIDefaultExecutionControl; 49 import jdk.jshell.spi.ExecutionControl.EngineTerminationException; 50 import jdk.jshell.spi.ExecutionControl.ExecutionControlException; 51 import jdk.jshell.spi.ExecutionEnv; 52 import static jdk.jshell.execution.Util.failOverExecutionControlGenerator; 53 import static java.util.stream.Collectors.collectingAndThen; 54 import static java.util.stream.Collectors.toList; 55 import static jdk.jshell.Util.expunge; 56 57 /** 58 * The JShell evaluation state engine. This is the central class in the JShell 59 * API. A {@code JShell} instance holds the evolving compilation and 60 * execution state. The state is changed with the instance methods 61 * {@link jdk.jshell.JShell#eval(java.lang.String) eval(String)}, 62 * {@link jdk.jshell.JShell#drop(jdk.jshell.PersistentSnippet) drop(PersistentSnippet)} and 63 * {@link jdk.jshell.JShell#addToClasspath(java.lang.String) addToClasspath(String)}. 64 * The majority of methods query the state. 65 * A {@code JShell} instance also allows registering for events with 66 * {@link jdk.jshell.JShell#onSnippetEvent(java.util.function.Consumer) onSnippetEvent(Consumer)} 67 * and {@link jdk.jshell.JShell#onShutdown(java.util.function.Consumer) onShutdown(Consumer)}, which 68 * are unregistered with 69 * {@link jdk.jshell.JShell#unsubscribe(jdk.jshell.JShell.Subscription) unsubscribe(Subscription)}. 70 * Access to the source analysis utilities is via 71 * {@link jdk.jshell.JShell#sourceCodeAnalysis()}. 72 * When complete the instance should be closed to free resources -- 73 * {@link jdk.jshell.JShell#close()}. 74 * <p> 75 * An instance of {@code JShell} is created with 76 * {@code JShell.create()}. 77 * <p> 78 * This class is not thread safe, except as noted, all access should be through 79 * a single thread. 80 * @author Robert Field 81 */ 82 public class JShell implements AutoCloseable { 83 84 final SnippetMaps maps; 85 final KeyMap keyMap; 86 final OuterWrapMap outerMap; 87 final TaskFactory taskFactory; 88 final InputStream in; 89 final PrintStream out; 90 final PrintStream err; 91 final Supplier<String> tempVariableNameGenerator; 92 final BiFunction<Snippet, Integer, String> idGenerator; 93 final List<String> extraRemoteVMOptions; 94 final List<String> extraCompilerOptions; 95 final ExecutionControl.Generator executionControlGenerator; 96 97 private int nextKeyIndex = 1; 98 99 final Eval eval; 100 final ClassTracker classTracker; 101 private final Map<Subscription, Consumer<JShell>> shutdownListeners = new HashMap<>(); 102 private final Map<Subscription, Consumer<SnippetEvent>> keyStatusListeners = new HashMap<>(); 103 private boolean closed = false; 104 105 private ExecutionControl executionControl = null; 106 private SourceCodeAnalysisImpl sourceCodeAnalysis = null; 107 108 private static final String L10N_RB_NAME = "jdk.jshell.resources.l10n"; 109 private static ResourceBundle outputRB = null; 110 111 JShell(Builder b) { 112 this.in = b.in; 113 this.out = b.out; 114 this.err = b.err; 115 this.tempVariableNameGenerator = b.tempVariableNameGenerator; 116 this.idGenerator = b.idGenerator; 117 this.extraRemoteVMOptions = b.extraRemoteVMOptions; 118 this.extraCompilerOptions = b.extraCompilerOptions; 119 this.executionControlGenerator = b.executionControlGenerator==null 120 ? failOverExecutionControlGenerator( 121 JDIDefaultExecutionControl.launch(), 122 JDIDefaultExecutionControl.listen()) 123 : b.executionControlGenerator; 124 125 this.maps = new SnippetMaps(this); 126 this.keyMap = new KeyMap(this); 127 this.outerMap = new OuterWrapMap(this); 128 this.taskFactory = new TaskFactory(this); 129 this.eval = new Eval(this); 130 this.classTracker = new ClassTracker(); 131 } 132 133 /** 134 * Builder for {@code JShell} instances. 135 * Create custom instances of {@code JShell} by using the setter 136 * methods on this class. After zero or more of these, use the 137 * {@link #build()} method to create a {@code JShell} instance. 138 * These can all be chained. For example, setting the remote output and 139 * error streams: 140 * <pre> 141 * {@code 142 * JShell myShell = 143 * JShell.builder() 144 * .out(myOutStream) 145 * .err(myErrStream) 146 * .build(); } </pre> 147 * If no special set-up is needed, just use 148 * {@code JShell.builder().build()} or the short-cut equivalent 149 * {@code JShell.create()}. 150 */ 151 public static class Builder { 152 153 InputStream in = new ByteArrayInputStream(new byte[0]); 154 PrintStream out = System.out; 155 PrintStream err = System.err; 156 Supplier<String> tempVariableNameGenerator = null; 157 BiFunction<Snippet, Integer, String> idGenerator = null; 158 List<String> extraRemoteVMOptions = new ArrayList<>(); 159 List<String> extraCompilerOptions = new ArrayList<>(); 160 ExecutionControl.Generator executionControlGenerator; 161 162 Builder() { } 163 164 /** 165 * Sets the input for the running evaluation (it's {@code System.in}). Note: 166 * applications that use {@code System.in} for snippet or other 167 * user input cannot use {@code System.in} as the input stream for 168 * the remote process. 169 * <p> 170 * The default, if this is not set, is to provide an empty input stream 171 * -- {@code new ByteArrayInputStream(new byte[0])}. 172 * 173 * @param in the {@code InputStream} to be channelled to 174 * {@code System.in} in the remote execution process 175 * @return the {@code Builder} instance (for use in chained 176 * initialization) 177 */ 178 public Builder in(InputStream in) { 179 this.in = in; 180 return this; 181 } 182 183 /** 184 * Sets the output for the running evaluation (it's {@code System.out}). 185 * The controlling process and 186 * the remote process can share {@code System.out}. 187 * <p> 188 * The default, if this is not set, is {@code System.out}. 189 * 190 * @param out the {@code PrintStream} to be channelled to 191 * {@code System.out} in the remote execution process 192 * @return the {@code Builder} instance (for use in chained 193 * initialization) 194 */ 195 public Builder out(PrintStream out) { 196 this.out = out; 197 return this; 198 } 199 200 /** 201 * Sets the error output for the running evaluation (it's 202 * {@code System.err}). The controlling process and the remote 203 * process can share {@code System.err}. 204 * <p> 205 * The default, if this is not set, is {@code System.err}. 206 * 207 * @param err the {@code PrintStream} to be channelled to 208 * {@code System.err} in the remote execution process 209 * @return the {@code Builder} instance (for use in chained 210 * initialization) 211 */ 212 public Builder err(PrintStream err) { 213 this.err = err; 214 return this; 215 } 216 217 /** 218 * Sets a generator of temp variable names for 219 * {@link jdk.jshell.VarSnippet} of 220 * {@link jdk.jshell.Snippet.SubKind#TEMP_VAR_EXPRESSION_SUBKIND}. 221 * <p> 222 * Do not use this method unless you have explicit need for it. 223 * <p> 224 * The generator will be used for newly created VarSnippet 225 * instances. The name of a variable is queried with 226 * {@link jdk.jshell.VarSnippet#name()}. 227 * <p> 228 * The callback is sent during the processing of the snippet, the 229 * JShell state is not stable. No calls whatsoever on the 230 * {@code JShell} instance may be made from the callback. 231 * <p> 232 * The generated name must be unique within active snippets. 233 * <p> 234 * The default behavior (if this is not set or {@code generator} 235 * is null) is to generate the name as a sequential number with a 236 * prefixing dollar sign ("$"). 237 * 238 * @param generator the {@code Supplier} to generate the temporary 239 * variable name string or {@code null} 240 * @return the {@code Builder} instance (for use in chained 241 * initialization) 242 */ 243 public Builder tempVariableNameGenerator(Supplier<String> generator) { 244 this.tempVariableNameGenerator = generator; 245 return this; 246 } 247 248 /** 249 * Sets the generator of identifying names for Snippets. 250 * <p> 251 * Do not use this method unless you have explicit need for it. 252 * <p> 253 * The generator will be used for newly created Snippet instances. The 254 * identifying name (id) is accessed with 255 * {@link jdk.jshell.Snippet#id()} and can be seen in the 256 * {@code StackTraceElement.getFileName()} for a 257 * {@link jdk.jshell.EvalException} and 258 * {@link jdk.jshell.UnresolvedReferenceException}. 259 * <p> 260 * The inputs to the generator are the {@link jdk.jshell.Snippet} and an 261 * integer. The integer will be the same for two Snippets which would 262 * overwrite one-another, but otherwise is unique. 263 * <p> 264 * The callback is sent during the processing of the snippet and the 265 * Snippet and the state as a whole are not stable. No calls to change 266 * system state (including Snippet state) should be made. Queries of 267 * Snippet may be made except to {@link jdk.jshell.Snippet#id()}. No 268 * calls on the {@code JShell} instance may be made from the 269 * callback, except to 270 * {@link #status(jdk.jshell.Snippet) status(Snippet)}. 271 * <p> 272 * The default behavior (if this is not set or {@code generator} 273 * is null) is to generate the id as the integer converted to a string. 274 * 275 * @param generator the {@code BiFunction} to generate the id 276 * string or {@code null} 277 * @return the {@code Builder} instance (for use in chained 278 * initialization) 279 */ 280 public Builder idGenerator(BiFunction<Snippet, Integer, String> generator) { 281 this.idGenerator = generator; 282 return this; 283 } 284 285 /** 286 * Sets additional VM options for launching the VM. 287 * 288 * @param options The options for the remote VM 289 * @return the {@code Builder} instance (for use in chained 290 * initialization) 291 */ 292 public Builder remoteVMOptions(String... options) { 293 this.extraRemoteVMOptions.addAll(Arrays.asList(options)); 294 return this; 295 } 296 297 /** 298 * Adds compiler options. These additional options will be used on 299 * parsing, analysis, and code generation calls to the compiler. 300 * Options which interfere with results are not supported and have 301 * undefined effects on JShell's operation. 302 * 303 * @param options the addition options for compiler invocations 304 * @return the {@code Builder} instance (for use in chained 305 * initialization) 306 */ 307 public Builder compilerOptions(String... options) { 308 this.extraCompilerOptions.addAll(Arrays.asList(options)); 309 return this; 310 } 311 312 /** 313 * Sets the custom engine for execution. Snippet execution will be 314 * provided by the specified {@link ExecutionControl} instance. 315 * 316 * @param executionControlGenerator the execution engine generator 317 * @return the {@code Builder} instance (for use in chained 318 * initialization) 319 */ 320 public Builder executionEngine(ExecutionControl.Generator executionControlGenerator) { 321 this.executionControlGenerator = executionControlGenerator; 322 return this; 323 } 324 325 /** 326 * Builds a JShell state engine. This is the entry-point to all JShell 327 * functionality. This creates a remote process for execution. It is 328 * thus important to close the returned instance. 329 * 330 * @return the state engine 331 */ 332 public JShell build() { 333 return new JShell(this); 334 } 335 } 336 337 // --- public API --- 338 339 /** 340 * Create a new JShell state engine. 341 * That is, create an instance of {@code JShell}. 342 * <p> 343 * Equivalent to {@link JShell#builder() JShell.builder()}{@link JShell.Builder#build() .build()}. 344 * @return an instance of {@code JShell}. 345 */ 346 public static JShell create() { 347 return builder().build(); 348 } 349 350 /** 351 * Factory method for {@code JShell.Builder} which, in-turn, is used 352 * for creating instances of {@code JShell}. 353 * Create a default instance of {@code JShell} with 354 * {@code JShell.builder().build()}. For more construction options 355 * see {@link jdk.jshell.JShell.Builder}. 356 * @return an instance of {@code Builder}. 357 * @see jdk.jshell.JShell.Builder 358 */ 359 public static Builder builder() { 360 return new Builder(); 361 } 362 363 /** 364 * Access to source code analysis functionality. 365 * An instance of {@code JShell} will always return the same 366 * {@code SourceCodeAnalysis} instance from 367 * {@code sourceCodeAnalysis()}. 368 * @return an instance of {@link SourceCodeAnalysis SourceCodeAnalysis} 369 * which can be used for source analysis such as completion detection and 370 * completion suggestions. 371 */ 372 public SourceCodeAnalysis sourceCodeAnalysis() { 373 if (sourceCodeAnalysis == null) { 374 sourceCodeAnalysis = new SourceCodeAnalysisImpl(this); 375 } 376 return sourceCodeAnalysis; 377 } 378 379 /** 380 * Evaluate the input String, including definition and/or execution, if 381 * applicable. The input is checked for errors, unless the errors can be 382 * deferred (as is the case with some unresolvedDependencies references), 383 * errors will abort evaluation. 384 * <p> 385 * The input should be 386 * exactly one complete snippet of source code, that is, one expression, 387 * statement, variable declaration, method declaration, class declaration, 388 * or import. 389 * To break arbitrary input into individual complete snippets, use 390 * {@link SourceCodeAnalysis#analyzeCompletion(String)}. 391 * <p> 392 * For imports, the import is added. Classes, interfaces. methods, 393 * and variables are defined. The initializer of variables, statements, 394 * and expressions are executed. 395 * The modifiers public, protected, private, static, and final are not 396 * allowed on op-level declarations and are ignored with a warning. 397 * Synchronized, native, abstract, and default top-level methods are not 398 * allowed and are errors. 399 * If a previous definition of a declaration is overwritten then there will 400 * be an event showing its status changed to OVERWRITTEN, this will not 401 * occur for dropped, rejected, or already overwritten declarations. 402 * <p> 403 * If execution environment is out of process, as is the default case, then 404 * if the evaluated code 405 * causes the execution environment to terminate, this {@code JShell} 406 * instance will be closed but the calling process and VM remain valid. 407 * @param input The input String to evaluate 408 * @return the list of events directly or indirectly caused by this evaluation. 409 * @throws IllegalStateException if this {@code JShell} instance is closed. 410 * @see SourceCodeAnalysis#analyzeCompletion(String) 411 * @see JShell#onShutdown(java.util.function.Consumer) 412 */ 413 public List<SnippetEvent> eval(String input) throws IllegalStateException { 414 SourceCodeAnalysisImpl a = sourceCodeAnalysis; 415 if (a != null) { 416 a.suspendIndexing(); 417 } 418 try { 419 checkIfAlive(); 420 List<SnippetEvent> events = eval.eval(input); 421 events.forEach(this::notifyKeyStatusEvent); 422 return Collections.unmodifiableList(events); 423 } finally { 424 if (a != null) { 425 a.resumeIndexing(); 426 } 427 } 428 } 429 430 /** 431 * Remove a declaration from the state. 432 * @param snippet The snippet to remove 433 * @return The list of events from updating declarations dependent on the 434 * dropped snippet. 435 * @throws IllegalStateException if this {@code JShell} instance is closed. 436 * @throws IllegalArgumentException if the snippet is not associated with 437 * this {@code JShell} instance. 438 */ 439 public List<SnippetEvent> drop(PersistentSnippet snippet) throws IllegalStateException { 440 checkIfAlive(); 441 checkValidSnippet(snippet); 442 List<SnippetEvent> events = eval.drop(snippet); 443 events.forEach(this::notifyKeyStatusEvent); 444 return Collections.unmodifiableList(events); 445 } 446 447 /** 448 * The specified path is added to the end of the classpath used in eval(). 449 * Note that the unnamed package is not accessible from the package in which 450 * {@link JShell#eval(String)} code is placed. 451 * @param path the path to add to the classpath. 452 */ 453 public void addToClasspath(String path) { 454 // Compiler 455 taskFactory.addToClasspath(path); 456 // Runtime 457 try { 458 executionControl().addToClasspath(path); 459 } catch (ExecutionControlException ex) { 460 debug(ex, "on addToClasspath(" + path + ")"); 461 } 462 if (sourceCodeAnalysis != null) { 463 sourceCodeAnalysis.classpathChanged(); 464 } 465 } 466 467 /** 468 * Attempt to stop currently running evaluation. When called while 469 * the {@link #eval(java.lang.String) } method is running and the 470 * user's code being executed, an attempt will be made to stop user's code. 471 * Note that typically this method needs to be called from a different thread 472 * than the one running the {@code eval} method. 473 * <p> 474 * If the {@link #eval(java.lang.String) } method is not running, does nothing. 475 * <p> 476 * The attempt to stop the user's code may fail in some case, which may include 477 * when the execution is blocked on an I/O operation, or when the user's code is 478 * catching the {@link ThreadDeath} exception. 479 */ 480 public void stop() { 481 if (executionControl != null) { 482 try { 483 executionControl.stop(); 484 } catch (ExecutionControlException ex) { 485 debug(ex, "on stop()"); 486 } 487 } 488 } 489 490 /** 491 * Close this state engine. Frees resources. Should be called when this 492 * state engine is no longer needed. 493 */ 494 @Override 495 public void close() { 496 if (!closed) { 497 closeDown(); 498 executionControl().close(); 499 } 500 } 501 502 /** 503 * Return all snippets. 504 * @return the snippets for all current snippets in id order. 505 * @throws IllegalStateException if this JShell instance is closed. 506 */ 507 public List<Snippet> snippets() throws IllegalStateException { 508 checkIfAlive(); 509 return Collections.unmodifiableList(maps.snippetList()); 510 } 511 512 /** 513 * Returns the active variable snippets. 514 * This convenience method is equivalent to {@code snippets()} filtered for 515 * {@link jdk.jshell.Snippet.Status#isActive() status(snippet).isActive()} 516 * {@code && snippet.kind() == Kind.VARIABLE} 517 * and cast to {@code VarSnippet}. 518 * @return the active declared variables. 519 * @throws IllegalStateException if this JShell instance is closed. 520 */ 521 public List<VarSnippet> variables() throws IllegalStateException { 522 return snippets().stream() 523 .filter(sn -> status(sn).isActive() && sn.kind() == Snippet.Kind.VAR) 524 .map(sn -> (VarSnippet) sn) 525 .collect(collectingAndThen(toList(), Collections::unmodifiableList)); 526 } 527 528 /** 529 * Returns the active method snippets. 530 * This convenience method is equivalent to {@code snippets()} filtered for 531 * {@link jdk.jshell.Snippet.Status#isActive() status(snippet).isActive()} 532 * {@code && snippet.kind() == Kind.METHOD} 533 * and cast to MethodSnippet. 534 * @return the active declared methods. 535 * @throws IllegalStateException if this JShell instance is closed. 536 */ 537 public List<MethodSnippet> methods() throws IllegalStateException { 538 return snippets().stream() 539 .filter(sn -> status(sn).isActive() && sn.kind() == Snippet.Kind.METHOD) 540 .map(sn -> (MethodSnippet)sn) 541 .collect(collectingAndThen(toList(), Collections::unmodifiableList)); 542 } 543 544 /** 545 * Returns the active type declaration (class, interface, annotation type, and enum) snippets. 546 * This convenience method is equivalent to {@code snippets()} filtered for 547 * {@link jdk.jshell.Snippet.Status#isActive() status(snippet).isActive()} 548 * {@code && snippet.kind() == Kind.TYPE_DECL} 549 * and cast to TypeDeclSnippet. 550 * @return the active declared type declarations. 551 * @throws IllegalStateException if this JShell instance is closed. 552 */ 553 public List<TypeDeclSnippet> types() throws IllegalStateException { 554 return snippets().stream() 555 .filter(sn -> status(sn).isActive() && sn.kind() == Snippet.Kind.TYPE_DECL) 556 .map(sn -> (TypeDeclSnippet) sn) 557 .collect(collectingAndThen(toList(), Collections::unmodifiableList)); 558 } 559 560 /** 561 * Returns the active import snippets. 562 * This convenience method is equivalent to {@code snippets()} filtered for 563 * {@link jdk.jshell.Snippet.Status#isActive() status(snippet).isActive()} 564 * {@code && snippet.kind() == Kind.IMPORT} 565 * and cast to ImportSnippet. 566 * @return the active declared import declarations. 567 * @throws IllegalStateException if this JShell instance is closed. 568 */ 569 public List<ImportSnippet> imports() throws IllegalStateException { 570 return snippets().stream() 571 .filter(sn -> status(sn).isActive() && sn.kind() == Snippet.Kind.IMPORT) 572 .map(sn -> (ImportSnippet) sn) 573 .collect(collectingAndThen(toList(), Collections::unmodifiableList)); 574 } 575 576 /** 577 * Return the status of the snippet. 578 * This is updated either because of an explicit {@code eval()} call or 579 * an automatic update triggered by a dependency. 580 * @param snippet the {@code Snippet} to look up 581 * @return the status corresponding to this snippet 582 * @throws IllegalStateException if this {@code JShell} instance is closed. 583 * @throws IllegalArgumentException if the snippet is not associated with 584 * this {@code JShell} instance. 585 */ 586 public Status status(Snippet snippet) { 587 return checkValidSnippet(snippet).status(); 588 } 589 590 /** 591 * Return the diagnostics of the most recent evaluation of the snippet. 592 * The evaluation can either because of an explicit {@code eval()} call or 593 * an automatic update triggered by a dependency. 594 * @param snippet the {@code Snippet} to look up 595 * @return the diagnostics corresponding to this snippet. This does not 596 * include unresolvedDependencies references reported in {@code unresolvedDependencies()}. 597 * @throws IllegalStateException if this {@code JShell} instance is closed. 598 * @throws IllegalArgumentException if the snippet is not associated with 599 * this {@code JShell} instance. 600 */ 601 public List<Diag> diagnostics(Snippet snippet) { 602 return Collections.unmodifiableList(checkValidSnippet(snippet).diagnostics()); 603 } 604 605 /** 606 * For {@link jdk.jshell.Snippet.Status#RECOVERABLE_DEFINED RECOVERABLE_DEFINED} or 607 * {@link jdk.jshell.Snippet.Status#RECOVERABLE_NOT_DEFINED RECOVERABLE_NOT_DEFINED} 608 * declarations, the names of current unresolved dependencies for 609 * the snippet. 610 * The returned value of this method, for a given method may change when an 611 * {@code eval()} or {@code drop()} of another snippet causes 612 * an update of a dependency. 613 * @param snippet the declaration {@code Snippet} to look up 614 * @return the list of symbol names that are currently unresolvedDependencies. 615 * @throws IllegalStateException if this {@code JShell} instance is closed. 616 * @throws IllegalArgumentException if the snippet is not associated with 617 * this {@code JShell} instance. 618 */ 619 public List<String> unresolvedDependencies(DeclarationSnippet snippet) { 620 return Collections.unmodifiableList(checkValidSnippet(snippet).unresolved()); 621 } 622 623 /** 624 * Get the current value of a variable. 625 * @param snippet the variable Snippet whose value is queried. 626 * @return the current value of the variable referenced by snippet. 627 * @throws IllegalStateException if this {@code JShell} instance is closed. 628 * @throws IllegalArgumentException if the snippet is not associated with 629 * this {@code JShell} instance. 630 * @throws IllegalArgumentException if the variable's status is anything but 631 * {@link jdk.jshell.Snippet.Status#VALID}. 632 */ 633 public String varValue(VarSnippet snippet) throws IllegalStateException { 634 checkIfAlive(); 635 checkValidSnippet(snippet); 636 if (snippet.status() != Status.VALID) { 637 throw new IllegalArgumentException( 638 messageFormat("jshell.exc.var.not.valid", snippet, snippet.status())); 639 } 640 String value; 641 try { 642 value = executionControl().varValue(snippet.classFullName(), snippet.name()); 643 } catch (EngineTerminationException ex) { 644 throw new IllegalStateException(ex.getMessage()); 645 } catch (ExecutionControlException ex) { 646 debug(ex, "In varValue()"); 647 return "[" + ex.getMessage() + "]"; 648 } 649 return expunge(value); 650 } 651 652 /** 653 * Register a callback to be called when the Status of a snippet changes. 654 * Each call adds a new subscription. 655 * @param listener Action to perform when the Status changes. 656 * @return A token which can be used to {@linkplain JShell#unsubscribe unsubscribe} this subscription. 657 * @throws IllegalStateException if this {@code JShell} instance is closed. 658 */ 659 public Subscription onSnippetEvent(Consumer<SnippetEvent> listener) 660 throws IllegalStateException { 661 return onX(keyStatusListeners, listener); 662 } 663 664 /** 665 * Register a callback to be called when this JShell instance terminates. 666 * This occurs either because the client process has ended (e.g. called System.exit(0)) 667 * or the connection has been shutdown, as by close(). 668 * Each call adds a new subscription. 669 * @param listener Action to perform when the state terminates. 670 * @return A token which can be used to {@linkplain JShell#unsubscribe unsubscribe} this subscription. 671 * @throws IllegalStateException if this JShell instance is closed 672 */ 673 public Subscription onShutdown(Consumer<JShell> listener) 674 throws IllegalStateException { 675 return onX(shutdownListeners, listener); 676 } 677 678 /** 679 * Cancel a callback subscription. 680 * @param token The token corresponding to the subscription to be unsubscribed. 681 */ 682 public void unsubscribe(Subscription token) { 683 synchronized (this) { 684 token.remover.accept(token); 685 } 686 } 687 688 /** 689 * Subscription is a token for referring to subscriptions so they can 690 * be {@linkplain JShell#unsubscribe unsubscribed}. 691 */ 692 public class Subscription { 693 694 Consumer<Subscription> remover; 695 696 Subscription(Consumer<Subscription> remover) { 697 this.remover = remover; 698 } 699 } 700 701 /** 702 * Provide the environment for a execution engine. 703 */ 704 class ExecutionEnvImpl implements ExecutionEnv { 705 706 @Override 707 public InputStream userIn() { 708 return in; 709 } 710 711 @Override 712 public PrintStream userOut() { 713 return out; 714 } 715 716 @Override 717 public PrintStream userErr() { 718 return err; 719 } 720 721 @Override 722 public List<String> extraRemoteVMOptions() { 723 return extraRemoteVMOptions; 724 } 725 726 @Override 727 public void closeDown() { 728 JShell.this.closeDown(); 729 } 730 731 } 732 733 // --- private / package-private implementation support --- 734 ExecutionControl executionControl() { 735 if (executionControl == null) { 736 try { 737 executionControl = executionControlGenerator.generate(new ExecutionEnvImpl()); 738 } catch (Throwable ex) { 739 throw new InternalError("Launching execution engine threw: " + ex.getMessage(), ex); 740 } 741 } 742 return executionControl; 743 } 744 745 void debug(int flags, String format, Object... args) { 746 InternalDebugControl.debug(this, err, flags, format, args); 747 } 748 749 void debug(Exception ex, String where) { 750 InternalDebugControl.debug(this, err, ex, where); 751 } 752 753 /** 754 * Generate the next key index, indicating a unique snippet signature. 755 * 756 * @return the next key index 757 */ 758 int nextKeyIndex() { 759 return nextKeyIndex++; 760 } 761 762 private synchronized <T> Subscription onX(Map<Subscription, Consumer<T>> map, Consumer<T> listener) 763 throws IllegalStateException { 764 Objects.requireNonNull(listener); 765 checkIfAlive(); 766 Subscription token = new Subscription(map::remove); 767 map.put(token, listener); 768 return token; 769 } 770 771 private synchronized void notifyKeyStatusEvent(SnippetEvent event) { 772 keyStatusListeners.values().forEach(l -> l.accept(event)); 773 } 774 775 private synchronized void notifyShutdownEvent(JShell state) { 776 shutdownListeners.values().forEach(l -> l.accept(state)); 777 } 778 779 void closeDown() { 780 if (!closed) { 781 // Send only once 782 closed = true; 783 try { 784 notifyShutdownEvent(this); 785 } catch (Throwable thr) { 786 // Don't care about dying exceptions 787 } 788 } 789 } 790 791 /** 792 * Check if this JShell has been closed 793 * @throws IllegalStateException if it is closed 794 */ 795 private void checkIfAlive() throws IllegalStateException { 796 if (closed) { 797 throw new IllegalStateException(messageFormat("jshell.exc.closed", this)); 798 } 799 } 800 801 /** 802 * Check a Snippet parameter coming from the API user 803 * @param sn the Snippet to check 804 * @throws NullPointerException if Snippet parameter is null 805 * @throws IllegalArgumentException if Snippet is not from this JShell 806 * @return the input Snippet (for chained calls) 807 */ 808 private Snippet checkValidSnippet(Snippet sn) { 809 if (sn == null) { 810 throw new NullPointerException(messageFormat("jshell.exc.null")); 811 } else { 812 if (sn.key().state() != this) { 813 throw new IllegalArgumentException(messageFormat("jshell.exc.alien")); 814 } 815 return sn; 816 } 817 } 818 819 /** 820 * Format using resource bundle look-up using MessageFormat 821 * 822 * @param key the resource key 823 * @param args 824 */ 825 String messageFormat(String key, Object... args) { 826 if (outputRB == null) { 827 try { 828 outputRB = ResourceBundle.getBundle(L10N_RB_NAME); 829 } catch (MissingResourceException mre) { 830 throw new InternalError("Cannot find ResourceBundle: " + L10N_RB_NAME); 831 } 832 } 833 String s; 834 try { 835 s = outputRB.getString(key); 836 } catch (MissingResourceException mre) { 837 throw new InternalError("Missing resource: " + key + " in " + L10N_RB_NAME); 838 } 839 return MessageFormat.format(s, args); 840 } 841 842 }