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