Skip to content

Instantly share code, notes, and snippets.

@attacco
Created November 14, 2018 17:45
Show Gist options
  • Save attacco/42c806061df3f20931825e49e505a5de to your computer and use it in GitHub Desktop.
Save attacco/42c806061df3f20931825e49e505a5de to your computer and use it in GitHub Desktop.
public class ResourceUtils {
// example: ${date:DD.MM}
private static final Pattern DATE_TIME_PLACEHOLDER_PATTERN =
Pattern.compile("\\$\\{(?<value>date:[\\w.:\\-\\s/\\\\]+)}");
// examples: ${number:N}
private static final Pattern NUMBER_PLACEHOLDER_PATTERN =
Pattern.compile("\\$\\{(?<value>number:[\\w.:\\-\\s/\\\\?]+)}");
private static final Map<String, Formatter> FORMATTERS = Stream.of(
new AbstractMap.SimpleEntry<>("date:DD.MM", new DateTimeFormatter("dd.MM")),
new AbstractMap.SimpleEntry<>("date:DD.MM.YYYY", new DateTimeFormatter("dd.MM.yyyy")),
new AbstractMap.SimpleEntry<>("date:DD.MM.YYYY HH:MM", new DateTimeFormatter("dd.MM.yyyy HH:mm")),
new AbstractMap.SimpleEntry<>("number:N", new NumberFormatter("0")),
new AbstractMap.SimpleEntry<>("number:N.00", new NumberFormatter("0.00")),
new AbstractMap.SimpleEntry<>("number:N.??", new NumberFormatter("0.##"))
).collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue));
private static final Formatter NULL_FORMATTER = arg -> "null";
public static String formatString(String str, Object[] args) {
if (StringUtils.isEmpty(str)) {
return str;
}
List<Operation> operations = new ArrayList<>();
collectOperations(operations, DATE_TIME_PLACEHOLDER_PATTERN, str);
collectOperations(operations, NUMBER_PLACEHOLDER_PATTERN, str);
if (operations.isEmpty()) {
return str;
} else if (operations.size() != args.length) {
throw new IllegalArgumentException(String.format(
"Arguments count [%d] doesn't matches to placeholders count [%d]",
args.length, operations.size()));
}
operations.sort(Comparator.comparing(Operation::getRangeStart));
StringBuilder sb = new StringBuilder();
Operation lastOperation = null;
for (int i = 0; i < operations.size(); i++) {
Operation operation = operations.get(i);
Object arg = args[i];
Formatter formatter = arg == null ? NULL_FORMATTER : FORMATTERS.get(operation.getPattern());
if (formatter == null) {
throw new IllegalArgumentException(String.format(
"Unable to find appropriate formatter for pattern [%s]", operation.getPattern()));
}
String formattedValue;
try {
formattedValue = formatter.format(arg);
} catch (RuntimeException e) {
throw new IllegalArgumentException(String.format("Unable to format arg [%s] using pattern [%s]",
arg, operation.getPattern()), e);
}
int startIndex = lastOperation == null ? 0 : lastOperation.getRangeEnd() + 1;
if (operation.getRangeStart() > startIndex) {
sb.append(str.substring(startIndex, operation.getRangeStart()));
}
sb.append(formattedValue);
lastOperation = operation;
}
if (lastOperation != null && lastOperation.getRangeEnd() < str.length() - 1) {
sb.append(str.substring(lastOperation.getRangeEnd() + 1, str.length()));
}
return sb.toString();
}
private static void collectOperations(List<Operation> operations, Pattern pattern, String str) {
Matcher matcher = pattern.matcher(str);
while (matcher.find()) {
operations.add(new Operation(matcher.start(), matcher.end() - 1, matcher.group("value")));
}
}
private interface Formatter {
String format(Object arg);
}
private static class DateTimeFormatter implements Formatter {
private final java.time.format.DateTimeFormatter dateTimeFormatter;
public DateTimeFormatter(String pattern) {
this.dateTimeFormatter = java.time.format.DateTimeFormatter.ofPattern(pattern);
}
@Override
public String format(Object arg) {
if (!(arg instanceof TemporalAccessor)) {
throw new IllegalArgumentException(
String.format("Argument [%s] can not be formatted as date-time value", arg));
}
return dateTimeFormatter.format((TemporalAccessor) arg);
}
}
private static class NumberFormatter implements Formatter {
private final DecimalFormat decimalFormat;
public NumberFormatter(String pattern) {
DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance();
dfs.setDecimalSeparator('.');
decimalFormat = new DecimalFormat(pattern);
decimalFormat.setDecimalFormatSymbols(dfs);
}
@Override
public synchronized String format(Object arg) {
return decimalFormat.format(arg);
}
}
private static class Operation {
private final int rangeStart;
private final int rangeEnd;
private final String pattern;
public Operation(int rangeStart, int rangeEnd, String pattern) {
this.rangeStart = rangeStart;
this.rangeEnd = rangeEnd;
this.pattern = pattern;
}
public int getRangeStart() {
return rangeStart;
}
public int getRangeEnd() {
return rangeEnd;
}
public String getPattern() {
return pattern;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment