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