1 /* 2 * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.internal.jshell.tool; 27 28 import java.util.ArrayList; 29 import java.util.Arrays; 30 import java.util.Collection; 31 import java.util.Collections; 32 import java.util.EnumSet; 33 import java.util.HashMap; 34 import java.util.HashSet; 35 import java.util.Iterator; 36 import java.util.List; 37 import java.util.Locale; 38 import java.util.Map; 39 import java.util.Map.Entry; 40 import java.util.Objects; 41 import java.util.Set; 42 import java.util.StringJoiner; 43 import java.util.function.BiConsumer; 44 import java.util.function.BinaryOperator; 45 import java.util.function.Consumer; 46 import java.util.function.Function; 47 import java.util.function.Supplier; 48 import java.util.regex.Matcher; 49 import java.util.regex.Pattern; 50 import java.util.stream.Collector; 51 import static java.util.stream.Collectors.joining; 52 import static java.util.stream.Collectors.toMap; 53 import static jdk.internal.jshell.tool.ContinuousCompletionProvider.PERFECT_MATCHER; 54 import jdk.internal.jshell.tool.JShellTool.CompletionProvider; 55 import static jdk.internal.jshell.tool.JShellTool.EMPTY_COMPLETION_PROVIDER; 56 57 /** 58 * Feedback customization support 59 * 60 * @author Robert Field 61 */ 62 class 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 shouldDisplayCommandFluff()92 public boolean shouldDisplayCommandFluff() { 93 return mode.commandFluff; 94 } 95 getPre()96 public String getPre() { 97 return mode.format("pre", ANY); 98 } 99 getPost()100 public String getPost() { 101 return mode.format("post", ANY); 102 } 103 getErrorPre()104 public String getErrorPre() { 105 return mode.format("errorpre", ANY); 106 } 107 getErrorPost()108 public String getErrorPost() { 109 return mode.format("errorpost", ANY); 110 } 111 format(FormatCase fc, FormatAction fa, FormatWhen fw, FormatResolve fr, FormatUnresolved fu, FormatErrors fe, String name, String type, String value, String unresolved, List<String> errorLines)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 format(String field, FormatCase fc, FormatAction fa, FormatWhen fw, FormatResolve fr, FormatUnresolved fu, FormatErrors fe, String name, String type, String value, String unresolved, List<String> errorLines)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 truncateVarValue(String value)126 public String truncateVarValue(String value) { 127 return mode.truncateVarValue(value); 128 } 129 getPrompt(String nextId)130 public String getPrompt(String nextId) { 131 return mode.getPrompt(nextId); 132 } 133 getContinuationPrompt(String nextId)134 public String getContinuationPrompt(String nextId) { 135 return mode.getContinuationPrompt(nextId); 136 } 137 setFeedback(MessageHandler messageHandler, ArgTokenizer at, Consumer<String> retainer)138 public boolean setFeedback(MessageHandler messageHandler, ArgTokenizer at, Consumer<String> retainer) { 139 return new Setter(messageHandler, at).setFeedback(retainer); 140 } 141 setFormat(MessageHandler messageHandler, ArgTokenizer at)142 public boolean setFormat(MessageHandler messageHandler, ArgTokenizer at) { 143 return new Setter(messageHandler, at).setFormat(); 144 } 145 setTruncation(MessageHandler messageHandler, ArgTokenizer at)146 public boolean setTruncation(MessageHandler messageHandler, ArgTokenizer at) { 147 return new Setter(messageHandler, at).setTruncation(); 148 } 149 setMode(MessageHandler messageHandler, ArgTokenizer at, Consumer<String> retainer)150 public boolean setMode(MessageHandler messageHandler, ArgTokenizer at, Consumer<String> retainer) { 151 return new Setter(messageHandler, at).setMode(retainer); 152 } 153 setPrompt(MessageHandler messageHandler, ArgTokenizer at)154 public boolean setPrompt(MessageHandler messageHandler, ArgTokenizer at) { 155 return new Setter(messageHandler, at).setPrompt(); 156 } 157 restoreEncodedModes(MessageHandler messageHandler, String encoded)158 public boolean restoreEncodedModes(MessageHandler messageHandler, String encoded) { 159 return new Setter(messageHandler, new ArgTokenizer("<init>", "")).restoreEncodedModes(encoded); 160 } 161 markModesReadOnly()162 public void markModesReadOnly() { 163 modeMap.values().stream() 164 .forEach(m -> m.readOnly = true); 165 } 166 modeCompletions()167 JShellTool.CompletionProvider modeCompletions() { 168 return modeCompletions(EMPTY_COMPLETION_PROVIDER); 169 } 170 modeCompletions(CompletionProvider successor)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 Setting(long enumBits, String format)226 Setting(long enumBits, String format) { 227 this.enumBits = enumBits; 228 this.format = format; 229 } 230 231 @Override equals(Object o)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 hashCode()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 */ Mode(String name)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 */ Mode(String name, Mode m)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 */ Mode(Iterator<String> it)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 equals(Object o)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 hashCode()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 */ setCommandFluff(boolean fluff)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 */ encode()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 add(String field, Setting ing)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 set(String field, Collection<FormatCase> cc, Collection<FormatAction> ca, Collection<FormatWhen> cw, Collection<FormatResolve> cr, Collection<FormatUnresolved> cu, Collection<FormatErrors> ce, String format)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 set(String field, long bits, String format)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 */ format(String field, long bits)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 truncateVarValue(String value)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 truncateValue(String value, long bits)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 format(FormatCase fc, FormatAction fa, FormatWhen fw, FormatResolve fr, FormatUnresolved fu, FormatErrors fe, String name, String type, String value, String unresolved, List<String> errorLines)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 format(String field, FormatCase fc, FormatAction fa, FormatWhen fw, FormatResolve fr, FormatUnresolved fu, FormatErrors fe, String name, String type, String value, String unresolved, List<String> errorLines)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 setPrompts(String prompt, String continuationPrompt)498 void setPrompts(String prompt, String continuationPrompt) { 499 this.prompt = prompt; 500 this.continuationPrompt = continuationPrompt; 501 } 502 getPrompt(String nextId)503 String getPrompt(String nextId) { 504 return String.format(prompt, nextId); 505 } 506 getContinuationPrompt(String nextId)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 bits(FormatCase fc, FormatAction fa, FormatWhen fw, FormatResolve fr, FormatUnresolved fu, FormatErrors fe)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 bits(Collection<FormatCase> cc, Collection<FormatAction> ca, Collection<FormatWhen> cw, Collection<FormatResolve> cr, Collection<FormatUnresolved> cu, Collection<FormatErrors> ce)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 unpackEnumbits(long enumBits)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>> { collector(Setter.SelectorList sl)586 SelectorCollector<E> collector(Setter.SelectorList sl); doc()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 collector(Setter.SelectorList sl)611 public SelectorCollector<FormatCase> collector(Setter.SelectorList sl) { 612 return sl.cases; 613 } 614 615 @Override doc()616 public String doc() { 617 return doc; 618 } 619 FormatCase(String doc)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 collector(Setter.SelectorList sl)640 public SelectorCollector<FormatAction> collector(Setter.SelectorList sl) { 641 return sl.actions; 642 } 643 644 @Override doc()645 public String doc() { 646 return doc; 647 } 648 FormatAction(String doc)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 collector(Setter.SelectorList sl)665 public SelectorCollector<FormatWhen> collector(Setter.SelectorList sl) { 666 return sl.whens; 667 } 668 669 @Override doc()670 public String doc() { 671 return doc; 672 } 673 FormatWhen(String doc)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 collector(Setter.SelectorList sl)691 public SelectorCollector<FormatResolve> collector(Setter.SelectorList sl) { 692 return sl.resolves; 693 } 694 695 @Override doc()696 public String doc() { 697 return doc; 698 } 699 FormatResolve(String doc)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 collector(Setter.SelectorList sl)717 public SelectorCollector<FormatUnresolved> collector(Setter.SelectorList sl) { 718 return sl.unresolvedCounts; 719 } 720 721 @Override doc()722 public String doc() { 723 return doc; 724 } 725 FormatUnresolved(String doc)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 collector(Setter.SelectorList sl)743 public SelectorCollector<FormatErrors> collector(Setter.SelectorList sl) { 744 return sl.errorCounts; 745 } 746 747 @Override doc()748 public String doc() { 749 return doc; 750 } 751 FormatErrors(String doc)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; SelectorCollector(EnumSet<E> all)760 SelectorCollector(EnumSet<E> all) { 761 this.all = all; 762 } add(Object o)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 isEmpty()773 boolean isEmpty() { 774 return set == null; 775 } 776 getSet()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 Setter(MessageHandler messageHandler, ArgTokenizer at)792 Setter(MessageHandler messageHandler, ArgTokenizer at) { 793 this.messageHandler = messageHandler; 794 this.at = at; 795 at.allowedOptions("-retain"); 796 } 797 fluff(String format, Object... args)798 void fluff(String format, Object... args) { 799 messageHandler.fluff(format, args); 800 } 801 hard(String format, Object... args)802 void hard(String format, Object... args) { 803 messageHandler.hard(format, args); 804 } 805 fluffmsg(String messageKey, Object... args)806 void fluffmsg(String messageKey, Object... args) { 807 messageHandler.fluffmsg(messageKey, args); 808 } 809 hardmsg(String messageKey, Object... args)810 void hardmsg(String messageKey, Object... args) { 811 messageHandler.hardmsg(messageKey, args); 812 } 813 showFluff()814 boolean showFluff() { 815 return messageHandler.showFluff(); 816 } 817 errorat(String messageKey, Object... args)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 selectorsToString(SelectorSets u)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 selectorToString(StringBuilder sb, Set<E> c, E[] values)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... showFormatSettings(Mode sm, String f)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 showTruncationSettings(Mode sm)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 showPromptSettings(Mode sm)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 showModeSettings(String umode, String msg)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 showModeSettings(Mode sm)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 showFeedbackSetting()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>" setPrompt()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 */ setMode(Consumer<String> retainer)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 1026 ? !retainedMap.containsKey(umode) && !modeMap.containsKey(umode) 1027 : !modeMap.containsKey(umode)) { 1028 // Cannot delete a mode that does not exist 1029 errorat("jshell.err.mode.unknown", umode); 1030 } else if (omode != null) { 1031 // old mode is for creation 1032 errorat("jshell.err.unexpected.at.end", omode); 1033 } else if (mode.name.equals(umode)) { 1034 // Cannot delete the current mode out from under us 1035 errorat("jshell.err.cannot.delete.current.mode", umode); 1036 } else if (retainOption && retainedCurrentMode != null && 1037 retainedCurrentMode.name.equals(umode)) { 1038 // Cannot delete the retained mode or re-start will have an error 1039 errorat("jshell.err.cannot.delete.retained.mode", umode); 1040 } else { 1041 Mode m = modeMap.get(umode); 1042 if (m != null && m.readOnly) { 1043 errorat("jshell.err.not.valid.with.predefined.mode", umode); 1044 } else { 1045 // Remove the mode 1046 modeMap.remove(umode); 1047 if (retainOption) { 1048 // Remove the retained mode 1049 retainedMap.remove(umode); 1050 updateRetainedModes(); 1051 } 1052 } 1053 } 1054 } 1055 1056 void retain() { 1057 if (commandOption || quietOption) { 1058 errorat("jshell.err.conflicting.options"); 1059 } else if (omode != null) { 1060 // old mode is for creation 1061 errorat("jshell.err.unexpected.at.end", omode); 1062 } else { 1063 Mode m = modeMap.get(umode); 1064 if (m == null) { 1065 // can only retain existing modes 1066 errorat("jshell.err.mode.unknown", umode); 1067 } else if (m.readOnly) { 1068 errorat("jshell.err.not.valid.with.predefined.mode", umode); 1069 } else { 1070 // Add to local cache of retained current encodings 1071 retainedMap.put(m.name, m.encode()); 1072 updateRetainedModes(); 1073 } 1074 } 1075 } 1076 1077 void updateRetainedModes() { 1078 // Join all the retained encodings 1079 String encoded = String.join(RECORD_SEPARATOR, retainedMap.values()); 1080 // Retain it 1081 retainer.accept(encoded); 1082 } 1083 1084 void create() { 1085 if (commandOption && quietOption) { 1086 errorat("jshell.err.conflicting.options"); 1087 } else if (!commandOption && !quietOption) { 1088 errorat("jshell.err.mode.creation"); 1089 } else if (modeMap.containsKey(umode)) { 1090 // Mode already exists 1091 errorat("jshell.err.mode.exists", umode); 1092 } else { 1093 Mode om = searchForMode(omode); 1094 if (valid) { 1095 // We are copying an existing mode and/or creating a 1096 // brand-new mode -- in either case create from scratch 1097 Mode m = (om != null) 1098 ? new Mode(umode, om) 1099 : new Mode(umode); 1100 modeMap.put(umode, m); 1101 fluffmsg("jshell.msg.feedback.new.mode", m.name); 1102 m.setCommandFluff(commandOption); 1103 } 1104 } 1105 } 1106 1107 boolean set() { 1108 if (valid && !commandOption && !quietOption && !deleteOption && 1109 omode == null && !retainOption) { 1110 // Not a creation, deletion, or retain -- show mode(s) 1111 showModeSettings(umode, "jshell.err.mode.creation"); 1112 } else if (valid && umode == null) { 1113 errorat("jshell.err.missing.mode"); 1114 } else if (valid && deleteOption) { 1115 delete(); 1116 } else if (valid && retainOption) { 1117 retain(); 1118 } else if (valid) { 1119 create(); 1120 } 1121 if (!valid) { 1122 fluffmsg("jshell.msg.see", "/help /set mode"); 1123 } 1124 return valid; 1125 } 1126 } 1127 return new SetMode().set(); 1128 } 1129 1130 // For /set format <mode> <field> "<format>" <selector>... setFormat()1131 boolean setFormat() { 1132 Mode m = nextMode(); 1133 String field = toIdentifier(next(), "jshell.err.field.name"); 1134 String format = nextFormat(); 1135 if (valid && format == null) { 1136 if (field != null && m != null && !m.cases.containsKey(field)) { 1137 errorat("jshell.err.field.name", field); 1138 } else { 1139 showFormatSettings(m, field); 1140 } 1141 } else { 1142 installFormat(m, field, format, "/help /set format"); 1143 } 1144 return valid; 1145 } 1146 1147 // For /set truncation <mode> <length> <selector>... setTruncation()1148 boolean setTruncation() { 1149 Mode m = nextMode(); 1150 String length = next(); 1151 if (length == null) { 1152 showTruncationSettings(m); 1153 } else { 1154 try { 1155 // Assure that integer format is correct 1156 Integer.parseUnsignedInt(length); 1157 } catch (NumberFormatException ex) { 1158 errorat("jshell.err.truncation.length.not.integer", length); 1159 } 1160 // install length into an internal format field 1161 installFormat(m, TRUNCATION_FIELD, length, "/help /set truncation"); 1162 } 1163 return valid; 1164 } 1165 1166 // For /set feedback <mode> setFeedback(Consumer<String> retainer)1167 boolean setFeedback(Consumer<String> retainer) { 1168 String umode = next(); 1169 checkOptionsAndRemainingInput(); 1170 boolean retainOption = at.hasOption("-retain"); 1171 if (valid && umode == null && !retainOption) { 1172 showFeedbackSetting(); 1173 hard(""); 1174 showFeedbackModes(); 1175 return true; 1176 } 1177 if (valid) { 1178 Mode m = umode == null 1179 ? mode 1180 : searchForMode(toModeIdentifier(umode)); 1181 if (valid && retainOption && !m.readOnly && !retainedMap.containsKey(m.name)) { 1182 errorat("jshell.err.retained.feedback.mode.must.be.retained.or.predefined"); 1183 } 1184 if (valid) { 1185 if (umode != null) { 1186 mode = m; 1187 fluffmsg("jshell.msg.feedback.mode", mode.name); 1188 } 1189 if (retainOption) { 1190 retainedCurrentMode = m; 1191 retainer.accept(m.name); 1192 } 1193 } 1194 } 1195 if (!valid) { 1196 fluffmsg("jshell.msg.see", "/help /set feedback"); 1197 return false; 1198 } 1199 return true; 1200 } 1201 restoreEncodedModes(String allEncoded)1202 boolean restoreEncodedModes(String allEncoded) { 1203 try { 1204 // Iterate over each record in each encoded mode 1205 Iterator<String> itr = encodedModeIterator(allEncoded); 1206 while (itr.hasNext()) { 1207 // Reconstruct the encoded mode 1208 Mode m = new Mode(itr); 1209 modeMap.put(m.name, m); 1210 // Continue to retain it a new retains occur 1211 retainedMap.put(m.name, m.encode()); 1212 } 1213 return true; 1214 } catch (Throwable exc) { 1215 // Catastrophic corruption -- clear map 1216 errorat("jshell.err.retained.mode.failure", exc); 1217 retainedMap.clear(); 1218 return false; 1219 } 1220 } 1221 encodedModeIterator(String encoded)1222 Iterator<String> encodedModeIterator(String encoded) { 1223 String[] ms = encoded.split(RECORD_SEPARATOR); 1224 return Arrays.asList(ms).iterator(); 1225 } 1226 1227 // install the format of a field under parsed selectors installFormat(Mode m, String field, String format, String help)1228 void installFormat(Mode m, String field, String format, String help) { 1229 String slRaw; 1230 List<SelectorList> slList = new ArrayList<>(); 1231 while (valid && (slRaw = next()) != null) { 1232 SelectorList sl = new SelectorList(); 1233 sl.parseSelectorList(slRaw); 1234 slList.add(sl); 1235 } 1236 checkOptionsAndRemainingInput(); 1237 if (valid) { 1238 if (m.readOnly) { 1239 errorat("jshell.err.not.valid.with.predefined.mode", m.name); 1240 } else if (slList.isEmpty()) { 1241 // No selectors specified, then always the format 1242 m.set(field, ALWAYS, format); 1243 } else { 1244 // Set the format of the field for specified selector 1245 slList.stream() 1246 .forEach(sl -> m.set(field, 1247 sl.cases.getSet(), sl.actions.getSet(), sl.whens.getSet(), 1248 sl.resolves.getSet(), sl.unresolvedCounts.getSet(), sl.errorCounts.getSet(), 1249 format)); 1250 } 1251 } else { 1252 fluffmsg("jshell.msg.see", help); 1253 } 1254 } 1255 checkOptionsAndRemainingInput()1256 void checkOptionsAndRemainingInput() { 1257 String junk = at.remainder(); 1258 if (!junk.isEmpty()) { 1259 errorat("jshell.err.unexpected.at.end", junk); 1260 } else { 1261 String bad = at.badOptions(); 1262 if (!bad.isEmpty()) { 1263 errorat("jshell.err.unknown.option", bad); 1264 } 1265 } 1266 } 1267 next()1268 String next() { 1269 String s = at.next(); 1270 if (s == null) { 1271 checkOptionsAndRemainingInput(); 1272 } 1273 return s; 1274 } 1275 1276 /** 1277 * Check that the specified string is an identifier (Java identifier). 1278 * If null display the missing error. If it is not an identifier, 1279 * display the error. 1280 * 1281 * @param id the string to check, MUST be the most recently retrieved 1282 * token from 'at'. 1283 * @param missing null for no null error, otherwise the resource error to display if id is null 1284 * @param err the resource error to display if not an identifier 1285 * @return the identifier string, or null if null or not an identifier 1286 */ toIdentifier(String id, String err)1287 private String toIdentifier(String id, String err) { 1288 if (!valid || id == null) { 1289 return null; 1290 } 1291 if (at.isQuoted() || 1292 !id.codePoints().allMatch(Character::isJavaIdentifierPart)) { 1293 errorat(err, id); 1294 return null; 1295 } 1296 return id; 1297 } 1298 toModeIdentifier(String id)1299 private String toModeIdentifier(String id) { 1300 return toIdentifier(id, "jshell.err.mode.name"); 1301 } 1302 nextModeIdentifier()1303 private String nextModeIdentifier() { 1304 return toModeIdentifier(next()); 1305 } 1306 nextMode()1307 private Mode nextMode() { 1308 String umode = nextModeIdentifier(); 1309 return searchForMode(umode); 1310 } 1311 searchForMode(String umode)1312 private Mode searchForMode(String umode) { 1313 return searchForMode(umode, null); 1314 } 1315 searchForMode(String umode, String msg)1316 private Mode searchForMode(String umode, String msg) { 1317 if (!valid || umode == null) { 1318 return null; 1319 } 1320 Mode m = modeMap.get(umode); 1321 if (m != null) { 1322 return m; 1323 } 1324 // Failing an exact match, go searching 1325 Mode[] matches = modeMap.entrySet().stream() 1326 .filter(e -> e.getKey().startsWith(umode)) 1327 .map(Entry::getValue) 1328 .toArray(Mode[]::new); 1329 if (matches.length == 1) { 1330 return matches[0]; 1331 } else { 1332 if (msg != null) { 1333 hardmsg(msg, ""); 1334 } 1335 if (matches.length == 0) { 1336 errorat("jshell.err.feedback.does.not.match.mode", umode); 1337 } else { 1338 errorat("jshell.err.feedback.ambiguous.mode", umode); 1339 } 1340 if (showFluff()) { 1341 showFeedbackModes(); 1342 } 1343 return null; 1344 } 1345 } 1346 showFeedbackModes()1347 void showFeedbackModes() { 1348 if (!retainedMap.isEmpty()) { 1349 hardmsg("jshell.msg.feedback.retained.mode.following"); 1350 retainedMap.keySet().stream() 1351 .sorted() 1352 .forEach(mk -> hard(" %s", mk)); 1353 } 1354 hardmsg("jshell.msg.feedback.mode.following"); 1355 modeMap.keySet().stream() 1356 .sorted() 1357 .forEach(mk -> hard(" %s", mk)); 1358 } 1359 1360 // Read and test if the format string is correctly nextFormat()1361 private String nextFormat() { 1362 return toFormat(next()); 1363 } 1364 1365 // Test if the format string is correctly toFormat(String format)1366 private String toFormat(String format) { 1367 if (!valid || format == null) { 1368 return null; 1369 } 1370 if (!at.isQuoted()) { 1371 errorat("jshell.err.feedback.must.be.quoted", format); 1372 return null; 1373 } 1374 return format; 1375 } 1376 1377 // Convert to a quoted string toStringLiteral(String s)1378 private String toStringLiteral(String s) { 1379 StringBuilder sb = new StringBuilder(); 1380 sb.append('"'); 1381 final int length = s.length(); 1382 for (int offset = 0; offset < length;) { 1383 final int codepoint = s.codePointAt(offset); 1384 1385 switch (codepoint) { 1386 case '\b': 1387 sb.append("\\b"); 1388 break; 1389 case '\t': 1390 sb.append("\\t"); 1391 break; 1392 case '\n': 1393 sb.append("\\n"); 1394 break; 1395 case '\f': 1396 sb.append("\\f"); 1397 break; 1398 case '\r': 1399 sb.append("\\r"); 1400 break; 1401 case '\"': 1402 sb.append("\\\""); 1403 break; 1404 case '\'': 1405 sb.append("\\'"); 1406 break; 1407 case '\\': 1408 sb.append("\\\\"); 1409 break; 1410 default: 1411 if (codepoint < 040) { 1412 sb.append(String.format("\\%o", codepoint)); 1413 } else { 1414 sb.appendCodePoint(codepoint); 1415 } 1416 break; 1417 } 1418 1419 // do something with the codepoint 1420 offset += Character.charCount(codepoint); 1421 1422 } 1423 sb.append('"'); 1424 return sb.toString(); 1425 } 1426 1427 class SelectorList { 1428 1429 SelectorCollector<FormatCase> cases = new SelectorCollector<>(FormatCase.all); 1430 SelectorCollector<FormatAction> actions = new SelectorCollector<>(FormatAction.all); 1431 SelectorCollector<FormatWhen> whens = new SelectorCollector<>(FormatWhen.all); 1432 SelectorCollector<FormatResolve> resolves = new SelectorCollector<>(FormatResolve.all); 1433 SelectorCollector<FormatUnresolved> unresolvedCounts = new SelectorCollector<>(FormatUnresolved.all); 1434 SelectorCollector<FormatErrors> errorCounts = new SelectorCollector<>(FormatErrors.all); 1435 parseSelectorList(String sl)1436 final void parseSelectorList(String sl) { 1437 for (String s : sl.split("-")) { 1438 SelectorCollector<?> lastCollector = null; 1439 for (String as : s.split(",")) { 1440 if (!as.isEmpty()) { 1441 Selector<?> sel = selectorMap.get(as); 1442 if (sel == null) { 1443 errorat("jshell.err.feedback.not.a.valid.selector", as, s); 1444 return; 1445 } 1446 SelectorCollector<?> collector = sel.collector(this); 1447 if (lastCollector == null) { 1448 if (!collector.isEmpty()) { 1449 errorat("jshell.err.feedback.multiple.sections", as, s); 1450 return; 1451 } 1452 } else if (collector != lastCollector) { 1453 errorat("jshell.err.feedback.different.selector.kinds", as, s); 1454 return; 1455 } 1456 collector.add(sel); 1457 lastCollector = collector; 1458 } 1459 } 1460 } 1461 } 1462 } 1463 } 1464 } 1465