
com.univocity.api.common.ParameterizedString Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of univocity-common-api Show documentation
Show all versions of univocity-common-api Show documentation
univocity public API - Common classes and utilites
The newest version!
/*
* Copyright (c) 2013 Univocity Software Pty Ltd. All rights reserved.
* This file is subject to the terms and conditions defined in file
* 'LICENSE.txt', which is part of this source code package.
*/
package com.univocity.api.common;
import java.util.*;
/**
* Utility class for handling {@code String}s with parameters. Use {@link #set(String, Object)} to set a parameter value,
* and {@link #applyParameterValues()} to obtain a result {@code String} with the values of all known parameters replaced.
*
* Parameters without values provided will not be replaced, therefore the string "zero/{one}/{two}/{one}", with parameter
* "one" set to 27 will evaluate to "zero/27/{two}/27"
*
* @author Univocity Software Pty Ltd - [email protected]
*/
public class ParameterizedString implements Cloneable {
private final String string;
private Parameter[] parameters;
private final Set parameterNames = new TreeSet();
private TreeMap parameterValues;
private String[] nonParameterSections;
private StringBuilder tmp;
private final String openBracket;
private final String closeBracket;
private String result = null;
private Object defaultValue = null;
private boolean convertDefaultValueToNull = true;
private boolean parameterValidationEnabled = true;
/**
* Creates a new parameterized string with custom open and closing brackets
*
* @param string the string with parameters
* @param openBracket the open bracket (before a parameter name)
* @param closeBracket the close bracket (after a paramter name)
*/
public ParameterizedString(String string, String openBracket, String closeBracket) {
Args.notBlank(string, "Input string");
Args.notBlank(openBracket, "Open bracket");
Args.notBlank(closeBracket, "Close bracket");
this.parameterValues = new TreeMap();
this.openBracket = openBracket;
this.closeBracket = closeBracket;
this.string = string;
this.tmp = new StringBuilder((int) (string.length() * 1.5));
collectParameters();
}
/**
* Creates a new parameterized string with parameter names enclosed within { and }
*
* @param string the string with parameters
*/
public ParameterizedString(String string) {
this(string, "{", "}");
}
private void collectParameters() {
List nonParameterSections = new ArrayList();
List parameters = new ArrayList();
int x = 0;
int nonParameterIndexStart = 0;
int openBracketIndex;
while ((openBracketIndex = string.indexOf(openBracket, x)) >= 0) {
if (openBracketIndex != 0 && openBracketIndex == nonParameterIndexStart) {
int errorPos = openBracketIndex;
StringBuilder errorMsg = new StringBuilder("At least one character between parameters is required.\nNo separation found after:\n'");
errorMsg.append(string).append("'\n");
for (int i = 0; i < errorPos; i++) {
errorMsg.append('.');
}
errorMsg.append('^');
throw new IllegalArgumentException(errorMsg.toString());
}
int closeBracketIndex = string.indexOf(closeBracket, openBracketIndex);
if (closeBracketIndex > 0) {
int nextOpenBracket;
do {
nextOpenBracket = string.indexOf(openBracket, openBracketIndex + openBracket.length());
if (nextOpenBracket > 0 && nextOpenBracket < closeBracketIndex) {
openBracketIndex = nextOpenBracket;
}
}
while (nextOpenBracket > 0 && nextOpenBracket < closeBracketIndex);
x = closeBracketIndex;
String nonParameterSection = string.substring(nonParameterIndexStart, openBracketIndex);
if (!nonParameterSection.isEmpty()) {
nonParameterSections.add(nonParameterSection);
}
nonParameterIndexStart = closeBracketIndex + closeBracket.length();
int start = openBracketIndex + openBracket.length();
if (hasWhitespace(start, closeBracketIndex)) {
continue; //skips content spread across multiple lines, or containing spaces.
}
String parameterizedName = string.substring(start, closeBracketIndex);
Parameter parameter = new Parameter(parameterizedName, openBracketIndex, closeBracketIndex + closeBracket.length());
parameters.add(parameter);
if (openBracketIndex == 0) {
nonParameterSections.add("");
}
parameterNames.add(parameter.name);
} else {
x = openBracketIndex + openBracket.length();
}
}
if (nonParameterIndexStart < string.length()) {
nonParameterSections.add(string.substring(nonParameterIndexStart));
}
this.nonParameterSections = nonParameterSections.toArray(new String[0]);
this.parameters = parameters.toArray(new Parameter[0]);
}
private boolean hasWhitespace(int from, int to) {
for (int i = from; i < to; i++) {
char ch = string.charAt(i);
if (ch <= ' ' || ch == ',') {
if (ch == ' ' && (i == from || i + 1 == to)) {
continue;
}
boolean foundComma = false;
for (; i < to; i++) {
ch = string.charAt(i);
if (ch < ' ') { //spaces allowed, but not other special chars
return true;
} else if (ch == ',') {
foundComma = true;
}
}
if (!foundComma) {
return true;
}
}
}
return false;
}
/**
* Sets multiple parameter values
*
* @param parametersAndValues map of parameter names and thier corresponding values.
*
* @throws IllegalArgumentException if a parameter name in found in the given map does not exist
*/
public final void set(Map parametersAndValues) throws IllegalArgumentException {
if (parametersAndValues == null) {
return;
}
for (Map.Entry e : parametersAndValues.entrySet()) {
set(e.getKey(), e.getValue());
}
}
/**
* Sets a parameter value
*
* @param parameter the parameter name
* @param value the parameter value
*
* @throws IllegalArgumentException if the parameter name does not exist and {@link #isParameterValidationEnabled()} evaluates to {@code true}
*/
public final void set(String parameter, Object value) throws IllegalArgumentException {
if(validateParameterName(parameter)){
if (convertDefaultValueToNull && value != null && defaultValue != null && value.equals(defaultValue)) {
value = null;
}
parameterValues.put(parameter, value);
result = null;
}
}
/**
* Returns the value of a given parameter.
*
* @param parameter the parameter name
*
* @return the parameter value
*
* @throws IllegalArgumentException if the parameter name does not exist and {@link #isParameterValidationEnabled()} evaluates to {@code true}
*/
public final Object get(String parameter) throws IllegalArgumentException {
if(validateParameterName(parameter)) {
return parameterValues.get(parameter);
} else {
return null;
}
}
private void invalidInput(String input) {
clearValues();
throw new IllegalArgumentException("The input:\n'" + input + "'\nDoes not match the parameter pattern:\n'" + string + "'");
}
/**
* Parses the {@code String input} and extracts the parameter values storing them as regular parameters.
* The {@link Map} of parameters is returned as a convenience, but parameter values can also be retrieved using:
*
* - {@link #get(String)} - for individual parameters
* - {@link #getParameterValues()} - for all of them
*
*
* @param input the input String to be parsed
*
* @return the {@link Map} of parameters to their assigned values
*/
public final Map parse(String input) {
if (parameters.length == 0) {
return Collections.emptyMap();
}
TreeSet parsedParams = new TreeSet();
int valueStart = 0;
for (int i = 0, p = 0; i < nonParameterSections.length && p < parameters.length; i++) {
String section = nonParameterSections[i];
valueStart = input.indexOf(section, valueStart);
if (valueStart == -1) {
invalidInput(input);
}
valueStart += section.length();
int valueEnd;
if (i + 1 < nonParameterSections.length) {
String nextSection = nonParameterSections[i + 1];
valueEnd = input.indexOf(nextSection, valueStart);
if (valueEnd == -1) {
invalidInput(input);
}
} else {
valueEnd = input.length();
}
String value = input.substring(valueStart, valueEnd);
Parameter parameter = parameters[p++];
Object existingValue = parameterValues.get(parameter.name);
if (existingValue != null && !existingValue.equals(value) && parsedParams.contains(parameter.name)) {
StringBuilder sb = new StringBuilder("Multiple values ('").append(existingValue).append("' and '").append(value).append("') found for parameter '");
sb.append(parameter.name);
sb.append("'\n");
sb.append(input);
sb.append('\n');
int errPos = input.length() - input.length() + input.indexOf(value);
for (int j = 0; j < errPos; j++) {
sb.append('.');
}
sb.append('^');
throw new IllegalArgumentException(sb.toString());
}
parsedParams.add(parameter.name);
set(parameter.name, value);
}
if (nonParameterSections.length == 0) {
if (parameters.length == 1) {
set(parameters[0].name, input);
}
}
return getParameterValues();
}
private boolean validateParameterName(String parameter) {
if (parameterValidationEnabled) {
Args.notBlank(parameter, "Parameter name");
}
if (!parameterNames.contains(parameter)) {
if (parameterValidationEnabled) {
throw new IllegalArgumentException("Parameter '" + parameter + "' not found in " + string + ". Available parameters: " + parameterNames);
}
return false;
}
return true;
}
/**
* Returns the original {@code String} provided in the constructor of this class, no parameters are replaced
*
* @return the {@code String} with parameters
*/
@Override
public final String toString() {
return string;
}
/**
* Applies the parameter values provided using {@link #set(String, Object)} and returns the resulting {@code String}
*
* Unless a default value is provided via {@link #getDefaultValue()}, parameters without values provided will not
* be replaced. Therefore the {@code String} "zero/{one}/{two}/{one}", with parameter "one" set to 27 will
* evaluate to "zero/27/{two}/27"
*
* @return the resulting {@code String} with all parameters replaced by their values.
*/
public final String applyParameterValues() {
if (result == null) {
tmp.setLength(0);
applyParameterValues(tmp);
result = tmp.toString();
}
return result;
}
/**
* Applies the parameter values provided using {@link #set(String, Object)} and appends the resulting {@code String}
* to a given {@code StringBuilder}
*
* Unless a default value is provided via {@link #getDefaultValue()}, parameters without values provided will not
* be replaced. Therefore the {@code String} "zero/{one}/{two}/{one}", with parameter "one" set to 27 will
* evaluate to "zero/27/{two}/27"
*
* @param out the {@code StringBuilder} that will be appended with the result of all parameters replaced by their values.
*/
public final void applyParameterValues(StringBuilder out) {
if (result != null) {
out.append(result);
} else {
int initialLength = out.length();
int lastClosedBracketIndex = 0;
for (int i = 0; i < parameters.length; i++) {
Object parameterValue = parameterValues.get(parameters[i].name);
if (parameterValue == null && defaultValue != null) {
parameterValue = defaultValue;
}
if (parameterValue != null) {
int openBracketIndex = parameters[i].startPosition;
int closedBracketIndex = parameters[i].endPosition;
if (out.length() == initialLength) {
out.append(string, 0, openBracketIndex);
} else {
out.append(string, lastClosedBracketIndex, openBracketIndex);
}
out.append(parameterValue);
lastClosedBracketIndex = closedBracketIndex;
}
}
out.append(string, lastClosedBracketIndex, string.length());
}
}
/**
* Returns a set of all parameter names found in the input string given in the constructor of this class.
*
* @return the unmodifiable set of available parameter names.
*/
public final Set getParameters() {
return Collections.unmodifiableSet(parameterNames);
}
/**
* Clears the values of all parameters.
*/
public final void clearValues() {
parameterValues.clear();
result = null;
}
/**
* Tests whether a given parameter name exists in this parameterized string.
*
* @param parameterName the name of the parameter
*
* @return {@code true} if the parameter name exists in this parameterized string, otherwise {@code false}
*/
public final boolean contains(String parameterName) {
return parameterNames.contains(parameterName);
}
/**
* Returns the format associated with a given parameter
*
* @param parameterName the name of the parameter
*
* @return the format of parameter as {@code String}. Returns {@code null} if the parameter does not exist or format was not set.
*
* @throws IllegalArgumentException if the parameter name does not exist and {@link #isParameterValidationEnabled()} evaluates to {@code true}
*/
public final String getFormat(String parameterName) throws IllegalArgumentException {
if(validateParameterName(parameterName)) {
for (Parameter parameter : parameters) {
if (parameter.name.equals(parameterName)) {
return parameter.format;
}
}
}
return null;
}
/**
* Clones this parameterzied string object. Current parameter values are copied as well.
*
* @return a clone of this object.
*/
@Override
public final ParameterizedString clone() {
try {
ParameterizedString clone = (ParameterizedString) super.clone();
clone.parameterValues = (TreeMap) this.parameterValues.clone();
clone.tmp = new StringBuilder(this.tmp.capacity());
return clone;
} catch (CloneNotSupportedException e) {
throw new IllegalStateException("Could not clone parameterized string", e);
}
}
/**
* Returns the current parameter names and their values.
*
* @return an unmodifiable copy of the map of parameter names and their values.
*/
public final Map getParameterValues() {
return Collections.unmodifiableMap(parameterValues);
}
/**
* Defines a default value to be used for parameters that have no value associated.
*
* Defaults to {@code null}, in which case the original parameter will appear in the result of
* {@link #applyParameterValues()}.
*
* @param defaultValue the default value to be used when one or more parameters have no value associated.
*/
public final void setDefaultValue(Object defaultValue) {
this.defaultValue = defaultValue;
if (defaultValue != null && !parameterValues.isEmpty()) {
for (Map.Entry e : parameterValues.entrySet()) {
if (defaultValue.equals(e.getValue())) {
e.setValue(null);
}
}
}
}
/**
* Returns the default value to be used for parameters that have no value associated.
*
* Defaults to {@code null}, in which case the original parameter will appear in the result of
* {@link #applyParameterValues()}.
*
* @return the default value to be used when one or more parameters have no value associated.
*/
public final Object getDefaultValue() {
return defaultValue;
}
/**
* Returns the index before the first parameter in this parameterized string.
*
* @return index before the first parameter, or {@code -1} if no parameters exist.
*/
public final int getIndexBeforeFirstParameter() {
if (parameters.length > 0) {
return parameters[0].startPosition;
}
return -1;
}
/**
* Returns the index after the last parameter in this parameterized string.
*
* @return index after the last parameter, or {@code -1} if no parameters exist.
*/
public final int getIndexAfterLastParameter() {
if (parameters.length > 0) {
return parameters[parameters.length - 1].endPosition;
}
return -1;
}
/**
* Returns the content before the first parameter in this parameterized string.
*
* @return text content before the first parameter, or the entire {@code String} if no parameters exist.
*/
public final String getContentBeforeFirstParameter() {
if (parameters.length == 0) {
return string;
}
int index = getIndexBeforeFirstParameter();
return string.substring(0, index);
}
/**
* Returns the content after the last parameter in this parameterized string.
*
* @return text content after the last parameter, or the entire {@code String} if no parameters exist.
*/
public final String getContentAfterLastParameter() {
if (parameters.length == 0) {
return string;
}
int index = getIndexAfterLastParameter();
return string.substring(index, string.length());
}
static private final class Parameter {
final String name;
final int startPosition;
final int endPosition;
final String format;
Parameter(String name, int startPosition, int endPosition) {
name = name.trim();
if (name.contains(",")) {
this.name = name.substring(0, name.indexOf(",")).trim();
this.format = name.substring(name.indexOf(",") + 1).trim();
if (format.length() == 0) {
throw new IllegalArgumentException("Expected formatting parameter after ',' in '" + name + "'");
}
} else {
this.name = name;
format = null;
}
this.startPosition = startPosition;
this.endPosition = endPosition;
}
@Override
public final String toString() {
return "Parameter{" +
"name='" + name + '\'' +
", startPosition=" + startPosition +
", endPosition=" + endPosition +
", format='" + format + '\'' +
'}';
}
@Override
public final boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Parameter parameter = (Parameter) o;
if (startPosition != parameter.startPosition) {
return false;
}
if (endPosition != parameter.endPosition) {
return false;
}
if (!name.equals(parameter.name)) {
return false;
}
return format != null ? format.equals(parameter.format) : parameter.format == null;
}
@Override
public final int hashCode() {
int result = name.hashCode();
result = 31 * result + startPosition;
result = 31 * result + endPosition;
result = 31 * result + (format != null ? format.hashCode() : 0);
return result;
}
}
@Override
public final boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ParameterizedString that = (ParameterizedString) o;
if (string != null ? !string.equals(that.string) : that.string != null) {
return false;
}
if (parameters != null ? !parameters.equals(that.parameters) : that.parameters != null) {
return false;
}
if (parameterNames != null ? !parameterNames.equals(that.parameterNames) : that.parameterNames != null) {
return false;
}
if (openBracket != null ? !openBracket.equals(that.openBracket) : that.openBracket != null) {
return false;
}
if (closeBracket != null ? !closeBracket.equals(that.closeBracket) : that.closeBracket != null) {
return false;
}
return parameterValues != null ? parameterValues.equals(that.parameterValues) : that.parameterValues == null;
}
@Override
public final int hashCode() {
int result = string != null ? string.hashCode() : 0;
result = 31 * result + (parameters != null ? parameters.hashCode() : 0);
result = 31 * result + (parameterNames != null ? parameterNames.hashCode() : 0);
result = 31 * result + (openBracket != null ? openBracket.hashCode() : 0);
result = 31 * result + (closeBracket != null ? closeBracket.hashCode() : 0);
result = 31 * result + (parameterValues != null ? parameterValues.hashCode() : 0);
return result;
}
/**
* Flag indicating that values parsed from this {@code ParameterizedString} will be converted to {@code null}, when
* equal to the String representation of the value returned by {@link #getDefaultValue()}.
*
*
* - When {@code true} all methods that return values associated with a parameter will return {@code null} instead of the specified default value.
* - When {@code false} all methods that return values associated with a parameter will default value returned by {@link #getDefaultValue()}.
*
*
* @return {@code true} if default values should be converted to {@code null} when reading parameter values, otherwise {@code false}
*/
public final boolean getConvertDefaultValueToNull() {
return convertDefaultValueToNull;
}
/**
* Defines whether values parsed from this {@code ParameterizedString} should be converted to {@code null}, when
* equal to the String representation of the value returned by {@link #getDefaultValue()}.
*
*
* - When {@code true} all methods that return values associated with a parameter will return {@code null} instead of the specified default value.
* - When {@code false} all methods that return values associated with a parameter will default value returned by {@link #getDefaultValue()}.
*
*
* @param convertDefaultValueToNull flag indicating whether default values should be converted to {@code null} when reading parameter values
*/
public final void setConvertDefaultValueToNull(boolean convertDefaultValueToNull) {
this.convertDefaultValueToNull = convertDefaultValueToNull;
}
/**
* Flag indicating whether parameter validation is enabled. If {@code true},
* setting the value of a parameter that does not exist will throw an {@link IllegalArgumentException}.
* If {@code false}, the value associated with an unknown parameter is simply ignored.
*
* Defaults to {@code true}
*
* @return whether parameter names should be validated.
*/
public boolean isParameterValidationEnabled() {
return parameterValidationEnabled;
}
/**
* Determines whether parameter names should be validated. If {@code true},
* setting the value of a parameter that does not exist will throw an {@link IllegalArgumentException}.
* If {@code false}, the value associated with an unknown parameter is simply ignored.
*
* Defaults to {@code true}
*
* @param parameterValidationEnabled flag to enable or disable the parameter name validation
*/
public void setParameterValidationEnabled(boolean parameterValidationEnabled) {
this.parameterValidationEnabled = parameterValidationEnabled;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy