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:
parent
a439881dc3
commit
af7c36c65a
|
@ -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>
|
||||
|
|
|
@ -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;
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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("");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package ctbrec.variableexpansion;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface VarArgsFunction<I, O> {
|
||||
|
||||
O apply(I... params);
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package ctbrec.variableexpansion;
|
||||
|
||||
public class VariableExpansionException extends RuntimeException {
|
||||
public VariableExpansionException(String s, Exception e) {
|
||||
super(s, e);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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("");
|
||||
}
|
||||
}
|
|
@ -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("");
|
||||
}
|
||||
}
|
|
@ -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("");
|
||||
}
|
||||
}
|
|
@ -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("");
|
||||
}
|
||||
}
|
|
@ -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("");
|
||||
}
|
||||
}
|
|
@ -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("");
|
||||
}
|
||||
}
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue