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