Feedback.java revision 3420:9291bcd53e07
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.EnumSet; 32import java.util.HashMap; 33import java.util.Iterator; 34import java.util.List; 35import java.util.Locale; 36import java.util.Map; 37import java.util.Map.Entry; 38import java.util.regex.Matcher; 39import java.util.regex.Pattern; 40import static java.util.stream.Collectors.joining; 41 42/** 43 * Feedback customization support 44 * 45 * @author Robert Field 46 */ 47class Feedback { 48 49 // Patern for substituted fields within a customized format string 50 private static final Pattern FIELD_PATTERN = Pattern.compile("\\{(.*?)\\}"); 51 52 // Internal field name for truncation length 53 private static final String TRUNCATION_FIELD = "<truncation>"; 54 55 // For encoding to Properties String 56 private static final String RECORD_SEPARATOR = "\u241E"; 57 58 // Current mode 59 private Mode mode = new Mode("", false); // initial value placeholder during start-up 60 61 // Mapping of mode name to mode 62 private final Map<String, Mode> modeMap = new HashMap<>(); 63 64 // Mapping of mode names to encoded retained mode 65 private final Map<String, String> retainedMap = new HashMap<>(); 66 67 // Mapping selector enum names to enums 68 private final Map<String, Selector<?>> selectorMap = new HashMap<>(); 69 70 private static final long ALWAYS = bits(FormatCase.all, FormatAction.all, FormatWhen.all, 71 FormatResolve.all, FormatUnresolved.all, FormatErrors.all); 72 private static final long ANY = 0L; 73 74 public boolean shouldDisplayCommandFluff() { 75 return mode.commandFluff; 76 } 77 78 public String getPre() { 79 return mode.format("pre", ANY); 80 } 81 82 public String getPost() { 83 return mode.format("post", ANY); 84 } 85 86 public String getErrorPre() { 87 return mode.format("errorpre", ANY); 88 } 89 90 public String getErrorPost() { 91 return mode.format("errorpost", ANY); 92 } 93 94 public String format(FormatCase fc, FormatAction fa, FormatWhen fw, 95 FormatResolve fr, FormatUnresolved fu, FormatErrors fe, 96 String name, String type, String value, String unresolved, List<String> errorLines) { 97 return mode.format(fc, fa, fw, fr, fu, fe, 98 name, type, value, unresolved, errorLines); 99 } 100 101 public String getPrompt(String nextId) { 102 return mode.getPrompt(nextId); 103 } 104 105 public String getContinuationPrompt(String nextId) { 106 return mode.getContinuationPrompt(nextId); 107 } 108 109 public boolean setFeedback(MessageHandler messageHandler, ArgTokenizer at) { 110 return new Setter(messageHandler, at).setFeedback(); 111 } 112 113 public boolean setFormat(MessageHandler messageHandler, ArgTokenizer at) { 114 return new Setter(messageHandler, at).setFormat(); 115 } 116 117 public boolean setTruncation(MessageHandler messageHandler, ArgTokenizer at) { 118 return new Setter(messageHandler, at).setTruncation(); 119 } 120 121 public boolean setNewMode(MessageHandler messageHandler, ArgTokenizer at) { 122 return new Setter(messageHandler, at).setNewMode(); 123 } 124 125 public boolean setPrompt(MessageHandler messageHandler, ArgTokenizer at) { 126 return new Setter(messageHandler, at).setPrompt(); 127 } 128 129 public String retainFeedback(MessageHandler messageHandler, ArgTokenizer at) { 130 return new Setter(messageHandler, at).retainFeedback(); 131 } 132 133 public String retainMode(MessageHandler messageHandler, ArgTokenizer at) { 134 return new Setter(messageHandler, at).retainMode(); 135 } 136 137 public boolean restoreEncodedModes(MessageHandler messageHandler, String encoded) { 138 return new Setter(messageHandler, new ArgTokenizer("")).restoreEncodedModes(encoded); 139 } 140 141 public void markModesReadOnly() { 142 modeMap.values().stream() 143 .forEach(m -> m.readOnly = true); 144 } 145 146 { 147 for (FormatCase e : EnumSet.allOf(FormatCase.class)) 148 selectorMap.put(e.name().toLowerCase(Locale.US), e); 149 for (FormatAction e : EnumSet.allOf(FormatAction.class)) 150 selectorMap.put(e.name().toLowerCase(Locale.US), e); 151 for (FormatResolve e : EnumSet.allOf(FormatResolve.class)) 152 selectorMap.put(e.name().toLowerCase(Locale.US), e); 153 for (FormatUnresolved e : EnumSet.allOf(FormatUnresolved.class)) 154 selectorMap.put(e.name().toLowerCase(Locale.US), e); 155 for (FormatErrors e : EnumSet.allOf(FormatErrors.class)) 156 selectorMap.put(e.name().toLowerCase(Locale.US), e); 157 for (FormatWhen e : EnumSet.allOf(FormatWhen.class)) 158 selectorMap.put(e.name().toLowerCase(Locale.US), e); 159 } 160 161 /** 162 * Holds all the context of a mode mode 163 */ 164 private static class Mode { 165 166 // Name of mode 167 final String name; 168 169 // Display command verification/information 170 final boolean commandFluff; 171 172 // Event cases: class, method, expression, ... 173 final Map<String, List<Setting>> cases; 174 175 boolean readOnly = false; 176 177 String prompt = "\n-> "; 178 String continuationPrompt = ">> "; 179 180 static class Setting { 181 final long enumBits; 182 final String format; 183 Setting(long enumBits, String format) { 184 this.enumBits = enumBits; 185 this.format = format; 186 } 187 } 188 189 /** 190 * Set up an empty mode. 191 * 192 * @param name 193 * @param commandFluff True if should display command fluff messages 194 */ 195 Mode(String name, boolean commandFluff) { 196 this.name = name; 197 this.commandFluff = commandFluff; 198 cases = new HashMap<>(); 199 add("name", new Setting(ALWAYS, "%1$s")); 200 add("type", new Setting(ALWAYS, "%2$s")); 201 add("value", new Setting(ALWAYS, "%3$s")); 202 add("unresolved", new Setting(ALWAYS, "%4$s")); 203 add("errors", new Setting(ALWAYS, "%5$s")); 204 add("err", new Setting(ALWAYS, "%6$s")); 205 206 add("errorline", new Setting(ALWAYS, " {err}%n")); 207 208 add("pre", new Setting(ALWAYS, "| ")); 209 add("post", new Setting(ALWAYS, "%n")); 210 add("errorpre", new Setting(ALWAYS, "| ")); 211 add("errorpost", new Setting(ALWAYS, "%n")); 212 } 213 214 /** 215 * Set up a copied mode. 216 * 217 * @param name 218 * @param commandFluff True if should display command fluff messages 219 * @param m Mode to copy, or null for no fresh 220 */ 221 Mode(String name, boolean commandFluff, Mode m) { 222 this.name = name; 223 this.commandFluff = commandFluff; 224 cases = new HashMap<>(); 225 226 m.cases.entrySet().stream() 227 .forEach(fes -> fes.getValue() 228 .forEach(ing -> add(fes.getKey(), ing))); 229 230 this.prompt = m.prompt; 231 this.continuationPrompt = m.continuationPrompt; 232 } 233 234 /** 235 * Set up a mode reconstituted from a preferences string. 236 * 237 * @param it the encoded Mode broken into String chunks, may contain 238 * subsequent encoded modes 239 */ 240 Mode(Iterator<String> it) { 241 this.name = it.next(); 242 this.commandFluff = Boolean.parseBoolean(it.next()); 243 this.prompt = it.next(); 244 this.continuationPrompt = it.next(); 245 cases = new HashMap<>(); 246 String field; 247 while (!(field = it.next()).equals("***")) { 248 String open = it.next(); 249 assert open.equals("("); 250 List<Setting> settings = new ArrayList<>(); 251 String bits; 252 while (!(bits = it.next()).equals(")")) { 253 String format = it.next(); 254 Setting ing = new Setting(Long.parseLong(bits), format); 255 settings.add(ing); 256 } 257 cases.put(field, settings); 258 } 259 } 260 261 /** 262 * Encodes the mode into a String so it can be saved in Preferences. 263 * 264 * @return the string representation 265 */ 266 String encode() { 267 List<String> el = new ArrayList<>(); 268 el.add(name); 269 el.add(String.valueOf(commandFluff)); 270 el.add(prompt); 271 el.add(continuationPrompt); 272 for (Entry<String, List<Setting>> es : cases.entrySet()) { 273 el.add(es.getKey()); 274 el.add("("); 275 for (Setting ing : es.getValue()) { 276 el.add(String.valueOf(ing.enumBits)); 277 el.add(ing.format); 278 } 279 el.add(")"); 280 } 281 el.add("***"); 282 return String.join(RECORD_SEPARATOR, el); 283 } 284 285 private boolean add(String field, Setting ing) { 286 List<Setting> settings = cases.computeIfAbsent(field, k -> new ArrayList<>()); 287 if (settings == null) { 288 return false; 289 } 290 settings.add(ing); 291 return true; 292 } 293 294 void set(String field, 295 Collection<FormatCase> cc, Collection<FormatAction> ca, Collection<FormatWhen> cw, 296 Collection<FormatResolve> cr, Collection<FormatUnresolved> cu, Collection<FormatErrors> ce, 297 String format) { 298 long bits = bits(cc, ca, cw, cr, cu, ce); 299 set(field, bits, format); 300 } 301 302 void set(String field, long bits, String format) { 303 add(field, new Setting(bits, format)); 304 } 305 306 /** 307 * Lookup format Replace fields with context specific formats. 308 * 309 * @return format string 310 */ 311 String format(String field, long bits) { 312 List<Setting> settings = cases.get(field); 313 if (settings == null) { 314 return ""; //TODO error? 315 } 316 String format = null; 317 for (int i = settings.size() - 1; i >= 0; --i) { 318 Setting ing = settings.get(i); 319 long mask = ing.enumBits; 320 if ((bits & mask) == bits) { 321 format = ing.format; 322 break; 323 } 324 } 325 if (format == null || format.isEmpty()) { 326 return ""; 327 } 328 Matcher m = FIELD_PATTERN.matcher(format); 329 StringBuffer sb = new StringBuffer(format.length()); 330 while (m.find()) { 331 String fieldName = m.group(1); 332 String sub = format(fieldName, bits); 333 m.appendReplacement(sb, Matcher.quoteReplacement(sub)); 334 } 335 m.appendTail(sb); 336 return sb.toString(); 337 } 338 339 // Compute the display output given full context and values 340 String format(FormatCase fc, FormatAction fa, FormatWhen fw, 341 FormatResolve fr, FormatUnresolved fu, FormatErrors fe, 342 String name, String type, String value, String unresolved, List<String> errorLines) { 343 // Convert the context into a bit representation used as selectors for store field formats 344 long bits = bits(fc, fa, fw, fr, fu, fe); 345 String fname = name==null? "" : name; 346 String ftype = type==null? "" : type; 347 // Compute the representation of value 348 String fvalue; 349 if (value==null) { 350 fvalue = ""; 351 } else { 352 // Retrieve the truncation length 353 String truncField = format(TRUNCATION_FIELD, bits); 354 if (truncField.isEmpty()) { 355 // No truncation set, use whole value 356 fvalue = value; 357 } else { 358 // Convert truncation length to int 359 // this is safe since it has been tested before it is set 360 int trunc = Integer.parseUnsignedInt(truncField); 361 if (value.length() > trunc) { 362 if (trunc <= 5) { 363 // Very short truncations have no room for "..." 364 fvalue = value.substring(0, trunc); 365 } else { 366 // Normal truncation, make total length equal truncation length 367 fvalue = value.substring(0, trunc - 4) + " ..."; 368 } 369 } else { 370 // Within truncation length, use whole value 371 fvalue = value; 372 } 373 } 374 } 375 String funresolved = unresolved==null? "" : unresolved; 376 String errors = errorLines.stream() 377 .map(el -> String.format( 378 format("errorline", bits), 379 fname, ftype, fvalue, funresolved, "*cannot-use-errors-here*", el)) 380 .collect(joining()); 381 return String.format( 382 format("display", bits), 383 fname, ftype, fvalue, funresolved, errors, "*cannot-use-err-here*"); 384 } 385 386 void setPrompts(String prompt, String continuationPrompt) { 387 this.prompt = prompt; 388 this.continuationPrompt = continuationPrompt; 389 } 390 391 String getPrompt(String nextId) { 392 return String.format(prompt, nextId); 393 } 394 395 String getContinuationPrompt(String nextId) { 396 return String.format(continuationPrompt, nextId); 397 } 398 } 399 400 // Representation of one instance of all the enum values as bits in a long 401 private static long bits(FormatCase fc, FormatAction fa, FormatWhen fw, 402 FormatResolve fr, FormatUnresolved fu, FormatErrors fe) { 403 long res = 0L; 404 res |= 1 << fc.ordinal(); 405 res <<= FormatAction.count; 406 res |= 1 << fa.ordinal(); 407 res <<= FormatWhen.count; 408 res |= 1 << fw.ordinal(); 409 res <<= FormatResolve.count; 410 res |= 1 << fr.ordinal(); 411 res <<= FormatUnresolved.count; 412 res |= 1 << fu.ordinal(); 413 res <<= FormatErrors.count; 414 res |= 1 << fe.ordinal(); 415 return res; 416 } 417 418 // Representation of a space of enum values as or'edbits in a long 419 private static long bits(Collection<FormatCase> cc, Collection<FormatAction> ca, Collection<FormatWhen> cw, 420 Collection<FormatResolve> cr, Collection<FormatUnresolved> cu, Collection<FormatErrors> ce) { 421 long res = 0L; 422 for (FormatCase fc : cc) 423 res |= 1 << fc.ordinal(); 424 res <<= FormatAction.count; 425 for (FormatAction fa : ca) 426 res |= 1 << fa.ordinal(); 427 res <<= FormatWhen.count; 428 for (FormatWhen fw : cw) 429 res |= 1 << fw.ordinal(); 430 res <<= FormatResolve.count; 431 for (FormatResolve fr : cr) 432 res |= 1 << fr.ordinal(); 433 res <<= FormatUnresolved.count; 434 for (FormatUnresolved fu : cu) 435 res |= 1 << fu.ordinal(); 436 res <<= FormatErrors.count; 437 for (FormatErrors fe : ce) 438 res |= 1 << fe.ordinal(); 439 return res; 440 } 441 442 interface Selector<E extends Enum<E> & Selector<E>> { 443 SelectorCollector<E> collector(Setter.SelectorList sl); 444 String doc(); 445 } 446 447 /** 448 * The event cases 449 */ 450 public enum FormatCase implements Selector<FormatCase> { 451 IMPORT("import declaration"), 452 CLASS("class declaration"), 453 INTERFACE("interface declaration"), 454 ENUM("enum declaration"), 455 ANNOTATION("annotation interface declaration"), 456 METHOD("method declaration -- note: {type}==parameter-types"), 457 VARDECL("variable declaration without init"), 458 VARINIT("variable declaration with init"), 459 EXPRESSION("expression -- note: {name}==scratch-variable-name"), 460 VARVALUE("variable value expression"), 461 ASSIGNMENT("assign variable"), 462 STATEMENT("statement"); 463 String doc; 464 static final EnumSet<FormatCase> all = EnumSet.allOf(FormatCase.class); 465 static final int count = all.size(); 466 467 @Override 468 public SelectorCollector<FormatCase> collector(Setter.SelectorList sl) { 469 return sl.cases; 470 } 471 472 @Override 473 public String doc() { 474 return doc; 475 } 476 477 private FormatCase(String doc) { 478 this.doc = doc; 479 } 480 } 481 482 /** 483 * The event actions 484 */ 485 public enum FormatAction implements Selector<FormatAction> { 486 ADDED("snippet has been added"), 487 MODIFIED("an existing snippet has been modified"), 488 REPLACED("an existing snippet has been replaced with a new snippet"), 489 OVERWROTE("an existing snippet has been overwritten"), 490 DROPPED("snippet has been dropped"), 491 USED("snippet was used when it cannot be"); 492 String doc; 493 static final EnumSet<FormatAction> all = EnumSet.allOf(FormatAction.class); 494 static final int count = all.size(); 495 496 @Override 497 public SelectorCollector<FormatAction> collector(Setter.SelectorList sl) { 498 return sl.actions; 499 } 500 501 @Override 502 public String doc() { 503 return doc; 504 } 505 506 private FormatAction(String doc) { 507 this.doc = doc; 508 } 509 } 510 511 /** 512 * When the event occurs: primary or update 513 */ 514 public enum FormatWhen implements Selector<FormatWhen> { 515 PRIMARY("the entered snippet"), 516 UPDATE("an update to a dependent snippet"); 517 String doc; 518 static final EnumSet<FormatWhen> all = EnumSet.allOf(FormatWhen.class); 519 static final int count = all.size(); 520 521 @Override 522 public SelectorCollector<FormatWhen> collector(Setter.SelectorList sl) { 523 return sl.whens; 524 } 525 526 @Override 527 public String doc() { 528 return doc; 529 } 530 531 private FormatWhen(String doc) { 532 this.doc = doc; 533 } 534 } 535 536 /** 537 * Resolution problems 538 */ 539 public enum FormatResolve implements Selector<FormatResolve> { 540 OK("resolved correctly"), 541 DEFINED("defined despite recoverably unresolved references"), 542 NOTDEFINED("not defined because of recoverably unresolved references"); 543 String doc; 544 static final EnumSet<FormatResolve> all = EnumSet.allOf(FormatResolve.class); 545 static final int count = all.size(); 546 547 @Override 548 public SelectorCollector<FormatResolve> collector(Setter.SelectorList sl) { 549 return sl.resolves; 550 } 551 552 @Override 553 public String doc() { 554 return doc; 555 } 556 557 private FormatResolve(String doc) { 558 this.doc = doc; 559 } 560 } 561 562 /** 563 * Count of unresolved references 564 */ 565 public enum FormatUnresolved implements Selector<FormatUnresolved> { 566 UNRESOLVED0("no names are unresolved"), 567 UNRESOLVED1("one name is unresolved"), 568 UNRESOLVED2("two or more names are unresolved"); 569 String doc; 570 static final EnumSet<FormatUnresolved> all = EnumSet.allOf(FormatUnresolved.class); 571 static final int count = all.size(); 572 573 @Override 574 public SelectorCollector<FormatUnresolved> collector(Setter.SelectorList sl) { 575 return sl.unresolvedCounts; 576 } 577 578 @Override 579 public String doc() { 580 return doc; 581 } 582 583 private FormatUnresolved(String doc) { 584 this.doc = doc; 585 } 586 } 587 588 /** 589 * Count of unresolved references 590 */ 591 public enum FormatErrors implements Selector<FormatErrors> { 592 ERROR0("no errors"), 593 ERROR1("one error"), 594 ERROR2("two or more errors"); 595 String doc; 596 static final EnumSet<FormatErrors> all = EnumSet.allOf(FormatErrors.class); 597 static final int count = all.size(); 598 599 @Override 600 public SelectorCollector<FormatErrors> collector(Setter.SelectorList sl) { 601 return sl.errorCounts; 602 } 603 604 @Override 605 public String doc() { 606 return doc; 607 } 608 609 private FormatErrors(String doc) { 610 this.doc = doc; 611 } 612 } 613 614 class SelectorCollector<E extends Enum<E> & Selector<E>> { 615 final EnumSet<E> all; 616 EnumSet<E> set = null; 617 SelectorCollector(EnumSet<E> all) { 618 this.all = all; 619 } 620 void add(Object o) { 621 @SuppressWarnings("unchecked") 622 E e = (E) o; 623 if (set == null) { 624 set = EnumSet.of(e); 625 } else { 626 set.add(e); 627 } 628 } 629 630 boolean isEmpty() { 631 return set == null; 632 } 633 634 EnumSet<E> getSet() { 635 return set == null 636 ? all 637 : set; 638 } 639 } 640 641 // Class used to set custom eval output formats 642 // For both /set format -- Parse arguments, setting custom format, or printing error 643 private class Setter { 644 645 private final ArgTokenizer at; 646 private final MessageHandler messageHandler; 647 boolean valid = true; 648 649 Setter(MessageHandler messageHandler, ArgTokenizer at) { 650 this.messageHandler = messageHandler; 651 this.at = at; 652 } 653 654 void fluff(String format, Object... args) { 655 messageHandler.fluff(format, args); 656 } 657 658 void fluffmsg(String messageKey, Object... args) { 659 messageHandler.fluffmsg(messageKey, args); 660 } 661 662 void errorat(String messageKey, Object... args) { 663 Object[] a2 = Arrays.copyOf(args, args.length + 2); 664 a2[args.length] = at.whole(); 665 messageHandler.errormsg(messageKey, a2); 666 } 667 668 // For /set prompt <mode> "<prompt>" "<continuation-prompt>" 669 boolean setPrompt() { 670 Mode m = nextMode(); 671 if (valid && m.readOnly) { 672 errorat("jshell.err.not.valid.with.predefined.mode", m.name); 673 valid = false; 674 } 675 String prompt = valid ? nextFormat() : null; 676 String continuationPrompt = valid ? nextFormat() : null; 677 if (valid) { 678 m.setPrompts(prompt, continuationPrompt); 679 } else { 680 fluffmsg("jshell.msg.see", "/help /set prompt"); 681 } 682 return valid; 683 } 684 685 // For /set newmode <new-mode> [-command|-quiet [<old-mode>]] 686 boolean setNewMode() { 687 String umode = at.next(); 688 if (umode == null || !at.isIdentifier()) { 689 errorat("jshell.err.feedback.expected.new.feedback.mode"); 690 valid = false; 691 } 692 if (modeMap.containsKey(umode)) { 693 errorat("jshell.err.feedback.expected.mode.name", umode); 694 valid = false; 695 } 696 String[] fluffOpt = at.next("-command", "-quiet"); 697 boolean fluff = fluffOpt == null || fluffOpt.length != 1 || "-command".equals(fluffOpt[0]); 698 if (fluffOpt != null && fluffOpt.length != 1) { 699 errorat("jshell.err.feedback.command.quiet"); 700 valid = false; 701 } 702 Mode om = null; 703 String omode = at.next(); 704 if (omode != null) { 705 om = toMode(omode); 706 } 707 if (valid) { 708 Mode nm = (om != null) 709 ? new Mode(umode, fluff, om) 710 : new Mode(umode, fluff); 711 modeMap.put(umode, nm); 712 fluffmsg("jshell.msg.feedback.new.mode", nm.name); 713 } else { 714 fluffmsg("jshell.msg.see", "/help /set newmode"); 715 } 716 return valid; 717 } 718 719 // For /set feedback <mode> 720 boolean setFeedback() { 721 Mode m = nextMode(); 722 if (valid) { 723 mode = m; 724 fluffmsg("jshell.msg.feedback.mode", mode.name); 725 } else { 726 fluffmsg("jshell.msg.see", "/help /set feedback"); 727 printFeedbackModes(); 728 } 729 return valid; 730 } 731 732 // For /set format <mode> "<format>" <selector>... 733 boolean setFormat() { 734 Mode m = nextMode(); 735 if (valid && m.readOnly) { 736 errorat("jshell.err.not.valid.with.predefined.mode", m.name); 737 valid = false; 738 } 739 String field = at.next(); 740 if (field == null || !at.isIdentifier()) { 741 errorat("jshell.err.feedback.expected.field"); 742 valid = false; 743 } 744 String format = valid ? nextFormat() : null; 745 return installFormat(m, field, format, "/help /set format"); 746 } 747 748 // For /set truncation <mode> <length> <selector>... 749 boolean setTruncation() { 750 Mode m = nextMode(); 751 if (valid && m.readOnly) { 752 errorat("jshell.err.not.valid.with.predefined.mode", m.name); 753 valid = false; 754 } 755 String length = at.next(); 756 if (length == null) { 757 errorat("jshell.err.truncation.expected.length"); 758 valid = false; 759 } else { 760 try { 761 // Assure that integer format is correct 762 Integer.parseUnsignedInt(length); 763 } catch (NumberFormatException ex) { 764 errorat("jshell.err.truncation.length.not.integer", length); 765 valid = false; 766 } 767 } 768 // install length into an internal format field 769 return installFormat(m, TRUNCATION_FIELD, length, "/help /set truncation"); 770 } 771 772 String retainFeedback() { 773 String umode = at.next(); 774 if (umode != null) { 775 Mode m = toMode(umode); 776 if (valid && !m.readOnly && !retainedMap.containsKey(m.name)) { 777 errorat("jshell.err.retained.feedback.mode.must.be.retained.or.predefined"); 778 valid = false; 779 } 780 if (valid) { 781 mode = m; 782 fluffmsg("jshell.msg.feedback.mode", mode.name); 783 } else { 784 fluffmsg("jshell.msg.see", "/help /retain feedback"); 785 return null; 786 } 787 } 788 return mode.name; 789 } 790 791 String retainMode() { 792 Mode m = nextMode(); 793 if (valid && m.readOnly) { 794 errorat("jshell.err.not.valid.with.predefined.mode", m.name); 795 valid = false; 796 } 797 if (valid) { 798 retainedMap.put(m.name, m.encode()); 799 return String.join(RECORD_SEPARATOR, retainedMap.values()); 800 } else { 801 fluffmsg("jshell.msg.see", "/help /retain mode"); 802 return null; 803 } 804 } 805 806 boolean restoreEncodedModes(String allEncoded) { 807 // Iterate over each record in each encoded mode 808 String[] ms = allEncoded.split(RECORD_SEPARATOR); 809 Iterator<String> itr = Arrays.asList(ms).iterator(); 810 while (itr.hasNext()) { 811 // Reconstruct the encoded mode 812 Mode m = new Mode(itr); 813 modeMap.put(m.name, m); 814 // Continue to retain it a new retains occur 815 retainedMap.put(m.name, m.encode()); 816 } 817 return true; 818 } 819 820 // install the format of a field under parsed selectors 821 boolean installFormat(Mode m, String field, String format, String help) { 822 String slRaw; 823 List<SelectorList> slList = new ArrayList<>(); 824 while (valid && (slRaw = at.next()) != null) { 825 SelectorList sl = new SelectorList(); 826 sl.parseSelectorList(slRaw); 827 slList.add(sl); 828 } 829 if (valid) { 830 if (slList.isEmpty()) { 831 // No selectors specified, then always the format 832 m.set(field, ALWAYS, format); 833 } else { 834 // Set the format of the field for specified selector 835 slList.stream() 836 .forEach(sl -> m.set(field, 837 sl.cases.getSet(), sl.actions.getSet(), sl.whens.getSet(), 838 sl.resolves.getSet(), sl.unresolvedCounts.getSet(), sl.errorCounts.getSet(), 839 format)); 840 } 841 } else { 842 fluffmsg("jshell.msg.see", help); 843 } 844 return valid; 845 } 846 847 Mode nextMode() { 848 String umode = at.next(); 849 return toMode(umode); 850 } 851 852 Mode toMode(String umode) { 853 if (umode == null || !at.isIdentifier()) { 854 errorat("jshell.err.feedback.expected.mode"); 855 valid = false; 856 return null; 857 } 858 Mode m = modeMap.get(umode); 859 if (m != null) { 860 return m; 861 } 862 // Failing an exact match, go searching 863 Mode[] matches = modeMap.entrySet().stream() 864 .filter(e -> e.getKey().startsWith(umode)) 865 .map(e -> e.getValue()) 866 .toArray(size -> new Mode[size]); 867 if (matches.length == 1) { 868 return matches[0]; 869 } else { 870 valid = false; 871 if (matches.length == 0) { 872 errorat("jshell.err.feedback.does.not.match.mode", umode); 873 } else { 874 errorat("jshell.err.feedback.ambiguous.mode", umode); 875 } 876 printFeedbackModes(); 877 return null; 878 } 879 } 880 881 void printFeedbackModes() { 882 fluffmsg("jshell.msg.feedback.mode.following"); 883 modeMap.keySet().stream() 884 .forEach(mk -> fluff(" %s", mk)); 885 } 886 887 // Test if the format string is correctly 888 final String nextFormat() { 889 String format = at.next(); 890 if (format == null) { 891 errorat("jshell.err.feedback.expected.format"); 892 valid = false; 893 return null; 894 } 895 if (!at.isQuoted()) { 896 errorat("jshell.err.feedback.must.be.quoted", format); 897 valid = false; 898 return null; 899 } 900 return format; 901 } 902 903 class SelectorList { 904 905 SelectorCollector<FormatCase> cases = new SelectorCollector<>(FormatCase.all); 906 SelectorCollector<FormatAction> actions = new SelectorCollector<>(FormatAction.all); 907 SelectorCollector<FormatWhen> whens = new SelectorCollector<>(FormatWhen.all); 908 SelectorCollector<FormatResolve> resolves = new SelectorCollector<>(FormatResolve.all); 909 SelectorCollector<FormatUnresolved> unresolvedCounts = new SelectorCollector<>(FormatUnresolved.all); 910 SelectorCollector<FormatErrors> errorCounts = new SelectorCollector<>(FormatErrors.all); 911 912 final void parseSelectorList(String sl) { 913 for (String s : sl.split("-")) { 914 SelectorCollector<?> lastCollector = null; 915 for (String as : s.split(",")) { 916 if (!as.isEmpty()) { 917 Selector<?> sel = selectorMap.get(as); 918 if (sel == null) { 919 errorat("jshell.err.feedback.not.a.valid.selector", as, s); 920 valid = false; 921 return; 922 } 923 SelectorCollector<?> collector = sel.collector(this); 924 if (lastCollector == null) { 925 if (!collector.isEmpty()) { 926 errorat("jshell.err.feedback.multiple.sections", as, s); 927 valid = false; 928 return; 929 } 930 } else if (collector != lastCollector) { 931 errorat("jshell.err.feedback.different.selector.kinds", as, s); 932 valid = false; 933 return; 934 } 935 collector.add(sel); 936 lastCollector = collector; 937 } 938 } 939 } 940 } 941 } 942 } 943} 944