package jdk.internal.jshell.tool;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.StringJoiner;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collector;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toMap;
import static jdk.internal.jshell.tool.ContinuousCompletionProvider.PERFECT_MATCHER;
import jdk.internal.jshell.tool.JShellTool.CompletionProvider;
import static jdk.internal.jshell.tool.JShellTool.EMPTY_COMPLETION_PROVIDER;
class Feedback {
private static final Pattern FIELD_PATTERN = Pattern.compile("\\{(.*?)\\}");
private static final String TRUNCATION_FIELD = "<truncation>";
private static final String RECORD_SEPARATOR = "\u241E";
private Mode mode = new Mode("");
private Mode retainedCurrentMode = null;
private final Map<String, Mode> modeMap = new HashMap<>();
private final Map<String, String> retainedMap = new HashMap<>();
private final Map<String, Selector<?>> selectorMap = new HashMap<>();
private static final long ALWAYS = bits(FormatCase.all, FormatAction.all, FormatWhen.all,
FormatResolve.all, FormatUnresolved.all, FormatErrors.all);
private static final long ANY = 0L;
public boolean shouldDisplayCommandFluff() {
return mode.commandFluff;
}
public String getPre() {
return mode.format("pre", ANY);
}
public String getPost() {
return mode.format("post", ANY);
}
public String getErrorPre() {
return mode.format("errorpre", ANY);
}
public String getErrorPost() {
return mode.format("errorpost", ANY);
}
public String format(FormatCase fc, FormatAction fa, FormatWhen fw,
FormatResolve fr, FormatUnresolved fu, FormatErrors fe,
String name, String type, String value, String unresolved, List<String> errorLines) {
return mode.format(fc, fa, fw, fr, fu, fe,
name, type, value, unresolved, errorLines);
}
public String 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) {
return mode.format(field, fc, fa, fw, fr, fu, fe,
name, type, value, unresolved, errorLines);
}
public String truncateVarValue(String value) {
return mode.truncateVarValue(value);
}
public String getPrompt(String nextId) {
return mode.getPrompt(nextId);
}
public String getContinuationPrompt(String nextId) {
return mode.getContinuationPrompt(nextId);
}
public boolean setFeedback(MessageHandler messageHandler, ArgTokenizer at, Consumer<String> retainer) {
return new Setter(messageHandler, at).setFeedback(retainer);
}
public boolean setFormat(MessageHandler messageHandler, ArgTokenizer at) {
return new Setter(messageHandler, at).setFormat();
}
public boolean setTruncation(MessageHandler messageHandler, ArgTokenizer at) {
return new Setter(messageHandler, at).setTruncation();
}
public boolean setMode(MessageHandler messageHandler, ArgTokenizer at, Consumer<String> retainer) {
return new Setter(messageHandler, at).setMode(retainer);
}
public boolean setPrompt(MessageHandler messageHandler, ArgTokenizer at) {
return new Setter(messageHandler, at).setPrompt();
}
public boolean restoreEncodedModes(MessageHandler messageHandler, String encoded) {
return new Setter(messageHandler, new ArgTokenizer("<init>", "")).restoreEncodedModes(encoded);
}
public void markModesReadOnly() {
modeMap.values().stream()
.forEach(m -> m.readOnly = true);
}
JShellTool.CompletionProvider modeCompletions() {
return modeCompletions(EMPTY_COMPLETION_PROVIDER);
}
JShellTool.CompletionProvider modeCompletions(CompletionProvider successor) {
return new ContinuousCompletionProvider(
() -> modeMap.keySet().stream()
.collect(toMap(Function.identity(), m -> successor)),
PERFECT_MATCHER);
}
{
for (FormatCase e : FormatCase.all)
selectorMap.put(e.name().toLowerCase(Locale.US), e);
for (FormatAction e : FormatAction.all)
selectorMap.put(e.name().toLowerCase(Locale.US), e);
for (FormatResolve e : FormatResolve.all)
selectorMap.put(e.name().toLowerCase(Locale.US), e);
for (FormatUnresolved e : FormatUnresolved.all)
selectorMap.put(e.name().toLowerCase(Locale.US), e);
for (FormatErrors e : FormatErrors.all)
selectorMap.put(e.name().toLowerCase(Locale.US), e);
for (FormatWhen e : FormatWhen.all)
selectorMap.put(e.name().toLowerCase(Locale.US), e);
}
private static class SelectorSets {
Set<FormatCase> cc;
Set<FormatAction> ca;
Set<FormatWhen> cw;
Set<FormatResolve> cr;
Set<FormatUnresolved> cu;
Set<FormatErrors> ce;
}
private static class Mode {
final String name;
boolean commandFluff;
final Map<String, List<Setting>> cases;
boolean readOnly = false;
String prompt = "\n-> ";
String continuationPrompt = ">> ";
static class Setting {
final long enumBits;
final String format;
Setting(long enumBits, String format) {
this.enumBits = enumBits;
this.format = format;
}
@Override
public boolean equals(Object o) {
if (o instanceof Setting) {
Setting ing = (Setting) o;
return enumBits == ing.enumBits && format.equals(ing.format);
} else {
return false;
}
}
@Override
public int hashCode() {
int hash = 7;
hash = 67 * hash + (int) (this.enumBits ^ (this.enumBits >>> 32));
hash = 67 * hash + Objects.hashCode(this.format);
return hash;
}
}
Mode(String name) {
this.name = name;
this.cases = new HashMap<>();
add("name", new Setting(ALWAYS, "%1$s"));
add("type", new Setting(ALWAYS, "%2$s"));
add("value", new Setting(ALWAYS, "%3$s"));
add("unresolved", new Setting(ALWAYS, "%4$s"));
add("errors", new Setting(ALWAYS, "%5$s"));
add("err", new Setting(ALWAYS, "%6$s"));
add("errorline", new Setting(ALWAYS, " {err}%n"));
add("pre", new Setting(ALWAYS, "| "));
add("post", new Setting(ALWAYS, "%n"));
add("errorpre", new Setting(ALWAYS, "| "));
add("errorpost", new Setting(ALWAYS, "%n"));
}
Mode(String name, Mode m) {
this.name = name;
this.commandFluff = m.commandFluff;
this.prompt = m.prompt;
this.continuationPrompt = m.continuationPrompt;
this.cases = new HashMap<>();
m.cases.entrySet().stream()
.forEach(fes -> fes.getValue()
.forEach(ing -> add(fes.getKey(), ing)));
}
Mode(Iterator<String> it) {
this.name = it.next();
this.commandFluff = Boolean.parseBoolean(it.next());
this.prompt = it.next();
this.continuationPrompt = it.next();
cases = new HashMap<>();
String field;
while (!(field = it.next()).equals("***")) {
String open = it.next();
assert open.equals("(");
List<Setting> settings = new ArrayList<>();
String bits;
while (!(bits = it.next()).equals(")")) {
String format = it.next();
Setting ing = new Setting(Long.parseLong(bits), format);
settings.add(ing);
}
cases.put(field, settings);
}
}
@Override
public boolean equals(Object o) {
if (o instanceof Mode) {
Mode m = (Mode) o;
return name.equals((m.name))
&& commandFluff == m.commandFluff
&& prompt.equals((m.prompt))
&& continuationPrompt.equals((m.continuationPrompt))
&& cases.equals((m.cases));
} else {
return false;
}
}
@Override
public int hashCode() {
return Objects.hashCode(name);
}
void setCommandFluff(boolean fluff) {
commandFluff = fluff;
}
String encode() {
List<String> el = new ArrayList<>();
el.add(name);
el.add(String.valueOf(commandFluff));
el.add(prompt);
el.add(continuationPrompt);
for (Entry<String, List<Setting>> es : cases.entrySet()) {
el.add(es.getKey());
el.add("(");
for (Setting ing : es.getValue()) {
el.add(String.valueOf(ing.enumBits));
el.add(ing.format);
}
el.add(")");
}
el.add("***");
return String.join(RECORD_SEPARATOR, el);
}
private void add(String field, Setting ing) {
List<Setting> settings = cases.get(field);
if (settings == null) {
settings = new ArrayList<>();
cases.put(field, settings);
} else {
long mask = ~ing.enumBits;
settings.removeIf(t -> (t.enumBits & mask) == 0);
}
settings.add(ing);
}
void set(String field,
Collection<FormatCase> cc, Collection<FormatAction> ca, Collection<FormatWhen> cw,
Collection<FormatResolve> cr, Collection<FormatUnresolved> cu, Collection<FormatErrors> ce,
String format) {
long bits = bits(cc, ca, cw, cr, cu, ce);
set(field, bits, format);
}
void set(String field, long bits, String format) {
add(field, new Setting(bits, format));
}
String format(String field, long bits) {
List<Setting> settings = cases.get(field);
if (settings == null) {
return "";
}
String format = null;
for (int i = settings.size() - 1; i >= 0; --i) {
Setting ing = settings.get(i);
long mask = ing.enumBits;
if ((bits & mask) == bits) {
format = ing.format;
break;
}
}
if (format == null || format.isEmpty()) {
return "";
}
Matcher m = FIELD_PATTERN.matcher(format);
StringBuffer sb = new StringBuffer(format.length());
while (m.find()) {
String fieldName = m.group(1);
String sub = format(fieldName, bits);
m.appendReplacement(sb, Matcher.quoteReplacement(sub));
}
m.appendTail(sb);
return sb.toString();
}
String truncateVarValue(String value) {
return truncateValue(value,
bits(FormatCase.VARVALUE, FormatAction.ADDED,
FormatWhen.PRIMARY, FormatResolve.OK,
FormatUnresolved.UNRESOLVED0, FormatErrors.ERROR0));
}
String truncateValue(String value, long bits) {
if (value==null) {
return "";
} else {
String truncField = format(TRUNCATION_FIELD, bits);
if (truncField.isEmpty()) {
return value;
} else {
int trunc = Integer.parseUnsignedInt(truncField);
int len = value.length();
if (len > trunc) {
if (trunc <= 13) {
return value.substring(0, trunc);
} else {
int endLen = trunc / 3;
int startLen = trunc - 5 - endLen;
return value.substring(0, startLen) + " ... " + value.substring(len -endLen);
}
} else {
return value;
}
}
}
}
String format(FormatCase fc, FormatAction fa, FormatWhen fw,
FormatResolve fr, FormatUnresolved fu, FormatErrors fe,
String name, String type, String value, String unresolved, List<String> errorLines) {
return format("display", fc, fa, fw, fr, fu, fe,
name, type, value, unresolved, errorLines);
}
String 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) {
long bits = bits(fc, fa, fw, fr, fu, fe);
String fname = name==null? "" : name;
String ftype = type==null? "" : type;
String fvalue = truncateValue(value, bits);
String funresolved = unresolved==null? "" : unresolved;
String errors = errorLines.stream()
.map(el -> String.format(
format("errorline", bits),
fname, ftype, fvalue, funresolved, "*cannot-use-errors-here*", el))
.collect(joining());
return String.format(
format(field, bits),
fname, ftype, fvalue, funresolved, errors, "*cannot-use-err-here*");
}
void setPrompts(String prompt, String continuationPrompt) {
this.prompt = prompt;
this.continuationPrompt = continuationPrompt;
}
String getPrompt(String nextId) {
return String.format(prompt, nextId);
}
String getContinuationPrompt(String nextId) {
return String.format(continuationPrompt, nextId);
}
}
private static long bits(FormatCase fc, FormatAction fa, FormatWhen fw,
FormatResolve fr, FormatUnresolved fu, FormatErrors fe) {
long res = 0L;
res |= 1 << fc.ordinal();
res <<= FormatAction.count;
res |= 1 << fa.ordinal();
res <<= FormatWhen.count;
res |= 1 << fw.ordinal();
res <<= FormatResolve.count;
res |= 1 << fr.ordinal();
res <<= FormatUnresolved.count;
res |= 1 << fu.ordinal();
res <<= FormatErrors.count;
res |= 1 << fe.ordinal();
return res;
}
private static long bits(Collection<FormatCase> cc, Collection<FormatAction> ca, Collection<FormatWhen> cw,
Collection<FormatResolve> cr, Collection<FormatUnresolved> cu, Collection<FormatErrors> ce) {
long res = 0L;
for (FormatCase fc : cc)
res |= 1 << fc.ordinal();
res <<= FormatAction.count;
for (FormatAction fa : ca)
res |= 1 << fa.ordinal();
res <<= FormatWhen.count;
for (FormatWhen fw : cw)
res |= 1 << fw.ordinal();
res <<= FormatResolve.count;
for (FormatResolve fr : cr)
res |= 1 << fr.ordinal();
res <<= FormatUnresolved.count;
for (FormatUnresolved fu : cu)
res |= 1 << fu.ordinal();
res <<= FormatErrors.count;
for (FormatErrors fe : ce)
res |= 1 << fe.ordinal();
return res;
}
private static SelectorSets unpackEnumbits(long enumBits) {
class Unpacker {
SelectorSets u = new SelectorSets();
long b = enumBits;
<E extends Enum<E>> Set<E> unpackEnumbits(E[] values) {
Set<E> c = new HashSet<>();
for (int i = 0; i < values.length; ++i) {
if ((b & (1 << i)) != 0) {
c.add(values[i]);
}
}
b >>>= values.length;
return c;
}
SelectorSets unpack() {
u.ce = unpackEnumbits(FormatErrors.values());
u.cu = unpackEnumbits(FormatUnresolved.values());
u.cr = unpackEnumbits(FormatResolve.values());
u.cw = unpackEnumbits(FormatWhen.values());
u.ca = unpackEnumbits(FormatAction.values());
u.cc = unpackEnumbits(FormatCase.values());
return u;
}
}
return new Unpacker().unpack();
}
interface Selector<E extends Enum<E> & Selector<E>> {
SelectorCollector<E> collector(Setter.SelectorList sl);
String doc();
}
public enum FormatCase implements Selector<FormatCase> {
IMPORT("import declaration"),
CLASS("class declaration"),
INTERFACE("interface declaration"),
ENUM("enum declaration"),
ANNOTATION("annotation interface declaration"),
RECORD("record declaration"),
METHOD("method declaration -- note: {type}==parameter-types"),
VARDECL("variable declaration without init"),
VARINIT("variable declaration with init"),
EXPRESSION("expression -- note: {name}==scratch-variable-name"),
VARVALUE("variable value expression"),
ASSIGNMENT("assign variable"),
STATEMENT("statement");
String doc;
static final EnumSet<FormatCase> all = EnumSet.allOf(FormatCase.class);
static final int count = all.size();
@Override
public SelectorCollector<FormatCase> collector(Setter.SelectorList sl) {
return sl.cases;
}
@Override
public String doc() {
return doc;
}
private FormatCase(String doc) {
this.doc = doc;
}
}
public enum FormatAction implements Selector<FormatAction> {
ADDED("snippet has been added"),
MODIFIED("an existing snippet has been modified"),
REPLACED("an existing snippet has been replaced with a new snippet"),
OVERWROTE("an existing snippet has been overwritten"),
DROPPED("snippet has been dropped"),
USED("snippet was used when it cannot be");
String doc;
static final EnumSet<FormatAction> all = EnumSet.allOf(FormatAction.class);
static final int count = all.size();
@Override
public SelectorCollector<FormatAction> collector(Setter.SelectorList sl) {
return sl.actions;
}
@Override
public String doc() {
return doc;
}
private FormatAction(String doc) {
this.doc = doc;
}
}
public enum FormatWhen implements Selector<FormatWhen> {
PRIMARY("the entered snippet"),
UPDATE("an update to a dependent snippet");
String doc;
static final EnumSet<FormatWhen> all = EnumSet.allOf(FormatWhen.class);
static final int count = all.size();
@Override
public SelectorCollector<FormatWhen> collector(Setter.SelectorList sl) {
return sl.whens;
}
@Override
public String doc() {
return doc;
}
private FormatWhen(String doc) {
this.doc = doc;
}
}
public enum FormatResolve implements Selector<FormatResolve> {
OK("resolved correctly"),
DEFINED("defined despite recoverably unresolved references"),
NOTDEFINED("not defined because of recoverably unresolved references");
String doc;
static final EnumSet<FormatResolve> all = EnumSet.allOf(FormatResolve.class);
static final int count = all.size();
@Override
public SelectorCollector<FormatResolve> collector(Setter.SelectorList sl) {
return sl.resolves;
}
@Override
public String doc() {
return doc;
}
private FormatResolve(String doc) {
this.doc = doc;
}
}
public enum FormatUnresolved implements Selector<FormatUnresolved> {
UNRESOLVED0("no names are unresolved"),
UNRESOLVED1("one name is unresolved"),
UNRESOLVED2("two or more names are unresolved");
String doc;
static final EnumSet<FormatUnresolved> all = EnumSet.allOf(FormatUnresolved.class);
static final int count = all.size();
@Override
public SelectorCollector<FormatUnresolved> collector(Setter.SelectorList sl) {
return sl.unresolvedCounts;
}
@Override
public String doc() {
return doc;
}
private FormatUnresolved(String doc) {
this.doc = doc;
}
}
public enum FormatErrors implements Selector<FormatErrors> {
ERROR0("no errors"),
ERROR1("one error"),
ERROR2("two or more errors");
String doc;
static final EnumSet<FormatErrors> all = EnumSet.allOf(FormatErrors.class);
static final int count = all.size();
@Override
public SelectorCollector<FormatErrors> collector(Setter.SelectorList sl) {
return sl.errorCounts;
}
@Override
public String doc() {
return doc;
}
private FormatErrors(String doc) {
this.doc = doc;
}
}
class SelectorCollector<E extends Enum<E> & Selector<E>> {
final EnumSet<E> all;
EnumSet<E> set = null;
SelectorCollector(EnumSet<E> all) {
this.all = all;
}
void add(Object o) {
@SuppressWarnings("unchecked")
E e = (E) o;
if (set == null) {
set = EnumSet.of(e);
} else {
set.add(e);
}
}
boolean isEmpty() {
return set == null;
}
EnumSet<E> getSet() {
return set == null
? all
: set;
}
}
private class Setter {
private final ArgTokenizer at;
private final MessageHandler messageHandler;
boolean valid = true;
Setter(MessageHandler messageHandler, ArgTokenizer at) {
this.messageHandler = messageHandler;
this.at = at;
at.allowedOptions("-retain");
}
void fluff(String format, Object... args) {
messageHandler.fluff(format, args);
}
void hard(String format, Object... args) {
messageHandler.hard(format, args);
}
void fluffmsg(String messageKey, Object... args) {
messageHandler.fluffmsg(messageKey, args);
}
void hardmsg(String messageKey, Object... args) {
messageHandler.hardmsg(messageKey, args);
}
boolean showFluff() {
return messageHandler.showFluff();
}
void errorat(String messageKey, Object... args) {
if (!valid) {
return;
}
valid = false;
Object[] a2 = Arrays.copyOf(args, args.length + 2);
a2[args.length] = at.whole();
messageHandler.errormsg(messageKey, a2);
}
String selectorsToString(SelectorSets u) {
StringBuilder sb = new StringBuilder();
selectorToString(sb, u.cc, FormatCase.values());
selectorToString(sb, u.ca, FormatAction.values());
selectorToString(sb, u.cw, FormatWhen.values());
selectorToString(sb, u.cr, FormatResolve.values());
selectorToString(sb, u.cu, FormatUnresolved.values());
selectorToString(sb, u.ce, FormatErrors.values());
return sb.toString();
}
private <E extends Enum<E>> void selectorToString(StringBuilder sb, Set<E> c, E[] values) {
if (!c.containsAll(Arrays.asList(values))) {
sb.append(c.stream()
.sorted((x, y) -> x.ordinal() - y.ordinal())
.map(v -> v.name().toLowerCase(Locale.US))
.collect(new Collector<CharSequence, StringJoiner, String>() {
@Override
public BiConsumer<StringJoiner, CharSequence> accumulator() {
return StringJoiner::add;
}
@Override
public Supplier<StringJoiner> supplier() {
return () -> new StringJoiner(",", (sb.length() == 0)? "" : "-", "")
.setEmptyValue("");
}
@Override
public BinaryOperator<StringJoiner> combiner() {
return StringJoiner::merge;
}
@Override
public Function<StringJoiner, String> finisher() {
return StringJoiner::toString;
}
@Override
public Set<Characteristics> characteristics() {
return Collections.emptySet();
}
}));
}
}
void showFormatSettings(Mode sm, String f) {
if (sm == null) {
modeMap.entrySet().stream()
.sorted((es1, es2) -> es1.getKey().compareTo(es2.getKey()))
.forEach(m -> showFormatSettings(m.getValue(), f));
} else {
sm.cases.entrySet().stream()
.filter(ec -> (f == null)
? !ec.getKey().equals(TRUNCATION_FIELD)
: ec.getKey().equals(f))
.sorted((ec1, ec2) -> ec1.getKey().compareTo(ec2.getKey()))
.forEach(ec -> {
ec.getValue().forEach(s -> {
hard("/set format %s %s %s %s",
sm.name, ec.getKey(), toStringLiteral(s.format),
selectorsToString(unpackEnumbits(s.enumBits)));
});
});
}
}
void showTruncationSettings(Mode sm) {
if (sm == null) {
modeMap.values().forEach(this::showTruncationSettings);
} else {
List<Mode.Setting> trunc = sm.cases.get(TRUNCATION_FIELD);
if (trunc != null) {
trunc.forEach(s -> {
hard("/set truncation %s %s %s",
sm.name, s.format,
selectorsToString(unpackEnumbits(s.enumBits)));
});
}
}
}
void showPromptSettings(Mode sm) {
if (sm == null) {
modeMap.values().forEach(this::showPromptSettings);
} else {
hard("/set prompt %s %s %s",
sm.name,
toStringLiteral(sm.prompt),
toStringLiteral(sm.continuationPrompt));
}
}
void showModeSettings(String umode, String msg) {
if (umode == null) {
modeMap.values().forEach(this::showModeSettings);
} else {
Mode m;
String retained = retainedMap.get(umode);
if (retained == null) {
m = searchForMode(umode, msg);
if (m == null) {
return;
}
umode = m.name;
retained = retainedMap.get(umode);
} else {
m = modeMap.get(umode);
}
if (retained != null) {
Mode rm = new Mode(encodedModeIterator(retained));
showModeSettings(rm);
hard("/set mode -retain %s", umode);
if (m != null && !m.equals(rm)) {
hard("");
showModeSettings(m);
}
} else {
showModeSettings(m);
}
}
}
void showModeSettings(Mode sm) {
hard("/set mode %s %s",
sm.name, sm.commandFluff ? "-command" : "-quiet");
showPromptSettings(sm);
showFormatSettings(sm, null);
showTruncationSettings(sm);
}
void showFeedbackSetting() {
if (retainedCurrentMode != null) {
hard("/set feedback -retain %s", retainedCurrentMode.name);
}
if (mode != retainedCurrentMode) {
hard("/set feedback %s", mode.name);
}
}
boolean setPrompt() {
Mode m = nextMode();
String prompt = nextFormat();
String continuationPrompt = nextFormat();
checkOptionsAndRemainingInput();
if (valid && prompt == null) {
showPromptSettings(m);
return valid;
}
if (valid && m.readOnly) {
errorat("jshell.err.not.valid.with.predefined.mode", m.name);
} else if (continuationPrompt == null) {
errorat("jshell.err.continuation.prompt.required");
}
if (valid) {
m.setPrompts(prompt, continuationPrompt);
} else {
fluffmsg("jshell.msg.see", "/help /set prompt");
}
return valid;
}
boolean setMode(Consumer<String> retainer) {
class SetMode {
final String umode;
final String omode;
final boolean commandOption;
final boolean quietOption;
final boolean deleteOption;
final boolean retainOption;
SetMode() {
at.allowedOptions("-command", "-quiet", "-delete", "-retain");
umode = nextModeIdentifier();
omode = nextModeIdentifier();
checkOptionsAndRemainingInput();
commandOption = at.hasOption("-command");
quietOption = at.hasOption("-quiet");
deleteOption = at.hasOption("-delete");
retainOption = at.hasOption("-retain");
}
void delete() {
if (commandOption || quietOption) {
errorat("jshell.err.conflicting.options");
} else if (retainOption
? !retainedMap.containsKey(umode) && !modeMap.containsKey(umode)
: !modeMap.containsKey(umode)) {
errorat("jshell.err.mode.unknown", umode);
} else if (omode != null) {
errorat("jshell.err.unexpected.at.end", omode);
} else if (mode.name.equals(umode)) {
errorat("jshell.err.cannot.delete.current.mode", umode);
} else if (retainOption && retainedCurrentMode != null &&
retainedCurrentMode.name.equals(umode)) {
errorat("jshell.err.cannot.delete.retained.mode", umode);
} else {
Mode m = modeMap.get(umode);
if (m != null && m.readOnly) {
errorat("jshell.err.not.valid.with.predefined.mode", umode);
} else {
modeMap.remove(umode);
if (retainOption) {
retainedMap.remove(umode);
updateRetainedModes();
}
}
}
}
void retain() {
if (commandOption || quietOption) {
errorat("jshell.err.conflicting.options");
} else if (omode != null) {
errorat("jshell.err.unexpected.at.end", omode);
} else {
Mode m = modeMap.get(umode);
if (m == null) {
errorat("jshell.err.mode.unknown", umode);
} else if (m.readOnly) {
errorat("jshell.err.not.valid.with.predefined.mode", umode);
} else {
retainedMap.put(m.name, m.encode());
updateRetainedModes();
}
}
}
void updateRetainedModes() {
String encoded = String.join(RECORD_SEPARATOR, retainedMap.values());
retainer.accept(encoded);
}
void create() {
if (commandOption && quietOption) {
errorat("jshell.err.conflicting.options");
} else if (!commandOption && !quietOption) {
errorat("jshell.err.mode.creation");
} else if (modeMap.containsKey(umode)) {
errorat("jshell.err.mode.exists", umode);
} else {
Mode om = searchForMode(omode);
if (valid) {
Mode m = (om != null)
? new Mode(umode, om)
: new Mode(umode);
modeMap.put(umode, m);
fluffmsg("jshell.msg.feedback.new.mode", m.name);
m.setCommandFluff(commandOption);
}
}
}
boolean set() {
if (valid && !commandOption && !quietOption && !deleteOption &&
omode == null && !retainOption) {
showModeSettings(umode, "jshell.err.mode.creation");
} else if (valid && umode == null) {
errorat("jshell.err.missing.mode");
} else if (valid && deleteOption) {
delete();
} else if (valid && retainOption) {
retain();
} else if (valid) {
create();
}
if (!valid) {
fluffmsg("jshell.msg.see", "/help /set mode");
}
return valid;
}
}
return new SetMode().set();
}
boolean setFormat() {
Mode m = nextMode();
String field = toIdentifier(next(), "jshell.err.field.name");
String format = nextFormat();
if (valid && format == null) {
if (field != null && m != null && !m.cases.containsKey(field)) {
errorat("jshell.err.field.name", field);
} else {
showFormatSettings(m, field);
}
} else {
installFormat(m, field, format, "/help /set format");
}
return valid;
}
boolean setTruncation() {
Mode m = nextMode();
String length = next();
if (length == null) {
showTruncationSettings(m);
} else {
try {
Integer.parseUnsignedInt(length);
} catch (NumberFormatException ex) {
errorat("jshell.err.truncation.length.not.integer", length);
}
installFormat(m, TRUNCATION_FIELD, length, "/help /set truncation");
}
return valid;
}
boolean setFeedback(Consumer<String> retainer) {
String umode = next();
checkOptionsAndRemainingInput();
boolean retainOption = at.hasOption("-retain");
if (valid && umode == null && !retainOption) {
showFeedbackSetting();
hard("");
showFeedbackModes();
return true;
}
if (valid) {
Mode m = umode == null
? mode
: searchForMode(toModeIdentifier(umode));
if (valid && retainOption && !m.readOnly && !retainedMap.containsKey(m.name)) {
errorat("jshell.err.retained.feedback.mode.must.be.retained.or.predefined");
}
if (valid) {
if (umode != null) {
mode = m;
fluffmsg("jshell.msg.feedback.mode", mode.name);
}
if (retainOption) {
retainedCurrentMode = m;
retainer.accept(m.name);
}
}
}
if (!valid) {
fluffmsg("jshell.msg.see", "/help /set feedback");
return false;
}
return true;
}
boolean restoreEncodedModes(String allEncoded) {
try {
Iterator<String> itr = encodedModeIterator(allEncoded);
while (itr.hasNext()) {
Mode m = new Mode(itr);
modeMap.put(m.name, m);
retainedMap.put(m.name, m.encode());
}
return true;
} catch (Throwable exc) {
errorat("jshell.err.retained.mode.failure", exc);
retainedMap.clear();
return false;
}
}
Iterator<String> encodedModeIterator(String encoded) {
String[] ms = encoded.split(RECORD_SEPARATOR);
return Arrays.asList(ms).iterator();
}
void installFormat(Mode m, String field, String format, String help) {
String slRaw;
List<SelectorList> slList = new ArrayList<>();
while (valid && (slRaw = next()) != null) {
SelectorList sl = new SelectorList();
sl.parseSelectorList(slRaw);
slList.add(sl);
}
checkOptionsAndRemainingInput();
if (valid) {
if (m.readOnly) {
errorat("jshell.err.not.valid.with.predefined.mode", m.name);
} else if (slList.isEmpty()) {
m.set(field, ALWAYS, format);
} else {
slList.stream()
.forEach(sl -> m.set(field,
sl.cases.getSet(), sl.actions.getSet(), sl.whens.getSet(),
sl.resolves.getSet(), sl.unresolvedCounts.getSet(), sl.errorCounts.getSet(),
format));
}
} else {
fluffmsg("jshell.msg.see", help);
}
}
void checkOptionsAndRemainingInput() {
String junk = at.remainder();
if (!junk.isEmpty()) {
errorat("jshell.err.unexpected.at.end", junk);
} else {
String bad = at.badOptions();
if (!bad.isEmpty()) {
errorat("jshell.err.unknown.option", bad);
}
}
}
String next() {
String s = at.next();
if (s == null) {
checkOptionsAndRemainingInput();
}
return s;
}
private String toIdentifier(String id, String err) {
if (!valid || id == null) {
return null;
}
if (at.isQuoted() ||
!id.codePoints().allMatch(Character::isJavaIdentifierPart)) {
errorat(err, id);
return null;
}
return id;
}
private String toModeIdentifier(String id) {
return toIdentifier(id, "jshell.err.mode.name");
}
private String nextModeIdentifier() {
return toModeIdentifier(next());
}
private Mode nextMode() {
String umode = nextModeIdentifier();
return searchForMode(umode);
}
private Mode searchForMode(String umode) {
return searchForMode(umode, null);
}
private Mode searchForMode(String umode, String msg) {
if (!valid || umode == null) {
return null;
}
Mode m = modeMap.get(umode);
if (m != null) {
return m;
}
Mode[] matches = modeMap.entrySet().stream()
.filter(e -> e.getKey().startsWith(umode))
.map(Entry::getValue)
.toArray(Mode[]::new);
if (matches.length == 1) {
return matches[0];
} else {
if (msg != null) {
hardmsg(msg, "");
}
if (matches.length == 0) {
errorat("jshell.err.feedback.does.not.match.mode", umode);
} else {
errorat("jshell.err.feedback.ambiguous.mode", umode);
}
if (showFluff()) {
showFeedbackModes();
}
return null;
}
}
void showFeedbackModes() {
if (!retainedMap.isEmpty()) {
hardmsg("jshell.msg.feedback.retained.mode.following");
retainedMap.keySet().stream()
.sorted()
.forEach(mk -> hard(" %s", mk));
}
hardmsg("jshell.msg.feedback.mode.following");
modeMap.keySet().stream()
.sorted()
.forEach(mk -> hard(" %s", mk));
}
private String nextFormat() {
return toFormat(next());
}
private String toFormat(String format) {
if (!valid || format == null) {
return null;
}
if (!at.isQuoted()) {
errorat("jshell.err.feedback.must.be.quoted", format);
return null;
}
return format;
}
private String toStringLiteral(String s) {
StringBuilder sb = new StringBuilder();
sb.append('"');
final int length = s.length();
for (int offset = 0; offset < length;) {
final int codepoint = s.codePointAt(offset);
switch (codepoint) {
case '\b':
sb.append("\\b");
break;
case '\t':
sb.append("\\t");
break;
case '\n':
sb.append("\\n");
break;
case '\f':
sb.append("\\f");
break;
case '\r':
sb.append("\\r");
break;
case '\"':
sb.append("\\\"");
break;
case '\'':
sb.append("\\'");
break;
case '\\':
sb.append("\\\\");
break;
default:
if (codepoint < 040) {
sb.append(String.format("\\%o", codepoint));
} else {
sb.appendCodePoint(codepoint);
}
break;
}
offset += Character.charCount(codepoint);
}
sb.append('"');
return sb.toString();
}
class SelectorList {
SelectorCollector<FormatCase> cases = new SelectorCollector<>(FormatCase.all);
SelectorCollector<FormatAction> actions = new SelectorCollector<>(FormatAction.all);
SelectorCollector<FormatWhen> whens = new SelectorCollector<>(FormatWhen.all);
SelectorCollector<FormatResolve> resolves = new SelectorCollector<>(FormatResolve.all);
SelectorCollector<FormatUnresolved> unresolvedCounts = new SelectorCollector<>(FormatUnresolved.all);
SelectorCollector<FormatErrors> errorCounts = new SelectorCollector<>(FormatErrors.all);
final void parseSelectorList(String sl) {
for (String s : sl.split("-")) {
SelectorCollector<?> lastCollector = null;
for (String as : s.split(",")) {
if (!as.isEmpty()) {
Selector<?> sel = selectorMap.get(as);
if (sel == null) {
errorat("jshell.err.feedback.not.a.valid.selector", as, s);
return;
}
SelectorCollector<?> collector = sel.collector(this);
if (lastCollector == null) {
if (!collector.isEmpty()) {
errorat("jshell.err.feedback.multiple.sections", as, s);
return;
}
} else if (collector != lastCollector) {
errorat("jshell.err.feedback.different.selector.kinds", as, s);
return;
}
collector.add(sel);
lastCollector = collector;
}
}
}
}
}
}
}