|
|
@@ -0,0 +1,145 @@
|
|
|
+package org.leumasjaffe.format;
|
|
|
+
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.Objects;
|
|
|
+import java.util.regex.Matcher;
|
|
|
+import java.util.regex.Pattern;
|
|
|
+
|
|
|
+import lombok.AccessLevel;
|
|
|
+import lombok.experimental.FieldDefaults;
|
|
|
+
|
|
|
+@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
|
|
|
+public class StringFormatter {
|
|
|
+ private static final class NameMap extends HashMap<String, Object> {
|
|
|
+ /**
|
|
|
+ *
|
|
|
+ */
|
|
|
+ private static final long serialVersionUID = -2243574068000774442L;
|
|
|
+
|
|
|
+ NameMap(Object... args) {
|
|
|
+ for (Object arg : args) {
|
|
|
+ Objects.requireNonNull(arg);
|
|
|
+ if (arg instanceof Named) {
|
|
|
+ put(((Named) arg).getName(), arg);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static final class FmtStateMachine {
|
|
|
+ final StringBuilder str;
|
|
|
+
|
|
|
+ String fmt;
|
|
|
+ Object[] args;
|
|
|
+ int currentIdx;
|
|
|
+ final NameMap named;
|
|
|
+ int lastCh, endPos = 0;
|
|
|
+
|
|
|
+ FmtStateMachine(String fmt, Object... args) {
|
|
|
+ this(fmt, 0, args, new NameMap(args), '\0');
|
|
|
+ }
|
|
|
+
|
|
|
+ FmtStateMachine(String fmt, int idx, Object[] args, NameMap named, int lastCh) {
|
|
|
+ this.str = new StringBuilder(fmt.length());
|
|
|
+ this.fmt = fmt;
|
|
|
+ this.currentIdx = idx;
|
|
|
+ this.args = args;
|
|
|
+ this.named = named;
|
|
|
+ this.lastCh = lastCh;
|
|
|
+ }
|
|
|
+
|
|
|
+ FmtStateMachine formatMain() {
|
|
|
+ int lpos = 0;
|
|
|
+ for (int pos = fmt.indexOf('{'); pos != -1 && hasMore(pos); lpos = pos+1, pos = fmt.indexOf('{', lpos)) {
|
|
|
+ str.append(fmt.substring(lpos, pos).replaceAll("}}", "}"));
|
|
|
+ int epos = fmt.indexOf('}', pos);
|
|
|
+
|
|
|
+ if (fmt.charAt(pos+1) == '{') {
|
|
|
+ // Literal '{'
|
|
|
+ str.append('{');
|
|
|
+ epos = pos + 1;
|
|
|
+ } else if (epos == pos+1) {
|
|
|
+ // Unmarked '{}' -> get the next argument
|
|
|
+ str.append(getToken(null));
|
|
|
+ } else {
|
|
|
+ epos = formatToken(pos, epos);
|
|
|
+ }
|
|
|
+ pos = epos;
|
|
|
+ }
|
|
|
+ str.append(remaining(lpos).replaceAll("}}", "}"));
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String remaining(int lpos) {
|
|
|
+ this.endPos = fmt.indexOf(lastCh, lpos);
|
|
|
+ return lastCh == '\0' ? fmt.substring(lpos) : fmt.substring(lpos, endPos++);
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean hasMore(int pos) {
|
|
|
+ return lastCh == '\0' || pos < fmt.indexOf(lastCh);
|
|
|
+ }
|
|
|
+
|
|
|
+ private int formatToken(int pos, int epos) {
|
|
|
+ final String token = fmt.substring(++pos, epos);
|
|
|
+ // If token contains a '.', use reflection?
|
|
|
+ // If token starts with '%', make it a c-format expression
|
|
|
+ if (token.indexOf('?') != -1) {
|
|
|
+ final int b1 = fmt.indexOf('?', pos)+1;
|
|
|
+ boolean _if = getConditionToken(token, b1-pos-1);
|
|
|
+ FmtStateMachine then = new FmtStateMachine(fmt.substring(b1), currentIdx, args, named, ':').formatMain();
|
|
|
+ FmtStateMachine els = new FmtStateMachine(fmt.substring(then.endPos+b1), then.currentIdx, args, named, '}').formatMain();
|
|
|
+ currentIdx = els.currentIdx;
|
|
|
+ epos = els.endPos+then.endPos+b1-1;
|
|
|
+ str.append(_if ? then : els);
|
|
|
+ } else {
|
|
|
+ str.append(getToken(token));
|
|
|
+ }
|
|
|
+ return epos;
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean getConditionToken(final String token, final int end) {
|
|
|
+ final String cond = token.substring(0, end);
|
|
|
+ Matcher matcher = Pattern.compile("^(\\d*|[a-zA-Z_]+)(>=?|<=?|!=|==?)(\\d+)$").matcher(cond);
|
|
|
+ if (matcher.matches()) {
|
|
|
+ final int cmp = ((Integer) getToken(matcher.group(1))).compareTo(Integer.valueOf(matcher.group(3)));
|
|
|
+ switch (matcher.group(2)) {
|
|
|
+ case "=" : return cmp == 0;
|
|
|
+ case "==": return cmp == 0;
|
|
|
+ case "!=": return cmp != 0;
|
|
|
+ case "<" : return cmp < 0;
|
|
|
+ case "<=": return cmp <= 0;
|
|
|
+ case ">" : return cmp > 0;
|
|
|
+ case ">=": return cmp >= 0;
|
|
|
+ default: throw new IllegalStateException();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ return (Boolean) getToken(cond);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private Object getToken(final String token) {
|
|
|
+ if (token == null || token.isEmpty()) {
|
|
|
+ return args[currentIdx++];
|
|
|
+ } else if (Character.isDigit(token.charAt(0))) {
|
|
|
+ return args[Integer.parseInt(token)];
|
|
|
+ } else {
|
|
|
+ return named.get(token);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public String toString() {
|
|
|
+ return str.toString();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ String fmt;
|
|
|
+
|
|
|
+ public StringFormatter(String logFmtString) {
|
|
|
+ Objects.requireNonNull(logFmtString);
|
|
|
+ this.fmt = logFmtString;
|
|
|
+ }
|
|
|
+
|
|
|
+ public String format(Object... args) {
|
|
|
+ return new FmtStateMachine(fmt, args).formatMain().toString();
|
|
|
+ }
|
|
|
+}
|