com.metaeffekt.mirror.contents.eol.EolStringFormatter Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2021-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.metaeffekt.mirror.contents.eol;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
/**
* The strings provided by the EOL Date data source may contain variables and expressions in their link and releaseLabel
* attributes. This class provides a formatter to replace these variables and expressions with values.
*
* Examples:
*
* - https://github.com/rabbitmq/rabbitmq-server/releases/tag/rabbitmq_v{{'__LATEST__'|replace:'.','_'}}
* - https://archive.apache.org/dist/kafka/__LATEST__/RELEASE_NOTES.html
* - OS X __RELEASE_CYCLE__ (__CODENAME__)
*
* This is a two-step process:
*
* - Replace variables with values
* - Replace expressions with values
*
* As of now, there is only one function:
*
* - replace: replace all occurrences of the first argument with the second argument
{{'1.2.3'|replace:'.',''}}
*
*/
public abstract class EolStringFormatter {
private final static Map> VARIABLE_MAPPERS = new LinkedHashMap>() {{
put("__LATEST__", EolCycle::getLatest);
put("__VERSION__", EolCycle::getLatest);
put("__RELEASE_CYCLE__", EolCycle::getCycle);
put("__CODENAME__", EolCycle::getCodename);
}};
public static String format(String baseString, EolCycle cycle) {
if (baseString == null) return null;
return baseString.contains("__") || baseString.contains("{{")
? replaceExpressions(replaceVariables(baseString, cycle), cycle)
: baseString;
}
private static String replaceVariables(String baseString, EolCycle cycle) {
if (baseString == null) return null;
String result = baseString;
for (Map.Entry> entry : VARIABLE_MAPPERS.entrySet()) {
final String applied = entry.getValue().apply(cycle);
final String escaped = applied == null ? "" : applied.replace("\\", "\\\\").replace("'", "\\'");
result = result.replace(entry.getKey(), escaped);
}
return result;
}
private static String replaceExpressions(String baseString, EolCycle cycle) {
String result = baseString;
for (int i = 0; i < 100; i++) {
final String expression = extractNextExpression(result);
if (expression == null) {
break;
}
final String replacement = evaluateExpression(expression, cycle);
result = result.replace(expression, replacement);
}
return result;
}
private static String extractNextExpression(String result) {
if (result == null) {
return null;
}
final int start = result.lastIndexOf("{{");
if (start < 0) {
return null;
}
final int end = result.indexOf("}}", start);
if (end < 0) {
return null;
}
return result.substring(start, end + 2);
}
/**
* Evaluates expressions of the form {{'object'|function:'argument1','argument2'}}
.
*
* @param expression {{'object'|function:'argument1','argument2'}}
* @param cycle EOL cycle to get values from
* @return evaluated expression
*/
private static String evaluateExpression(String expression, EolCycle cycle) {
final String innerExpression = expression.substring(2, expression.length() - 2);
// go character per character and tokenize the string to prevent incorrect matching with the "'" or "\" character
final List tokens = tokenizeString(innerExpression);
// find object, function and arguments and call evaluateFunction
if (tokens.size() >= 3) {
final String object = tokens.get(0);
final String functionName = tokens.get(1);
final String[] arguments = tokens.subList(2, tokens.size()).toArray(new String[0]);
return evaluateFunction(object, functionName, arguments, cycle);
}
throw new IllegalArgumentException("Invalid expression: " + innerExpression);
}
private static String evaluateFunction(String object, String functionName, String[] arguments, EolCycle cycle) {
switch (functionName) {
case "replace":
return object.replace(arguments[0], arguments[1]);
case "trim":
return object.trim();
case "toLowerCase":
case "lowerCase":
case "lower":
return object.toLowerCase();
case "toUpperCase":
case "upperCase":
case "upper":
return object.toUpperCase();
case "substring":
if (arguments.length == 1) {
return object.substring(Integer.parseInt(arguments[0]));
} else if (arguments.length == 2) {
return object.substring(Integer.parseInt(arguments[0]), Integer.parseInt(arguments[1]));
} else {
throw new IllegalArgumentException("Invalid number of arguments for substring: " + arguments.length);
}
default:
throw new IllegalArgumentException("Unknown function: " + functionName);
}
}
private static List tokenizeString(String innerExpression) {
final StringBuilder tokenBuilder = new StringBuilder();
final List tokens = new ArrayList<>();
boolean inString = false;
boolean wasInStringBeforeExiting = false;
for (int i = 0; i < innerExpression.length(); i++) {
final char c = innerExpression.charAt(i);
if (c == '\\') {
tokenBuilder.append(innerExpression.charAt(++i));
} else if (c == '\'') {
inString = !inString;
wasInStringBeforeExiting = true;
} else if ((c == ':' || c == '|' || c == ',') && !inString) {
tokens.add(tokenBuilder.toString());
tokenBuilder.setLength(0);
wasInStringBeforeExiting = false;
} else {
tokenBuilder.append(c);
}
}
if (tokenBuilder.length() > 0 || wasInStringBeforeExiting) {
tokens.add(tokenBuilder.toString());
}
return tokens;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy