StringFormatter.java 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. package org.leumasjaffe.format;
  2. import java.util.HashMap;
  3. import java.util.Objects;
  4. import java.util.regex.Matcher;
  5. import java.util.regex.Pattern;
  6. import lombok.AccessLevel;
  7. import lombok.experimental.FieldDefaults;
  8. @FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
  9. public class StringFormatter {
  10. private static final class NameMap extends HashMap<String, Object> {
  11. /**
  12. *
  13. */
  14. private static final long serialVersionUID = -2243574068000774442L;
  15. NameMap(Object... args) {
  16. for (Object arg : args) {
  17. Objects.requireNonNull(arg);
  18. if (arg instanceof Named) {
  19. put(((Named) arg).getName(), arg);
  20. }
  21. }
  22. }
  23. }
  24. private static final class FmtStateMachine {
  25. final StringBuilder str;
  26. String fmt;
  27. Object[] args;
  28. int currentIdx;
  29. final NameMap named;
  30. int lastCh, endPos = 0;
  31. FmtStateMachine(String fmt, Object... args) {
  32. this(fmt, 0, args, new NameMap(args), '\0');
  33. }
  34. FmtStateMachine(String fmt, int idx, Object[] args, NameMap named, int lastCh) {
  35. this.str = new StringBuilder(fmt.length());
  36. this.fmt = fmt;
  37. this.currentIdx = idx;
  38. this.args = args;
  39. this.named = named;
  40. this.lastCh = lastCh;
  41. }
  42. FmtStateMachine formatMain() {
  43. int lpos = 0;
  44. for (int pos = fmt.indexOf('{'); pos != -1 && hasMore(pos); lpos = pos+1, pos = fmt.indexOf('{', lpos)) {
  45. str.append(fmt.substring(lpos, pos).replaceAll("}}", "}"));
  46. int epos = fmt.indexOf('}', pos);
  47. if (fmt.charAt(pos+1) == '{') {
  48. // Literal '{'
  49. str.append('{');
  50. epos = pos + 1;
  51. } else if (epos == pos+1) {
  52. // Unmarked '{}' -> get the next argument
  53. str.append(getToken(null));
  54. } else {
  55. epos = formatToken(pos, epos);
  56. }
  57. pos = epos;
  58. }
  59. str.append(remaining(lpos).replaceAll("}}", "}"));
  60. return this;
  61. }
  62. private String remaining(int lpos) {
  63. this.endPos = fmt.indexOf(lastCh, lpos);
  64. return lastCh == '\0' ? fmt.substring(lpos) : fmt.substring(lpos, endPos++);
  65. }
  66. private boolean hasMore(int pos) {
  67. return lastCh == '\0' || pos < fmt.indexOf(lastCh);
  68. }
  69. private int formatToken(int pos, int epos) {
  70. final String token = fmt.substring(++pos, epos);
  71. // If token contains a '.', use reflection?
  72. // If token starts with '%', make it a c-format expression
  73. if (token.indexOf('?') != -1) {
  74. final int b1 = fmt.indexOf('?', pos)+1;
  75. boolean _if = getConditionToken(token, b1-pos-1);
  76. FmtStateMachine then = new FmtStateMachine(fmt.substring(b1), currentIdx, args, named, ':').formatMain();
  77. FmtStateMachine els = new FmtStateMachine(fmt.substring(then.endPos+b1), then.currentIdx, args, named, '}').formatMain();
  78. currentIdx = els.currentIdx;
  79. epos = els.endPos+then.endPos+b1-1;
  80. str.append(_if ? then : els);
  81. } else {
  82. str.append(getToken(token));
  83. }
  84. return epos;
  85. }
  86. private boolean getConditionToken(final String token, final int end) {
  87. final String cond = token.substring(0, end);
  88. Matcher matcher = Pattern.compile("^(\\d*|[a-zA-Z_]+)(>=?|<=?|!=|==?)(\\d+)$").matcher(cond);
  89. if (matcher.matches()) {
  90. final int cmp = ((Integer) getToken(matcher.group(1))).compareTo(Integer.valueOf(matcher.group(3)));
  91. switch (matcher.group(2)) {
  92. case "=" : return cmp == 0;
  93. case "==": return cmp == 0;
  94. case "!=": return cmp != 0;
  95. case "<" : return cmp < 0;
  96. case "<=": return cmp <= 0;
  97. case ">" : return cmp > 0;
  98. case ">=": return cmp >= 0;
  99. default: throw new IllegalStateException();
  100. }
  101. } else {
  102. return (Boolean) getToken(cond);
  103. }
  104. }
  105. private Object getToken(final String token) {
  106. if (token == null || token.isEmpty()) {
  107. return args[currentIdx++];
  108. } else if (Character.isDigit(token.charAt(0))) {
  109. return args[Integer.parseInt(token)];
  110. } else {
  111. return named.get(token);
  112. }
  113. }
  114. public String toString() {
  115. return str.toString();
  116. }
  117. }
  118. String fmt;
  119. public StringFormatter(String logFmtString) {
  120. Objects.requireNonNull(logFmtString);
  121. this.fmt = logFmtString;
  122. }
  123. public String format(Object... args) {
  124. return new FmtStateMachine(fmt, args).formatMain().toString();
  125. }
  126. }