Feedback.java revision 3360:8102be8ddff2
172017Scg/*
272017Scg * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
372017Scg * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
472017Scg *
572017Scg * This code is free software; you can redistribute it and/or modify it
672017Scg * under the terms of the GNU General Public License version 2 only, as
772017Scg * published by the Free Software Foundation.  Oracle designates this
872017Scg * particular file as subject to the "Classpath" exception as provided
972017Scg * by Oracle in the LICENSE file that accompanied this code.
1072017Scg *
1172017Scg * This code is distributed in the hope that it will be useful, but WITHOUT
1272017Scg * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
1372017Scg * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
1472017Scg * version 2 for more details (a copy is included in the LICENSE file that
1572017Scg * accompanied this code).
1672017Scg *
1772017Scg * You should have received a copy of the GNU General Public License version
1872017Scg * 2 along with this work; if not, write to the Free Software Foundation,
1972017Scg * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
2072017Scg *
2172017Scg * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2272017Scg * or visit www.oracle.com if you need additional information or have any
2372017Scg * questions.
2472017Scg */
2572017Scg
2672017Scgpackage jdk.internal.jshell.tool;
2772017Scg
2872455Scgimport java.util.ArrayList;
2972455Scgimport java.util.Arrays;
3072455Scgimport java.util.Collection;
3172017Scgimport java.util.EnumSet;
3272017Scgimport java.util.HashMap;
3372017Scgimport java.util.List;
3472017Scgimport java.util.Locale;
3572017Scgimport java.util.Map;
3672017Scgimport java.util.regex.Matcher;
3772017Scgimport java.util.regex.Pattern;
3872017Scgimport static java.util.stream.Collectors.joining;
3972017Scg
4082180Scg/**
4182180Scg * Feedback customization support
4284771Sorion *
4372017Scg * @author Robert Field
4472017Scg */
4572017Scgclass Feedback {
4672017Scg
4772017Scg    // Patern for substituted fields within a customized format string
4872017Scg    private static final Pattern FIELD_PATTERN = Pattern.compile("\\{(.*?)\\}");
4972017Scg
5072017Scg    // Current mode
5172017Scg    private Mode mode = new Mode("", false); // initial value placeholder during start-up
5272017Scg
5372017Scg    // Mapping of mode names to mode modes
5472017Scg    private final Map<String, Mode> modeMap = new HashMap<>();
5572017Scg
5672017Scg    // Mapping selector enum names to enums
5772017Scg    private final Map<String, Selector<?>> selectorMap = new HashMap<>();
5872017Scg
5972017Scg    private static final long ALWAYS = bits(FormatCase.all, FormatAction.all, FormatWhen.all,
6072017Scg            FormatResolve.all, FormatUnresolved.all, FormatErrors.all);
6172017Scg    private static final long ANY = 0L;
6272017Scg
6372017Scg    public boolean shouldDisplayCommandFluff() {
6472017Scg        return mode.commandFluff;
6572017Scg    }
6672017Scg
6772017Scg    public String getPre() {
6872017Scg        return mode.format("pre", ANY);
6972017Scg    }
7072017Scg
7174763Scg    public String getPost() {
7274763Scg        return mode.format("post", ANY);
7372017Scg    }
7472455Scg
7572455Scg    public String getErrorPre() {
7672455Scg        return mode.format("errorpre", ANY);
7772017Scg    }
7872017Scg
7972017Scg    public String getErrorPost() {
8072017Scg        return mode.format("errorpost", ANY);
8172017Scg    }
8272017Scg
8372017Scg    public String format(FormatCase fc, FormatAction fa, FormatWhen fw,
8472017Scg                    FormatResolve fr, FormatUnresolved fu, FormatErrors fe,
8572017Scg                    String name, String type, String value, String unresolved, List<String> errorLines) {
8672017Scg        return mode.format(fc, fa, fw, fr, fu, fe,
8772017Scg                name, type, value, unresolved, errorLines);
8872017Scg    }
8972017Scg
9072017Scg    public String getPrompt(String nextId) {
9172017Scg        return mode.getPrompt(nextId);
9272017Scg    }
9384771Sorion
9472017Scg    public String getContinuationPrompt(String nextId) {
9572017Scg        return mode.getContinuationPrompt(nextId);
9672017Scg    }
9772017Scg
9872017Scg    public boolean setFeedback(MessageHandler messageHandler, ArgTokenizer at) {
9972017Scg        return new Setter(messageHandler, at).setFeedback();
10072017Scg    }
10172017Scg
10272017Scg    public boolean setFormat(MessageHandler messageHandler, ArgTokenizer at) {
10372017Scg        return new Setter(messageHandler, at).setFormat();
10472017Scg    }
10572455Scg
10672017Scg    public boolean setNewMode(MessageHandler messageHandler, ArgTokenizer at) {
10772017Scg        return new Setter(messageHandler, at).setNewMode();
10872017Scg    }
10972017Scg
11072017Scg    public boolean setPrompt(MessageHandler messageHandler, ArgTokenizer at) {
11172017Scg        return new Setter(messageHandler, at).setPrompt();
11272017Scg    }
11372017Scg
11472017Scg    {
11572017Scg        for (FormatCase e : EnumSet.allOf(FormatCase.class))
11672017Scg            selectorMap.put(e.name().toLowerCase(Locale.US), e);
11772017Scg        for (FormatAction e : EnumSet.allOf(FormatAction.class))
11872017Scg            selectorMap.put(e.name().toLowerCase(Locale.US), e);
11972017Scg        for (FormatResolve e : EnumSet.allOf(FormatResolve.class))
12072017Scg            selectorMap.put(e.name().toLowerCase(Locale.US), e);
12172017Scg        for (FormatUnresolved e : EnumSet.allOf(FormatUnresolved.class))
12272017Scg            selectorMap.put(e.name().toLowerCase(Locale.US), e);
12372017Scg        for (FormatErrors e : EnumSet.allOf(FormatErrors.class))
12472017Scg            selectorMap.put(e.name().toLowerCase(Locale.US), e);
12572017Scg        for (FormatWhen e : EnumSet.allOf(FormatWhen.class))
12672017Scg            selectorMap.put(e.name().toLowerCase(Locale.US), e);
12772017Scg    }
12872017Scg
12972017Scg    /**
13072017Scg     * Holds all the context of a mode mode
13172017Scg     */
13272017Scg    private static class Mode {
13372017Scg
13472017Scg        // Name of mode
13572017Scg        final String name;
13672017Scg
13772017Scg        // Display command verification/information
13874763Scg        final boolean commandFluff;
13972017Scg
14072017Scg        // Event cases: class, method, expression, ...
14172017Scg        final Map<String, List<Setting>> cases;
14272017Scg
14372017Scg        String prompt = "\n-> ";
14472017Scg        String continuationPrompt = ">> ";
14572017Scg
14672017Scg        static class Setting {
14772017Scg            final long enumBits;
14872017Scg            final String format;
14972017Scg            Setting(long enumBits, String format) {
15072017Scg                this.enumBits = enumBits;
15172017Scg                this.format = format;
15272017Scg            }
15372017Scg        }
15472017Scg
15572017Scg        /**
15672017Scg         * Set up an empty mode.
15772017Scg         *
15872017Scg         * @param name
15972017Scg         * @param commandFluff True if should display command fluff messages
16072017Scg         */
16172017Scg        Mode(String name, boolean commandFluff) {
16272017Scg            this.name = name;
16372017Scg            this.commandFluff = commandFluff;
16472017Scg            cases = new HashMap<>();
16572017Scg            add("name",       new Setting(ALWAYS, "%1$s"));
16672017Scg            add("type",       new Setting(ALWAYS, "%2$s"));
16772017Scg            add("value",      new Setting(ALWAYS, "%3$s"));
16872017Scg            add("unresolved", new Setting(ALWAYS, "%4$s"));
16972017Scg            add("errors",     new Setting(ALWAYS, "%5$s"));
17072017Scg            add("err",        new Setting(ALWAYS, "%6$s"));
17172017Scg
17272017Scg            add("errorline",  new Setting(ALWAYS, "    {err}%n"));
17372017Scg
17472017Scg            add("pre",        new Setting(ALWAYS, "|  "));
17572017Scg            add("post",       new Setting(ALWAYS, "%n"));
17672017Scg            add("errorpre",   new Setting(ALWAYS, "|  "));
17772017Scg            add("errorpost",  new Setting(ALWAYS, "%n"));
17872017Scg        }
17972017Scg
18072017Scg        /**
18172017Scg         * Set up a copied mode.
18272017Scg         *
18372017Scg         * @param name
18472017Scg         * @param commandFluff True if should display command fluff messages
18572017Scg         * @param m Mode to copy, or null for no fresh
18672017Scg         */
18772017Scg        Mode(String name, boolean commandFluff, Mode m) {
18872017Scg            this.name = name;
18972017Scg            this.commandFluff = commandFluff;
19072017Scg            cases = new HashMap<>();
19172017Scg
19272017Scg            m.cases.entrySet().stream()
19372017Scg                    .forEach(fes -> fes.getValue()
19472017Scg                    .forEach(ing -> add(fes.getKey(), ing)));
19572017Scg
19672017Scg            this.prompt = m.prompt;
19772017Scg            this.continuationPrompt = m.continuationPrompt;
19872017Scg        }
19972017Scg
20072017Scg        private boolean add(String field, Setting ing) {
20172017Scg            List<Setting> settings =  cases.computeIfAbsent(field, k -> new ArrayList<>());
20272017Scg            if (settings == null) {
20372017Scg                return false;
20472017Scg            }
20572017Scg            settings.add(ing);
20672455Scg            return true;
20772017Scg        }
20872017Scg
20972017Scg        void set(String field,
21072017Scg                Collection<FormatCase> cc, Collection<FormatAction> ca, Collection<FormatWhen> cw,
21172017Scg                Collection<FormatResolve> cr, Collection<FormatUnresolved> cu, Collection<FormatErrors> ce,
21272017Scg                String format) {
21372017Scg            long bits = bits(cc, ca, cw, cr, cu, ce);
21472017Scg            set(field, bits, format);
21572017Scg        }
21672017Scg
21772017Scg        void set(String field, long bits, String format) {
21872017Scg            add(field, new Setting(bits, format));
21972017Scg        }
22072017Scg
22172017Scg        /**
22272017Scg         * Lookup format Replace fields with context specific formats.
22372017Scg         *
22472017Scg         * @return format string
22572017Scg         */
22672017Scg        String format(String field, long bits) {
22772017Scg            List<Setting> settings = cases.get(field);
22872017Scg            if (settings == null) {
22972017Scg                return ""; //TODO error?
23072017Scg            }
23172455Scg            String format = null;
23272017Scg            for (int i = settings.size() - 1; i >= 0; --i) {
23372017Scg                Setting ing = settings.get(i);
23472017Scg                long mask = ing.enumBits;
23572017Scg                if ((bits & mask) == bits) {
23672017Scg                    format = ing.format;
23772017Scg                    break;
23872017Scg                }
23972455Scg            }
24072017Scg            if (format == null || format.isEmpty()) {
24172017Scg                return "";
24272455Scg            }
24372017Scg            Matcher m = FIELD_PATTERN.matcher(format);
24472017Scg            StringBuffer sb = new StringBuffer(format.length());
24572017Scg            while (m.find()) {
24672017Scg                String fieldName = m.group(1);
24772017Scg                String sub = format(fieldName, bits);
24872017Scg                m.appendReplacement(sb, Matcher.quoteReplacement(sub));
24972017Scg            }
25072017Scg            m.appendTail(sb);
25172017Scg            return sb.toString();
25272017Scg        }
25372017Scg
25472017Scg        String format(FormatCase fc, FormatAction fa, FormatWhen fw,
25572455Scg                    FormatResolve fr, FormatUnresolved fu, FormatErrors fe,
25672017Scg                    String name, String type, String value, String unresolved, List<String> errorLines) {
25772017Scg            long bits = bits(fc, fa, fw, fr, fu, fe);
25872017Scg            String fname = name==null? "" : name;
25972017Scg            String ftype = type==null? "" : type;
26072455Scg            String fvalue = value==null? "" : value;
26172017Scg            String funresolved = unresolved==null? "" : unresolved;
26272017Scg            String errors = errorLines.stream()
26372017Scg                    .map(el -> String.format(
26472017Scg                            format("errorline", bits),
26572455Scg                            fname, ftype, fvalue, funresolved, "*cannot-use-errors-here*", el))
26672455Scg                    .collect(joining());
26772017Scg            return String.format(
26872017Scg                    format("display", bits),
26972017Scg                    fname, ftype, fvalue, funresolved, errors, "*cannot-use-err-here*");
27072017Scg        }
27172017Scg
27272017Scg        void setPrompts(String prompt, String continuationPrompt) {
27372017Scg            this.prompt = prompt;
27472017Scg            this.continuationPrompt = continuationPrompt;
27572017Scg        }
27672017Scg
27772017Scg        String getPrompt(String nextId) {
27872017Scg            return String.format(prompt, nextId);
27972017Scg        }
28072455Scg
28172455Scg        String getContinuationPrompt(String nextId) {
28272017Scg            return String.format(continuationPrompt, nextId);
28372017Scg        }
28472017Scg    }
28572017Scg
28672017Scg    // Representation of one instance of all the enum values as bits in a long
28772017Scg    private static long bits(FormatCase fc, FormatAction fa, FormatWhen fw,
28872017Scg            FormatResolve fr, FormatUnresolved fu, FormatErrors fe) {
28972455Scg        long res = 0L;
29072017Scg        res |= 1 << fc.ordinal();
29172017Scg        res <<= FormatAction.count;
29272017Scg        res |= 1 << fa.ordinal();
29372017Scg        res <<= FormatWhen.count;
29472017Scg        res |= 1 << fw.ordinal();
29572455Scg        res <<= FormatResolve.count;
29672017Scg        res |= 1 << fr.ordinal();
29772455Scg        res <<= FormatUnresolved.count;
29872017Scg        res |= 1 << fu.ordinal();
29972017Scg        res <<= FormatErrors.count;
30072017Scg        res |= 1 << fe.ordinal();
30172017Scg        return res;
30272017Scg    }
30372017Scg
30472017Scg    // Representation of a space of enum values as or'edbits in a long
30572017Scg    private static long bits(Collection<FormatCase> cc, Collection<FormatAction> ca, Collection<FormatWhen> cw,
30672017Scg                Collection<FormatResolve> cr, Collection<FormatUnresolved> cu, Collection<FormatErrors> ce) {
30772017Scg        long res = 0L;
30872017Scg        for (FormatCase fc : cc)
30972017Scg            res |= 1 << fc.ordinal();
31072017Scg        res <<= FormatAction.count;
31172017Scg        for (FormatAction fa : ca)
31272017Scg            res |= 1 << fa.ordinal();
31372017Scg        res <<= FormatWhen.count;
31474763Scg        for (FormatWhen fw : cw)
31572017Scg            res |= 1 << fw.ordinal();
31672017Scg        res <<= FormatResolve.count;
31772017Scg        for (FormatResolve fr : cr)
31872017Scg            res |= 1 << fr.ordinal();
31972017Scg        res <<= FormatUnresolved.count;
32084771Sorion        for (FormatUnresolved fu : cu)
32172017Scg            res |= 1 << fu.ordinal();
32272017Scg        res <<= FormatErrors.count;
32372017Scg        for (FormatErrors fe : ce)
32472017Scg            res |= 1 << fe.ordinal();
32572017Scg        return res;
32672017Scg    }
32772017Scg
32872017Scg    interface Selector<E extends Enum<E> & Selector<E>> {
32972455Scg        SelectorCollector<E> collector(Setter.SelectorList sl);
33072017Scg        String doc();
33172017Scg    }
33272017Scg
33372017Scg    /**
33472017Scg     * The event cases
33572017Scg     */
33672017Scg    public enum FormatCase implements Selector<FormatCase> {
33772017Scg        IMPORT("import declaration"),
33872017Scg        CLASS("class declaration"),
33972017Scg        INTERFACE("interface declaration"),
34072017Scg        ENUM("enum declaration"),
34172017Scg        ANNOTATION("annotation interface declaration"),
34272017Scg        METHOD("method declaration -- note: {type}==parameter-types"),
34372017Scg        VARDECL("variable declaration without init"),
34484771Sorion        VARINIT("variable declaration with init"),
34572455Scg        EXPRESSION("expression -- note: {name}==scratch-variable-name"),
34672017Scg        VARVALUE("variable value expression"),
34772017Scg        ASSIGNMENT("assign variable"),
34872017Scg        STATEMENT("statement");
34972017Scg        String doc;
35072017Scg        static final EnumSet<FormatCase> all = EnumSet.allOf(FormatCase.class);
35184771Sorion        static final int count = all.size();
35272455Scg
35372017Scg        @Override
35472017Scg        public SelectorCollector<FormatCase> collector(Setter.SelectorList sl) {
35572017Scg            return sl.cases;
35672017Scg        }
35784771Sorion
35872017Scg        @Override
35982837Sorion        public String doc() {
36072017Scg            return doc;
36172017Scg        }
36272017Scg
36372017Scg        private FormatCase(String doc) {
36472017Scg            this.doc = doc;
36572017Scg        }
36672017Scg    }
36772017Scg
36872017Scg    /**
36972017Scg     * The event actions
37072017Scg     */
37172017Scg    public enum FormatAction implements Selector<FormatAction> {
37272017Scg        ADDED("snippet has been added"),
37372017Scg        MODIFIED("an existing snippet has been modified"),
37472017Scg        REPLACED("an existing snippet has been replaced with a new snippet"),
37572017Scg        OVERWROTE("an existing snippet has been overwritten"),
37672017Scg        DROPPED("snippet has been dropped"),
37772017Scg        USED("snippet was used when it cannot be");
37872017Scg        String doc;
37972017Scg        static final EnumSet<FormatAction> all = EnumSet.allOf(FormatAction.class);
38072017Scg        static final int count = all.size();
38172017Scg
38272017Scg        @Override
38372017Scg        public SelectorCollector<FormatAction> collector(Setter.SelectorList sl) {
38472017Scg            return sl.actions;
38572017Scg        }
38672017Scg
38772017Scg        @Override
38872017Scg        public String doc() {
38972017Scg            return doc;
39072455Scg        }
39172017Scg
39272017Scg        private FormatAction(String doc) {
39372017Scg            this.doc = doc;
39472017Scg        }
39572017Scg    }
39672017Scg
39772017Scg    /**
39872017Scg     * When the event occurs: primary or update
39972017Scg     */
40072017Scg    public enum FormatWhen implements Selector<FormatWhen> {
40172017Scg        PRIMARY("the entered snippet"),
40272017Scg        UPDATE("an update to a dependent snippet");
40372017Scg        String doc;
40472017Scg        static final EnumSet<FormatWhen> all = EnumSet.allOf(FormatWhen.class);
40572017Scg        static final int count = all.size();
40672017Scg
40772017Scg        @Override
40872017Scg        public SelectorCollector<FormatWhen> collector(Setter.SelectorList sl) {
40972017Scg            return sl.whens;
41072017Scg        }
41172017Scg
41272017Scg        @Override
41372017Scg        public String doc() {
41472017Scg            return doc;
41572017Scg        }
41672017Scg
41772017Scg        private FormatWhen(String doc) {
41872017Scg            this.doc = doc;
41972017Scg        }
42072017Scg    }
42172017Scg
42272017Scg    /**
42372017Scg     * Resolution problems
42472017Scg     */
42572017Scg    public enum FormatResolve implements Selector<FormatResolve> {
42672017Scg        OK("resolved correctly"),
42772017Scg        DEFINED("defined despite recoverably unresolved references"),
42872017Scg        NOTDEFINED("not defined because of recoverably unresolved references");
42972017Scg        String doc;
43072017Scg        static final EnumSet<FormatResolve> all = EnumSet.allOf(FormatResolve.class);
43172017Scg        static final int count = all.size();
43272017Scg
43372017Scg        @Override
43472017Scg        public SelectorCollector<FormatResolve> collector(Setter.SelectorList sl) {
43572017Scg            return sl.resolves;
43672017Scg        }
43772017Scg
43872017Scg        @Override
43972017Scg        public String doc() {
44072017Scg            return doc;
44172017Scg        }
44274763Scg
44372017Scg        private FormatResolve(String doc) {
44472017Scg            this.doc = doc;
44572017Scg        }
44672017Scg    }
44772017Scg
44872017Scg    /**
44972017Scg     * Count of unresolved references
45072017Scg     */
45172017Scg    public enum FormatUnresolved implements Selector<FormatUnresolved> {
45272017Scg        UNRESOLVED0("no names are unresolved"),
45372017Scg        UNRESOLVED1("one name is unresolved"),
45472017Scg        UNRESOLVED2("two or more names are unresolved");
45572017Scg        String doc;
45672017Scg        static final EnumSet<FormatUnresolved> all = EnumSet.allOf(FormatUnresolved.class);
45772017Scg        static final int count = all.size();
45872017Scg
45972017Scg        @Override
46072017Scg        public SelectorCollector<FormatUnresolved> collector(Setter.SelectorList sl) {
46172017Scg            return sl.unresolvedCounts;
46272017Scg        }
46372017Scg
46472017Scg        @Override
46572017Scg        public String doc() {
46672017Scg            return doc;
46772017Scg        }
46872017Scg
46972017Scg        private FormatUnresolved(String doc) {
47072017Scg            this.doc = doc;
47172455Scg        }
47272017Scg    }
47372017Scg
47472455Scg    /**
47572017Scg     * Count of unresolved references
47672455Scg     */
47772017Scg    public enum FormatErrors implements Selector<FormatErrors> {
47872017Scg        ERROR0("no errors"),
47972017Scg        ERROR1("one error"),
48072017Scg        ERROR2("two or more errors");
48172017Scg        String doc;
48272017Scg        static final EnumSet<FormatErrors> all = EnumSet.allOf(FormatErrors.class);
48372017Scg        static final int count = all.size();
48472017Scg
48572455Scg        @Override
48672017Scg        public SelectorCollector<FormatErrors> collector(Setter.SelectorList sl) {
48772017Scg            return sl.errorCounts;
48872017Scg        }
48972017Scg
49072017Scg        @Override
49172017Scg        public String doc() {
49272017Scg            return doc;
49372017Scg        }
49472455Scg
49572017Scg        private FormatErrors(String doc) {
49672017Scg            this.doc = doc;
49772017Scg        }
49872017Scg    }
49972017Scg
50072017Scg    class SelectorCollector<E extends Enum<E> & Selector<E>> {
50172017Scg        final EnumSet<E> all;
50272017Scg        EnumSet<E> set = null;
50372017Scg        SelectorCollector(EnumSet<E> all) {
50472017Scg            this.all = all;
50572017Scg        }
50672017Scg        void add(Object o) {
50772017Scg            @SuppressWarnings("unchecked")
50872017Scg            E e = (E) o;
50972017Scg            if (set == null) {
51072017Scg                set = EnumSet.of(e);
51172455Scg            } else {
51272017Scg                set.add(e);
51372017Scg            }
51472017Scg        }
51572017Scg
51672017Scg        boolean isEmpty() {
51772017Scg            return set == null;
51872017Scg        }
51972017Scg
52072017Scg        EnumSet<E> getSet() {
52172017Scg            return set == null
52272017Scg                    ? all
52372017Scg                    : set;
52472017Scg        }
52572455Scg    }
52672017Scg
52772017Scg    // Class used to set custom eval output formats
52872017Scg    // For both /set format  -- Parse arguments, setting custom format, or printing error
52972455Scg    private class Setter {
53072017Scg
53172017Scg        private final ArgTokenizer at;
53272017Scg        private final MessageHandler messageHandler;
53372017Scg        boolean valid = true;
53472455Scg
53572017Scg        Setter(MessageHandler messageHandler, ArgTokenizer at) {
53672455Scg            this.messageHandler = messageHandler;
53772455Scg            this.at = at;
53872455Scg        }
53972455Scg
54072455Scg        void fluff(String format, Object... args) {
54172017Scg            messageHandler.fluff(format, args);
54272455Scg        }
54372455Scg
54472455Scg        void fluffmsg(String messageKey, Object... args) {
54572455Scg            messageHandler.fluffmsg(messageKey, args);
54672017Scg        }
54772017Scg
54872455Scg        void errorat(String messageKey, Object... args) {
54972455Scg            Object[] a2 = Arrays.copyOf(args, args.length + 2);
55072017Scg            a2[args.length] = at.whole();
55172017Scg            messageHandler.errormsg(messageKey, a2);
55272017Scg        }
55372017Scg
55472017Scg        // For /set prompt <mode> "<prompt>" "<continuation-prompt>"
55572017Scg        boolean setPrompt() {
55672017Scg            Mode m = nextMode();
55772017Scg            String prompt = nextFormat();
55872017Scg            String continuationPrompt = nextFormat();
55972017Scg            if (valid) {
56072017Scg                m.setPrompts(prompt, continuationPrompt);
56172017Scg            } else {
56272017Scg                fluffmsg("jshell.msg.see", "/help /set prompt");
56372455Scg            }
56472017Scg            return valid;
56572017Scg        }
56672017Scg
56772017Scg        // For /set newmode <new-mode> [command|quiet [<old-mode>]]
56872017Scg        boolean setNewMode() {
56972017Scg            String umode = at.next();
57072017Scg            if (umode == null) {
57172017Scg                errorat("jshell.err.feedback.expected.new.feedback.mode");
57272017Scg                valid = false;
57372455Scg            }
57472017Scg            if (modeMap.containsKey(umode)) {
57572017Scg                errorat("jshell.err.feedback.expected.mode.name", umode);
57672455Scg                valid = false;
57772017Scg            }
57872455Scg            String[] fluffOpt = at.next("command", "quiet");
57972455Scg            boolean fluff = fluffOpt == null || fluffOpt.length != 1 || "command".equals(fluffOpt[0]);
58072017Scg            if (fluffOpt != null && fluffOpt.length != 1) {
58172017Scg                errorat("jshell.err.feedback.command.quiet");
58272455Scg                valid = false;
58372017Scg            }
58472017Scg            Mode om = null;
58572017Scg            String omode = at.next();
58672017Scg            if (omode != null) {
58772455Scg                om = toMode(omode);
58872455Scg            }
58972455Scg            if (valid) {
59072017Scg                Mode nm = (om != null)
59172017Scg                        ? new Mode(umode, fluff, om)
59272017Scg                        : new Mode(umode, fluff);
59372017Scg                modeMap.put(umode, nm);
59472017Scg                fluffmsg("jshell.msg.feedback.new.mode", nm.name);
59572017Scg            } else {
59672455Scg                fluffmsg("jshell.msg.see", "/help /set newmode");
59772017Scg            }
59872017Scg            return valid;
59972017Scg        }
60072017Scg
60172017Scg        // For /set feedback <mode>
60272017Scg        boolean setFeedback() {
60372017Scg            Mode m = nextMode();
60472017Scg            if (valid && m != null) {
60572017Scg                mode = m;
60672017Scg                fluffmsg("jshell.msg.feedback.mode", mode.name);
60772017Scg            } else {
60872455Scg                fluffmsg("jshell.msg.see", "/help /set feedback");
60972017Scg                printFeedbackModes();
61072017Scg            }
61172017Scg            return valid;
61272455Scg        }
61372455Scg
61472017Scg        // For /set format <mode> "<format>" <selector>...
61572017Scg        boolean setFormat() {
61672017Scg            Mode m = nextMode();
61772017Scg            String field = at.next();
61872017Scg            if (field == null || at.isQuoted()) {
61972017Scg                errorat("jshell.err.feedback.expected.field");
62072017Scg                valid = false;
62172017Scg            }
62272017Scg            String format = valid? nextFormat() : null;
62372017Scg            String slRaw;
62472017Scg            List<SelectorList> slList = new ArrayList<>();
62572017Scg            while (valid && (slRaw = at.next()) != null) {
62672017Scg                SelectorList sl = new SelectorList();
62772017Scg                sl.parseSelectorList(slRaw);
62872017Scg                slList.add(sl);
62972017Scg            }
63072017Scg            if (valid) {
63172017Scg                if (slList.isEmpty()) {
63272017Scg                    m.set(field, ALWAYS, format);
63372455Scg                } else {
63472455Scg                    slList.stream()
63572455Scg                            .forEach(sl -> m.set(field,
63672017Scg                                sl.cases.getSet(), sl.actions.getSet(), sl.whens.getSet(),
63772017Scg                                sl.resolves.getSet(), sl.unresolvedCounts.getSet(), sl.errorCounts.getSet(),
63872017Scg                                format));
63972455Scg                }
64072017Scg            } else {
64172017Scg                fluffmsg("jshell.msg.see", "/help /set format");
64272455Scg            }
64372455Scg            return valid;
64472017Scg        }
64572017Scg
64672017Scg        Mode nextMode() {
64772017Scg            String umode = at.next();
64872017Scg            return toMode(umode);
64972017Scg        }
65072017Scg
65172017Scg        Mode toMode(String umode) {
65272455Scg            if (umode == null) {
65372017Scg                errorat("jshell.err.feedback.expected.mode");
65472017Scg                valid = false;
65572017Scg                return null;
65672017Scg            }
65772017Scg            Mode m = modeMap.get(umode);
65872017Scg            if (m != null) {
65972017Scg                return m;
66072017Scg            }
66172017Scg            // Failing an exact match, go searching
66272017Scg            Mode[] matches = modeMap.entrySet().stream()
66372455Scg                    .filter(e -> e.getKey().startsWith(umode))
66472017Scg                    .map(e -> e.getValue())
66572017Scg                    .toArray(size -> new Mode[size]);
66672017Scg            if (matches.length == 1) {
66772017Scg                return matches[0];
66872017Scg            } else {
66972017Scg                valid = false;
67072017Scg                if (matches.length == 0) {
67172017Scg                    errorat("jshell.err.feedback.does.not.match.mode", umode);
67272017Scg                } else {
67372017Scg                    errorat("jshell.err.feedback.ambiguous.mode", umode);
67472017Scg                }
67572017Scg                printFeedbackModes();
67672017Scg                return null;
67772017Scg            }
67872017Scg        }
67972017Scg
68072017Scg        void printFeedbackModes() {
68172017Scg            fluffmsg("jshell.msg.feedback.mode.following");
68272017Scg            modeMap.keySet().stream()
68372017Scg                    .forEach(mk -> fluff("   %s", mk));
68472017Scg        }
68572017Scg
68672017Scg        // Test if the format string is correctly
68772017Scg        final String nextFormat() {
68872017Scg            String format = at.next();
68972017Scg            if (format == null) {
69072017Scg                errorat("jshell.err.feedback.expected.format");
69172017Scg                valid = false;
69272017Scg                return null;
69372017Scg            }
69472017Scg            if (!at.isQuoted()) {
69572017Scg                errorat("jshell.err.feedback.must.be.quoted", format);
69672017Scg                valid = false;
69772017Scg                return null;
69872017Scg            }
69972017Scg            return format;
70072017Scg        }
70172017Scg
70272017Scg        class SelectorList {
70372017Scg
70472017Scg            SelectorCollector<FormatCase> cases = new SelectorCollector<>(FormatCase.all);
70572017Scg            SelectorCollector<FormatAction> actions = new SelectorCollector<>(FormatAction.all);
70672017Scg            SelectorCollector<FormatWhen> whens = new SelectorCollector<>(FormatWhen.all);
70772017Scg            SelectorCollector<FormatResolve> resolves = new SelectorCollector<>(FormatResolve.all);
70872017Scg            SelectorCollector<FormatUnresolved> unresolvedCounts = new SelectorCollector<>(FormatUnresolved.all);
70972017Scg            SelectorCollector<FormatErrors> errorCounts = new SelectorCollector<>(FormatErrors.all);
71072017Scg
71172017Scg            final void parseSelectorList(String sl) {
71272017Scg                for (String s : sl.split("-")) {
71372017Scg                    SelectorCollector<?> lastCollector = null;
71472017Scg                    for (String as : s.split(",")) {
71572017Scg                        if (!as.isEmpty()) {
71672017Scg                            Selector<?> sel = selectorMap.get(as);
71772455Scg                            if (sel == null) {
71872017Scg                                errorat("jshell.err.feedback.not.a.valid.selector", as, s);
71972455Scg                                valid = false;
72072455Scg                                return;
72172017Scg                            }
72272017Scg                            SelectorCollector<?> collector = sel.collector(this);
72372017Scg                            if (lastCollector == null) {
72472017Scg                                if (!collector.isEmpty()) {
72572017Scg                                    errorat("jshell.err.feedback.multiple.sections", as, s);
72672017Scg                                    valid = false;
72772017Scg                                    return;
72872017Scg                                }
72972017Scg                            } else if (collector != lastCollector) {
73072017Scg                                errorat("jshell.err.feedback.different.selector.kinds", as, s);
73172017Scg                                valid = false;
73272017Scg                                return;
73372017Scg                            }
73472017Scg                            collector.add(sel);
73572017Scg                            lastCollector = collector;
73672017Scg                        }
73772017Scg                    }
73872017Scg                }
73972017Scg            }
74072017Scg        }
74172017Scg    }
74272017Scg}
74372017Scg