JShellTool.java revision 3849:b2e915d476be
1/* 2 * Copyright (c) 2014, 2016, 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.internal.jshell.tool; 27 28import java.io.BufferedReader; 29import java.io.BufferedWriter; 30import java.io.File; 31import java.io.FileNotFoundException; 32import java.io.FileReader; 33import java.io.IOException; 34import java.io.InputStream; 35import java.io.InputStreamReader; 36import java.io.PrintStream; 37import java.io.Reader; 38import java.io.StringReader; 39import java.net.URL; 40import java.nio.charset.Charset; 41import java.nio.file.AccessDeniedException; 42import java.nio.file.FileSystems; 43import java.nio.file.Files; 44import java.nio.file.NoSuchFileException; 45import java.nio.file.Path; 46import java.nio.file.Paths; 47import java.text.MessageFormat; 48import java.util.ArrayList; 49import java.util.Arrays; 50import java.util.Collections; 51import java.util.HashSet; 52import java.util.Iterator; 53import java.util.LinkedHashMap; 54import java.util.LinkedHashSet; 55import java.util.List; 56import java.util.Locale; 57import java.util.Map; 58import java.util.Map.Entry; 59import java.util.Scanner; 60import java.util.Set; 61import java.util.function.Consumer; 62import java.util.function.Predicate; 63import java.util.prefs.Preferences; 64import java.util.regex.Matcher; 65import java.util.regex.Pattern; 66import java.util.stream.Collectors; 67import java.util.stream.Stream; 68import java.util.stream.StreamSupport; 69 70import jdk.internal.jshell.debug.InternalDebugControl; 71import jdk.internal.jshell.tool.IOContext.InputInterruptedException; 72import jdk.jshell.DeclarationSnippet; 73import jdk.jshell.Diag; 74import jdk.jshell.EvalException; 75import jdk.jshell.ExpressionSnippet; 76import jdk.jshell.ImportSnippet; 77import jdk.jshell.JShell; 78import jdk.jshell.JShell.Subscription; 79import jdk.jshell.MethodSnippet; 80import jdk.jshell.Snippet; 81import jdk.jshell.Snippet.Status; 82import jdk.jshell.SnippetEvent; 83import jdk.jshell.SourceCodeAnalysis; 84import jdk.jshell.SourceCodeAnalysis.CompletionInfo; 85import jdk.jshell.SourceCodeAnalysis.Suggestion; 86import jdk.jshell.TypeDeclSnippet; 87import jdk.jshell.UnresolvedReferenceException; 88import jdk.jshell.VarSnippet; 89 90import static java.nio.file.StandardOpenOption.CREATE; 91import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; 92import static java.nio.file.StandardOpenOption.WRITE; 93import java.util.MissingResourceException; 94import java.util.Optional; 95import java.util.ResourceBundle; 96import java.util.ServiceLoader; 97import java.util.Spliterators; 98import java.util.function.Function; 99import java.util.function.Supplier; 100import jdk.internal.joptsimple.*; 101import jdk.internal.jshell.tool.Feedback.FormatAction; 102import jdk.internal.jshell.tool.Feedback.FormatCase; 103import jdk.internal.jshell.tool.Feedback.FormatErrors; 104import jdk.internal.jshell.tool.Feedback.FormatResolve; 105import jdk.internal.jshell.tool.Feedback.FormatUnresolved; 106import jdk.internal.jshell.tool.Feedback.FormatWhen; 107import jdk.internal.editor.spi.BuildInEditorProvider; 108import jdk.internal.editor.external.ExternalEditor; 109import static java.util.Arrays.asList; 110import static java.util.Arrays.stream; 111import static java.util.stream.Collectors.joining; 112import static java.util.stream.Collectors.toList; 113import static jdk.jshell.Snippet.SubKind.VAR_VALUE_SUBKIND; 114import static java.util.stream.Collectors.toMap; 115import static jdk.internal.jshell.debug.InternalDebugControl.DBG_COMPA; 116import static jdk.internal.jshell.debug.InternalDebugControl.DBG_DEP; 117import static jdk.internal.jshell.debug.InternalDebugControl.DBG_EVNT; 118import static jdk.internal.jshell.debug.InternalDebugControl.DBG_FMGR; 119import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN; 120import static jdk.internal.jshell.tool.ContinuousCompletionProvider.STARTSWITH_MATCHER; 121 122/** 123 * Command line REPL tool for Java using the JShell API. 124 * @author Robert Field 125 */ 126public class JShellTool implements MessageHandler { 127 128 private static final String LINE_SEP = System.getProperty("line.separator"); 129 private static final Pattern LINEBREAK = Pattern.compile("\\R"); 130 private static final String RECORD_SEPARATOR = "\u241E"; 131 private static final String RB_NAME_PREFIX = "jdk.internal.jshell.tool.resources"; 132 private static final String VERSION_RB_NAME = RB_NAME_PREFIX + ".version"; 133 private static final String L10N_RB_NAME = RB_NAME_PREFIX + ".l10n"; 134 135 final InputStream cmdin; 136 final PrintStream cmdout; 137 final PrintStream cmderr; 138 final PrintStream console; 139 final InputStream userin; 140 final PrintStream userout; 141 final PrintStream usererr; 142 final PersistentStorage prefs; 143 final Map<String, String> envvars; 144 final Locale locale; 145 146 final Feedback feedback = new Feedback(); 147 148 /** 149 * The complete constructor for the tool (used by test harnesses). 150 * @param cmdin command line input -- snippets and commands 151 * @param cmdout command line output, feedback including errors 152 * @param cmderr start-up errors and debugging info 153 * @param console console control interaction 154 * @param userin code execution input, or null to use IOContext 155 * @param userout code execution output -- System.out.printf("hi") 156 * @param usererr code execution error stream -- System.err.printf("Oops") 157 * @param prefs persistence implementation to use 158 * @param envvars environment variable mapping to use 159 * @param locale locale to use 160 */ 161 JShellTool(InputStream cmdin, PrintStream cmdout, PrintStream cmderr, 162 PrintStream console, 163 InputStream userin, PrintStream userout, PrintStream usererr, 164 PersistentStorage prefs, Map<String, String> envvars, Locale locale) { 165 this.cmdin = cmdin; 166 this.cmdout = cmdout; 167 this.cmderr = cmderr; 168 this.console = console; 169 this.userin = userin != null ? userin : new InputStream() { 170 @Override 171 public int read() throws IOException { 172 return input.readUserInput(); 173 } 174 }; 175 this.userout = userout; 176 this.usererr = usererr; 177 this.prefs = prefs; 178 this.envvars = envvars; 179 this.locale = locale; 180 } 181 182 private ResourceBundle versionRB = null; 183 private ResourceBundle outputRB = null; 184 185 private IOContext input = null; 186 private boolean regenerateOnDeath = true; 187 private boolean live = false; 188 private boolean feedbackInitialized = false; 189 private String commandLineFeedbackMode = null; 190 private List<String> remoteVMOptions = new ArrayList<>(); 191 private List<String> compilerOptions = new ArrayList<>(); 192 193 SourceCodeAnalysis analysis; 194 JShell state = null; 195 Subscription shutdownSubscription = null; 196 197 static final EditorSetting BUILT_IN_EDITOR = new EditorSetting(null, false); 198 199 private boolean debug = false; 200 public boolean testPrompt = false; 201 private String cmdlineClasspath = null; 202 private String defaultStartup = null; 203 private String startup = null; 204 private String executionControlSpec = null; 205 private EditorSetting editor = BUILT_IN_EDITOR; 206 207 private static final String[] EDITOR_ENV_VARS = new String[] { 208 "JSHELLEDITOR", "VISUAL", "EDITOR"}; 209 210 // Commands and snippets which should be replayed 211 private List<String> replayableHistory; 212 private List<String> replayableHistoryPrevious; 213 214 static final String STARTUP_KEY = "STARTUP"; 215 static final String EDITOR_KEY = "EDITOR"; 216 static final String FEEDBACK_KEY = "FEEDBACK"; 217 static final String MODE_KEY = "MODE"; 218 static final String REPLAY_RESTORE_KEY = "REPLAY_RESTORE"; 219 220 static final String DEFAULT_STARTUP_NAME = "DEFAULT"; 221 static final Pattern BUILTIN_FILE_PATTERN = Pattern.compile("\\w+"); 222 static final String BUILTIN_FILE_PATH_FORMAT = "jrt:/jdk.jshell/jdk/jshell/tool/resources/%s.jsh"; 223 224 // Tool id (tid) mapping: the three name spaces 225 NameSpace mainNamespace; 226 NameSpace startNamespace; 227 NameSpace errorNamespace; 228 229 // Tool id (tid) mapping: the current name spaces 230 NameSpace currentNameSpace; 231 232 Map<Snippet,SnippetInfo> mapSnippet; 233 234 /** 235 * Is the input/output currently interactive 236 * 237 * @return true if console 238 */ 239 boolean interactive() { 240 return input != null && input.interactiveOutput(); 241 } 242 243 void debug(String format, Object... args) { 244 if (debug) { 245 cmderr.printf(format + "\n", args); 246 } 247 } 248 249 /** 250 * Base output for command output -- no pre- or post-fix 251 * 252 * @param printf format 253 * @param printf args 254 */ 255 void rawout(String format, Object... args) { 256 cmdout.printf(format, args); 257 } 258 259 /** 260 * Must show command output 261 * 262 * @param format printf format 263 * @param args printf args 264 */ 265 @Override 266 public void hard(String format, Object... args) { 267 rawout(prefix(format), args); 268 } 269 270 /** 271 * Error command output 272 * 273 * @param format printf format 274 * @param args printf args 275 */ 276 void error(String format, Object... args) { 277 rawout(prefixError(format), args); 278 } 279 280 /** 281 * Should optional informative be displayed? 282 * @return true if they should be displayed 283 */ 284 @Override 285 public boolean showFluff() { 286 return feedback.shouldDisplayCommandFluff() && interactive(); 287 } 288 289 /** 290 * Optional output 291 * 292 * @param format printf format 293 * @param args printf args 294 */ 295 @Override 296 public void fluff(String format, Object... args) { 297 if (showFluff()) { 298 hard(format, args); 299 } 300 } 301 302 /** 303 * Resource bundle look-up 304 * 305 * @param key the resource key 306 */ 307 String getResourceString(String key) { 308 if (outputRB == null) { 309 try { 310 outputRB = ResourceBundle.getBundle(L10N_RB_NAME, locale); 311 } catch (MissingResourceException mre) { 312 error("Cannot find ResourceBundle: %s for locale: %s", L10N_RB_NAME, locale); 313 return ""; 314 } 315 } 316 String s; 317 try { 318 s = outputRB.getString(key); 319 } catch (MissingResourceException mre) { 320 error("Missing resource: %s in %s", key, L10N_RB_NAME); 321 return ""; 322 } 323 return s; 324 } 325 326 /** 327 * Add normal prefixing/postfixing to embedded newlines in a string, 328 * bracketing with normal prefix/postfix 329 * 330 * @param s the string to prefix 331 * @return the pre/post-fixed and bracketed string 332 */ 333 String prefix(String s) { 334 return prefix(s, feedback.getPre(), feedback.getPost()); 335 } 336 337 /** 338 * Add error prefixing/postfixing to embedded newlines in a string, 339 * bracketing with error prefix/postfix 340 * 341 * @param s the string to prefix 342 * @return the pre/post-fixed and bracketed string 343 */ 344 String prefixError(String s) { 345 return prefix(s, feedback.getErrorPre(), feedback.getErrorPost()); 346 } 347 348 /** 349 * Add prefixing/postfixing to embedded newlines in a string, 350 * bracketing with prefix/postfix 351 * 352 * @param s the string to prefix 353 * @param pre the string to prepend to each line 354 * @param post the string to append to each line (replacing newline) 355 * @return the pre/post-fixed and bracketed string 356 */ 357 String prefix(String s, String pre, String post) { 358 if (s == null) { 359 return ""; 360 } 361 String pp = s.replaceAll("\\R", post + pre); 362 return pre + pp + post; 363 } 364 365 /** 366 * Print using resource bundle look-up and adding prefix and postfix 367 * 368 * @param key the resource key 369 */ 370 void hardrb(String key) { 371 hard(getResourceString(key)); 372 } 373 374 /** 375 * Format using resource bundle look-up using MessageFormat 376 * 377 * @param key the resource key 378 * @param args 379 */ 380 String messageFormat(String key, Object... args) { 381 String rs = getResourceString(key); 382 return MessageFormat.format(rs, args); 383 } 384 385 /** 386 * Print using resource bundle look-up, MessageFormat, and add prefix and 387 * postfix 388 * 389 * @param key the resource key 390 * @param args 391 */ 392 @Override 393 public void hardmsg(String key, Object... args) { 394 hard(messageFormat(key, args)); 395 } 396 397 /** 398 * Print error using resource bundle look-up, MessageFormat, and add prefix 399 * and postfix 400 * 401 * @param key the resource key 402 * @param args 403 */ 404 @Override 405 public void errormsg(String key, Object... args) { 406 if (isRunningInteractive()) { 407 rawout(prefixError(messageFormat(key, args))); 408 } else { 409 startmsg(key, args); 410 } 411 } 412 413 /** 414 * Print command-line error using resource bundle look-up, MessageFormat 415 * 416 * @param key the resource key 417 * @param args 418 */ 419 void startmsg(String key, Object... args) { 420 cmderr.println(messageFormat(key, args)); 421 } 422 423 /** 424 * Print (fluff) using resource bundle look-up, MessageFormat, and add 425 * prefix and postfix 426 * 427 * @param key the resource key 428 * @param args 429 */ 430 @Override 431 public void fluffmsg(String key, Object... args) { 432 if (showFluff()) { 433 hardmsg(key, args); 434 } 435 } 436 437 <T> void hardPairs(Stream<T> stream, Function<T, String> a, Function<T, String> b) { 438 Map<String, String> a2b = stream.collect(toMap(a, b, 439 (m1, m2) -> m1, 440 LinkedHashMap::new)); 441 for (Entry<String, String> e : a2b.entrySet()) { 442 hard("%s", e.getKey()); 443 rawout(prefix(e.getValue(), feedback.getPre() + "\t", feedback.getPost())); 444 } 445 } 446 447 /** 448 * Trim whitespace off end of string 449 * 450 * @param s 451 * @return 452 */ 453 static String trimEnd(String s) { 454 int last = s.length() - 1; 455 int i = last; 456 while (i >= 0 && Character.isWhitespace(s.charAt(i))) { 457 --i; 458 } 459 if (i != last) { 460 return s.substring(0, i + 1); 461 } else { 462 return s; 463 } 464 } 465 466 public void start(String[] args) throws Exception { 467 List<String> loadList = processCommandArgs(args); 468 if (loadList == null) { 469 // Abort 470 return; 471 } 472 try (IOContext in = new ConsoleIOContext(this, cmdin, console)) { 473 start(in, loadList); 474 } 475 } 476 477 private void start(IOContext in, List<String> loadList) { 478 // If startup hasn't been set by command line, set from retained/default 479 if (startup == null) { 480 startup = prefs.get(STARTUP_KEY); 481 if (startup == null) { 482 startup = defaultStartup(); 483 } 484 } 485 486 configEditor(); 487 488 resetState(); // Initialize 489 490 // Read replay history from last jshell session into previous history 491 String prevReplay = prefs.get(REPLAY_RESTORE_KEY); 492 if (prevReplay != null) { 493 replayableHistoryPrevious = Arrays.asList(prevReplay.split(RECORD_SEPARATOR)); 494 } 495 496 for (String loadFile : loadList) { 497 runFile(loadFile, "jshell"); 498 } 499 500 if (regenerateOnDeath && feedback.shouldDisplayCommandFluff()) { 501 hardmsg("jshell.msg.welcome", version()); 502 } 503 504 try { 505 while (regenerateOnDeath) { 506 if (!live) { 507 resetState(); 508 } 509 run(in); 510 } 511 } finally { 512 closeState(); 513 } 514 } 515 516 private EditorSetting configEditor() { 517 // Read retained editor setting (if any) 518 editor = EditorSetting.fromPrefs(prefs); 519 if (editor != null) { 520 return editor; 521 } 522 // Try getting editor setting from OS environment variables 523 for (String envvar : EDITOR_ENV_VARS) { 524 String v = envvars.get(envvar); 525 if (v != null) { 526 return editor = new EditorSetting(v.split("\\s+"), false); 527 } 528 } 529 // Default to the built-in editor 530 return editor = BUILT_IN_EDITOR; 531 } 532 533 /** 534 * Process the command line arguments. 535 * Set options. 536 * @param args the command line arguments 537 * @return the list of files to be loaded 538 */ 539 private List<String> processCommandArgs(String[] args) { 540 OptionParser parser = new OptionParser(); 541 OptionSpec<String> cp = parser.accepts("class-path").withRequiredArg(); 542 OptionSpec<String> mpath = parser.accepts("module-path").withRequiredArg(); 543 OptionSpec<String> amods = parser.accepts("add-modules").withRequiredArg(); 544 OptionSpec<String> st = parser.accepts("startup").withRequiredArg(); 545 parser.acceptsAll(asList("n", "no-startup")); 546 OptionSpec<String> fb = parser.accepts("feedback").withRequiredArg(); 547 OptionSpec<String> ec = parser.accepts("execution").withRequiredArg(); 548 parser.accepts("q"); 549 parser.accepts("s"); 550 parser.accepts("v"); 551 OptionSpec<String> r = parser.accepts("R").withRequiredArg(); 552 OptionSpec<String> c = parser.accepts("C").withRequiredArg(); 553 parser.acceptsAll(asList("h", "help")); 554 parser.accepts("version"); 555 parser.accepts("full-version"); 556 557 parser.accepts("X"); 558 OptionSpec<String> addExports = parser.accepts("add-exports").withRequiredArg(); 559 560 NonOptionArgumentSpec<String> loadFileSpec = parser.nonOptions(); 561 562 OptionSet options; 563 try { 564 options = parser.parse(args); 565 } catch (OptionException ex) { 566 if (ex.options().isEmpty()) { 567 startmsg("jshell.err.opt.invalid", stream(args).collect(joining(", "))); 568 } else { 569 boolean isKnown = parser.recognizedOptions().containsKey(ex.options().iterator().next()); 570 startmsg(isKnown 571 ? "jshell.err.opt.arg" 572 : "jshell.err.opt.unknown", 573 ex.options() 574 .stream() 575 .collect(joining(", "))); 576 } 577 return null; 578 } 579 580 if (options.has("help")) { 581 printUsage(); 582 return null; 583 } 584 if (options.has("X")) { 585 printUsageX(); 586 return null; 587 } 588 if (options.has("version")) { 589 cmdout.printf("jshell %s\n", version()); 590 return null; 591 } 592 if (options.has("full-version")) { 593 cmdout.printf("jshell %s\n", fullVersion()); 594 return null; 595 } 596 if (options.has(cp)) { 597 List<String> cps = options.valuesOf(cp); 598 if (cps.size() > 1) { 599 startmsg("jshell.err.opt.one", "--class-path"); 600 return null; 601 } 602 cmdlineClasspath = cps.get(0); 603 } 604 if (options.has(st)) { 605 List<String> sts = options.valuesOf(st); 606 if (options.has("no-startup")) { 607 startmsg("jshell.err.opt.startup.conflict"); 608 return null; 609 } 610 StringBuilder sb = new StringBuilder(); 611 for (String fn : sts) { 612 String s = readFile(fn, "--startup"); 613 if (s == null) { 614 return null; 615 } 616 sb.append(s); 617 } 618 startup = sb.toString(); 619 } else if (options.has("no-startup")) { 620 startup = ""; 621 } 622 if ((options.valuesOf(fb).size() + 623 (options.has("q") ? 1 : 0) + 624 (options.has("s") ? 1 : 0) + 625 (options.has("v") ? 1 : 0)) > 1) { 626 startmsg("jshell.err.opt.feedback.one"); 627 return null; 628 } else if (options.has(fb)) { 629 commandLineFeedbackMode = options.valueOf(fb); 630 } else if (options.has("q")) { 631 commandLineFeedbackMode = "concise"; 632 } else if (options.has("s")) { 633 commandLineFeedbackMode = "silent"; 634 } else if (options.has("v")) { 635 commandLineFeedbackMode = "verbose"; 636 } 637 if (options.has(r)) { 638 remoteVMOptions.addAll(options.valuesOf(r)); 639 } 640 if (options.has(c)) { 641 compilerOptions.addAll(options.valuesOf(c)); 642 } 643 if (options.has(mpath)) { 644 compilerOptions.add("--module-path"); 645 compilerOptions.addAll(options.valuesOf(mpath)); 646 remoteVMOptions.add("--module-path"); 647 remoteVMOptions.addAll(options.valuesOf(mpath)); 648 } 649 if (options.has(amods)) { 650 compilerOptions.add("--add-modules"); 651 compilerOptions.addAll(options.valuesOf(amods)); 652 remoteVMOptions.add("--add-modules"); 653 remoteVMOptions.addAll(options.valuesOf(amods)); 654 } 655 if (options.has(ec)) { 656 executionControlSpec = options.valueOf(ec); 657 } 658 659 if (options.has(addExports)) { 660 List<String> exports = options.valuesOf(addExports).stream() 661 .map(mp -> mp + "=ALL-UNNAMED") 662 .flatMap(mp -> Stream.of("--add-exports", mp)) 663 .collect(toList()); 664 remoteVMOptions.addAll(exports); 665 compilerOptions.addAll(exports); 666 } 667 668 return options.valuesOf(loadFileSpec); 669 } 670 671 private void printUsage() { 672 cmdout.print(getResourceString("help.usage")); 673 } 674 675 private void printUsageX() { 676 cmdout.print(getResourceString("help.usage.x")); 677 } 678 679 /** 680 * Message handler to use during initial start-up. 681 */ 682 private class InitMessageHandler implements MessageHandler { 683 684 @Override 685 public void fluff(String format, Object... args) { 686 //ignore 687 } 688 689 @Override 690 public void fluffmsg(String messageKey, Object... args) { 691 //ignore 692 } 693 694 @Override 695 public void hard(String format, Object... args) { 696 //ignore 697 } 698 699 @Override 700 public void hardmsg(String messageKey, Object... args) { 701 //ignore 702 } 703 704 @Override 705 public void errormsg(String messageKey, Object... args) { 706 startmsg(messageKey, args); 707 } 708 709 @Override 710 public boolean showFluff() { 711 return false; 712 } 713 } 714 715 private void resetState() { 716 closeState(); 717 718 // Initialize tool id mapping 719 mainNamespace = new NameSpace("main", ""); 720 startNamespace = new NameSpace("start", "s"); 721 errorNamespace = new NameSpace("error", "e"); 722 mapSnippet = new LinkedHashMap<>(); 723 currentNameSpace = startNamespace; 724 725 // Reset the replayable history, saving the old for restore 726 replayableHistoryPrevious = replayableHistory; 727 replayableHistory = new ArrayList<>(); 728 JShell.Builder builder = 729 JShell.builder() 730 .in(userin) 731 .out(userout) 732 .err(usererr) 733 .tempVariableNameGenerator(() -> "$" + currentNameSpace.tidNext()) 734 .idGenerator((sn, i) -> (currentNameSpace == startNamespace || state.status(sn).isActive()) 735 ? currentNameSpace.tid(sn) 736 : errorNamespace.tid(sn)) 737 .remoteVMOptions(remoteVMOptions.stream().toArray(String[]::new)) 738 .compilerOptions(compilerOptions.stream().toArray(String[]::new)); 739 if (executionControlSpec != null) { 740 builder.executionEngine(executionControlSpec); 741 } 742 state = builder.build(); 743 shutdownSubscription = state.onShutdown((JShell deadState) -> { 744 if (deadState == state) { 745 hardmsg("jshell.msg.terminated"); 746 live = false; 747 } 748 }); 749 analysis = state.sourceCodeAnalysis(); 750 live = true; 751 if (!feedbackInitialized) { 752 // One time per run feedback initialization 753 feedbackInitialized = true; 754 initFeedback(); 755 } 756 757 if (cmdlineClasspath != null) { 758 state.addToClasspath(cmdlineClasspath); 759 } 760 761 startUpRun(startup); 762 currentNameSpace = mainNamespace; 763 } 764 765 private boolean isRunningInteractive() { 766 return currentNameSpace != null && currentNameSpace == mainNamespace; 767 } 768 769 //where -- one-time per run initialization of feedback modes 770 private void initFeedback() { 771 // No fluff, no prefix, for init failures 772 MessageHandler initmh = new InitMessageHandler(); 773 // Execute the feedback initialization code in the resource file 774 startUpRun(getResourceString("startup.feedback")); 775 // These predefined modes are read-only 776 feedback.markModesReadOnly(); 777 // Restore user defined modes retained on previous run with /set mode -retain 778 String encoded = prefs.get(MODE_KEY); 779 if (encoded != null && !encoded.isEmpty()) { 780 if (!feedback.restoreEncodedModes(initmh, encoded)) { 781 // Catastrophic corruption -- remove the retained modes 782 prefs.remove(MODE_KEY); 783 } 784 } 785 if (commandLineFeedbackMode != null) { 786 // The feedback mode to use was specified on the command line, use it 787 if (!setFeedback(initmh, new ArgTokenizer("--feedback", commandLineFeedbackMode))) { 788 regenerateOnDeath = false; 789 } 790 commandLineFeedbackMode = null; 791 } else { 792 String fb = prefs.get(FEEDBACK_KEY); 793 if (fb != null) { 794 // Restore the feedback mode to use that was retained 795 // on a previous run with /set feedback -retain 796 setFeedback(initmh, new ArgTokenizer("previous retain feedback", "-retain " + fb)); 797 } 798 } 799 } 800 801 //where 802 private void startUpRun(String start) { 803 try (IOContext suin = new ScannerIOContext(new StringReader(start))) { 804 run(suin); 805 } catch (Exception ex) { 806 hardmsg("jshell.err.startup.unexpected.exception", ex); 807 ex.printStackTrace(cmdout); 808 } 809 } 810 811 private void closeState() { 812 live = false; 813 JShell oldState = state; 814 if (oldState != null) { 815 oldState.unsubscribe(shutdownSubscription); // No notification 816 oldState.close(); 817 } 818 } 819 820 /** 821 * Main loop 822 * @param in the line input/editing context 823 */ 824 private void run(IOContext in) { 825 IOContext oldInput = input; 826 input = in; 827 try { 828 String incomplete = ""; 829 while (live) { 830 String prompt; 831 if (isRunningInteractive()) { 832 prompt = testPrompt 833 ? incomplete.isEmpty() 834 ? "\u0005" //ENQ 835 : "\u0006" //ACK 836 : incomplete.isEmpty() 837 ? feedback.getPrompt(currentNameSpace.tidNext()) 838 : feedback.getContinuationPrompt(currentNameSpace.tidNext()) 839 ; 840 } else { 841 prompt = ""; 842 } 843 String raw; 844 try { 845 raw = in.readLine(prompt, incomplete); 846 } catch (InputInterruptedException ex) { 847 //input interrupted - clearing current state 848 incomplete = ""; 849 continue; 850 } 851 if (raw == null) { 852 //EOF 853 if (in.interactiveOutput()) { 854 // End after user ctrl-D 855 regenerateOnDeath = false; 856 } 857 break; 858 } 859 String trimmed = trimEnd(raw); 860 if (!trimmed.isEmpty()) { 861 String line = incomplete + trimmed; 862 863 // No commands in the middle of unprocessed source 864 if (incomplete.isEmpty() && line.startsWith("/") && !line.startsWith("//") && !line.startsWith("/*")) { 865 processCommand(line.trim()); 866 } else { 867 incomplete = processSourceCatchingReset(line); 868 } 869 } 870 } 871 } catch (IOException ex) { 872 errormsg("jshell.err.unexpected.exception", ex); 873 } finally { 874 input = oldInput; 875 } 876 } 877 878 private void addToReplayHistory(String s) { 879 if (isRunningInteractive()) { 880 replayableHistory.add(s); 881 } 882 } 883 884 private String processSourceCatchingReset(String src) { 885 try { 886 input.beforeUserCode(); 887 return processSource(src); 888 } catch (IllegalStateException ex) { 889 hard("Resetting..."); 890 live = false; // Make double sure 891 return ""; 892 } finally { 893 input.afterUserCode(); 894 } 895 } 896 897 private void processCommand(String cmd) { 898 if (cmd.startsWith("/-")) { 899 try { 900 //handle "/-[number]" 901 cmdUseHistoryEntry(Integer.parseInt(cmd.substring(1))); 902 return ; 903 } catch (NumberFormatException ex) { 904 //ignore 905 } 906 } 907 String arg = ""; 908 int idx = cmd.indexOf(' '); 909 if (idx > 0) { 910 arg = cmd.substring(idx + 1).trim(); 911 cmd = cmd.substring(0, idx); 912 } 913 Command[] candidates = findCommand(cmd, c -> c.kind.isRealCommand); 914 switch (candidates.length) { 915 case 0: 916 if (!rerunHistoryEntryById(cmd.substring(1))) { 917 errormsg("jshell.err.no.such.command.or.snippet.id", cmd); 918 fluffmsg("jshell.msg.help.for.help"); 919 } break; 920 case 1: 921 Command command = candidates[0]; 922 // If comand was successful and is of a replayable kind, add it the replayable history 923 if (command.run.apply(arg) && command.kind == CommandKind.REPLAY) { 924 addToReplayHistory((command.command + " " + arg).trim()); 925 } break; 926 default: 927 errormsg("jshell.err.command.ambiguous", cmd, 928 Arrays.stream(candidates).map(c -> c.command).collect(Collectors.joining(", "))); 929 fluffmsg("jshell.msg.help.for.help"); 930 break; 931 } 932 } 933 934 private Command[] findCommand(String cmd, Predicate<Command> filter) { 935 Command exact = commands.get(cmd); 936 if (exact != null) 937 return new Command[] {exact}; 938 939 return commands.values() 940 .stream() 941 .filter(filter) 942 .filter(command -> command.command.startsWith(cmd)) 943 .toArray(Command[]::new); 944 } 945 946 private static Path toPathResolvingUserHome(String pathString) { 947 if (pathString.replace(File.separatorChar, '/').startsWith("~/")) 948 return Paths.get(System.getProperty("user.home"), pathString.substring(2)); 949 else 950 return Paths.get(pathString); 951 } 952 953 static final class Command { 954 public final String command; 955 public final String helpKey; 956 public final Function<String,Boolean> run; 957 public final CompletionProvider completions; 958 public final CommandKind kind; 959 960 // NORMAL Commands 961 public Command(String command, Function<String,Boolean> run, CompletionProvider completions) { 962 this(command, run, completions, CommandKind.NORMAL); 963 } 964 965 // Special kinds of Commands 966 public Command(String command, Function<String,Boolean> run, CompletionProvider completions, CommandKind kind) { 967 this(command, "help." + command.substring(1), 968 run, completions, kind); 969 } 970 971 // Documentation pseudo-commands 972 public Command(String command, String helpKey, CommandKind kind) { 973 this(command, helpKey, 974 arg -> { throw new IllegalStateException(); }, 975 EMPTY_COMPLETION_PROVIDER, 976 kind); 977 } 978 979 public Command(String command, String helpKey, Function<String,Boolean> run, CompletionProvider completions, CommandKind kind) { 980 this.command = command; 981 this.helpKey = helpKey; 982 this.run = run; 983 this.completions = completions; 984 this.kind = kind; 985 } 986 987 } 988 989 interface CompletionProvider { 990 List<Suggestion> completionSuggestions(String input, int cursor, int[] anchor); 991 992 } 993 994 enum CommandKind { 995 NORMAL(true, true, true), 996 REPLAY(true, true, true), 997 HIDDEN(true, false, false), 998 HELP_ONLY(false, true, false), 999 HELP_SUBJECT(false, false, false); 1000 1001 final boolean isRealCommand; 1002 final boolean showInHelp; 1003 final boolean shouldSuggestCompletions; 1004 private CommandKind(boolean isRealCommand, boolean showInHelp, boolean shouldSuggestCompletions) { 1005 this.isRealCommand = isRealCommand; 1006 this.showInHelp = showInHelp; 1007 this.shouldSuggestCompletions = shouldSuggestCompletions; 1008 } 1009 } 1010 1011 static final class FixedCompletionProvider implements CompletionProvider { 1012 1013 private final String[] alternatives; 1014 1015 public FixedCompletionProvider(String... alternatives) { 1016 this.alternatives = alternatives; 1017 } 1018 1019 @Override 1020 public List<Suggestion> completionSuggestions(String input, int cursor, int[] anchor) { 1021 List<Suggestion> result = new ArrayList<>(); 1022 1023 for (String alternative : alternatives) { 1024 if (alternative.startsWith(input)) { 1025 result.add(new ArgSuggestion(alternative)); 1026 } 1027 } 1028 1029 anchor[0] = 0; 1030 1031 return result; 1032 } 1033 1034 } 1035 1036 static final CompletionProvider EMPTY_COMPLETION_PROVIDER = new FixedCompletionProvider(); 1037 private static final CompletionProvider SNIPPET_HISTORY_OPTION_COMPLETION_PROVIDER = new FixedCompletionProvider("-all", "-start ", "-history"); 1038 private static final CompletionProvider SAVE_OPTION_COMPLETION_PROVIDER = new FixedCompletionProvider("-all ", "-start ", "-history "); 1039 private static final CompletionProvider SNIPPET_OPTION_COMPLETION_PROVIDER = new FixedCompletionProvider("-all", "-start " ); 1040 private static final CompletionProvider RELOAD_OPTIONS_COMPLETION_PROVIDER = new FixedCompletionProvider("-restore ", "-quiet "); 1041 private static final CompletionProvider RESTORE_COMPLETION_PROVIDER = new FixedCompletionProvider("-restore"); 1042 private static final CompletionProvider QUIET_COMPLETION_PROVIDER = new FixedCompletionProvider("-quiet"); 1043 private static final CompletionProvider SET_MODE_OPTIONS_COMPLETION_PROVIDER = new FixedCompletionProvider("-command", "-quiet", "-delete"); 1044 private static final CompletionProvider FILE_COMPLETION_PROVIDER = fileCompletions(p -> true); 1045 private final Map<String, Command> commands = new LinkedHashMap<>(); 1046 private void registerCommand(Command cmd) { 1047 commands.put(cmd.command, cmd); 1048 } 1049 1050 private static CompletionProvider skipWordThenCompletion(CompletionProvider completionProvider) { 1051 return (input, cursor, anchor) -> { 1052 List<Suggestion> result = Collections.emptyList(); 1053 1054 int space = input.indexOf(' '); 1055 if (space != -1) { 1056 String rest = input.substring(space + 1); 1057 result = completionProvider.completionSuggestions(rest, cursor - space - 1, anchor); 1058 anchor[0] += space + 1; 1059 } 1060 1061 return result; 1062 }; 1063 } 1064 1065 private static CompletionProvider fileCompletions(Predicate<Path> accept) { 1066 return (code, cursor, anchor) -> { 1067 int lastSlash = code.lastIndexOf('/'); 1068 String path = code.substring(0, lastSlash + 1); 1069 String prefix = lastSlash != (-1) ? code.substring(lastSlash + 1) : code; 1070 Path current = toPathResolvingUserHome(path); 1071 List<Suggestion> result = new ArrayList<>(); 1072 try (Stream<Path> dir = Files.list(current)) { 1073 dir.filter(f -> accept.test(f) && f.getFileName().toString().startsWith(prefix)) 1074 .map(f -> new ArgSuggestion(f.getFileName() + (Files.isDirectory(f) ? "/" : ""))) 1075 .forEach(result::add); 1076 } catch (IOException ex) { 1077 //ignore... 1078 } 1079 if (path.isEmpty()) { 1080 StreamSupport.stream(FileSystems.getDefault().getRootDirectories().spliterator(), false) 1081 .filter(root -> Files.exists(root)) 1082 .filter(root -> accept.test(root) && root.toString().startsWith(prefix)) 1083 .map(root -> new ArgSuggestion(root.toString())) 1084 .forEach(result::add); 1085 } 1086 anchor[0] = path.length(); 1087 return result; 1088 }; 1089 } 1090 1091 private static CompletionProvider classPathCompletion() { 1092 return fileCompletions(p -> Files.isDirectory(p) || 1093 p.getFileName().toString().endsWith(".zip") || 1094 p.getFileName().toString().endsWith(".jar")); 1095 } 1096 1097 // Completion based on snippet supplier 1098 private CompletionProvider snippetCompletion(Supplier<Stream<? extends Snippet>> snippetsSupplier) { 1099 return (prefix, cursor, anchor) -> { 1100 anchor[0] = 0; 1101 int space = prefix.lastIndexOf(' '); 1102 Set<String> prior = new HashSet<>(Arrays.asList(prefix.split(" "))); 1103 if (prior.contains("-all") || prior.contains("-history")) { 1104 return Collections.emptyList(); 1105 } 1106 String argPrefix = prefix.substring(space + 1); 1107 return snippetsSupplier.get() 1108 .filter(k -> !prior.contains(String.valueOf(k.id())) 1109 && (!(k instanceof DeclarationSnippet) 1110 || !prior.contains(((DeclarationSnippet) k).name()))) 1111 .flatMap(k -> (k instanceof DeclarationSnippet) 1112 ? Stream.of(String.valueOf(k.id()) + " ", ((DeclarationSnippet) k).name() + " ") 1113 : Stream.of(String.valueOf(k.id()) + " ")) 1114 .filter(k -> k.startsWith(argPrefix)) 1115 .map(ArgSuggestion::new) 1116 .collect(Collectors.toList()); 1117 }; 1118 } 1119 1120 // Completion based on snippet supplier with -all -start (and sometimes -history) options 1121 private CompletionProvider snippetWithOptionCompletion(CompletionProvider optionProvider, 1122 Supplier<Stream<? extends Snippet>> snippetsSupplier) { 1123 return (code, cursor, anchor) -> { 1124 List<Suggestion> result = new ArrayList<>(); 1125 int pastSpace = code.lastIndexOf(' ') + 1; // zero if no space 1126 if (pastSpace == 0) { 1127 result.addAll(optionProvider.completionSuggestions(code, cursor, anchor)); 1128 } 1129 result.addAll(snippetCompletion(snippetsSupplier).completionSuggestions(code, cursor, anchor)); 1130 anchor[0] += pastSpace; 1131 return result; 1132 }; 1133 } 1134 1135 // Completion of help, commands and subjects 1136 private CompletionProvider helpCompletion() { 1137 return (code, cursor, anchor) -> { 1138 List<Suggestion> result; 1139 int pastSpace = code.indexOf(' ') + 1; // zero if no space 1140 if (pastSpace == 0) { 1141 result = new FixedCompletionProvider(commands.values().stream() 1142 .filter(cmd -> cmd.kind.showInHelp || cmd.kind == CommandKind.HELP_SUBJECT) 1143 .map(c -> c.command + " ") 1144 .toArray(String[]::new)) 1145 .completionSuggestions(code, cursor, anchor); 1146 } else if (code.startsWith("/se")) { 1147 result = new FixedCompletionProvider(SET_SUBCOMMANDS) 1148 .completionSuggestions(code.substring(pastSpace), cursor - pastSpace, anchor); 1149 } else { 1150 result = Collections.emptyList(); 1151 } 1152 anchor[0] += pastSpace; 1153 return result; 1154 }; 1155 } 1156 1157 private static CompletionProvider saveCompletion() { 1158 return (code, cursor, anchor) -> { 1159 List<Suggestion> result = new ArrayList<>(); 1160 int space = code.indexOf(' '); 1161 if (space == (-1)) { 1162 result.addAll(SAVE_OPTION_COMPLETION_PROVIDER.completionSuggestions(code, cursor, anchor)); 1163 } 1164 result.addAll(FILE_COMPLETION_PROVIDER.completionSuggestions(code.substring(space + 1), cursor - space - 1, anchor)); 1165 anchor[0] += space + 1; 1166 return result; 1167 }; 1168 } 1169 1170 private static CompletionProvider reloadCompletion() { 1171 return (code, cursor, anchor) -> { 1172 CompletionProvider provider; 1173 int pastSpace = code.indexOf(' ') + 1; // zero if no space 1174 if (pastSpace == 0) { 1175 provider = RELOAD_OPTIONS_COMPLETION_PROVIDER; 1176 } else { 1177 switch (code.substring(0, pastSpace - 1)) { 1178 case "-quiet": 1179 provider = RESTORE_COMPLETION_PROVIDER; 1180 break; 1181 case "-restore": 1182 provider = QUIET_COMPLETION_PROVIDER; 1183 break; 1184 default: 1185 provider = EMPTY_COMPLETION_PROVIDER; 1186 break; 1187 } 1188 } 1189 List<Suggestion> result = provider.completionSuggestions( 1190 code.substring(pastSpace), cursor - pastSpace, anchor); 1191 anchor[0] += pastSpace; 1192 return result; 1193 }; 1194 } 1195 1196 private static CompletionProvider orMostSpecificCompletion( 1197 CompletionProvider left, CompletionProvider right) { 1198 return (code, cursor, anchor) -> { 1199 int[] leftAnchor = {-1}; 1200 int[] rightAnchor = {-1}; 1201 1202 List<Suggestion> leftSuggestions = left.completionSuggestions(code, cursor, leftAnchor); 1203 List<Suggestion> rightSuggestions = right.completionSuggestions(code, cursor, rightAnchor); 1204 1205 List<Suggestion> suggestions = new ArrayList<>(); 1206 1207 if (leftAnchor[0] >= rightAnchor[0]) { 1208 anchor[0] = leftAnchor[0]; 1209 suggestions.addAll(leftSuggestions); 1210 } 1211 1212 if (leftAnchor[0] <= rightAnchor[0]) { 1213 anchor[0] = rightAnchor[0]; 1214 suggestions.addAll(rightSuggestions); 1215 } 1216 1217 return suggestions; 1218 }; 1219 } 1220 1221 // Snippet lists 1222 1223 Stream<Snippet> allSnippets() { 1224 return state.snippets(); 1225 } 1226 1227 Stream<Snippet> dropableSnippets() { 1228 return state.snippets() 1229 .filter(sn -> state.status(sn).isActive()); 1230 } 1231 1232 Stream<VarSnippet> allVarSnippets() { 1233 return state.snippets() 1234 .filter(sn -> sn.kind() == Snippet.Kind.VAR) 1235 .map(sn -> (VarSnippet) sn); 1236 } 1237 1238 Stream<MethodSnippet> allMethodSnippets() { 1239 return state.snippets() 1240 .filter(sn -> sn.kind() == Snippet.Kind.METHOD) 1241 .map(sn -> (MethodSnippet) sn); 1242 } 1243 1244 Stream<TypeDeclSnippet> allTypeSnippets() { 1245 return state.snippets() 1246 .filter(sn -> sn.kind() == Snippet.Kind.TYPE_DECL) 1247 .map(sn -> (TypeDeclSnippet) sn); 1248 } 1249 1250 // Table of commands -- with command forms, argument kinds, helpKey message, implementation, ... 1251 1252 { 1253 registerCommand(new Command("/list", 1254 this::cmdList, 1255 snippetWithOptionCompletion(SNIPPET_HISTORY_OPTION_COMPLETION_PROVIDER, 1256 this::allSnippets))); 1257 registerCommand(new Command("/edit", 1258 this::cmdEdit, 1259 snippetWithOptionCompletion(SNIPPET_OPTION_COMPLETION_PROVIDER, 1260 this::allSnippets))); 1261 registerCommand(new Command("/drop", 1262 this::cmdDrop, 1263 snippetCompletion(this::dropableSnippets), 1264 CommandKind.REPLAY)); 1265 registerCommand(new Command("/save", 1266 this::cmdSave, 1267 saveCompletion())); 1268 registerCommand(new Command("/open", 1269 this::cmdOpen, 1270 FILE_COMPLETION_PROVIDER)); 1271 registerCommand(new Command("/vars", 1272 this::cmdVars, 1273 snippetWithOptionCompletion(SNIPPET_OPTION_COMPLETION_PROVIDER, 1274 this::allVarSnippets))); 1275 registerCommand(new Command("/methods", 1276 this::cmdMethods, 1277 snippetWithOptionCompletion(SNIPPET_OPTION_COMPLETION_PROVIDER, 1278 this::allMethodSnippets))); 1279 registerCommand(new Command("/types", 1280 this::cmdTypes, 1281 snippetWithOptionCompletion(SNIPPET_OPTION_COMPLETION_PROVIDER, 1282 this::allTypeSnippets))); 1283 registerCommand(new Command("/imports", 1284 arg -> cmdImports(), 1285 EMPTY_COMPLETION_PROVIDER)); 1286 registerCommand(new Command("/exit", 1287 arg -> cmdExit(), 1288 EMPTY_COMPLETION_PROVIDER)); 1289 registerCommand(new Command("/reset", 1290 arg -> cmdReset(), 1291 EMPTY_COMPLETION_PROVIDER)); 1292 registerCommand(new Command("/reload", 1293 this::cmdReload, 1294 reloadCompletion())); 1295 registerCommand(new Command("/classpath", 1296 this::cmdClasspath, 1297 classPathCompletion(), 1298 CommandKind.REPLAY)); 1299 registerCommand(new Command("/history", 1300 arg -> cmdHistory(), 1301 EMPTY_COMPLETION_PROVIDER)); 1302 registerCommand(new Command("/debug", 1303 this::cmdDebug, 1304 EMPTY_COMPLETION_PROVIDER, 1305 CommandKind.HIDDEN)); 1306 registerCommand(new Command("/help", 1307 this::cmdHelp, 1308 helpCompletion())); 1309 registerCommand(new Command("/set", 1310 this::cmdSet, 1311 new ContinuousCompletionProvider(Map.of( 1312 // need more completion for format for usability 1313 "format", feedback.modeCompletions(), 1314 "truncation", feedback.modeCompletions(), 1315 "feedback", feedback.modeCompletions(), 1316 "mode", skipWordThenCompletion(orMostSpecificCompletion( 1317 feedback.modeCompletions(SET_MODE_OPTIONS_COMPLETION_PROVIDER), 1318 SET_MODE_OPTIONS_COMPLETION_PROVIDER)), 1319 "prompt", feedback.modeCompletions(), 1320 "editor", fileCompletions(Files::isExecutable), 1321 "start", FILE_COMPLETION_PROVIDER), 1322 STARTSWITH_MATCHER))); 1323 registerCommand(new Command("/?", 1324 "help.quest", 1325 this::cmdHelp, 1326 helpCompletion(), 1327 CommandKind.NORMAL)); 1328 registerCommand(new Command("/!", 1329 "help.bang", 1330 arg -> cmdUseHistoryEntry(-1), 1331 EMPTY_COMPLETION_PROVIDER, 1332 CommandKind.NORMAL)); 1333 1334 // Documentation pseudo-commands 1335 registerCommand(new Command("/<id>", 1336 "help.id", 1337 CommandKind.HELP_ONLY)); 1338 registerCommand(new Command("/-<n>", 1339 "help.previous", 1340 CommandKind.HELP_ONLY)); 1341 registerCommand(new Command("intro", 1342 "help.intro", 1343 CommandKind.HELP_SUBJECT)); 1344 registerCommand(new Command("shortcuts", 1345 "help.shortcuts", 1346 CommandKind.HELP_SUBJECT)); 1347 1348 commandCompletions = new ContinuousCompletionProvider( 1349 commands.values().stream() 1350 .filter(c -> c.kind.shouldSuggestCompletions) 1351 .collect(toMap(c -> c.command, c -> c.completions)), 1352 STARTSWITH_MATCHER); 1353 } 1354 1355 private ContinuousCompletionProvider commandCompletions; 1356 1357 public List<Suggestion> commandCompletionSuggestions(String code, int cursor, int[] anchor) { 1358 return commandCompletions.completionSuggestions(code, cursor, anchor); 1359 } 1360 1361 public String commandDocumentation(String code, int cursor, boolean shortDescription) { 1362 code = code.substring(0, cursor); 1363 int space = code.indexOf(' '); 1364 1365 if (space != (-1)) { 1366 String cmd = code.substring(0, space); 1367 Command command = commands.get(cmd); 1368 if (command != null) { 1369 return getResourceString(command.helpKey + (shortDescription ? ".summary" : "")); 1370 } 1371 } 1372 1373 return null; 1374 } 1375 1376 // --- Command implementations --- 1377 1378 private static final String[] SET_SUBCOMMANDS = new String[]{ 1379 "format", "truncation", "feedback", "mode", "prompt", "editor", "start"}; 1380 1381 final boolean cmdSet(String arg) { 1382 String cmd = "/set"; 1383 ArgTokenizer at = new ArgTokenizer(cmd, arg.trim()); 1384 String which = subCommand(cmd, at, SET_SUBCOMMANDS); 1385 if (which == null) { 1386 return false; 1387 } 1388 switch (which) { 1389 case "_retain": { 1390 errormsg("jshell.err.setting.to.retain.must.be.specified", at.whole()); 1391 return false; 1392 } 1393 case "_blank": { 1394 // show top-level settings 1395 new SetEditor().set(); 1396 showSetStart(); 1397 setFeedback(this, at); // no args so shows feedback setting 1398 hardmsg("jshell.msg.set.show.mode.settings"); 1399 return true; 1400 } 1401 case "format": 1402 return feedback.setFormat(this, at); 1403 case "truncation": 1404 return feedback.setTruncation(this, at); 1405 case "feedback": 1406 return setFeedback(this, at); 1407 case "mode": 1408 return feedback.setMode(this, at, 1409 retained -> prefs.put(MODE_KEY, retained)); 1410 case "prompt": 1411 return feedback.setPrompt(this, at); 1412 case "editor": 1413 return new SetEditor(at).set(); 1414 case "start": 1415 return setStart(at); 1416 default: 1417 errormsg("jshell.err.arg", cmd, at.val()); 1418 return false; 1419 } 1420 } 1421 1422 boolean setFeedback(MessageHandler messageHandler, ArgTokenizer at) { 1423 return feedback.setFeedback(messageHandler, at, 1424 fb -> prefs.put(FEEDBACK_KEY, fb)); 1425 } 1426 1427 // Find which, if any, sub-command matches. 1428 // Return null on error 1429 String subCommand(String cmd, ArgTokenizer at, String[] subs) { 1430 at.allowedOptions("-retain"); 1431 String sub = at.next(); 1432 if (sub == null) { 1433 // No sub-command was given 1434 return at.hasOption("-retain") 1435 ? "_retain" 1436 : "_blank"; 1437 } 1438 String[] matches = Arrays.stream(subs) 1439 .filter(s -> s.startsWith(sub)) 1440 .toArray(String[]::new); 1441 if (matches.length == 0) { 1442 // There are no matching sub-commands 1443 errormsg("jshell.err.arg", cmd, sub); 1444 fluffmsg("jshell.msg.use.one.of", Arrays.stream(subs) 1445 .collect(Collectors.joining(", ")) 1446 ); 1447 return null; 1448 } 1449 if (matches.length > 1) { 1450 // More than one sub-command matches the initial characters provided 1451 errormsg("jshell.err.sub.ambiguous", cmd, sub); 1452 fluffmsg("jshell.msg.use.one.of", Arrays.stream(matches) 1453 .collect(Collectors.joining(", ")) 1454 ); 1455 return null; 1456 } 1457 return matches[0]; 1458 } 1459 1460 static class EditorSetting { 1461 1462 static String BUILT_IN_REP = "-default"; 1463 static char WAIT_PREFIX = '-'; 1464 static char NORMAL_PREFIX = '*'; 1465 1466 final String[] cmd; 1467 final boolean wait; 1468 1469 EditorSetting(String[] cmd, boolean wait) { 1470 this.wait = wait; 1471 this.cmd = cmd; 1472 } 1473 1474 // returns null if not stored in preferences 1475 static EditorSetting fromPrefs(PersistentStorage prefs) { 1476 // Read retained editor setting (if any) 1477 String editorString = prefs.get(EDITOR_KEY); 1478 if (editorString == null || editorString.isEmpty()) { 1479 return null; 1480 } else if (editorString.equals(BUILT_IN_REP)) { 1481 return BUILT_IN_EDITOR; 1482 } else { 1483 boolean wait = false; 1484 char waitMarker = editorString.charAt(0); 1485 if (waitMarker == WAIT_PREFIX || waitMarker == NORMAL_PREFIX) { 1486 wait = waitMarker == WAIT_PREFIX; 1487 editorString = editorString.substring(1); 1488 } 1489 String[] cmd = editorString.split(RECORD_SEPARATOR); 1490 return new EditorSetting(cmd, wait); 1491 } 1492 } 1493 1494 static void removePrefs(PersistentStorage prefs) { 1495 prefs.remove(EDITOR_KEY); 1496 } 1497 1498 void toPrefs(PersistentStorage prefs) { 1499 prefs.put(EDITOR_KEY, (this == BUILT_IN_EDITOR) 1500 ? BUILT_IN_REP 1501 : (wait ? WAIT_PREFIX : NORMAL_PREFIX) + String.join(RECORD_SEPARATOR, cmd)); 1502 } 1503 1504 @Override 1505 public boolean equals(Object o) { 1506 if (o instanceof EditorSetting) { 1507 EditorSetting ed = (EditorSetting) o; 1508 return Arrays.equals(cmd, ed.cmd) && wait == ed.wait; 1509 } else { 1510 return false; 1511 } 1512 } 1513 1514 @Override 1515 public int hashCode() { 1516 int hash = 7; 1517 hash = 71 * hash + Arrays.deepHashCode(this.cmd); 1518 hash = 71 * hash + (this.wait ? 1 : 0); 1519 return hash; 1520 } 1521 } 1522 1523 class SetEditor { 1524 1525 private final ArgTokenizer at; 1526 private final String[] command; 1527 private final boolean hasCommand; 1528 private final boolean defaultOption; 1529 private final boolean deleteOption; 1530 private final boolean waitOption; 1531 private final boolean retainOption; 1532 private final int primaryOptionCount; 1533 1534 SetEditor(ArgTokenizer at) { 1535 at.allowedOptions("-default", "-wait", "-retain", "-delete"); 1536 String prog = at.next(); 1537 List<String> ed = new ArrayList<>(); 1538 while (at.val() != null) { 1539 ed.add(at.val()); 1540 at.nextToken(); // so that options are not interpreted as jshell options 1541 } 1542 this.at = at; 1543 this.command = ed.toArray(new String[ed.size()]); 1544 this.hasCommand = command.length > 0; 1545 this.defaultOption = at.hasOption("-default"); 1546 this.deleteOption = at.hasOption("-delete"); 1547 this.waitOption = at.hasOption("-wait"); 1548 this.retainOption = at.hasOption("-retain"); 1549 this.primaryOptionCount = (hasCommand? 1 : 0) + (defaultOption? 1 : 0) + (deleteOption? 1 : 0); 1550 } 1551 1552 SetEditor() { 1553 this(new ArgTokenizer("", "")); 1554 } 1555 1556 boolean set() { 1557 if (!check()) { 1558 return false; 1559 } 1560 if (primaryOptionCount == 0 && !retainOption) { 1561 // No settings or -retain, so this is a query 1562 EditorSetting retained = EditorSetting.fromPrefs(prefs); 1563 if (retained != null) { 1564 // retained editor is set 1565 hard("/set editor -retain %s", format(retained)); 1566 } 1567 if (retained == null || !retained.equals(editor)) { 1568 // editor is not retained or retained is different from set 1569 hard("/set editor %s", format(editor)); 1570 } 1571 return true; 1572 } 1573 if (retainOption && deleteOption) { 1574 EditorSetting.removePrefs(prefs); 1575 } 1576 install(); 1577 if (retainOption && !deleteOption) { 1578 editor.toPrefs(prefs); 1579 fluffmsg("jshell.msg.set.editor.retain", format(editor)); 1580 } 1581 return true; 1582 } 1583 1584 private boolean check() { 1585 if (!checkOptionsAndRemainingInput(at)) { 1586 return false; 1587 } 1588 if (primaryOptionCount > 1) { 1589 errormsg("jshell.err.default.option.or.program", at.whole()); 1590 return false; 1591 } 1592 if (waitOption && !hasCommand) { 1593 errormsg("jshell.err.wait.applies.to.external.editor", at.whole()); 1594 return false; 1595 } 1596 return true; 1597 } 1598 1599 private void install() { 1600 if (hasCommand) { 1601 editor = new EditorSetting(command, waitOption); 1602 } else if (defaultOption) { 1603 editor = BUILT_IN_EDITOR; 1604 } else if (deleteOption) { 1605 configEditor(); 1606 } else { 1607 return; 1608 } 1609 fluffmsg("jshell.msg.set.editor.set", format(editor)); 1610 } 1611 1612 private String format(EditorSetting ed) { 1613 if (ed == BUILT_IN_EDITOR) { 1614 return "-default"; 1615 } else { 1616 Stream<String> elems = Arrays.stream(ed.cmd); 1617 if (ed.wait) { 1618 elems = Stream.concat(Stream.of("-wait"), elems); 1619 } 1620 return elems.collect(joining(" ")); 1621 } 1622 } 1623 } 1624 1625 // The sub-command: /set start <start-file> 1626 boolean setStart(ArgTokenizer at) { 1627 at.allowedOptions("-default", "-none", "-retain"); 1628 List<String> fns = new ArrayList<>(); 1629 while (at.next() != null) { 1630 fns.add(at.val()); 1631 } 1632 if (!checkOptionsAndRemainingInput(at)) { 1633 return false; 1634 } 1635 boolean defaultOption = at.hasOption("-default"); 1636 boolean noneOption = at.hasOption("-none"); 1637 boolean retainOption = at.hasOption("-retain"); 1638 boolean hasFile = !fns.isEmpty(); 1639 1640 int argCount = (defaultOption ? 1 : 0) + (noneOption ? 1 : 0) + (hasFile ? 1 : 0); 1641 if (argCount > 1) { 1642 errormsg("jshell.err.option.or.filename", at.whole()); 1643 return false; 1644 } 1645 if (argCount == 0 && !retainOption) { 1646 // no options or filename, show current setting 1647 showSetStart(); 1648 return true; 1649 } 1650 if (hasFile) { 1651 StringBuilder sb = new StringBuilder(); 1652 for (String fn : fns) { 1653 String s = readFile(fn, "/set start"); 1654 if (s == null) { 1655 return false; 1656 } 1657 sb.append(s); 1658 } 1659 startup = sb.toString(); 1660 } else if (defaultOption) { 1661 startup = defaultStartup(); 1662 } else if (noneOption) { 1663 startup = ""; 1664 } 1665 if (retainOption) { 1666 // retain startup setting 1667 prefs.put(STARTUP_KEY, startup); 1668 } 1669 return true; 1670 } 1671 1672 void showSetStart() { 1673 String retained = prefs.get(STARTUP_KEY); 1674 if (retained != null) { 1675 showSetStart(true, retained); 1676 } 1677 if (retained == null || !startup.equals(retained)) { 1678 showSetStart(false, startup); 1679 } 1680 } 1681 1682 void showSetStart(boolean isRetained, String start) { 1683 String cmd = "/set start" + (isRetained ? " -retain " : " "); 1684 String stset; 1685 if (start.equals(defaultStartup())) { 1686 stset = cmd + "-default"; 1687 } else if (start.isEmpty()) { 1688 stset = cmd + "-none"; 1689 } else { 1690 stset = "startup.jsh:\n" + start + "\n" + cmd + "startup.jsh"; 1691 } 1692 hard(stset); 1693 } 1694 1695 boolean cmdClasspath(String arg) { 1696 if (arg.isEmpty()) { 1697 errormsg("jshell.err.classpath.arg"); 1698 return false; 1699 } else { 1700 state.addToClasspath(toPathResolvingUserHome(arg).toString()); 1701 fluffmsg("jshell.msg.classpath", arg); 1702 return true; 1703 } 1704 } 1705 1706 boolean cmdDebug(String arg) { 1707 if (arg.isEmpty()) { 1708 debug = !debug; 1709 InternalDebugControl.setDebugFlags(state, debug ? DBG_GEN : 0); 1710 fluff("Debugging %s", debug ? "on" : "off"); 1711 } else { 1712 int flags = 0; 1713 for (char ch : arg.toCharArray()) { 1714 switch (ch) { 1715 case '0': 1716 flags = 0; 1717 debug = false; 1718 fluff("Debugging off"); 1719 break; 1720 case 'r': 1721 debug = true; 1722 fluff("REPL tool debugging on"); 1723 break; 1724 case 'g': 1725 flags |= DBG_GEN; 1726 fluff("General debugging on"); 1727 break; 1728 case 'f': 1729 flags |= DBG_FMGR; 1730 fluff("File manager debugging on"); 1731 break; 1732 case 'c': 1733 flags |= DBG_COMPA; 1734 fluff("Completion analysis debugging on"); 1735 break; 1736 case 'd': 1737 flags |= DBG_DEP; 1738 fluff("Dependency debugging on"); 1739 break; 1740 case 'e': 1741 flags |= DBG_EVNT; 1742 fluff("Event debugging on"); 1743 break; 1744 default: 1745 hard("Unknown debugging option: %c", ch); 1746 fluff("Use: 0 r g f c d"); 1747 return false; 1748 } 1749 } 1750 InternalDebugControl.setDebugFlags(state, flags); 1751 } 1752 return true; 1753 } 1754 1755 private boolean cmdExit() { 1756 regenerateOnDeath = false; 1757 live = false; 1758 if (!replayableHistory.isEmpty()) { 1759 // Prevent history overflow by calculating what will fit, starting 1760 // with most recent 1761 int sepLen = RECORD_SEPARATOR.length(); 1762 int length = 0; 1763 int first = replayableHistory.size(); 1764 while(length < Preferences.MAX_VALUE_LENGTH && --first >= 0) { 1765 length += replayableHistory.get(first).length() + sepLen; 1766 } 1767 String hist = String.join(RECORD_SEPARATOR, 1768 replayableHistory.subList(first + 1, replayableHistory.size())); 1769 prefs.put(REPLAY_RESTORE_KEY, hist); 1770 } 1771 prefs.flush(); 1772 fluffmsg("jshell.msg.goodbye"); 1773 return true; 1774 } 1775 1776 boolean cmdHelp(String arg) { 1777 ArgTokenizer at = new ArgTokenizer("/help", arg); 1778 String subject = at.next(); 1779 if (subject != null) { 1780 Command[] matches = commands.values().stream() 1781 .filter(c -> c.command.startsWith(subject)) 1782 .toArray(Command[]::new); 1783 if (matches.length == 1) { 1784 String cmd = matches[0].command; 1785 if (cmd.equals("/set")) { 1786 // Print the help doc for the specified sub-command 1787 String which = subCommand(cmd, at, SET_SUBCOMMANDS); 1788 if (which == null) { 1789 return false; 1790 } 1791 if (!which.equals("_blank")) { 1792 hardrb("help.set." + which); 1793 return true; 1794 } 1795 } 1796 } 1797 if (matches.length > 0) { 1798 for (Command c : matches) { 1799 hard(""); 1800 hard("%s", c.command); 1801 hard(""); 1802 hardrb(c.helpKey); 1803 } 1804 return true; 1805 } else { 1806 errormsg("jshell.err.help.arg", arg); 1807 } 1808 } 1809 hardmsg("jshell.msg.help.begin"); 1810 hardPairs(commands.values().stream() 1811 .filter(cmd -> cmd.kind.showInHelp), 1812 cmd -> cmd.command + " " + getResourceString(cmd.helpKey + ".args"), 1813 cmd -> getResourceString(cmd.helpKey + ".summary") 1814 ); 1815 hardmsg("jshell.msg.help.subject"); 1816 hardPairs(commands.values().stream() 1817 .filter(cmd -> cmd.kind == CommandKind.HELP_SUBJECT), 1818 cmd -> cmd.command, 1819 cmd -> getResourceString(cmd.helpKey + ".summary") 1820 ); 1821 return true; 1822 } 1823 1824 private boolean cmdHistory() { 1825 cmdout.println(); 1826 for (String s : input.currentSessionHistory()) { 1827 // No number prefix, confusing with snippet ids 1828 cmdout.printf("%s\n", s); 1829 } 1830 return true; 1831 } 1832 1833 /** 1834 * Avoid parameterized varargs possible heap pollution warning. 1835 */ 1836 private interface SnippetPredicate<T extends Snippet> extends Predicate<T> { } 1837 1838 /** 1839 * Apply filters to a stream until one that is non-empty is found. 1840 * Adapted from Stuart Marks 1841 * 1842 * @param supplier Supply the Snippet stream to filter 1843 * @param filters Filters to attempt 1844 * @return The non-empty filtered Stream, or null 1845 */ 1846 @SafeVarargs 1847 private static <T extends Snippet> Stream<T> nonEmptyStream(Supplier<Stream<T>> supplier, 1848 SnippetPredicate<T>... filters) { 1849 for (SnippetPredicate<T> filt : filters) { 1850 Iterator<T> iterator = supplier.get().filter(filt).iterator(); 1851 if (iterator.hasNext()) { 1852 return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false); 1853 } 1854 } 1855 return null; 1856 } 1857 1858 private boolean inStartUp(Snippet sn) { 1859 return mapSnippet.get(sn).space == startNamespace; 1860 } 1861 1862 private boolean isActive(Snippet sn) { 1863 return state.status(sn).isActive(); 1864 } 1865 1866 private boolean mainActive(Snippet sn) { 1867 return !inStartUp(sn) && isActive(sn); 1868 } 1869 1870 private boolean matchingDeclaration(Snippet sn, String name) { 1871 return sn instanceof DeclarationSnippet 1872 && ((DeclarationSnippet) sn).name().equals(name); 1873 } 1874 1875 /** 1876 * Convert user arguments to a Stream of snippets referenced by those 1877 * arguments (or lack of arguments). 1878 * 1879 * @param snippets the base list of possible snippets 1880 * @param defFilter the filter to apply to the arguments if no argument 1881 * @param rawargs the user's argument to the command, maybe be the empty 1882 * string 1883 * @return a Stream of referenced snippets or null if no matches are found 1884 */ 1885 private <T extends Snippet> Stream<T> argsOptionsToSnippets(Supplier<Stream<T>> snippetSupplier, 1886 Predicate<Snippet> defFilter, String rawargs, String cmd) { 1887 ArgTokenizer at = new ArgTokenizer(cmd, rawargs.trim()); 1888 at.allowedOptions("-all", "-start"); 1889 List<String> args = new ArrayList<>(); 1890 String s; 1891 while ((s = at.next()) != null) { 1892 args.add(s); 1893 } 1894 if (!checkOptionsAndRemainingInput(at)) { 1895 return null; 1896 } 1897 if (at.optionCount() > 0 && args.size() > 0) { 1898 errormsg("jshell.err.may.not.specify.options.and.snippets", at.whole()); 1899 return null; 1900 } 1901 if (at.optionCount() > 1) { 1902 errormsg("jshell.err.conflicting.options", at.whole()); 1903 return null; 1904 } 1905 if (at.hasOption("-all")) { 1906 // all snippets including start-up, failed, and overwritten 1907 return snippetSupplier.get(); 1908 } 1909 if (at.hasOption("-start")) { 1910 // start-up snippets 1911 return snippetSupplier.get() 1912 .filter(this::inStartUp); 1913 } 1914 if (args.isEmpty()) { 1915 // Default is all active user snippets 1916 return snippetSupplier.get() 1917 .filter(defFilter); 1918 } 1919 return argsToSnippets(snippetSupplier, args); 1920 } 1921 1922 /** 1923 * Convert user arguments to a Stream of snippets referenced by those 1924 * arguments. 1925 * 1926 * @param snippetSupplier the base list of possible snippets 1927 * @param args the user's argument to the command, maybe be the empty list 1928 * @return a Stream of referenced snippets or null if no matches to specific 1929 * arg 1930 */ 1931 private <T extends Snippet> Stream<T> argsToSnippets(Supplier<Stream<T>> snippetSupplier, 1932 List<String> args) { 1933 Stream<T> result = null; 1934 for (String arg : args) { 1935 // Find the best match 1936 Stream<T> st = layeredSnippetSearch(snippetSupplier, arg); 1937 if (st == null) { 1938 Stream<Snippet> est = layeredSnippetSearch(state::snippets, arg); 1939 if (est == null) { 1940 errormsg("jshell.err.no.such.snippets", arg); 1941 } else { 1942 errormsg("jshell.err.the.snippet.cannot.be.used.with.this.command", 1943 arg, est.findFirst().get().source()); 1944 } 1945 return null; 1946 } 1947 if (result == null) { 1948 result = st; 1949 } else { 1950 result = Stream.concat(result, st); 1951 } 1952 } 1953 return result; 1954 } 1955 1956 private <T extends Snippet> Stream<T> layeredSnippetSearch(Supplier<Stream<T>> snippetSupplier, String arg) { 1957 return nonEmptyStream( 1958 // the stream supplier 1959 snippetSupplier, 1960 // look for active user declarations matching the name 1961 sn -> isActive(sn) && matchingDeclaration(sn, arg), 1962 // else, look for any declarations matching the name 1963 sn -> matchingDeclaration(sn, arg), 1964 // else, look for an id of this name 1965 sn -> sn.id().equals(arg) 1966 ); 1967 } 1968 1969 private boolean cmdDrop(String rawargs) { 1970 ArgTokenizer at = new ArgTokenizer("/drop", rawargs.trim()); 1971 at.allowedOptions(); 1972 List<String> args = new ArrayList<>(); 1973 String s; 1974 while ((s = at.next()) != null) { 1975 args.add(s); 1976 } 1977 if (!checkOptionsAndRemainingInput(at)) { 1978 return false; 1979 } 1980 if (args.isEmpty()) { 1981 errormsg("jshell.err.drop.arg"); 1982 return false; 1983 } 1984 Stream<Snippet> stream = argsToSnippets(this::dropableSnippets, args); 1985 if (stream == null) { 1986 // Snippet not found. Error already printed 1987 fluffmsg("jshell.msg.see.classes.etc"); 1988 return false; 1989 } 1990 List<Snippet> snippets = stream.collect(toList()); 1991 if (snippets.size() > args.size()) { 1992 // One of the args references more thean one snippet 1993 errormsg("jshell.err.drop.ambiguous"); 1994 fluffmsg("jshell.msg.use.one.of", snippets.stream() 1995 .map(sn -> String.format("\n/drop %-5s : %s", sn.id(), sn.source().replace("\n", "\n "))) 1996 .collect(Collectors.joining(", ")) 1997 ); 1998 return false; 1999 } 2000 snippets.stream() 2001 .forEach(sn -> state.drop(sn).forEach(this::handleEvent)); 2002 return true; 2003 } 2004 2005 private boolean cmdEdit(String arg) { 2006 Stream<Snippet> stream = argsOptionsToSnippets(state::snippets, 2007 this::mainActive, arg, "/edit"); 2008 if (stream == null) { 2009 return false; 2010 } 2011 Set<String> srcSet = new LinkedHashSet<>(); 2012 stream.forEachOrdered(sn -> { 2013 String src = sn.source(); 2014 switch (sn.subKind()) { 2015 case VAR_VALUE_SUBKIND: 2016 break; 2017 case ASSIGNMENT_SUBKIND: 2018 case OTHER_EXPRESSION_SUBKIND: 2019 case TEMP_VAR_EXPRESSION_SUBKIND: 2020 case STATEMENT_SUBKIND: 2021 case UNKNOWN_SUBKIND: 2022 if (!src.endsWith(";")) { 2023 src = src + ";"; 2024 } 2025 srcSet.add(src); 2026 break; 2027 default: 2028 srcSet.add(src); 2029 break; 2030 } 2031 }); 2032 StringBuilder sb = new StringBuilder(); 2033 for (String s : srcSet) { 2034 sb.append(s); 2035 sb.append('\n'); 2036 } 2037 String src = sb.toString(); 2038 Consumer<String> saveHandler = new SaveHandler(src, srcSet); 2039 Consumer<String> errorHandler = s -> hard("Edit Error: %s", s); 2040 if (editor == BUILT_IN_EDITOR) { 2041 return builtInEdit(src, saveHandler, errorHandler); 2042 } else { 2043 // Changes have occurred in temp edit directory, 2044 // transfer the new sources to JShell (unless the editor is 2045 // running directly in JShell's window -- don't make a mess) 2046 String[] buffer = new String[1]; 2047 Consumer<String> extSaveHandler = s -> { 2048 if (input.terminalEditorRunning()) { 2049 buffer[0] = s; 2050 } else { 2051 saveHandler.accept(s); 2052 } 2053 }; 2054 ExternalEditor.edit(editor.cmd, src, 2055 errorHandler, extSaveHandler, 2056 () -> input.suspend(), 2057 () -> input.resume(), 2058 editor.wait, 2059 () -> hardrb("jshell.msg.press.return.to.leave.edit.mode")); 2060 if (buffer[0] != null) { 2061 saveHandler.accept(buffer[0]); 2062 } 2063 } 2064 return true; 2065 } 2066 //where 2067 // start the built-in editor 2068 private boolean builtInEdit(String initialText, 2069 Consumer<String> saveHandler, Consumer<String> errorHandler) { 2070 try { 2071 ServiceLoader<BuildInEditorProvider> sl 2072 = ServiceLoader.load(BuildInEditorProvider.class); 2073 // Find the highest ranking provider 2074 BuildInEditorProvider provider = null; 2075 for (BuildInEditorProvider p : sl) { 2076 if (provider == null || p.rank() > provider.rank()) { 2077 provider = p; 2078 } 2079 } 2080 if (provider != null) { 2081 provider.edit(getResourceString("jshell.label.editpad"), 2082 initialText, saveHandler, errorHandler); 2083 return true; 2084 } else { 2085 errormsg("jshell.err.no.builtin.editor"); 2086 } 2087 } catch (RuntimeException ex) { 2088 errormsg("jshell.err.cant.launch.editor", ex); 2089 } 2090 fluffmsg("jshell.msg.try.set.editor"); 2091 return false; 2092 } 2093 //where 2094 // receives editor requests to save 2095 private class SaveHandler implements Consumer<String> { 2096 2097 String src; 2098 Set<String> currSrcs; 2099 2100 SaveHandler(String src, Set<String> ss) { 2101 this.src = src; 2102 this.currSrcs = ss; 2103 } 2104 2105 @Override 2106 public void accept(String s) { 2107 if (!s.equals(src)) { // quick check first 2108 src = s; 2109 try { 2110 Set<String> nextSrcs = new LinkedHashSet<>(); 2111 boolean failed = false; 2112 while (true) { 2113 CompletionInfo an = analysis.analyzeCompletion(s); 2114 if (!an.completeness().isComplete()) { 2115 break; 2116 } 2117 String tsrc = trimNewlines(an.source()); 2118 if (!failed && !currSrcs.contains(tsrc)) { 2119 failed = processCompleteSource(tsrc); 2120 } 2121 nextSrcs.add(tsrc); 2122 if (an.remaining().isEmpty()) { 2123 break; 2124 } 2125 s = an.remaining(); 2126 } 2127 currSrcs = nextSrcs; 2128 } catch (IllegalStateException ex) { 2129 hardmsg("jshell.msg.resetting"); 2130 resetState(); 2131 currSrcs = new LinkedHashSet<>(); // re-process everything 2132 } 2133 } 2134 } 2135 2136 private String trimNewlines(String s) { 2137 int b = 0; 2138 while (b < s.length() && s.charAt(b) == '\n') { 2139 ++b; 2140 } 2141 int e = s.length() -1; 2142 while (e >= 0 && s.charAt(e) == '\n') { 2143 --e; 2144 } 2145 return s.substring(b, e + 1); 2146 } 2147 } 2148 2149 private boolean cmdList(String arg) { 2150 if (arg.length() >= 2 && "-history".startsWith(arg)) { 2151 return cmdHistory(); 2152 } 2153 Stream<Snippet> stream = argsOptionsToSnippets(state::snippets, 2154 this::mainActive, arg, "/list"); 2155 if (stream == null) { 2156 return false; 2157 } 2158 2159 // prevent double newline on empty list 2160 boolean[] hasOutput = new boolean[1]; 2161 stream.forEachOrdered(sn -> { 2162 if (!hasOutput[0]) { 2163 cmdout.println(); 2164 hasOutput[0] = true; 2165 } 2166 cmdout.printf("%4s : %s\n", sn.id(), sn.source().replace("\n", "\n ")); 2167 }); 2168 return true; 2169 } 2170 2171 private boolean cmdOpen(String filename) { 2172 return runFile(filename, "/open"); 2173 } 2174 2175 private boolean runFile(String filename, String context) { 2176 if (!filename.isEmpty()) { 2177 try { 2178 Path path = toPathResolvingUserHome(filename); 2179 Reader reader; 2180 String resource; 2181 if (!Files.exists(path) && (resource = getResource(filename)) != null) { 2182 // Not found as file, but found as resource 2183 reader = new StringReader(resource); 2184 } else { 2185 reader = new FileReader(path.toString()); 2186 } 2187 run(new ScannerIOContext(reader)); 2188 return true; 2189 } catch (FileNotFoundException e) { 2190 errormsg("jshell.err.file.not.found", context, filename, e.getMessage()); 2191 } catch (Exception e) { 2192 errormsg("jshell.err.file.exception", context, filename, e); 2193 } 2194 } else { 2195 errormsg("jshell.err.file.filename", context); 2196 } 2197 return false; 2198 } 2199 2200 /** 2201 * Read an external file. Error messages accessed via keyPrefix 2202 * 2203 * @param filename file to access or null 2204 * @param context printable non-natural language context for errors 2205 * @return contents of file as string 2206 */ 2207 String readFile(String filename, String context) { 2208 if (filename != null) { 2209 try { 2210 byte[] encoded = Files.readAllBytes(toPathResolvingUserHome(filename)); 2211 return new String(encoded); 2212 } catch (AccessDeniedException e) { 2213 errormsg("jshell.err.file.not.accessible", context, filename, e.getMessage()); 2214 } catch (NoSuchFileException e) { 2215 String resource = getResource(filename); 2216 if (resource != null) { 2217 // Not found as file, but found as resource 2218 return resource; 2219 } 2220 errormsg("jshell.err.file.not.found", context, filename); 2221 } catch (Exception e) { 2222 errormsg("jshell.err.file.exception", context, filename, e); 2223 } 2224 } else { 2225 errormsg("jshell.err.file.filename", context); 2226 } 2227 return null; 2228 2229 } 2230 2231 // Read a built-in file from resources or null 2232 String getResource(String name) { 2233 if (BUILTIN_FILE_PATTERN.matcher(name).matches()) { 2234 try { 2235 return readResource(name); 2236 } catch (Throwable t) { 2237 // Fall-through to null 2238 } 2239 } 2240 return null; 2241 } 2242 2243 // Read a built-in file from resources 2244 String readResource(String name) throws IOException { 2245 // Attempt to find the file as a resource 2246 String spec = String.format(BUILTIN_FILE_PATH_FORMAT, name); 2247 URL url = new URL(spec); 2248 BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream())); 2249 return reader.lines().collect(Collectors.joining("\n")); 2250 } 2251 2252 // retrieve the default startup string 2253 String defaultStartup() { 2254 if (defaultStartup == null) { 2255 defaultStartup = ""; // failure case 2256 try { 2257 defaultStartup = readResource(DEFAULT_STARTUP_NAME); 2258 } catch (AccessDeniedException e) { 2259 errormsg("jshell.err.file.not.accessible", "jshell", DEFAULT_STARTUP_NAME, e.getMessage()); 2260 } catch (NoSuchFileException e) { 2261 errormsg("jshell.err.file.not.found", "jshell", DEFAULT_STARTUP_NAME); 2262 } catch (Exception e) { 2263 errormsg("jshell.err.file.exception", "jshell", DEFAULT_STARTUP_NAME, e); 2264 } 2265 } 2266 return defaultStartup; 2267 } 2268 2269 private boolean cmdReset() { 2270 live = false; 2271 fluffmsg("jshell.msg.resetting.state"); 2272 return true; 2273 } 2274 2275 private boolean cmdReload(String rawargs) { 2276 ArgTokenizer at = new ArgTokenizer("/reload", rawargs.trim()); 2277 at.allowedOptions("-restore", "-quiet"); 2278 if (!checkOptionsAndRemainingInput(at)) { 2279 return false; 2280 } 2281 Iterable<String> history; 2282 if (at.hasOption("-restore")) { 2283 if (replayableHistoryPrevious == null) { 2284 errormsg("jshell.err.reload.no.previous"); 2285 return false; 2286 } 2287 history = replayableHistoryPrevious; 2288 fluffmsg("jshell.err.reload.restarting.previous.state"); 2289 } else { 2290 history = replayableHistory; 2291 fluffmsg("jshell.err.reload.restarting.state"); 2292 } 2293 boolean echo = !at.hasOption("-quiet"); 2294 resetState(); 2295 run(new ReloadIOContext(history, 2296 echo ? cmdout : null)); 2297 return true; 2298 } 2299 2300 private boolean cmdSave(String rawargs) { 2301 ArgTokenizer at = new ArgTokenizer("/save", rawargs.trim()); 2302 at.allowedOptions("-all", "-start", "-history"); 2303 String filename = at.next(); 2304 if (filename == null) { 2305 errormsg("jshell.err.file.filename", "/save"); 2306 return false; 2307 } 2308 if (!checkOptionsAndRemainingInput(at)) { 2309 return false; 2310 } 2311 if (at.optionCount() > 1) { 2312 errormsg("jshell.err.conflicting.options", at.whole()); 2313 return false; 2314 } 2315 try (BufferedWriter writer = Files.newBufferedWriter(toPathResolvingUserHome(filename), 2316 Charset.defaultCharset(), 2317 CREATE, TRUNCATE_EXISTING, WRITE)) { 2318 if (at.hasOption("-history")) { 2319 for (String s : input.currentSessionHistory()) { 2320 writer.write(s); 2321 writer.write("\n"); 2322 } 2323 } else if (at.hasOption("-start")) { 2324 writer.append(startup); 2325 } else { 2326 String sources = (at.hasOption("-all") 2327 ? state.snippets() 2328 : state.snippets().filter(this::mainActive)) 2329 .map(Snippet::source) 2330 .collect(Collectors.joining("\n")); 2331 writer.write(sources); 2332 } 2333 } catch (FileNotFoundException e) { 2334 errormsg("jshell.err.file.not.found", "/save", filename, e.getMessage()); 2335 return false; 2336 } catch (Exception e) { 2337 errormsg("jshell.err.file.exception", "/save", filename, e); 2338 return false; 2339 } 2340 return true; 2341 } 2342 2343 private boolean cmdVars(String arg) { 2344 Stream<VarSnippet> stream = argsOptionsToSnippets(this::allVarSnippets, 2345 this::isActive, arg, "/vars"); 2346 if (stream == null) { 2347 return false; 2348 } 2349 stream.forEachOrdered(vk -> 2350 { 2351 String val = state.status(vk) == Status.VALID 2352 ? feedback.truncateVarValue(state.varValue(vk)) 2353 : getResourceString("jshell.msg.vars.not.active"); 2354 hard(" %s %s = %s", vk.typeName(), vk.name(), val); 2355 }); 2356 return true; 2357 } 2358 2359 private boolean cmdMethods(String arg) { 2360 Stream<MethodSnippet> stream = argsOptionsToSnippets(this::allMethodSnippets, 2361 this::isActive, arg, "/methods"); 2362 if (stream == null) { 2363 return false; 2364 } 2365 stream.forEachOrdered(mk 2366 -> hard(" %s %s", mk.name(), mk.signature()) 2367 ); 2368 return true; 2369 } 2370 2371 private boolean cmdTypes(String arg) { 2372 Stream<TypeDeclSnippet> stream = argsOptionsToSnippets(this::allTypeSnippets, 2373 this::isActive, arg, "/types"); 2374 if (stream == null) { 2375 return false; 2376 } 2377 stream.forEachOrdered(ck 2378 -> { 2379 String kind; 2380 switch (ck.subKind()) { 2381 case INTERFACE_SUBKIND: 2382 kind = "interface"; 2383 break; 2384 case CLASS_SUBKIND: 2385 kind = "class"; 2386 break; 2387 case ENUM_SUBKIND: 2388 kind = "enum"; 2389 break; 2390 case ANNOTATION_TYPE_SUBKIND: 2391 kind = "@interface"; 2392 break; 2393 default: 2394 assert false : "Wrong kind" + ck.subKind(); 2395 kind = "class"; 2396 break; 2397 } 2398 hard(" %s %s", kind, ck.name()); 2399 }); 2400 return true; 2401 } 2402 2403 private boolean cmdImports() { 2404 state.imports().forEach(ik -> { 2405 hard(" import %s%s", ik.isStatic() ? "static " : "", ik.fullname()); 2406 }); 2407 return true; 2408 } 2409 2410 private boolean cmdUseHistoryEntry(int index) { 2411 List<Snippet> keys = state.snippets().collect(toList()); 2412 if (index < 0) 2413 index += keys.size(); 2414 else 2415 index--; 2416 if (index >= 0 && index < keys.size()) { 2417 rerunSnippet(keys.get(index)); 2418 } else { 2419 errormsg("jshell.err.out.of.range"); 2420 return false; 2421 } 2422 return true; 2423 } 2424 2425 boolean checkOptionsAndRemainingInput(ArgTokenizer at) { 2426 String junk = at.remainder(); 2427 if (!junk.isEmpty()) { 2428 errormsg("jshell.err.unexpected.at.end", junk, at.whole()); 2429 return false; 2430 } else { 2431 String bad = at.badOptions(); 2432 if (!bad.isEmpty()) { 2433 errormsg("jshell.err.unknown.option", bad, at.whole()); 2434 return false; 2435 } 2436 } 2437 return true; 2438 } 2439 2440 private boolean rerunHistoryEntryById(String id) { 2441 Optional<Snippet> snippet = state.snippets() 2442 .filter(s -> s.id().equals(id)) 2443 .findFirst(); 2444 return snippet.map(s -> { 2445 rerunSnippet(s); 2446 return true; 2447 }).orElse(false); 2448 } 2449 2450 private void rerunSnippet(Snippet snippet) { 2451 String source = snippet.source(); 2452 cmdout.printf("%s\n", source); 2453 input.replaceLastHistoryEntry(source); 2454 processSourceCatchingReset(source); 2455 } 2456 2457 /** 2458 * Filter diagnostics for only errors (no warnings, ...) 2459 * @param diagnostics input list 2460 * @return filtered list 2461 */ 2462 List<Diag> errorsOnly(List<Diag> diagnostics) { 2463 return diagnostics.stream() 2464 .filter(Diag::isError) 2465 .collect(toList()); 2466 } 2467 2468 void displayDiagnostics(String source, Diag diag, List<String> toDisplay) { 2469 for (String line : diag.getMessage(null).split("\\r?\\n")) { // TODO: Internationalize 2470 if (!line.trim().startsWith("location:")) { 2471 toDisplay.add(line); 2472 } 2473 } 2474 2475 int pstart = (int) diag.getStartPosition(); 2476 int pend = (int) diag.getEndPosition(); 2477 Matcher m = LINEBREAK.matcher(source); 2478 int pstartl = 0; 2479 int pendl = -2; 2480 while (m.find(pstartl)) { 2481 pendl = m.start(); 2482 if (pendl >= pstart) { 2483 break; 2484 } else { 2485 pstartl = m.end(); 2486 } 2487 } 2488 if (pendl < pstart) { 2489 pendl = source.length(); 2490 } 2491 toDisplay.add(source.substring(pstartl, pendl)); 2492 2493 StringBuilder sb = new StringBuilder(); 2494 int start = pstart - pstartl; 2495 for (int i = 0; i < start; ++i) { 2496 sb.append(' '); 2497 } 2498 sb.append('^'); 2499 boolean multiline = pend > pendl; 2500 int end = (multiline ? pendl : pend) - pstartl - 1; 2501 if (end > start) { 2502 for (int i = start + 1; i < end; ++i) { 2503 sb.append('-'); 2504 } 2505 if (multiline) { 2506 sb.append("-..."); 2507 } else { 2508 sb.append('^'); 2509 } 2510 } 2511 toDisplay.add(sb.toString()); 2512 2513 debug("printDiagnostics start-pos = %d ==> %d -- wrap = %s", diag.getStartPosition(), start, this); 2514 debug("Code: %s", diag.getCode()); 2515 debug("Pos: %d (%d - %d)", diag.getPosition(), 2516 diag.getStartPosition(), diag.getEndPosition()); 2517 } 2518 2519 private String processSource(String srcInput) throws IllegalStateException { 2520 while (true) { 2521 CompletionInfo an = analysis.analyzeCompletion(srcInput); 2522 if (!an.completeness().isComplete()) { 2523 return an.remaining(); 2524 } 2525 boolean failed = processCompleteSource(an.source()); 2526 if (failed || an.remaining().isEmpty()) { 2527 return ""; 2528 } 2529 srcInput = an.remaining(); 2530 } 2531 } 2532 //where 2533 private boolean processCompleteSource(String source) throws IllegalStateException { 2534 debug("Compiling: %s", source); 2535 boolean failed = false; 2536 boolean isActive = false; 2537 List<SnippetEvent> events = state.eval(source); 2538 for (SnippetEvent e : events) { 2539 // Report the event, recording failure 2540 failed |= handleEvent(e); 2541 2542 // If any main snippet is active, this should be replayable 2543 // also ignore var value queries 2544 isActive |= e.causeSnippet() == null && 2545 e.status().isActive() && 2546 e.snippet().subKind() != VAR_VALUE_SUBKIND; 2547 } 2548 // If this is an active snippet and it didn't cause the backend to die, 2549 // add it to the replayable history 2550 if (isActive && live) { 2551 addToReplayHistory(source); 2552 } 2553 2554 return failed; 2555 } 2556 2557 // Handle incoming snippet events -- return true on failure 2558 private boolean handleEvent(SnippetEvent ste) { 2559 Snippet sn = ste.snippet(); 2560 if (sn == null) { 2561 debug("Event with null key: %s", ste); 2562 return false; 2563 } 2564 List<Diag> diagnostics = state.diagnostics(sn).collect(toList()); 2565 String source = sn.source(); 2566 if (ste.causeSnippet() == null) { 2567 // main event 2568 for (Diag d : diagnostics) { 2569 hardmsg(d.isError()? "jshell.msg.error" : "jshell.msg.warning"); 2570 List<String> disp = new ArrayList<>(); 2571 displayDiagnostics(source, d, disp); 2572 disp.stream() 2573 .forEach(l -> hard("%s", l)); 2574 } 2575 2576 if (ste.status() != Status.REJECTED) { 2577 if (ste.exception() != null) { 2578 if (ste.exception() instanceof EvalException) { 2579 printEvalException((EvalException) ste.exception()); 2580 return true; 2581 } else if (ste.exception() instanceof UnresolvedReferenceException) { 2582 printUnresolvedException((UnresolvedReferenceException) ste.exception()); 2583 } else { 2584 hard("Unexpected execution exception: %s", ste.exception()); 2585 return true; 2586 } 2587 } else { 2588 new DisplayEvent(ste, false, ste.value(), diagnostics).displayDeclarationAndValue(); 2589 } 2590 } else { 2591 if (diagnostics.isEmpty()) { 2592 errormsg("jshell.err.failed"); 2593 } 2594 return true; 2595 } 2596 } else { 2597 // Update 2598 if (sn instanceof DeclarationSnippet) { 2599 List<Diag> other = errorsOnly(diagnostics); 2600 2601 // display update information 2602 new DisplayEvent(ste, true, ste.value(), other).displayDeclarationAndValue(); 2603 } 2604 } 2605 return false; 2606 } 2607 //where 2608 void printStackTrace(StackTraceElement[] stes) { 2609 for (StackTraceElement ste : stes) { 2610 StringBuilder sb = new StringBuilder(); 2611 String cn = ste.getClassName(); 2612 if (!cn.isEmpty()) { 2613 int dot = cn.lastIndexOf('.'); 2614 if (dot > 0) { 2615 sb.append(cn.substring(dot + 1)); 2616 } else { 2617 sb.append(cn); 2618 } 2619 sb.append("."); 2620 } 2621 if (!ste.getMethodName().isEmpty()) { 2622 sb.append(ste.getMethodName()); 2623 sb.append(" "); 2624 } 2625 String fileName = ste.getFileName(); 2626 int lineNumber = ste.getLineNumber(); 2627 String loc = ste.isNativeMethod() 2628 ? getResourceString("jshell.msg.native.method") 2629 : fileName == null 2630 ? getResourceString("jshell.msg.unknown.source") 2631 : lineNumber >= 0 2632 ? fileName + ":" + lineNumber 2633 : fileName; 2634 hard(" at %s(%s)", sb, loc); 2635 2636 } 2637 } 2638 //where 2639 void printUnresolvedException(UnresolvedReferenceException ex) { 2640 DeclarationSnippet corralled = ex.getSnippet(); 2641 List<Diag> otherErrors = errorsOnly(state.diagnostics(corralled).collect(toList())); 2642 new DisplayEvent(corralled, state.status(corralled), FormatAction.USED, true, null, otherErrors) 2643 .displayDeclarationAndValue(); 2644 } 2645 //where 2646 void printEvalException(EvalException ex) { 2647 if (ex.getMessage() == null) { 2648 hard("%s thrown", ex.getExceptionClassName()); 2649 } else { 2650 hard("%s thrown: %s", ex.getExceptionClassName(), ex.getMessage()); 2651 } 2652 printStackTrace(ex.getStackTrace()); 2653 } 2654 2655 private FormatAction toAction(Status status, Status previousStatus, boolean isSignatureChange) { 2656 FormatAction act; 2657 switch (status) { 2658 case VALID: 2659 case RECOVERABLE_DEFINED: 2660 case RECOVERABLE_NOT_DEFINED: 2661 if (previousStatus.isActive()) { 2662 act = isSignatureChange 2663 ? FormatAction.REPLACED 2664 : FormatAction.MODIFIED; 2665 } else { 2666 act = FormatAction.ADDED; 2667 } 2668 break; 2669 case OVERWRITTEN: 2670 act = FormatAction.OVERWROTE; 2671 break; 2672 case DROPPED: 2673 act = FormatAction.DROPPED; 2674 break; 2675 case REJECTED: 2676 case NONEXISTENT: 2677 default: 2678 // Should not occur 2679 error("Unexpected status: " + previousStatus.toString() + "=>" + status.toString()); 2680 act = FormatAction.DROPPED; 2681 } 2682 return act; 2683 } 2684 2685 class DisplayEvent { 2686 private final Snippet sn; 2687 private final FormatAction action; 2688 private final boolean update; 2689 private final String value; 2690 private final List<String> errorLines; 2691 private final FormatResolve resolution; 2692 private final String unresolved; 2693 private final FormatUnresolved unrcnt; 2694 private final FormatErrors errcnt; 2695 2696 DisplayEvent(SnippetEvent ste, boolean update, String value, List<Diag> errors) { 2697 this(ste.snippet(), ste.status(), toAction(ste.status(), ste.previousStatus(), ste.isSignatureChange()), update, value, errors); 2698 } 2699 2700 DisplayEvent(Snippet sn, Status status, FormatAction action, boolean update, String value, List<Diag> errors) { 2701 this.sn = sn; 2702 this.action = action; 2703 this.update = update; 2704 this.value = value; 2705 this.errorLines = new ArrayList<>(); 2706 for (Diag d : errors) { 2707 displayDiagnostics(sn.source(), d, errorLines); 2708 } 2709 long unresolvedCount; 2710 if (sn instanceof DeclarationSnippet && (status == Status.RECOVERABLE_DEFINED || status == Status.RECOVERABLE_NOT_DEFINED)) { 2711 resolution = (status == Status.RECOVERABLE_NOT_DEFINED) 2712 ? FormatResolve.NOTDEFINED 2713 : FormatResolve.DEFINED; 2714 unresolved = unresolved((DeclarationSnippet) sn); 2715 unresolvedCount = state.unresolvedDependencies((DeclarationSnippet) sn).count(); 2716 } else { 2717 resolution = FormatResolve.OK; 2718 unresolved = ""; 2719 unresolvedCount = 0; 2720 } 2721 unrcnt = unresolvedCount == 0 2722 ? FormatUnresolved.UNRESOLVED0 2723 : unresolvedCount == 1 2724 ? FormatUnresolved.UNRESOLVED1 2725 : FormatUnresolved.UNRESOLVED2; 2726 errcnt = errors.isEmpty() 2727 ? FormatErrors.ERROR0 2728 : errors.size() == 1 2729 ? FormatErrors.ERROR1 2730 : FormatErrors.ERROR2; 2731 } 2732 2733 private String unresolved(DeclarationSnippet key) { 2734 List<String> unr = state.unresolvedDependencies(key).collect(toList()); 2735 StringBuilder sb = new StringBuilder(); 2736 int fromLast = unr.size(); 2737 if (fromLast > 0) { 2738 sb.append(" "); 2739 } 2740 for (String u : unr) { 2741 --fromLast; 2742 sb.append(u); 2743 switch (fromLast) { 2744 // No suffix 2745 case 0: 2746 break; 2747 case 1: 2748 sb.append(", and "); 2749 break; 2750 default: 2751 sb.append(", "); 2752 break; 2753 } 2754 } 2755 return sb.toString(); 2756 } 2757 2758 private void custom(FormatCase fcase, String name) { 2759 custom(fcase, name, null); 2760 } 2761 2762 private void custom(FormatCase fcase, String name, String type) { 2763 String display = feedback.format(fcase, action, (update ? FormatWhen.UPDATE : FormatWhen.PRIMARY), 2764 resolution, unrcnt, errcnt, 2765 name, type, value, unresolved, errorLines); 2766 if (interactive()) { 2767 cmdout.print(display); 2768 } 2769 } 2770 2771 @SuppressWarnings("fallthrough") 2772 private void displayDeclarationAndValue() { 2773 switch (sn.subKind()) { 2774 case CLASS_SUBKIND: 2775 custom(FormatCase.CLASS, ((TypeDeclSnippet) sn).name()); 2776 break; 2777 case INTERFACE_SUBKIND: 2778 custom(FormatCase.INTERFACE, ((TypeDeclSnippet) sn).name()); 2779 break; 2780 case ENUM_SUBKIND: 2781 custom(FormatCase.ENUM, ((TypeDeclSnippet) sn).name()); 2782 break; 2783 case ANNOTATION_TYPE_SUBKIND: 2784 custom(FormatCase.ANNOTATION, ((TypeDeclSnippet) sn).name()); 2785 break; 2786 case METHOD_SUBKIND: 2787 custom(FormatCase.METHOD, ((MethodSnippet) sn).name(), ((MethodSnippet) sn).parameterTypes()); 2788 break; 2789 case VAR_DECLARATION_SUBKIND: { 2790 VarSnippet vk = (VarSnippet) sn; 2791 custom(FormatCase.VARDECL, vk.name(), vk.typeName()); 2792 break; 2793 } 2794 case VAR_DECLARATION_WITH_INITIALIZER_SUBKIND: { 2795 VarSnippet vk = (VarSnippet) sn; 2796 custom(FormatCase.VARINIT, vk.name(), vk.typeName()); 2797 break; 2798 } 2799 case TEMP_VAR_EXPRESSION_SUBKIND: { 2800 VarSnippet vk = (VarSnippet) sn; 2801 custom(FormatCase.EXPRESSION, vk.name(), vk.typeName()); 2802 break; 2803 } 2804 case OTHER_EXPRESSION_SUBKIND: 2805 error("Unexpected expression form -- value is: %s", (value)); 2806 break; 2807 case VAR_VALUE_SUBKIND: { 2808 ExpressionSnippet ek = (ExpressionSnippet) sn; 2809 custom(FormatCase.VARVALUE, ek.name(), ek.typeName()); 2810 break; 2811 } 2812 case ASSIGNMENT_SUBKIND: { 2813 ExpressionSnippet ek = (ExpressionSnippet) sn; 2814 custom(FormatCase.ASSIGNMENT, ek.name(), ek.typeName()); 2815 break; 2816 } 2817 case SINGLE_TYPE_IMPORT_SUBKIND: 2818 case TYPE_IMPORT_ON_DEMAND_SUBKIND: 2819 case SINGLE_STATIC_IMPORT_SUBKIND: 2820 case STATIC_IMPORT_ON_DEMAND_SUBKIND: 2821 custom(FormatCase.IMPORT, ((ImportSnippet) sn).name()); 2822 break; 2823 case STATEMENT_SUBKIND: 2824 custom(FormatCase.STATEMENT, null); 2825 break; 2826 } 2827 } 2828 } 2829 2830 /** The current version number as a string. 2831 */ 2832 String version() { 2833 return version("release"); // mm.nn.oo[-milestone] 2834 } 2835 2836 /** The current full version number as a string. 2837 */ 2838 String fullVersion() { 2839 return version("full"); // mm.mm.oo[-milestone]-build 2840 } 2841 2842 private String version(String key) { 2843 if (versionRB == null) { 2844 try { 2845 versionRB = ResourceBundle.getBundle(VERSION_RB_NAME, locale); 2846 } catch (MissingResourceException e) { 2847 return "(version info not available)"; 2848 } 2849 } 2850 try { 2851 return versionRB.getString(key); 2852 } 2853 catch (MissingResourceException e) { 2854 return "(version info not available)"; 2855 } 2856 } 2857 2858 class NameSpace { 2859 final String spaceName; 2860 final String prefix; 2861 private int nextNum; 2862 2863 NameSpace(String spaceName, String prefix) { 2864 this.spaceName = spaceName; 2865 this.prefix = prefix; 2866 this.nextNum = 1; 2867 } 2868 2869 String tid(Snippet sn) { 2870 String tid = prefix + nextNum++; 2871 mapSnippet.put(sn, new SnippetInfo(sn, this, tid)); 2872 return tid; 2873 } 2874 2875 String tidNext() { 2876 return prefix + nextNum; 2877 } 2878 } 2879 2880 static class SnippetInfo { 2881 final Snippet snippet; 2882 final NameSpace space; 2883 final String tid; 2884 2885 SnippetInfo(Snippet snippet, NameSpace space, String tid) { 2886 this.snippet = snippet; 2887 this.space = space; 2888 this.tid = tid; 2889 } 2890 } 2891 2892 static class ArgSuggestion implements Suggestion { 2893 2894 private final String continuation; 2895 2896 /** 2897 * Create a {@code Suggestion} instance. 2898 * 2899 * @param continuation a candidate continuation of the user's input 2900 */ 2901 public ArgSuggestion(String continuation) { 2902 this.continuation = continuation; 2903 } 2904 2905 /** 2906 * The candidate continuation of the given user's input. 2907 * 2908 * @return the continuation string 2909 */ 2910 @Override 2911 public String continuation() { 2912 return continuation; 2913 } 2914 2915 /** 2916 * Indicates whether input continuation matches the target type and is thus 2917 * more likely to be the desired continuation. A matching continuation is 2918 * preferred. 2919 * 2920 * @return {@code false}, non-types analysis 2921 */ 2922 @Override 2923 public boolean matchesType() { 2924 return false; 2925 } 2926 } 2927} 2928 2929abstract class NonInteractiveIOContext extends IOContext { 2930 2931 @Override 2932 public boolean interactiveOutput() { 2933 return false; 2934 } 2935 2936 @Override 2937 public Iterable<String> currentSessionHistory() { 2938 return Collections.emptyList(); 2939 } 2940 2941 @Override 2942 public boolean terminalEditorRunning() { 2943 return false; 2944 } 2945 2946 @Override 2947 public void suspend() { 2948 } 2949 2950 @Override 2951 public void resume() { 2952 } 2953 2954 @Override 2955 public void beforeUserCode() { 2956 } 2957 2958 @Override 2959 public void afterUserCode() { 2960 } 2961 2962 @Override 2963 public void replaceLastHistoryEntry(String source) { 2964 } 2965} 2966 2967class ScannerIOContext extends NonInteractiveIOContext { 2968 private final Scanner scannerIn; 2969 2970 ScannerIOContext(Scanner scannerIn) { 2971 this.scannerIn = scannerIn; 2972 } 2973 2974 ScannerIOContext(Reader rdr) throws FileNotFoundException { 2975 this(new Scanner(rdr)); 2976 } 2977 2978 @Override 2979 public String readLine(String prompt, String prefix) { 2980 if (scannerIn.hasNextLine()) { 2981 return scannerIn.nextLine(); 2982 } else { 2983 return null; 2984 } 2985 } 2986 2987 @Override 2988 public void close() { 2989 scannerIn.close(); 2990 } 2991 2992 @Override 2993 public int readUserInput() { 2994 return -1; 2995 } 2996} 2997 2998class ReloadIOContext extends NonInteractiveIOContext { 2999 private final Iterator<String> it; 3000 private final PrintStream echoStream; 3001 3002 ReloadIOContext(Iterable<String> history, PrintStream echoStream) { 3003 this.it = history.iterator(); 3004 this.echoStream = echoStream; 3005 } 3006 3007 @Override 3008 public String readLine(String prompt, String prefix) { 3009 String s = it.hasNext() 3010 ? it.next() 3011 : null; 3012 if (echoStream != null && s != null) { 3013 String p = "-: "; 3014 String p2 = "\n "; 3015 echoStream.printf("%s%s\n", p, s.replace("\n", p2)); 3016 } 3017 return s; 3018 } 3019 3020 @Override 3021 public void close() { 3022 } 3023 3024 @Override 3025 public int readUserInput() { 3026 return -1; 3027 } 3028} 3029