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