Feedback.java revision 3746:46f2219faf5a
1/* 2 * Copyright (c) 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.util.ArrayList; 29import java.util.Arrays; 30import java.util.Collection; 31import java.util.Collections; 32import java.util.EnumSet; 33import java.util.HashMap; 34import java.util.HashSet; 35import java.util.Iterator; 36import java.util.List; 37import java.util.Locale; 38import java.util.Map; 39import java.util.Map.Entry; 40import java.util.Objects; 41import java.util.Set; 42import java.util.StringJoiner; 43import java.util.function.BiConsumer; 44import java.util.function.BinaryOperator; 45import java.util.function.Consumer; 46import java.util.function.Function; 47import java.util.function.Supplier; 48import java.util.regex.Matcher; 49import java.util.regex.Pattern; 50import java.util.stream.Collector; 51import static java.util.stream.Collectors.joining; 52import static java.util.stream.Collectors.toMap; 53import static jdk.internal.jshell.tool.ContinuousCompletionProvider.PERFECT_MATCHER; 54import jdk.internal.jshell.tool.JShellTool.CompletionProvider; 55import static jdk.internal.jshell.tool.JShellTool.EMPTY_COMPLETION_PROVIDER; 56 57/** 58 * Feedback customization support 59 * 60 * @author Robert Field 61 */ 62class Feedback { 63 64 // Patern for substituted fields within a customized format string 65 private static final Pattern FIELD_PATTERN = Pattern.compile("\\{(.*?)\\}"); 66 67 // Internal field name for truncation length 68 private static final String TRUNCATION_FIELD = "<truncation>"; 69 70 // For encoding to Properties String 71 private static final String RECORD_SEPARATOR = "\u241E"; 72 73 // Current mode -- initial value is placeholder during start-up 74 private Mode mode = new Mode(""); 75 76 // Retained current mode -- for checks 77 private Mode retainedCurrentMode = null; 78 79 // Mapping of mode name to mode 80 private final Map<String, Mode> modeMap = new HashMap<>(); 81 82 // Mapping of mode names to encoded retained mode 83 private final Map<String, String> retainedMap = new HashMap<>(); 84 85 // Mapping selector enum names to enums 86 private final Map<String, Selector<?>> selectorMap = new HashMap<>(); 87 88 private static final long ALWAYS = bits(FormatCase.all, FormatAction.all, FormatWhen.all, 89 FormatResolve.all, FormatUnresolved.all, FormatErrors.all); 90 private static final long ANY = 0L; 91 92 public boolean shouldDisplayCommandFluff() { 93 return mode.commandFluff; 94 } 95 96 public String getPre() { 97 return mode.format("pre", ANY); 98 } 99 100 public String getPost() { 101 return mode.format("post", ANY); 102 } 103 104 public String getErrorPre() { 105 return mode.format("errorpre", ANY); 106 } 107 108 public String getErrorPost() { 109 return mode.format("errorpost", ANY); 110 } 111 112 public String format(FormatCase fc, FormatAction fa, FormatWhen fw, 113 FormatResolve fr, FormatUnresolved fu, FormatErrors fe, 114 String name, String type, String value, String unresolved, List<String> errorLines) { 115 return mode.format(fc, fa, fw, fr, fu, fe, 116 name, type, value, unresolved, errorLines); 117 } 118 119 public String truncateVarValue(String value) { 120 return mode.truncateVarValue(value); 121 } 122 123 public String getPrompt(String nextId) { 124 return mode.getPrompt(nextId); 125 } 126 127 public String getContinuationPrompt(String nextId) { 128 return mode.getContinuationPrompt(nextId); 129 } 130 131 public boolean setFeedback(MessageHandler messageHandler, ArgTokenizer at, Consumer<String> retainer) { 132 return new Setter(messageHandler, at).setFeedback(retainer); 133 } 134 135 public boolean setFormat(MessageHandler messageHandler, ArgTokenizer at) { 136 return new Setter(messageHandler, at).setFormat(); 137 } 138 139 public boolean setTruncation(MessageHandler messageHandler, ArgTokenizer at) { 140 return new Setter(messageHandler, at).setTruncation(); 141 } 142 143 public boolean setMode(MessageHandler messageHandler, ArgTokenizer at, Consumer<String> retainer) { 144 return new Setter(messageHandler, at).setMode(retainer); 145 } 146 147 public boolean setPrompt(MessageHandler messageHandler, ArgTokenizer at) { 148 return new Setter(messageHandler, at).setPrompt(); 149 } 150 151 public boolean restoreEncodedModes(MessageHandler messageHandler, String encoded) { 152 return new Setter(messageHandler, new ArgTokenizer("<init>", "")).restoreEncodedModes(encoded); 153 } 154 155 public void markModesReadOnly() { 156 modeMap.values().stream() 157 .forEach(m -> m.readOnly = true); 158 } 159 160 JShellTool.CompletionProvider modeCompletions() { 161 return modeCompletions(EMPTY_COMPLETION_PROVIDER); 162 } 163 164 JShellTool.CompletionProvider modeCompletions(CompletionProvider successor) { 165 return new ContinuousCompletionProvider( 166 () -> modeMap.keySet().stream() 167 .collect(toMap(Function.identity(), m -> successor)), 168 PERFECT_MATCHER); 169 } 170 171 { 172 for (FormatCase e : FormatCase.all) 173 selectorMap.put(e.name().toLowerCase(Locale.US), e); 174 for (FormatAction e : FormatAction.all) 175 selectorMap.put(e.name().toLowerCase(Locale.US), e); 176 for (FormatResolve e : FormatResolve.all) 177 selectorMap.put(e.name().toLowerCase(Locale.US), e); 178 for (FormatUnresolved e : FormatUnresolved.all) 179 selectorMap.put(e.name().toLowerCase(Locale.US), e); 180 for (FormatErrors e : FormatErrors.all) 181 selectorMap.put(e.name().toLowerCase(Locale.US), e); 182 for (FormatWhen e : FormatWhen.all) 183 selectorMap.put(e.name().toLowerCase(Locale.US), e); 184 } 185 186 private static class SelectorSets { 187 Set<FormatCase> cc; 188 Set<FormatAction> ca; 189 Set<FormatWhen> cw; 190 Set<FormatResolve> cr; 191 Set<FormatUnresolved> cu; 192 Set<FormatErrors> ce; 193 } 194 195 /** 196 * Holds all the context of a mode mode 197 */ 198 private static class Mode { 199 200 // Name of mode 201 final String name; 202 203 // Display command verification/information 204 boolean commandFluff; 205 206 // Event cases: class, method, expression, ... 207 final Map<String, List<Setting>> cases; 208 209 boolean readOnly = false; 210 211 String prompt = "\n-> "; 212 String continuationPrompt = ">> "; 213 214 static class Setting { 215 216 final long enumBits; 217 final String format; 218 219 Setting(long enumBits, String format) { 220 this.enumBits = enumBits; 221 this.format = format; 222 } 223 224 @Override 225 public boolean equals(Object o) { 226 if (o instanceof Setting) { 227 Setting ing = (Setting) o; 228 return enumBits == ing.enumBits && format.equals(ing.format); 229 } else { 230 return false; 231 } 232 } 233 234 @Override 235 public int hashCode() { 236 int hash = 7; 237 hash = 67 * hash + (int) (this.enumBits ^ (this.enumBits >>> 32)); 238 hash = 67 * hash + Objects.hashCode(this.format); 239 return hash; 240 } 241 } 242 243 /** 244 * Set up an empty mode. 245 * 246 * @param name 247 * @param commandFluff True if should display command fluff messages 248 */ 249 Mode(String name) { 250 this.name = name; 251 this.cases = new HashMap<>(); 252 add("name", new Setting(ALWAYS, "%1$s")); 253 add("type", new Setting(ALWAYS, "%2$s")); 254 add("value", new Setting(ALWAYS, "%3$s")); 255 add("unresolved", new Setting(ALWAYS, "%4$s")); 256 add("errors", new Setting(ALWAYS, "%5$s")); 257 add("err", new Setting(ALWAYS, "%6$s")); 258 259 add("errorline", new Setting(ALWAYS, " {err}%n")); 260 261 add("pre", new Setting(ALWAYS, "| ")); 262 add("post", new Setting(ALWAYS, "%n")); 263 add("errorpre", new Setting(ALWAYS, "| ")); 264 add("errorpost", new Setting(ALWAYS, "%n")); 265 } 266 267 /** 268 * Set up a copied mode. 269 * 270 * @param name 271 * @param m Mode to copy, or null for no fresh 272 */ 273 Mode(String name, Mode m) { 274 this.name = name; 275 this.commandFluff = m.commandFluff; 276 this.prompt = m.prompt; 277 this.continuationPrompt = m.continuationPrompt; 278 this.cases = new HashMap<>(); 279 m.cases.entrySet().stream() 280 .forEach(fes -> fes.getValue() 281 .forEach(ing -> add(fes.getKey(), ing))); 282 283 } 284 285 /** 286 * Set up a mode reconstituted from a preferences string. 287 * 288 * @param it the encoded Mode broken into String chunks, may contain 289 * subsequent encoded modes 290 */ 291 Mode(Iterator<String> it) { 292 this.name = it.next(); 293 this.commandFluff = Boolean.parseBoolean(it.next()); 294 this.prompt = it.next(); 295 this.continuationPrompt = it.next(); 296 cases = new HashMap<>(); 297 String field; 298 while (!(field = it.next()).equals("***")) { 299 String open = it.next(); 300 assert open.equals("("); 301 List<Setting> settings = new ArrayList<>(); 302 String bits; 303 while (!(bits = it.next()).equals(")")) { 304 String format = it.next(); 305 Setting ing = new Setting(Long.parseLong(bits), format); 306 settings.add(ing); 307 } 308 cases.put(field, settings); 309 } 310 } 311 312 @Override 313 public boolean equals(Object o) { 314 if (o instanceof Mode) { 315 Mode m = (Mode) o; 316 return name.equals((m.name)) 317 && commandFluff == m.commandFluff 318 && prompt.equals((m.prompt)) 319 && continuationPrompt.equals((m.continuationPrompt)) 320 && cases.equals((m.cases)); 321 } else { 322 return false; 323 } 324 } 325 326 @Override 327 public int hashCode() { 328 return Objects.hashCode(name); 329 } 330 331 /** 332 * Set if this mode displays informative/confirmational messages on 333 * commands. 334 * 335 * @param fluff the value to set 336 */ 337 void setCommandFluff(boolean fluff) { 338 commandFluff = fluff; 339 } 340 341 /** 342 * Encodes the mode into a String so it can be saved in Preferences. 343 * 344 * @return the string representation 345 */ 346 String encode() { 347 List<String> el = new ArrayList<>(); 348 el.add(name); 349 el.add(String.valueOf(commandFluff)); 350 el.add(prompt); 351 el.add(continuationPrompt); 352 for (Entry<String, List<Setting>> es : cases.entrySet()) { 353 el.add(es.getKey()); 354 el.add("("); 355 for (Setting ing : es.getValue()) { 356 el.add(String.valueOf(ing.enumBits)); 357 el.add(ing.format); 358 } 359 el.add(")"); 360 } 361 el.add("***"); 362 return String.join(RECORD_SEPARATOR, el); 363 } 364 365 private void add(String field, Setting ing) { 366 List<Setting> settings = cases.get(field); 367 if (settings == null) { 368 settings = new ArrayList<>(); 369 cases.put(field, settings); 370 } else { 371 // remove obscured settings 372 long mask = ~ing.enumBits; 373 settings.removeIf(t -> (t.enumBits & mask) == 0); 374 } 375 settings.add(ing); 376 } 377 378 void set(String field, 379 Collection<FormatCase> cc, Collection<FormatAction> ca, Collection<FormatWhen> cw, 380 Collection<FormatResolve> cr, Collection<FormatUnresolved> cu, Collection<FormatErrors> ce, 381 String format) { 382 long bits = bits(cc, ca, cw, cr, cu, ce); 383 set(field, bits, format); 384 } 385 386 void set(String field, long bits, String format) { 387 add(field, new Setting(bits, format)); 388 } 389 390 /** 391 * Lookup format Replace fields with context specific formats. 392 * 393 * @return format string 394 */ 395 String format(String field, long bits) { 396 List<Setting> settings = cases.get(field); 397 if (settings == null) { 398 return ""; //TODO error? 399 } 400 String format = null; 401 for (int i = settings.size() - 1; i >= 0; --i) { 402 Setting ing = settings.get(i); 403 long mask = ing.enumBits; 404 if ((bits & mask) == bits) { 405 format = ing.format; 406 break; 407 } 408 } 409 if (format == null || format.isEmpty()) { 410 return ""; 411 } 412 Matcher m = FIELD_PATTERN.matcher(format); 413 StringBuffer sb = new StringBuffer(format.length()); 414 while (m.find()) { 415 String fieldName = m.group(1); 416 String sub = format(fieldName, bits); 417 m.appendReplacement(sb, Matcher.quoteReplacement(sub)); 418 } 419 m.appendTail(sb); 420 return sb.toString(); 421 } 422 423 String truncateVarValue(String value) { 424 return truncateValue(value, 425 bits(FormatCase.VARVALUE, FormatAction.ADDED, 426 FormatWhen.PRIMARY, FormatResolve.OK, 427 FormatUnresolved.UNRESOLVED0, FormatErrors.ERROR0)); 428 } 429 430 String truncateValue(String value, long bits) { 431 if (value==null) { 432 return ""; 433 } else { 434 // Retrieve the truncation length 435 String truncField = format(TRUNCATION_FIELD, bits); 436 if (truncField.isEmpty()) { 437 // No truncation set, use whole value 438 return value; 439 } else { 440 // Convert truncation length to int 441 // this is safe since it has been tested before it is set 442 int trunc = Integer.parseUnsignedInt(truncField); 443 int len = value.length(); 444 if (len > trunc) { 445 if (trunc <= 13) { 446 // Very short truncations have no room for "..." 447 return value.substring(0, trunc); 448 } else { 449 // Normal truncation, make total length equal truncation length 450 int endLen = trunc / 3; 451 int startLen = trunc - 5 - endLen; 452 return value.substring(0, startLen) + " ... " + value.substring(len -endLen); 453 } 454 } else { 455 // Within truncation length, use whole value 456 return value; 457 } 458 } 459 } 460 } 461 462 // Compute the display output given full context and values 463 String format(FormatCase fc, FormatAction fa, FormatWhen fw, 464 FormatResolve fr, FormatUnresolved fu, FormatErrors fe, 465 String name, String type, String value, String unresolved, List<String> errorLines) { 466 // Convert the context into a bit representation used as selectors for store field formats 467 long bits = bits(fc, fa, fw, fr, fu, fe); 468 String fname = name==null? "" : name; 469 String ftype = type==null? "" : type; 470 // Compute the representation of value 471 String fvalue = truncateValue(value, bits); 472 String funresolved = unresolved==null? "" : unresolved; 473 String errors = errorLines.stream() 474 .map(el -> String.format( 475 format("errorline", bits), 476 fname, ftype, fvalue, funresolved, "*cannot-use-errors-here*", el)) 477 .collect(joining()); 478 return String.format( 479 format("display", bits), 480 fname, ftype, fvalue, funresolved, errors, "*cannot-use-err-here*"); 481 } 482 483 void setPrompts(String prompt, String continuationPrompt) { 484 this.prompt = prompt; 485 this.continuationPrompt = continuationPrompt; 486 } 487 488 String getPrompt(String nextId) { 489 return String.format(prompt, nextId); 490 } 491 492 String getContinuationPrompt(String nextId) { 493 return String.format(continuationPrompt, nextId); 494 } 495 } 496 497 // Representation of one instance of all the enum values as bits in a long 498 private static long bits(FormatCase fc, FormatAction fa, FormatWhen fw, 499 FormatResolve fr, FormatUnresolved fu, FormatErrors fe) { 500 long res = 0L; 501 res |= 1 << fc.ordinal(); 502 res <<= FormatAction.count; 503 res |= 1 << fa.ordinal(); 504 res <<= FormatWhen.count; 505 res |= 1 << fw.ordinal(); 506 res <<= FormatResolve.count; 507 res |= 1 << fr.ordinal(); 508 res <<= FormatUnresolved.count; 509 res |= 1 << fu.ordinal(); 510 res <<= FormatErrors.count; 511 res |= 1 << fe.ordinal(); 512 return res; 513 } 514 515 // Representation of a space of enum values as or'edbits in a long 516 private static long bits(Collection<FormatCase> cc, Collection<FormatAction> ca, Collection<FormatWhen> cw, 517 Collection<FormatResolve> cr, Collection<FormatUnresolved> cu, Collection<FormatErrors> ce) { 518 long res = 0L; 519 for (FormatCase fc : cc) 520 res |= 1 << fc.ordinal(); 521 res <<= FormatAction.count; 522 for (FormatAction fa : ca) 523 res |= 1 << fa.ordinal(); 524 res <<= FormatWhen.count; 525 for (FormatWhen fw : cw) 526 res |= 1 << fw.ordinal(); 527 res <<= FormatResolve.count; 528 for (FormatResolve fr : cr) 529 res |= 1 << fr.ordinal(); 530 res <<= FormatUnresolved.count; 531 for (FormatUnresolved fu : cu) 532 res |= 1 << fu.ordinal(); 533 res <<= FormatErrors.count; 534 for (FormatErrors fe : ce) 535 res |= 1 << fe.ordinal(); 536 return res; 537 } 538 539 private static SelectorSets unpackEnumbits(long enumBits) { 540 class Unpacker { 541 542 SelectorSets u = new SelectorSets(); 543 long b = enumBits; 544 545 <E extends Enum<E>> Set<E> unpackEnumbits(E[] values) { 546 Set<E> c = new HashSet<>(); 547 for (int i = 0; i < values.length; ++i) { 548 if ((b & (1 << i)) != 0) { 549 c.add(values[i]); 550 } 551 } 552 b >>>= values.length; 553 return c; 554 } 555 556 SelectorSets unpack() { 557 // inverseof the order they were packed 558 u.ce = unpackEnumbits(FormatErrors.values()); 559 u.cu = unpackEnumbits(FormatUnresolved.values()); 560 u.cr = unpackEnumbits(FormatResolve.values()); 561 u.cw = unpackEnumbits(FormatWhen.values()); 562 u.ca = unpackEnumbits(FormatAction.values()); 563 u.cc = unpackEnumbits(FormatCase.values()); 564 return u; 565 } 566 } 567 return new Unpacker().unpack(); 568 } 569 570 interface Selector<E extends Enum<E> & Selector<E>> { 571 SelectorCollector<E> collector(Setter.SelectorList sl); 572 String doc(); 573 } 574 575 /** 576 * The event cases 577 */ 578 public enum FormatCase implements Selector<FormatCase> { 579 IMPORT("import declaration"), 580 CLASS("class declaration"), 581 INTERFACE("interface declaration"), 582 ENUM("enum declaration"), 583 ANNOTATION("annotation interface declaration"), 584 METHOD("method declaration -- note: {type}==parameter-types"), 585 VARDECL("variable declaration without init"), 586 VARINIT("variable declaration with init"), 587 EXPRESSION("expression -- note: {name}==scratch-variable-name"), 588 VARVALUE("variable value expression"), 589 ASSIGNMENT("assign variable"), 590 STATEMENT("statement"); 591 String doc; 592 static final EnumSet<FormatCase> all = EnumSet.allOf(FormatCase.class); 593 static final int count = all.size(); 594 595 @Override 596 public SelectorCollector<FormatCase> collector(Setter.SelectorList sl) { 597 return sl.cases; 598 } 599 600 @Override 601 public String doc() { 602 return doc; 603 } 604 605 private FormatCase(String doc) { 606 this.doc = doc; 607 } 608 } 609 610 /** 611 * The event actions 612 */ 613 public enum FormatAction implements Selector<FormatAction> { 614 ADDED("snippet has been added"), 615 MODIFIED("an existing snippet has been modified"), 616 REPLACED("an existing snippet has been replaced with a new snippet"), 617 OVERWROTE("an existing snippet has been overwritten"), 618 DROPPED("snippet has been dropped"), 619 USED("snippet was used when it cannot be"); 620 String doc; 621 static final EnumSet<FormatAction> all = EnumSet.allOf(FormatAction.class); 622 static final int count = all.size(); 623 624 @Override 625 public SelectorCollector<FormatAction> collector(Setter.SelectorList sl) { 626 return sl.actions; 627 } 628 629 @Override 630 public String doc() { 631 return doc; 632 } 633 634 private FormatAction(String doc) { 635 this.doc = doc; 636 } 637 } 638 639 /** 640 * When the event occurs: primary or update 641 */ 642 public enum FormatWhen implements Selector<FormatWhen> { 643 PRIMARY("the entered snippet"), 644 UPDATE("an update to a dependent snippet"); 645 String doc; 646 static final EnumSet<FormatWhen> all = EnumSet.allOf(FormatWhen.class); 647 static final int count = all.size(); 648 649 @Override 650 public SelectorCollector<FormatWhen> collector(Setter.SelectorList sl) { 651 return sl.whens; 652 } 653 654 @Override 655 public String doc() { 656 return doc; 657 } 658 659 private FormatWhen(String doc) { 660 this.doc = doc; 661 } 662 } 663 664 /** 665 * Resolution problems 666 */ 667 public enum FormatResolve implements Selector<FormatResolve> { 668 OK("resolved correctly"), 669 DEFINED("defined despite recoverably unresolved references"), 670 NOTDEFINED("not defined because of recoverably unresolved references"); 671 String doc; 672 static final EnumSet<FormatResolve> all = EnumSet.allOf(FormatResolve.class); 673 static final int count = all.size(); 674 675 @Override 676 public SelectorCollector<FormatResolve> collector(Setter.SelectorList sl) { 677 return sl.resolves; 678 } 679 680 @Override 681 public String doc() { 682 return doc; 683 } 684 685 private FormatResolve(String doc) { 686 this.doc = doc; 687 } 688 } 689 690 /** 691 * Count of unresolved references 692 */ 693 public enum FormatUnresolved implements Selector<FormatUnresolved> { 694 UNRESOLVED0("no names are unresolved"), 695 UNRESOLVED1("one name is unresolved"), 696 UNRESOLVED2("two or more names are unresolved"); 697 String doc; 698 static final EnumSet<FormatUnresolved> all = EnumSet.allOf(FormatUnresolved.class); 699 static final int count = all.size(); 700 701 @Override 702 public SelectorCollector<FormatUnresolved> collector(Setter.SelectorList sl) { 703 return sl.unresolvedCounts; 704 } 705 706 @Override 707 public String doc() { 708 return doc; 709 } 710 711 private FormatUnresolved(String doc) { 712 this.doc = doc; 713 } 714 } 715 716 /** 717 * Count of unresolved references 718 */ 719 public enum FormatErrors implements Selector<FormatErrors> { 720 ERROR0("no errors"), 721 ERROR1("one error"), 722 ERROR2("two or more errors"); 723 String doc; 724 static final EnumSet<FormatErrors> all = EnumSet.allOf(FormatErrors.class); 725 static final int count = all.size(); 726 727 @Override 728 public SelectorCollector<FormatErrors> collector(Setter.SelectorList sl) { 729 return sl.errorCounts; 730 } 731 732 @Override 733 public String doc() { 734 return doc; 735 } 736 737 private FormatErrors(String doc) { 738 this.doc = doc; 739 } 740 } 741 742 class SelectorCollector<E extends Enum<E> & Selector<E>> { 743 final EnumSet<E> all; 744 EnumSet<E> set = null; 745 SelectorCollector(EnumSet<E> all) { 746 this.all = all; 747 } 748 void add(Object o) { 749 @SuppressWarnings("unchecked") 750 E e = (E) o; 751 if (set == null) { 752 set = EnumSet.of(e); 753 } else { 754 set.add(e); 755 } 756 } 757 758 boolean isEmpty() { 759 return set == null; 760 } 761 762 EnumSet<E> getSet() { 763 return set == null 764 ? all 765 : set; 766 } 767 } 768 769 // Class used to set custom eval output formats 770 // For both /set format -- Parse arguments, setting custom format, or printing error 771 private class Setter { 772 773 private final ArgTokenizer at; 774 private final MessageHandler messageHandler; 775 boolean valid = true; 776 777 Setter(MessageHandler messageHandler, ArgTokenizer at) { 778 this.messageHandler = messageHandler; 779 this.at = at; 780 at.allowedOptions("-retain"); 781 } 782 783 void fluff(String format, Object... args) { 784 messageHandler.fluff(format, args); 785 } 786 787 void hard(String format, Object... args) { 788 messageHandler.hard(format, args); 789 } 790 791 void fluffmsg(String messageKey, Object... args) { 792 messageHandler.fluffmsg(messageKey, args); 793 } 794 795 void hardmsg(String messageKey, Object... args) { 796 messageHandler.hardmsg(messageKey, args); 797 } 798 799 boolean showFluff() { 800 return messageHandler.showFluff(); 801 } 802 803 void errorat(String messageKey, Object... args) { 804 if (!valid) { 805 // no spew of errors 806 return; 807 } 808 valid = false; 809 Object[] a2 = Arrays.copyOf(args, args.length + 2); 810 a2[args.length] = at.whole(); 811 messageHandler.errormsg(messageKey, a2); 812 } 813 814 String selectorsToString(SelectorSets u) { 815 StringBuilder sb = new StringBuilder(); 816 selectorToString(sb, u.cc, FormatCase.values()); 817 selectorToString(sb, u.ca, FormatAction.values()); 818 selectorToString(sb, u.cw, FormatWhen.values()); 819 selectorToString(sb, u.cr, FormatResolve.values()); 820 selectorToString(sb, u.cu, FormatUnresolved.values()); 821 selectorToString(sb, u.ce, FormatErrors.values()); 822 return sb.toString(); 823 } 824 825 private <E extends Enum<E>> void selectorToString(StringBuilder sb, Set<E> c, E[] values) { 826 if (!c.containsAll(Arrays.asList(values))) { 827 sb.append(c.stream() 828 .sorted((x, y) -> x.ordinal() - y.ordinal()) 829 .map(v -> v.name().toLowerCase(Locale.US)) 830 .collect(new Collector<CharSequence, StringJoiner, String>() { 831 @Override 832 public BiConsumer<StringJoiner, CharSequence> accumulator() { 833 return StringJoiner::add; 834 } 835 836 @Override 837 public Supplier<StringJoiner> supplier() { 838 return () -> new StringJoiner(",", (sb.length() == 0)? "" : "-", "") 839 .setEmptyValue(""); 840 } 841 842 @Override 843 public BinaryOperator<StringJoiner> combiner() { 844 return StringJoiner::merge; 845 } 846 847 @Override 848 public Function<StringJoiner, String> finisher() { 849 return StringJoiner::toString; 850 } 851 852 @Override 853 public Set<Characteristics> characteristics() { 854 return Collections.emptySet(); 855 } 856 })); 857 } 858 } 859 860 // Show format settings -- in a predictable order, for testing... 861 void showFormatSettings(Mode sm, String f) { 862 if (sm == null) { 863 modeMap.entrySet().stream() 864 .sorted((es1, es2) -> es1.getKey().compareTo(es2.getKey())) 865 .forEach(m -> showFormatSettings(m.getValue(), f)); 866 } else { 867 sm.cases.entrySet().stream() 868 .filter(ec -> (f == null) 869 ? !ec.getKey().equals(TRUNCATION_FIELD) 870 : ec.getKey().equals(f)) 871 .sorted((ec1, ec2) -> ec1.getKey().compareTo(ec2.getKey())) 872 .forEach(ec -> { 873 ec.getValue().forEach(s -> { 874 hard("/set format %s %s %s %s", 875 sm.name, ec.getKey(), toStringLiteral(s.format), 876 selectorsToString(unpackEnumbits(s.enumBits))); 877 878 }); 879 }); 880 } 881 } 882 883 void showTruncationSettings(Mode sm) { 884 if (sm == null) { 885 modeMap.values().forEach(m -> showTruncationSettings(m)); 886 } else { 887 List<Mode.Setting> trunc = sm.cases.get(TRUNCATION_FIELD); 888 if (trunc != null) { 889 trunc.forEach(s -> { 890 hard("/set truncation %s %s %s", 891 sm.name, s.format, 892 selectorsToString(unpackEnumbits(s.enumBits))); 893 }); 894 } 895 } 896 } 897 898 void showPromptSettings(Mode sm) { 899 if (sm == null) { 900 modeMap.values().forEach(m -> showPromptSettings(m)); 901 } else { 902 hard("/set prompt %s %s %s", 903 sm.name, 904 toStringLiteral(sm.prompt), 905 toStringLiteral(sm.continuationPrompt)); 906 } 907 } 908 909 void showModeSettings(String umode, String msg) { 910 if (umode == null) { 911 modeMap.values().forEach(n -> showModeSettings(n)); 912 } else { 913 Mode m; 914 String retained = retainedMap.get(umode); 915 if (retained == null) { 916 m = searchForMode(umode, msg); 917 if (m == null) { 918 return; 919 } 920 umode = m.name; 921 retained = retainedMap.get(umode); 922 } else { 923 m = modeMap.get(umode); 924 } 925 if (retained != null) { 926 Mode rm = new Mode(encodedModeIterator(retained)); 927 showModeSettings(rm); 928 hard("/set mode -retain %s", umode); 929 if (m != null && !m.equals(rm)) { 930 hard(""); 931 showModeSettings(m); 932 } 933 } else { 934 showModeSettings(m); 935 } 936 } 937 } 938 939 void showModeSettings(Mode sm) { 940 hard("/set mode %s %s", 941 sm.name, sm.commandFluff ? "-command" : "-quiet"); 942 showPromptSettings(sm); 943 showFormatSettings(sm, null); 944 showTruncationSettings(sm); 945 } 946 947 void showFeedbackSetting() { 948 if (retainedCurrentMode != null) { 949 hard("/set feedback -retain %s", retainedCurrentMode.name); 950 } 951 if (mode != retainedCurrentMode) { 952 hard("/set feedback %s", mode.name); 953 } 954 } 955 956 // For /set prompt <mode> "<prompt>" "<continuation-prompt>" 957 boolean setPrompt() { 958 Mode m = nextMode(); 959 String prompt = nextFormat(); 960 String continuationPrompt = nextFormat(); 961 checkOptionsAndRemainingInput(); 962 if (valid && prompt == null) { 963 showPromptSettings(m); 964 return valid; 965 } 966 if (valid && m.readOnly) { 967 errorat("jshell.err.not.valid.with.predefined.mode", m.name); 968 } else if (continuationPrompt == null) { 969 errorat("jshell.err.continuation.prompt.required"); 970 } 971 if (valid) { 972 m.setPrompts(prompt, continuationPrompt); 973 } else { 974 fluffmsg("jshell.msg.see", "/help /set prompt"); 975 } 976 return valid; 977 } 978 979 /** 980 * Set mode. Create, changed, or delete a feedback mode. For @{code /set 981 * mode <mode> [<old-mode>] [-command|-quiet|-delete]}. 982 * 983 * @return true if successful 984 */ 985 boolean setMode(Consumer<String> retainer) { 986 class SetMode { 987 988 final String umode; 989 final String omode; 990 final boolean commandOption; 991 final boolean quietOption; 992 final boolean deleteOption; 993 final boolean retainOption; 994 995 SetMode() { 996 at.allowedOptions("-command", "-quiet", "-delete", "-retain"); 997 umode = nextModeIdentifier(); 998 omode = nextModeIdentifier(); 999 checkOptionsAndRemainingInput(); 1000 commandOption = at.hasOption("-command"); 1001 quietOption = at.hasOption("-quiet"); 1002 deleteOption = at.hasOption("-delete"); 1003 retainOption = at.hasOption("-retain"); 1004 } 1005 1006 void delete() { 1007 // Note: delete, for safety reasons, does NOT do name matching 1008 if (commandOption || quietOption) { 1009 errorat("jshell.err.conflicting.options"); 1010 } else if (!(retainOption ? retainedMap : modeMap).containsKey(umode)) { 1011 // Cannot delete a mode that does not exist 1012 errorat("jshell.err.mode.unknown", umode); 1013 } else if (omode != null) { 1014 // old mode is for creation 1015 errorat("jshell.err.unexpected.at.end", omode); 1016 } else if (mode.name.equals(umode)) { 1017 // Cannot delete the current mode out from under us 1018 errorat("jshell.err.cannot.delete.current.mode", umode); 1019 } else if (retainOption && retainedCurrentMode != null && 1020 retainedCurrentMode.name.equals(umode)) { 1021 // Cannot delete the retained mode or re-start will have an error 1022 errorat("jshell.err.cannot.delete.retained.mode", umode); 1023 } else { 1024 Mode m = modeMap.get(umode); 1025 if (m != null && m.readOnly) { 1026 errorat("jshell.err.not.valid.with.predefined.mode", umode); 1027 } else { 1028 // Remove the mode 1029 modeMap.remove(umode); 1030 if (retainOption) { 1031 // Remove the retained mode 1032 retainedMap.remove(umode); 1033 updateRetainedModes(); 1034 } 1035 } 1036 } 1037 } 1038 1039 void retain() { 1040 if (commandOption || quietOption) { 1041 errorat("jshell.err.conflicting.options"); 1042 } else if (omode != null) { 1043 // old mode is for creation 1044 errorat("jshell.err.unexpected.at.end", omode); 1045 } else { 1046 Mode m = modeMap.get(umode); 1047 if (m == null) { 1048 // can only retain existing modes 1049 errorat("jshell.err.mode.unknown", umode); 1050 } else if (m.readOnly) { 1051 errorat("jshell.err.not.valid.with.predefined.mode", umode); 1052 } else { 1053 // Add to local cache of retained current encodings 1054 retainedMap.put(m.name, m.encode()); 1055 updateRetainedModes(); 1056 } 1057 } 1058 } 1059 1060 void updateRetainedModes() { 1061 // Join all the retained encodings 1062 String encoded = String.join(RECORD_SEPARATOR, retainedMap.values()); 1063 // Retain it 1064 retainer.accept(encoded); 1065 } 1066 1067 void create() { 1068 if (commandOption && quietOption) { 1069 errorat("jshell.err.conflicting.options"); 1070 } else if (!commandOption && !quietOption) { 1071 errorat("jshell.err.mode.creation"); 1072 } else if (modeMap.containsKey(umode)) { 1073 // Mode already exists 1074 errorat("jshell.err.mode.exists", umode); 1075 } else { 1076 Mode om = searchForMode(omode); 1077 if (valid) { 1078 // We are copying an existing mode and/or creating a 1079 // brand-new mode -- in either case create from scratch 1080 Mode m = (om != null) 1081 ? new Mode(umode, om) 1082 : new Mode(umode); 1083 modeMap.put(umode, m); 1084 fluffmsg("jshell.msg.feedback.new.mode", m.name); 1085 m.setCommandFluff(commandOption); 1086 } 1087 } 1088 } 1089 1090 boolean set() { 1091 if (valid && !commandOption && !quietOption && !deleteOption && 1092 omode == null && !retainOption) { 1093 // Not a creation, deletion, or retain -- show mode(s) 1094 showModeSettings(umode, "jshell.err.mode.creation"); 1095 } else if (valid && umode == null) { 1096 errorat("jshell.err.missing.mode"); 1097 } else if (valid && deleteOption) { 1098 delete(); 1099 } else if (valid && retainOption) { 1100 retain(); 1101 } else if (valid) { 1102 create(); 1103 } 1104 if (!valid) { 1105 fluffmsg("jshell.msg.see", "/help /set mode"); 1106 } 1107 return valid; 1108 } 1109 } 1110 return new SetMode().set(); 1111 } 1112 1113 // For /set format <mode> <field> "<format>" <selector>... 1114 boolean setFormat() { 1115 Mode m = nextMode(); 1116 String field = toIdentifier(next(), "jshell.err.field.name"); 1117 String format = nextFormat(); 1118 if (valid && format == null) { 1119 if (field != null && m != null && !m.cases.containsKey(field)) { 1120 errorat("jshell.err.field.name", field); 1121 } else { 1122 showFormatSettings(m, field); 1123 } 1124 } else { 1125 installFormat(m, field, format, "/help /set format"); 1126 } 1127 return valid; 1128 } 1129 1130 // For /set truncation <mode> <length> <selector>... 1131 boolean setTruncation() { 1132 Mode m = nextMode(); 1133 String length = next(); 1134 if (length == null) { 1135 showTruncationSettings(m); 1136 } else { 1137 try { 1138 // Assure that integer format is correct 1139 Integer.parseUnsignedInt(length); 1140 } catch (NumberFormatException ex) { 1141 errorat("jshell.err.truncation.length.not.integer", length); 1142 } 1143 // install length into an internal format field 1144 installFormat(m, TRUNCATION_FIELD, length, "/help /set truncation"); 1145 } 1146 return valid; 1147 } 1148 1149 // For /set feedback <mode> 1150 boolean setFeedback(Consumer<String> retainer) { 1151 String umode = next(); 1152 checkOptionsAndRemainingInput(); 1153 boolean retainOption = at.hasOption("-retain"); 1154 if (valid && umode == null && !retainOption) { 1155 showFeedbackSetting(); 1156 hard(""); 1157 showFeedbackModes(); 1158 return true; 1159 } 1160 if (valid) { 1161 Mode m = umode == null 1162 ? mode 1163 : searchForMode(toModeIdentifier(umode)); 1164 if (valid && retainOption && !m.readOnly && !retainedMap.containsKey(m.name)) { 1165 errorat("jshell.err.retained.feedback.mode.must.be.retained.or.predefined"); 1166 } 1167 if (valid) { 1168 if (umode != null) { 1169 mode = m; 1170 fluffmsg("jshell.msg.feedback.mode", mode.name); 1171 } 1172 if (retainOption) { 1173 retainedCurrentMode = m; 1174 retainer.accept(m.name); 1175 } 1176 } 1177 } 1178 if (!valid) { 1179 fluffmsg("jshell.msg.see", "/help /set feedback"); 1180 return false; 1181 } 1182 return true; 1183 } 1184 1185 boolean restoreEncodedModes(String allEncoded) { 1186 try { 1187 // Iterate over each record in each encoded mode 1188 Iterator<String> itr = encodedModeIterator(allEncoded); 1189 while (itr.hasNext()) { 1190 // Reconstruct the encoded mode 1191 Mode m = new Mode(itr); 1192 modeMap.put(m.name, m); 1193 // Continue to retain it a new retains occur 1194 retainedMap.put(m.name, m.encode()); 1195 } 1196 return true; 1197 } catch (Throwable exc) { 1198 // Catastrophic corruption -- clear map 1199 errorat("jshell.err.retained.mode.failure", exc); 1200 retainedMap.clear(); 1201 return false; 1202 } 1203 } 1204 1205 Iterator<String> encodedModeIterator(String encoded) { 1206 String[] ms = encoded.split(RECORD_SEPARATOR); 1207 return Arrays.asList(ms).iterator(); 1208 } 1209 1210 // install the format of a field under parsed selectors 1211 void installFormat(Mode m, String field, String format, String help) { 1212 String slRaw; 1213 List<SelectorList> slList = new ArrayList<>(); 1214 while (valid && (slRaw = next()) != null) { 1215 SelectorList sl = new SelectorList(); 1216 sl.parseSelectorList(slRaw); 1217 slList.add(sl); 1218 } 1219 checkOptionsAndRemainingInput(); 1220 if (valid) { 1221 if (m.readOnly) { 1222 errorat("jshell.err.not.valid.with.predefined.mode", m.name); 1223 } else if (slList.isEmpty()) { 1224 // No selectors specified, then always the format 1225 m.set(field, ALWAYS, format); 1226 } else { 1227 // Set the format of the field for specified selector 1228 slList.stream() 1229 .forEach(sl -> m.set(field, 1230 sl.cases.getSet(), sl.actions.getSet(), sl.whens.getSet(), 1231 sl.resolves.getSet(), sl.unresolvedCounts.getSet(), sl.errorCounts.getSet(), 1232 format)); 1233 } 1234 } else { 1235 fluffmsg("jshell.msg.see", help); 1236 } 1237 } 1238 1239 void checkOptionsAndRemainingInput() { 1240 String junk = at.remainder(); 1241 if (!junk.isEmpty()) { 1242 errorat("jshell.err.unexpected.at.end", junk); 1243 } else { 1244 String bad = at.badOptions(); 1245 if (!bad.isEmpty()) { 1246 errorat("jshell.err.unknown.option", bad); 1247 } 1248 } 1249 } 1250 1251 String next() { 1252 String s = at.next(); 1253 if (s == null) { 1254 checkOptionsAndRemainingInput(); 1255 } 1256 return s; 1257 } 1258 1259 /** 1260 * Check that the specified string is an identifier (Java identifier). 1261 * If null display the missing error. If it is not an identifier, 1262 * display the error. 1263 * 1264 * @param id the string to check, MUST be the most recently retrieved 1265 * token from 'at'. 1266 * @param missing null for no null error, otherwise the resource error to display if id is null 1267 * @param err the resource error to display if not an identifier 1268 * @return the identifier string, or null if null or not an identifier 1269 */ 1270 private String toIdentifier(String id, String err) { 1271 if (!valid || id == null) { 1272 return null; 1273 } 1274 if (at.isQuoted() || 1275 !id.codePoints().allMatch(cp -> Character.isJavaIdentifierPart(cp))) { 1276 errorat(err, id); 1277 return null; 1278 } 1279 return id; 1280 } 1281 1282 private String toModeIdentifier(String id) { 1283 return toIdentifier(id, "jshell.err.mode.name"); 1284 } 1285 1286 private String nextModeIdentifier() { 1287 return toModeIdentifier(next()); 1288 } 1289 1290 private Mode nextMode() { 1291 String umode = nextModeIdentifier(); 1292 return searchForMode(umode); 1293 } 1294 1295 private Mode searchForMode(String umode) { 1296 return searchForMode(umode, null); 1297 } 1298 1299 private Mode searchForMode(String umode, String msg) { 1300 if (!valid || umode == null) { 1301 return null; 1302 } 1303 Mode m = modeMap.get(umode); 1304 if (m != null) { 1305 return m; 1306 } 1307 // Failing an exact match, go searching 1308 Mode[] matches = modeMap.entrySet().stream() 1309 .filter(e -> e.getKey().startsWith(umode)) 1310 .map(e -> e.getValue()) 1311 .toArray(size -> new Mode[size]); 1312 if (matches.length == 1) { 1313 return matches[0]; 1314 } else { 1315 if (msg != null) { 1316 hardmsg(msg, ""); 1317 } 1318 if (matches.length == 0) { 1319 errorat("jshell.err.feedback.does.not.match.mode", umode); 1320 } else { 1321 errorat("jshell.err.feedback.ambiguous.mode", umode); 1322 } 1323 if (showFluff()) { 1324 showFeedbackModes(); 1325 } 1326 return null; 1327 } 1328 } 1329 1330 void showFeedbackModes() { 1331 if (!retainedMap.isEmpty()) { 1332 hardmsg("jshell.msg.feedback.retained.mode.following"); 1333 retainedMap.keySet().stream() 1334 .sorted() 1335 .forEach(mk -> hard(" %s", mk)); 1336 } 1337 hardmsg("jshell.msg.feedback.mode.following"); 1338 modeMap.keySet().stream() 1339 .sorted() 1340 .forEach(mk -> hard(" %s", mk)); 1341 } 1342 1343 // Read and test if the format string is correctly 1344 private String nextFormat() { 1345 return toFormat(next()); 1346 } 1347 1348 // Test if the format string is correctly 1349 private String toFormat(String format) { 1350 if (!valid || format == null) { 1351 return null; 1352 } 1353 if (!at.isQuoted()) { 1354 errorat("jshell.err.feedback.must.be.quoted", format); 1355 return null; 1356 } 1357 return format; 1358 } 1359 1360 // Convert to a quoted string 1361 private String toStringLiteral(String s) { 1362 StringBuilder sb = new StringBuilder(); 1363 sb.append('"'); 1364 final int length = s.length(); 1365 for (int offset = 0; offset < length;) { 1366 final int codepoint = s.codePointAt(offset); 1367 1368 switch (codepoint) { 1369 case '\b': 1370 sb.append("\\b"); 1371 break; 1372 case '\t': 1373 sb.append("\\t"); 1374 break; 1375 case '\n': 1376 sb.append("\\n"); 1377 break; 1378 case '\f': 1379 sb.append("\\f"); 1380 break; 1381 case '\r': 1382 sb.append("\\r"); 1383 break; 1384 case '\"': 1385 sb.append("\\\""); 1386 break; 1387 case '\'': 1388 sb.append("\\'"); 1389 break; 1390 case '\\': 1391 sb.append("\\\\"); 1392 break; 1393 default: 1394 if (codepoint < 040) { 1395 sb.append(String.format("\\%o", codepoint)); 1396 } else { 1397 sb.appendCodePoint(codepoint); 1398 } 1399 break; 1400 } 1401 1402 // do something with the codepoint 1403 offset += Character.charCount(codepoint); 1404 1405 } 1406 sb.append('"'); 1407 return sb.toString(); 1408 } 1409 1410 class SelectorList { 1411 1412 SelectorCollector<FormatCase> cases = new SelectorCollector<>(FormatCase.all); 1413 SelectorCollector<FormatAction> actions = new SelectorCollector<>(FormatAction.all); 1414 SelectorCollector<FormatWhen> whens = new SelectorCollector<>(FormatWhen.all); 1415 SelectorCollector<FormatResolve> resolves = new SelectorCollector<>(FormatResolve.all); 1416 SelectorCollector<FormatUnresolved> unresolvedCounts = new SelectorCollector<>(FormatUnresolved.all); 1417 SelectorCollector<FormatErrors> errorCounts = new SelectorCollector<>(FormatErrors.all); 1418 1419 final void parseSelectorList(String sl) { 1420 for (String s : sl.split("-")) { 1421 SelectorCollector<?> lastCollector = null; 1422 for (String as : s.split(",")) { 1423 if (!as.isEmpty()) { 1424 Selector<?> sel = selectorMap.get(as); 1425 if (sel == null) { 1426 errorat("jshell.err.feedback.not.a.valid.selector", as, s); 1427 return; 1428 } 1429 SelectorCollector<?> collector = sel.collector(this); 1430 if (lastCollector == null) { 1431 if (!collector.isEmpty()) { 1432 errorat("jshell.err.feedback.multiple.sections", as, s); 1433 return; 1434 } 1435 } else if (collector != lastCollector) { 1436 errorat("jshell.err.feedback.different.selector.kinds", as, s); 1437 return; 1438 } 1439 collector.add(sel); 1440 lastCollector = collector; 1441 } 1442 } 1443 } 1444 } 1445 } 1446 } 1447} 1448