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