Use antlr4 for post-processing variable expansion

This will break the current syntax, but make it easier to extend functionality including the implementation of functions to convert data.
This commit is contained in:
0xb00bface 2023-02-19 17:01:58 +01:00
parent a439881dc3
commit af7c36c65a
17 changed files with 404 additions and 163 deletions

View File

@ -12,6 +12,10 @@
<relativePath>../master</relativePath>
</parent>
<properties>
<antlr.version>4.11.1</antlr.version>
</properties>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
@ -62,6 +66,32 @@
<version>2.3.1</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.antlr</groupId>
<artifactId>antlr4-runtime</artifactId>
<version>${antlr.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.antlr</groupId>
<artifactId>antlr4-maven-plugin</artifactId>
<version>${antlr.version}</version>
<executions>
<execution>
<goals>
<goal>antlr4</goal>
</goals>
<configuration>
<visitor>true</visitor>
<listener>true</listener>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,21 @@
grammar PostProcessing;
line: (text | variable | functionCall)* EOF;
functionCall: '$' identifier '(' (expression (',' expression)*)? ')';
text: CH+?;
identifier: CH+?;
parameter: text;
variable: '${' identifier ('(' parameter ')')? ('?' expression)? '}';
expression
: text
| variable
| functionCall
;
CH: .;
WS: [ \t\r\n]+ -> skip;

View File

@ -8,7 +8,8 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class StringUtil {
private StringUtil() {}
private StringUtil() {
}
public static boolean isBlank(String s) {
return s == null || s.trim().isEmpty();
@ -55,8 +56,8 @@ public class StringUtil {
/**
* Converts one byte to its hex representation with leading zeros. E.g. 255 -> FF, 12 -> 0C
*
* @param b
* @return
* @param b the byte value to represent in hex
* @return the hexadecimal representation as string
*/
public static String toHexString(int b) {
String hex = Integer.toHexString(b & 0xFF);
@ -122,20 +123,20 @@ public class StringUtil {
}
s = s.toLowerCase();
s = s.replaceAll("-", " ");
s = s.replaceAll(":", " ");
s = s.replaceAll(";", " ");
s = s.replaceAll("\\|", " ");
s = s.replaceAll("_", " ");
s = s.replaceAll("\\.", "\\. ");
s = s.replace("-", " ");
s = s.replace(":", " ");
s = s.replace(";", " ");
s = s.replace("\\|", " ");
s = s.replace("_", " ");
s = s.replace("\\.", "\\. ");
s = s.trim();
t = t.toLowerCase();
t = t.replaceAll("-", " ");
t = t.replaceAll(":", " ");
t = t.replaceAll(";", " ");
t = t.replaceAll("\\|", " ");
t = t.replaceAll("_", " ");
t = t.replaceAll("\\.", "\\. ");
t = t.replace("-", " ");
t = t.replace(":", " ");
t = t.replace(";", " ");
t = t.replace("\\|", " ");
t = t.replace("_", " ");
t = t.replace("\\.", "\\. ");
t = t.trim();
// calculate levenshteinDistance
@ -150,7 +151,7 @@ public class StringUtil {
public static int getLevenshteinDistance(String s, String t) {
int n = s.length();
int m = t.length();
int d[][] = new int[n + 1][m + 1];
int[][] d = new int[n + 1][m + 1];
int i;
int j;
int cost;
@ -208,4 +209,34 @@ public class StringUtil {
}
return result.toArray(new String[0]);
}
public static String capitalize(String string) {
if (string.length() > 0) {
StringTokenizer st = new StringTokenizer(string, " _-.", true);
var sb = new StringBuilder();
while (st.hasMoreTokens()) {
replaceBlacklistedCharacters(sb, st.nextToken());
}
string = sb.toString();
}
return string;
}
private static void replaceBlacklistedCharacters(StringBuilder sb, String token) {
StringBuilder temp = new StringBuilder(token);
char first = temp.charAt(0);
if (first >= 'a' && first <= 'z') { // if first is a letter
first -= 32;
temp.setCharAt(0, first);
} else {
if (temp.length() > 1) {
char second = temp.charAt(1);
if (second >= 'a' && second <= 'z') {
second -= 32;
temp.setCharAt(1, second);
}
}
}
sb.append(temp);
}
}

View File

@ -4,98 +4,80 @@ import ctbrec.Config;
import ctbrec.Recording;
import ctbrec.StringUtil;
import ctbrec.variableexpansion.ModelVariableExpander;
import ctbrec.variableexpansion.ParserVisitor;
import ctbrec.variableexpansion.VariableExpansionException;
import ctbrec.variableexpansion.antlr.PostProcessingLexer;
import ctbrec.variableexpansion.antlr.PostProcessingParser;
import ctbrec.variableexpansion.functions.AntlrSyntacErrorAdapter;
import lombok.extern.slf4j.Slf4j;
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.ParseTree;
import java.io.IOException;
import java.io.StringReader;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Function;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import static java.util.Optional.ofNullable;
@Slf4j
public abstract class AbstractPlaceholderAwarePostProcessor extends AbstractPostProcessor {
public String fillInPlaceHolders(String input, PostProcessingContext ctx) {
Recording rec = ctx.getRecording();
Config config = ctx.getConfig();
ModelVariableExpander modelExpander = new ModelVariableExpander(rec.getModel(), config, ctx.getRecorder());
Map<String, Function<String, Optional<String>>> placeholderValueSuppliers = new HashMap<>(modelExpander.getPlaceholderValueSuppliers());
placeholderValueSuppliers.put("recordingNotes", r -> getSanitizedRecordingNotes(rec));
placeholderValueSuppliers.put("fileSuffix", r -> getFileSuffix(rec));
placeholderValueSuppliers.put("recordingsDir", r -> Optional.of(config.getSettings().recordingsDir));
placeholderValueSuppliers.put("absolutePath", r -> Optional.of(rec.getPostProcessedFile().getAbsolutePath()));
placeholderValueSuppliers.put("absoluteParentPath", r -> Optional.of(rec.getPostProcessedFile().getParentFile().getAbsolutePath()));
placeholderValueSuppliers.put("epochSecond", r -> ofNullable(rec.getStartDate()).map(Instant::getEpochSecond).map(l -> Long.toString(l))); // NOSONAR
placeholderValueSuppliers.put("utcDateTime", pattern -> replaceUtcDateTime(rec, pattern));
placeholderValueSuppliers.put("localDateTime", pattern -> replaceLocalDateTime(rec, pattern));
Map<String, Optional<Object>> variables = new HashMap<>(modelExpander.getPlaceholderValueSuppliers());
variables.put("recordingNotes", getSanitizedRecordingNotes(rec));
variables.put("fileSuffix", getFileSuffix(rec));
variables.put("recordingsDir", Optional.of(config.getSettings().recordingsDir));
variables.put("absolutePath", Optional.of(rec.getPostProcessedFile().getAbsolutePath()));
variables.put("absoluteParentPath", Optional.of(rec.getPostProcessedFile().getParentFile().getAbsolutePath()));
variables.put("epochSecond", ofNullable(rec.getStartDate()).map(Instant::getEpochSecond).map(l -> Long.toString(l))); // NOSONAR
variables.put("utcDateTime", getUtcDateTime(rec));
variables.put("localDateTime", getLocalDateTime(rec));
String output = fillInPlaceHolders(input, placeholderValueSuppliers);
return output;
return fillInPlaceHolders(input, variables);
}
private String fillInPlaceHolders(String input, Map<String, Function<String, Optional<String>>> placeholderValueSuppliers) {
boolean somethingReplaced;
do {
somethingReplaced = false;
int end = input.indexOf("}");
if (end > 0) {
int start = input.substring(0, end).lastIndexOf("${");
if (start >= 0) {
String placeholder = input.substring(start, end + 1);
String placeholderName = placeholder.substring(2, placeholder.length() - 1);
String defaultValue;
String expression = null;
int questionMark = placeholder.indexOf('?');
if (questionMark > 0) {
placeholderName = placeholder.substring(2, questionMark);
defaultValue = placeholder.substring(questionMark + 1, placeholder.length() - 1);
} else {
defaultValue = "";
}
int bracket = placeholder.indexOf('(');
if (bracket > 0) {
placeholderName = placeholder.substring(2, bracket);
expression = placeholder.substring(bracket + 1, placeholder.indexOf(')', bracket));
}
final String name = placeholderName;
Optional<String> optionalValue = placeholderValueSuppliers.getOrDefault(name, r -> Optional.of(name)).apply(expression);
String value = optionalValue.orElse(defaultValue);
StringBuilder sb = new StringBuilder(input);
String output = sb.replace(start, end + 1, value).toString();
somethingReplaced = !Objects.equals(input, output);
input = output;
private String fillInPlaceHolders(String input, Map<String, Optional<Object>> variables) {
try (StringReader reader = new StringReader(input)) {
CharStream s = CharStreams.fromReader(reader);
PostProcessingLexer lexer = new PostProcessingLexer(s);
CommonTokenStream tokens = new CommonTokenStream(lexer);
PostProcessingParser parser = new PostProcessingParser(tokens);
parser.addErrorListener(new AntlrSyntacErrorAdapter() {
@Override
public void syntaxError(Recognizer<?, ?> recognizer, Object o, int line, int pos, String s, RecognitionException e) {
log.warn("Syntax error at {}:{} {}", line, pos, s);
}
}
} while (somethingReplaced);
return input;
});
ParseTree parseTree = parser.line();
ParserVisitor visitor = new ParserVisitor(variables);
return visitor.visit(parseTree);
} catch (IOException e) {
throw new VariableExpansionException("Couldn't replace placeholders", e);
}
}
private Optional<String> replaceUtcDateTime(Recording rec, String pattern) {
return replaceDateTime(rec, pattern, ZoneOffset.UTC);
protected Optional<Object> getUtcDateTime(Recording rec) {
return Optional.ofNullable(rec)
.map(Recording::getStartDate)
.map(i -> i.atZone(ZoneOffset.UTC));
}
private Optional<String> replaceLocalDateTime(Recording rec, String filename) {
return replaceDateTime(rec, filename, ZoneId.systemDefault());
protected Optional<Object> getLocalDateTime(Recording rec) {
return Optional.ofNullable(rec)
.map(Recording::getStartDate)
.map(i -> i.atZone(ZoneId.systemDefault()));
}
private Optional<String> replaceDateTime(Recording rec, String pattern, ZoneId zone) {
pattern = pattern != null ? pattern : "yyyy-MM-dd_HH-mm-ss";
return getDateTime(rec, pattern, zone);
}
private Optional<String> getDateTime(Recording rec, String pattern, ZoneId zone) {
return Optional.ofNullable(rec.getStartDate()) //
.map(DateTimeFormatter.ofPattern(pattern) //
.withLocale(Locale.getDefault()) //
.withZone(zone) //
::format);
}
private Optional<String> getFileSuffix(Recording rec) {
private Optional<Object> getFileSuffix(Recording rec) {
if (rec.isSingleFile()) {
String filename = rec.getPostProcessedFile().getName();
return Optional.of(filename.substring(filename.lastIndexOf('.') + 1));
@ -104,7 +86,7 @@ public abstract class AbstractPlaceholderAwarePostProcessor extends AbstractPost
}
}
private Optional<String> getSanitizedRecordingNotes(Recording rec) {
private Optional<Object> getSanitizedRecordingNotes(Recording rec) {
Optional<String> notes = ofNullable(rec.getNote());
return notes.map(StringUtil::sanitize);
}

View File

@ -1,59 +1,45 @@
package ctbrec.variableexpansion;
import ctbrec.variableexpansion.antlr.PostProcessingLexer;
import ctbrec.variableexpansion.antlr.PostProcessingParser;
import ctbrec.variableexpansion.functions.AntlrSyntacErrorAdapter;
import lombok.extern.slf4j.Slf4j;
import org.antlr.v4.runtime.*;
import java.io.IOException;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
@Slf4j
abstract class AbstractVariableExpander {
protected Map<String, Function<String, Optional<String>>> placeholderValueSuppliers = new HashMap<>();
protected Map<String, Optional<Object>> placeholderValueSuppliers = new HashMap<>();
protected String fillInPlaceHolders(String input, Map<String, Function<String, Optional<String>>> placeholderValueSuppliers) {
boolean somethingReplaced = false;
do {
somethingReplaced = false;
int end = input.indexOf("}");
if (end > 0) {
int start = input.substring(0, end).lastIndexOf("${");
if (start >= 0) {
String placeholder = input.substring(start, end + 1);
String placeholderName = placeholder.substring(2, placeholder.length() - 1);
String defaultValue = null;
String expression = null;
int questionMark = placeholder.indexOf('?');
if (questionMark > 0) {
placeholderName = placeholder.substring(2, questionMark);
defaultValue = placeholder.substring(questionMark + 1, placeholder.length() - 1);
} else {
defaultValue = "";
}
int bracket = placeholder.indexOf('(');
if (bracket > 0) {
placeholderName = placeholder.substring(2, bracket);
expression = placeholder.substring(bracket + 1, placeholder.indexOf(')', bracket));
}
final String name = placeholderName;
Optional<String> optionalValue = placeholderValueSuppliers.getOrDefault(name, r -> Optional.of(name)).apply(expression);
String value = optionalValue.orElse(defaultValue);
StringBuilder sb = new StringBuilder(input);
String output = sb.replace(start, end+1, value).toString();
somethingReplaced = !Objects.equals(input, output);
input = output;
protected String fillInPlaceHolders(String input, Map<String, Optional<Object>> variables) {
try (StringReader reader = new StringReader(input)) {
CharStream s = CharStreams.fromReader(reader);
PostProcessingLexer lexer = new PostProcessingLexer(s);
CommonTokenStream tokens = new CommonTokenStream(lexer);
PostProcessingParser parser = new PostProcessingParser(tokens);
parser.addErrorListener(new AntlrSyntacErrorAdapter() {
@Override
public void syntaxError(Recognizer<?, ?> recognizer, Object o, int line, int pos, String s, RecognitionException e) {
log.warn("Syntax error at {}:{} {}", line, pos, s);
}
}
} while (somethingReplaced);
});
PostProcessingParser.LineContext ctx = parser.line();
ParserVisitor visitor = new ParserVisitor(variables);
return visitor.visit(ctx);
} catch (IOException e) {
log.error("Couldn't fill in placeholders", e);
}
return input;
}
public Set<String> getPlaceholderNames() {
return placeholderValueSuppliers.keySet();
}
public Map<String, Function<String, Optional<String>>> getPlaceholderValueSuppliers() {
public Map<String, Optional<Object>> getPlaceholderValueSuppliers() {
return placeholderValueSuppliers;
}
}

View File

@ -1,11 +1,5 @@
package ctbrec.variableexpansion;
import static ctbrec.StringUtil.*;
import static java.util.Optional.*;
import java.util.Optional;
import java.util.UUID;
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.ModelGroup;
@ -13,25 +7,30 @@ import ctbrec.StringUtil;
import ctbrec.recorder.Recorder;
import ctbrec.sites.Site;
import java.util.Optional;
import java.util.UUID;
import static java.util.Optional.ofNullable;
public class ModelVariableExpander extends AbstractVariableExpander {
public ModelVariableExpander(Model model, Config config, Recorder recorder) {
Optional<ModelGroup> modelGroup = Optional.ofNullable(recorder).flatMap(r -> r.getModelGroup(model));
placeholderValueSuppliers.put("modelName", r -> ofNullable(model.getName()));
placeholderValueSuppliers.put("modelDisplayName", r -> ofNullable(model.getDisplayName()));
placeholderValueSuppliers.put("modelSanitizedName", r -> getSanitizedName(model));
placeholderValueSuppliers.put("modelNotes", r -> getSanitizedModelNotes(config, model));
placeholderValueSuppliers.put("siteName", r -> ofNullable(model).map(Model::getSite).map(Site::getName));
placeholderValueSuppliers.put("siteSanitizedName", r -> getSanitizedSiteName(model));
placeholderValueSuppliers.put("modelGroupName", r -> modelGroup.map(ModelGroup::getName));
placeholderValueSuppliers.put("modelGroupId", r -> modelGroup.map(ModelGroup::getId).map(UUID::toString));
placeholderValueSuppliers.put("modelName", ofNullable(model.getName()));
placeholderValueSuppliers.put("modelDisplayName", ofNullable(model.getDisplayName()));
placeholderValueSuppliers.put("modelSanitizedName",getSanitizedName(model));
placeholderValueSuppliers.put("modelNotes",getSanitizedModelNotes(config, model));
placeholderValueSuppliers.put("siteName", ofNullable(model).map(Model::getSite).map(Site::getName));
placeholderValueSuppliers.put("siteSanitizedName",getSanitizedSiteName(model));
placeholderValueSuppliers.put("modelGroupName", modelGroup.map(ModelGroup::getName));
placeholderValueSuppliers.put("modelGroupId", modelGroup.map(ModelGroup::getId).map(UUID::toString));
}
public String expand(String input) {
return fillInPlaceHolders(input, placeholderValueSuppliers);
}
private Optional<String> getSanitizedName(Model model) {
private Optional<Object> getSanitizedName(Model model) {
String name = model.getSanitizedNamed();
if (StringUtil.isBlank(name)) {
return Optional.empty();
@ -40,21 +39,11 @@ public class ModelVariableExpander extends AbstractVariableExpander {
}
}
private Optional<String> getSanitizedSiteName(Model model) {
Optional<String> name = ofNullable(model).map(Model::getSite).map(Site::getName);
if (name.isPresent()) {
return Optional.of(sanitize(name.get()));
} else {
return Optional.empty();
}
private Optional<Object> getSanitizedSiteName(Model model) {
return ofNullable(model).map(Model::getSite).map(Site::getName).map(StringUtil::sanitize);
}
private Optional<String> getSanitizedModelNotes(Config config, Model m) {
Optional<String> notes = ofNullable(config.getModelNotes(m));
if (notes.isPresent()) {
return Optional.of(sanitize(notes.get()));
} else {
return Optional.empty();
}
private Optional<Object> getSanitizedModelNotes(Config config, Model m) {
return ofNullable(config.getModelNotes(m)).map(StringUtil::sanitize);
}
}

View File

@ -0,0 +1,64 @@
package ctbrec.variableexpansion;
import ctbrec.variableexpansion.antlr.PostProcessingBaseVisitor;
import ctbrec.variableexpansion.antlr.PostProcessingParser;
import ctbrec.variableexpansion.functions.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class ParserVisitor extends PostProcessingBaseVisitor<String> {
private final Map<String, VarArgsFunction<Object, String>> functions = new HashMap<>();
private final Map<String, Optional<Object>> variables;
public ParserVisitor(Map<String, Optional<Object>> variables) {
this.variables = variables;
functions.put("trim", new Trim());
functions.put("upper", new Upper());
functions.put("lower", new Lower());
functions.put("format", new Format());
functions.put("capitalize", new Capitalize());
functions.put("sanitize", new Sanitize());
}
@Override
public String visitFunctionCall(PostProcessingParser.FunctionCallContext ctx) {
String identifier = ctx.identifier().getText();
VarArgsFunction<Object, String> function = functions.get(identifier);
if (function != null) {
List<PostProcessingParser.ExpressionContext> parameters = ctx.expression();
List<String> params = parameters.stream().map(this::visitExpression).toList();
return function.apply(params.toArray(new Object[0]));
} else {
return ctx.getText();
}
}
@Override
public String visitVariable(PostProcessingParser.VariableContext ctx) {
String identifier = ctx.identifier().getText();
Optional<Object> value = variables.get(identifier);
if (value != null && value.isPresent()) { // NOSONAR
return value.get().toString();
} else {
if (ctx.expression() != null) {
return visitExpression(ctx.expression());
} else {
return "";
}
}
}
@Override
public String visitText(PostProcessingParser.TextContext ctx) {
return ctx.getText();
}
@Override
protected String aggregateResult(String aggregate, String nextResult) {
return Optional.ofNullable(aggregate).orElse("") + Optional.ofNullable(nextResult).orElse("");
}
}

View File

@ -0,0 +1,7 @@
package ctbrec.variableexpansion;
@FunctionalInterface
public interface VarArgsFunction<I, O> {
O apply(I... params);
}

View File

@ -0,0 +1,7 @@
package ctbrec.variableexpansion;
public class VariableExpansionException extends RuntimeException {
public VariableExpansionException(String s, Exception e) {
super(s, e);
}
}

View File

@ -0,0 +1,26 @@
package ctbrec.variableexpansion.functions;
import org.antlr.v4.runtime.ANTLRErrorListener;
import org.antlr.v4.runtime.Parser;
import org.antlr.v4.runtime.atn.ATNConfigSet;
import org.antlr.v4.runtime.dfa.DFA;
import java.util.BitSet;
public abstract class AntlrSyntacErrorAdapter implements ANTLRErrorListener {
@Override
public void reportAmbiguity(Parser parser, DFA dfa, int i, int i1, boolean b, BitSet bitSet, ATNConfigSet atnConfigSet) {
// not intereseted
}
@Override
public void reportAttemptingFullContext(Parser parser, DFA dfa, int i, int i1, BitSet bitSet, ATNConfigSet atnConfigSet) {
// not intereseted
}
@Override
public void reportContextSensitivity(Parser parser, DFA dfa, int i, int i1, int i2, ATNConfigSet atnConfigSet) {
// not intereseted
}
}

View File

@ -0,0 +1,13 @@
package ctbrec.variableexpansion.functions;
import ctbrec.StringUtil;
import ctbrec.variableexpansion.VarArgsFunction;
import java.util.Optional;
public class Capitalize implements VarArgsFunction<Object, String> {
@Override
public String apply(Object[] params) {
return Optional.ofNullable(params).map(p -> p[0]).map(String.class::cast).map(StringUtil::capitalize).orElse("");
}
}

View File

@ -0,0 +1,27 @@
package ctbrec.variableexpansion.functions;
import ctbrec.variableexpansion.VarArgsFunction;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Optional;
public class Format implements VarArgsFunction<Object, String> {
@Override
public String apply(Object[] params) {
return Optional.ofNullable(params).map(p -> {
String format = "yyyy-MM-dd_HH-mm-ss";
if (p.length > 1) {
format = (String) p[1];
}
ZonedDateTime zdt;
if (p[0] instanceof String date) {
zdt = ZonedDateTime.parse(date);
} else {
zdt = (ZonedDateTime) p[0];
}
return DateTimeFormatter.ofPattern(format).format(zdt);
})
.orElse("");
}
}

View File

@ -0,0 +1,12 @@
package ctbrec.variableexpansion.functions;
import ctbrec.variableexpansion.VarArgsFunction;
import java.util.Optional;
public class Lower implements VarArgsFunction<Object, String> {
@Override
public String apply(Object[] params) {
return Optional.ofNullable(params).map(sa -> sa[0]).map(String.class::cast).map(String::toLowerCase).orElse("");
}
}

View File

@ -0,0 +1,13 @@
package ctbrec.variableexpansion.functions;
import ctbrec.StringUtil;
import ctbrec.variableexpansion.VarArgsFunction;
import java.util.Optional;
public class Sanitize implements VarArgsFunction<Object, String> {
@Override
public String apply(Object[] params) {
return Optional.ofNullable(params).map(p -> p[0]).map(String.class::cast).map(StringUtil::sanitize).orElse("");
}
}

View File

@ -0,0 +1,12 @@
package ctbrec.variableexpansion.functions;
import ctbrec.variableexpansion.VarArgsFunction;
import java.util.Optional;
public class Trim implements VarArgsFunction<Object, String> {
@Override
public String apply(Object[] params) {
return Optional.ofNullable(params).map(sa -> sa[0]).map(String.class::cast).map(String::trim).orElse("");
}
}

View File

@ -0,0 +1,12 @@
package ctbrec.variableexpansion.functions;
import ctbrec.variableexpansion.VarArgsFunction;
import java.util.Optional;
public class Upper implements VarArgsFunction<Object, String> {
@Override
public String apply(Object[] params) {
return Optional.ofNullable(params).map(sa -> sa[0]).map(String.class::cast).map(String::toUpperCase).orElse("");
}
}

View File

@ -61,7 +61,7 @@ class AbstractPlaceholderAwarePostProcessorTest extends AbstractPpTest {
.withLocale(Locale.US)
.withZone(ZoneOffset.UTC)
.format(rec.getStartDate());
String input = "asdf_${utcDateTime}_asdf";
String input = "asdf_$format(${utcDateTime})_asdf";
assertEquals("asdf_" + date + "_asdf", placeHolderAwarePp.fillInPlaceHolders(input, createPostProcessingContext(rec, null, config)));
// with user defined pattern
@ -69,7 +69,7 @@ class AbstractPlaceholderAwarePostProcessorTest extends AbstractPpTest {
.withLocale(Locale.US)
.withZone(ZoneOffset.UTC)
.format(rec.getStartDate());
input = "asdf_${utcDateTime(yyyyMMdd-HHmmss)}_asdf";
input = "asdf_$format(${utcDateTime},yyyyMMdd-HHmmss)_asdf";
assertEquals("asdf_" + date + "_asdf", placeHolderAwarePp.fillInPlaceHolders(input, createPostProcessingContext(rec, null, config)));
// multiple occurences with user defined patterns
@ -77,7 +77,7 @@ class AbstractPlaceholderAwarePostProcessorTest extends AbstractPpTest {
.withLocale(Locale.US)
.withZone(ZoneOffset.UTC)
.format(rec.getStartDate());
input = "asdf_${utcDateTime(yyyy)}-${utcDateTime(MM)}-${utcDateTime(dd)}/${utcDateTime(yyyy)}_asdf";
input = "asdf_$format(${utcDateTime},yyyy)-$format(${utcDateTime},MM)-$format(${utcDateTime},dd)/$format(${utcDateTime},yyyy)_asdf";
assertEquals("asdf_" + date + "_asdf", placeHolderAwarePp.fillInPlaceHolders(input, createPostProcessingContext(rec, null, config)));
}
@ -87,14 +87,14 @@ class AbstractPlaceholderAwarePostProcessorTest extends AbstractPpTest {
.withLocale(Locale.US)
.withZone(ZoneId.systemDefault())
.format(rec.getStartDate());
String input = "asdf_${localDateTime}_asdf";
String input = "asdf_$format(${localDateTime})_asdf";
assertEquals("asdf_" + date + "_asdf", placeHolderAwarePp.fillInPlaceHolders(input, createPostProcessingContext(rec, null, config)));
date = DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss")
.withLocale(Locale.US)
.withZone(ZoneId.systemDefault())
.format(rec.getStartDate());
input = "asdf_${localDateTime(yyyyMMdd-HHmmss)}_asdf";
input = "asdf_$format(${localDateTime},yyyyMMdd-HHmmss)_asdf";
assertEquals("asdf_" + date + "_asdf", placeHolderAwarePp.fillInPlaceHolders(input, createPostProcessingContext(rec, null, config)));
}
@ -142,7 +142,7 @@ class AbstractPlaceholderAwarePostProcessorTest extends AbstractPpTest {
ctx.getRecording().getModel().setName(null);
assertEquals("asdf_anonymous_asdf", placeHolderAwarePp.fillInPlaceHolders(input, ctx));
input = "asdf_${modelGroupName?${utcDateTime(yyyy)?anonymous}}_asdf";
input = "asdf_${modelGroupName?$format(${utcDateTime},yyyy)}_asdf";
int year = LocalDate.now().getYear();
assertEquals("asdf_" + year + "_asdf", placeHolderAwarePp.fillInPlaceHolders(input, ctx));
@ -160,4 +160,13 @@ class AbstractPlaceholderAwarePostProcessorTest extends AbstractPpTest {
when(config.getModelNotes(any())).thenReturn(null);
assertEquals("asdf__asdf", placeHolderAwarePp.fillInPlaceHolders(input, createPostProcessingContext(rec, null, config)));
}
@Test
void testFunctionCalls() {
String input = "$upper(${modelName})";
assertEquals("MOCKITA BOOBILICIOUS", placeHolderAwarePp.fillInPlaceHolders(input, createPostProcessingContext(rec, null, config)));
input = "$upper(${doesNotExist?mockita boobilicious})";
assertEquals("MOCKITA BOOBILICIOUS", placeHolderAwarePp.fillInPlaceHolders(input, createPostProcessingContext(rec, null, config)));
}
}