package jdk.internal.jshell.tool;
import java.util.*;
import java.util.Map.Entry;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jdk.internal.jshell.tool.JShellTool.CompletionProvider;
import static java.util.stream.Collectors.*;
import static jdk.internal.jshell.tool.ContinuousCompletionProvider.PERFECT_MATCHER;
import static jdk.internal.jshell.tool.JShellTool.EMPTY_COMPLETION_PROVIDER;
import static jdk.internal.jshell.tool.Selector.SelectorKind;
import static jdk.internal.jshell.tool.Selector.SelectorInstanceWithDoc;
import static jdk.internal.jshell.tool.Selector.SelectorBuilder;
import static jdk.internal.jshell.tool.Selector.FormatAction;
import static jdk.internal.jshell.tool.Selector.FormatCase;
import static jdk.internal.jshell.tool.Selector.FormatErrors;
import static jdk.internal.jshell.tool.Selector.FormatResolve;
import static jdk.internal.jshell.tool.Selector.FormatUnresolved;
import static jdk.internal.jshell.tool.Selector.FormatWhen;
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 static final Selector VAR_VALUE_ADD_SELECTOR = new Selector(
FormatCase.VARVALUE,
FormatAction.ADDED,
FormatWhen.PRIMARY,
FormatResolve.OK,
FormatUnresolved.UNRESOLVED0,
FormatErrors.ERROR0);
private static final Selector RECORD_TYPEKIND_SELECTOR = new Selector(
EnumSet.of(FormatCase.RECORD),
EnumSet.noneOf(FormatAction.class),
EnumSet.noneOf(FormatWhen.class),
EnumSet.noneOf(FormatResolve.class),
EnumSet.noneOf(FormatUnresolved.class),
EnumSet.noneOf(FormatErrors.class));
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<>();
public boolean shouldDisplayCommandFluff() {
return mode.commandFluff;
}
public String getPre() {
return mode.format("pre", Selector.ANY);
}
public String getPost() {
return mode.format("post", Selector.ANY);
}
public String getErrorPre() {
return mode.format("errorpre", Selector.ANY);
}
public String getErrorPost() {
return mode.format("errorpost", Selector.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);
}
private static class Mode {
final String name;
boolean commandFluff;
final Map<String, List<Setting>> byField;
boolean readOnly = false;
String prompt = "\n-> ";
String continuationPrompt = ">> ";
static class Setting {
final String format;
final Selector selector;
Setting(String format, Selector selector) {
this.format = format;
this.selector = selector;
}
@Override
public boolean equals(Object o) {
if (o instanceof Setting) {
Setting ing = (Setting) o;
return format.equals(ing.format)
&& selector.equals(ing.selector);
} else {
return false;
}
}
@Override
public int hashCode() {
int hash = 7;
hash = 67 * hash + Objects.hashCode(this.selector);
hash = 67 * hash + Objects.hashCode(this.format);
return hash;
}
@Override
public String toString() {
return "Setting(" + format + "," + selector.toString() + ")";
}
}
Mode(String name) {
this.name = name;
this.byField = new HashMap<>();
set("name", "%1$s", Selector.ALWAYS);
set("type", "%2$s", Selector.ALWAYS);
set("value", "%3$s", Selector.ALWAYS);
set("unresolved", "%4$s", Selector.ALWAYS);
set("errors", "%5$s", Selector.ALWAYS);
set("err", "%6$s", Selector.ALWAYS);
set("errorline", " {err}%n", Selector.ALWAYS);
set("pre", "| ", Selector.ALWAYS);
set("post", "%n", Selector.ALWAYS);
set("errorpre", "| ", Selector.ALWAYS);
set("errorpost", "%n", Selector.ALWAYS);
}
private Mode(String name, boolean commandFluff, String prompt, String continuationPrompt) {
this.name = name;
this.commandFluff = commandFluff;
this.prompt = prompt;
this.continuationPrompt = continuationPrompt;
this.byField = new HashMap<>();
}
Mode(String name, Mode m) {
this(name, m.commandFluff, m.prompt, m.continuationPrompt);
m.byField.forEach((fieldName, settingList) ->
settingList.forEach(setting -> set(fieldName, setting.format, setting.selector)));
}
@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))
&& byField.equals((m.byField));
} 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 : byField.entrySet()) {
el.add(es.getKey());
el.add("(");
for (Setting ing : es.getValue()) {
el.add(ing.selector.toString());
el.add(ing.format);
}
el.add(")");
}
el.add("***");
return String.join(RECORD_SEPARATOR, el);
}
private void add(String field, Setting ing) {
List<Setting> settings = byField.get(field);
if (settings == null) {
settings = new ArrayList<>();
byField.put(field, settings);
} else {
Selector addedSelector = ing.selector;
settings.removeIf(t -> t.selector.includedIn(addedSelector));
}
settings.add(ing);
}
void set(String field, String format, Selector selector) {
add(field, new Setting(format, selector));
}
String format(String field, Selector selector) {
List<Setting> settings = byField.get(field);
if (settings == null) {
return "";
}
String format = null;
for (int i = settings.size() - 1; i >= 0; --i) {
Setting ing = settings.get(i);
if (ing.selector.covers(selector)) {
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, selector);
m.appendReplacement(sb, Matcher.quoteReplacement(sub));
}
m.appendTail(sb);
return sb.toString();
}
String truncateVarValue(String value) {
return truncateValue(value, VAR_VALUE_ADD_SELECTOR);
}
String truncateValue(String value, Selector selector) {
if (value==null) {
return "";
} else {
String truncField = format(TRUNCATION_FIELD, selector);
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) {
Selector selector = new Selector(fc, fa, fw, fr, fu, fe);
String fname = name==null? "" : name;
String ftype = type==null? "" : type;
String fvalue = truncateValue(value, selector);
String funresolved = unresolved==null? "" : unresolved;
String errors = errorLines.stream()
.map(el -> String.format(
format("errorline", selector),
fname, ftype, fvalue, funresolved, "*cannot-use-errors-here*", el))
.collect(joining());
return String.format(
format(field, selector),
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 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);
}
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.byField.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),
s.selector.toString());
});
});
}
}
void showTruncationSettings(Mode sm) {
if (sm == null) {
modeMap.values().forEach(this::showTruncationSettings);
} else {
List<Mode.Setting> trunc = sm.byField.get(TRUNCATION_FIELD);
if (trunc != null) {
trunc.forEach(s -> {
hard("/set truncation %s %s %s",
sm.name, s.format,
s.selector.toString());
});
}
}
}
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 = buildMode(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.byField.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 = buildMode(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;
}
}
private Mode buildMode(Iterator<String> it) {
Mode newMode = new Mode(it.next(), Boolean.parseBoolean(it.next()), it.next(), it.next());
Map<String, List<Mode.Setting>> fields = new HashMap<>();
long suspiciousBits = Selector.OLD_ALWAYS.asBits();
boolean suspicious = false;
String field;
while (!(field = it.next()).equals("***")) {
String open = it.next();
assert open.equals("(");
List<Mode.Setting> settings = new ArrayList<>();
String selectorText;
while (!(selectorText = it.next()).equals(")")) {
String format = it.next();
Selector selector;
if (selectorText.isEmpty()) {
selector = Selector.ALWAYS;
} else if (Character.isDigit(selectorText.charAt(0))) {
long bits = Long.parseLong(selectorText);
suspicious |= bits == suspiciousBits;
selector = new Selector(bits);
} else {
selector = parseSelector(selectorText);
}
Mode.Setting ing = new Mode.Setting(format, selector);
settings.add(ing);
}
fields.put(field, settings);
}
List<Mode.Setting> tk;
List<Mode.Setting> errf;
if (suspicious
&& ((tk = fields.get("typeKind")) == null
|| !tk.stream().anyMatch(tkc -> tkc.selector.equals(RECORD_TYPEKIND_SELECTOR)))
&& ((errf = fields.get("err")) == null
|| errf.stream().anyMatch(tkc -> tkc.selector.equals(Selector.OLD_ALWAYS)))) {
Mode base = modeMap.get("normal");
if (base == null) {
base = mode;
}
base.byField.forEach((fieldName, settingList) ->
settingList.forEach(setting -> newMode.set(fieldName, setting.format, setting.selector)));
fields.forEach((fieldName, settingList) -> {
settingList.forEach(setting -> newMode.set(fieldName, setting.format,
Selector.fromPreJDK14(setting.selector, !fieldName.equals("typeKind"))));
});
} else {
fields.forEach((fieldName, settingList) ->
settingList.forEach(setting -> newMode.set(fieldName, setting.format, setting.selector)));
}
return newMode;
}
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<Selector> selectorList = new ArrayList<>();
while (valid && (slRaw = next()) != null) {
selectorList.add(parseSelector(slRaw));
}
checkOptionsAndRemainingInput();
if (valid) {
if (m.readOnly) {
errorat("jshell.err.not.valid.with.predefined.mode", m.name);
} else if (selectorList.isEmpty()) {
m.set(field, format, Selector.ALWAYS);
} else {
selectorList.forEach(sel -> m.set(field, format, sel));
}
} 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();
}
private Selector parseSelector(String selectorText) {
SelectorBuilder seb = new SelectorBuilder(selectorText);
EnumSet<SelectorKind> seen = EnumSet.noneOf(SelectorKind.class);
for (String s : selectorText.split("-")) {
SelectorKind lastKind = null;
for (String as : s.split(",")) {
if (!as.isEmpty()) {
SelectorInstanceWithDoc<?> sel = Selector.selectorMap.get(as);
if (sel == null) {
errorat("jshell.err.feedback.not.a.valid.selector", as, s);
return Selector.ALWAYS;
}
SelectorKind kind = sel.kind();
if (lastKind == null) {
if (seen.contains(kind)) {
errorat("jshell.err.feedback.multiple.sections", as, s);
return Selector.ALWAYS;
}
} else if (kind != lastKind) {
errorat("jshell.err.feedback.different.selector.kinds", as, s);
return Selector.ALWAYS;
}
seb.add(sel);
seen.add(kind);
lastKind = kind;
}
}
}
return seb.toSelector();
}
}
}