/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.core.utils.common;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.ArrayList;
import java.util.IllegalFormatException;
import java.util.List;
import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Utility class for {@link String} objects.
*
* @author Doreen Seider
* @author Sascha Zur
* @author Robert Mischke
* @author Marc Stammerjohann
*/
public final class StringUtils {
/**
* The human-readable error message for a charset violation.
*/
public static final String COMMON_VALID_INPUT_CHARSET_ERROR =
"Invalid character: only letters, digits, space, and the characters _.,-+() are allowed";
/**
* The human-readable error message for an invalid first character.
*/
public static final String COMMON_VALID_INPUT_FIRST_CHARACTER_ERROR =
"Invalid first character: it must be a letter, a digit, or the underscore (\"_\")";
/**
* The human-readable error message for an invalid last character.
*/
public static final String COMMON_VALID_INPUT_LAST_CHARACTER_ERROR = "Invalid last character";
/**
* A regular expression to check whether a given string fits the "common valid input" charset. Empty strings are accepted by this
* regexp.
*/
protected static final Pattern COMMON_VALID_INPUT_CHARSET_REGEXP = Pattern.compile("^[a-zA-Z0-9 _\\.,\\-\\+\\(\\)]*$");
/**
* A regular expression to check whether a given first character fits the "common valid input" rules.
*/
protected static final Pattern COMMON_VALID_INPUT_FIRST_CHARACTER_REGEXP = Pattern.compile("^[a-zA-Z0-9_].*");
/**
* A regular expression to check whether a given last character fits the "common valid input" rules.
*/
protected static final Pattern COMMON_VALID_INPUT_LAST_CHARACTER_REGEXP = Pattern.compile(".*[^ ]$"); // "^ " = no space
/** Separator used to separate two semantically different Strings put into an single one. */
protected static final String SEPARATOR = ":";
/** Character used to escape the separator. */
protected static final String ESCAPE_CHARACTER = "\\";
/** Separator used to separate format string and values to be used for a readable fall back message. */
protected static final String FORMAT_SEPARATOR = ", ";
/**
* Represents an empty array after concatenization.
*
* TODO use "\[]" for better readability? "\" could still be parsed for compatibility - misc_ro
*/
protected static final String EMPTY_ARRAY_PLACEHOLDER = ESCAPE_CHARACTER;
/**
* Represents a null string as part of a serialized and concatenated array.
*/
protected static final String NULL_STRING_PLACEHOLDER = ESCAPE_CHARACTER + "0"; // "\0"
private static final Log sharedLog = LogFactory.getLog(StringUtils.class);
private StringUtils() {}
private static String escapeCharacter(String rawString, char characterToEscape) {
final StringBuilder result = new StringBuilder();
final StringCharacterIterator iterator = new StringCharacterIterator(rawString);
char character = iterator.current();
while (character != CharacterIterator.DONE) {
if (character == characterToEscape) {
result.append(ESCAPE_CHARACTER);
result.append(characterToEscape);
} else {
result.append(character);
}
character = iterator.next();
}
return result.toString();
}
private static String unescapeCharacter(String escapedString, char characterToUnescape) {
final StringBuilder result = new StringBuilder();
final StringCharacterIterator iterator = new StringCharacterIterator(escapedString);
char character = iterator.current();
while (character != CharacterIterator.DONE) {
if (character == ESCAPE_CHARACTER.toCharArray()[0]) {
character = iterator.next();
if (character == characterToUnescape) {
result.append(characterToUnescape);
} else {
result.append(ESCAPE_CHARACTER);
result.append(character);
}
} else {
result.append(character);
}
character = iterator.next();
}
return result.toString();
}
/**
* Escapes the separator within the given String object.
*
* @param rawString The {@link String} to escape.
* @return the escaped {@link String}.
*/
public static String escapeSeparator(String rawString) {
return escapeCharacter(rawString, SEPARATOR.toCharArray()[0]);
}
/**
* Replaces in the given {@link String} escaped separator with the separator itself.
*
* @param escapedString The {@link String} to unescape.
* @return the unescaped {@link String}.
*/
public static String unescapeSeparator(String escapedString) {
return unescapeCharacter(escapedString, SEPARATOR.toCharArray()[0]);
}
/**
* Splits the given {@link String} around the separator.
*
* @param completeString the {@link String} to split.
* @return the splitted String as array.
*/
public static String[] splitAndUnescape(String completeString) {
StringBuilder part = new StringBuilder();
List<String> parts = new ArrayList<String>();
int escapeCount = 0;
// special case: empty array
if (completeString.equals(EMPTY_ARRAY_PLACEHOLDER)) {
return new String[0];
}
for (int i = 0; i < completeString.length(); i++) {
if (completeString.charAt(i) == ESCAPE_CHARACTER.charAt(0)) {
escapeCount++;
part.append(completeString.charAt(i));
} else if (completeString.charAt(i) == SEPARATOR.charAt(0)) {
if (escapeCount % 2 == 0) {
parts.add(part.toString());
part = new StringBuilder();
} else {
part.append(completeString.charAt(i));
}
escapeCount = 0;
} else {
part.append(completeString.charAt(i));
escapeCount = 0;
}
}
parts.add(part.toString());
String[] partsArray = new String[parts.size()];
partsArray = parts.toArray(partsArray);
for (int i = 0; i < partsArray.length; i++) {
partsArray[i] = unwrap(partsArray[i]);
}
return partsArray;
}
/**
* Strings the given parts together to one String and escapes separator if needed.
*
* @param parts the given String parts which needs to string together.
* @return The String containing all parts separated by an separator.
*/
public static String escapeAndConcat(String... parts) {
// special case: empty array
if (parts.length == 0) {
return EMPTY_ARRAY_PLACEHOLDER;
}
StringBuilder stringBuilder = new StringBuilder();
for (String part : parts) {
String escapedPart = wrap(part);
stringBuilder.append(escapedPart);
stringBuilder.append(SEPARATOR);
}
return stringBuilder.substring(0, stringBuilder.length() - 1);
}
/**
* Strings the given parts together to one String and escapes separator if needed.
*
* @param parts the given String parts which needs to string together.
* @return The String containing all parts separated by an separator.
*/
public static String escapeAndConcat(List<String> parts) {
return escapeAndConcat(parts.toArray(new String[parts.size()]));
}
/**
* Escapes the given string so it can be directly embedded into a JSON file as a string value.
*
* @param input the input string
* @param addDoubleQuotes whether the returned value should be enclosed in double quotes; if false, the returned content MUST be placed
* between existing double quotes!
* @return the escaped string
*/
public static String escapeAsJsonStringContent(String input, boolean addDoubleQuotes) {
String escaped = input.replace("\\", "\\\\").replace("\"", "\\\"");
if (addDoubleQuotes) {
return format("\"%s\"", escaped);
} else {
return escaped;
}
}
/**
* Checks whether the given string is either empty, or is comprised only of characters from the "common valid input set" (see
* {@link #COMMON_VALID_INPUT_CHARSET_ERROR} for a human-readable description).
*
* @param input the input string to test
* @return null if successful, or a human-readable error message on violations
*/
public static String checkAgainstCommonInputRules(String input) {
if (input.isEmpty()) {
return null;
}
if (!COMMON_VALID_INPUT_CHARSET_REGEXP.matcher(input).matches()) {
return COMMON_VALID_INPUT_CHARSET_ERROR;
}
if (!COMMON_VALID_INPUT_FIRST_CHARACTER_REGEXP.matcher(input).matches()) {
return COMMON_VALID_INPUT_FIRST_CHARACTER_ERROR;
}
if (!COMMON_VALID_INPUT_LAST_CHARACTER_REGEXP.matcher(input).matches()) {
return COMMON_VALID_INPUT_LAST_CHARACTER_ERROR;
}
return null; // passed
}
/**
* Returns the given string instance as a non-null reference as result. If the given string reference is a null reference an empty
* string "" will be returned.
*
* @param text the text
* @return a valid <code>String</code> instance, an empty string if the parameter was a null reference
*/
public static String nullSafe(final String text) {
return nullSafe(text, "");
}
/**
* Returns the given string instance as a non-null reference as result. If the given string reference is a null reference the default
* value will be returned.
*
* @param text the text
* @param defaultValue the default value to return if the text is a null reference
* @return a valid <code>String</code> instance, an empty string if the parameter was a null reference
*/
public static String nullSafe(final String text, final String defaultValue) {
if (text != null) {
return text;
} else {
return defaultValue;
}
}
/**
* Tries to parse an integer from the given string (using {@link Integer#parseInt(String)}), and returns the given default value if a
* {@link NumberFormatException} occurs.
*
* @param input the string to parse
* @param defaultValue the default value to use on error
* @return the parsed value, or the default
*/
public static int nullSafeParseInt(String input, int defaultValue) {
try {
return Integer.parseInt(input);
} catch (NumberFormatException e) {
return defaultValue;
}
}
/**
* Returns the {@link #toString()} output of the given {@link Object}, or the default value if it is null.
*
* @param reference an object or null
* @param defaultValue the value to return if the first parameter is null
* @return the {@link #toString()} output of the given {@link Object}, or the default value if it is null
*/
public static String nullSafeToString(Object reference, String defaultValue) {
if (reference != null) {
return reference.toString();
} else {
return defaultValue;
}
}
/**
* Performs all escape steps so the string is ready for concatenation.
*
* @param input the original string
* @return the escaped/wrapped representation
*/
private static String wrap(String input) {
// special case: null string
if (input == null) {
return NULL_STRING_PLACEHOLDER;
}
String escapedPart = StringUtils.escapeCharacter(input, ESCAPE_CHARACTER.toCharArray()[0]);
escapedPart = StringUtils.escapeSeparator(escapedPart);
return escapedPart;
}
/**
* Reverts all escape steps to retrieve the original string from a part that resulted from splitting.
*
* @param wrapped the escaped/wrapped representation
* @return the original string
*/
private static String unwrap(String wrapped) {
// special case: null string
if (NULL_STRING_PLACEHOLDER.equals(wrapped)) {
return null;
}
String temp = unescapeSeparator(wrapped);
temp = unescapeCharacter(temp, ESCAPE_CHARACTER.toCharArray()[0]);
return temp;
}
/**
* Fault tolerant implementation of {@link String#format(String, Object...)}. If the {@link IllegalFormatException} is thrown, the raw
* format string is concatenated with the values.
*
* @param format A format string
* @param args Arguments to replace the placeholder in the format string
*
* @return a formatted string or a concatenated string
*/
public static String format(String format, Object... args) {
String result = null;
try {
result = String.format(format, args);
} catch (IllegalFormatException e) {
String values = "";
for (int i = 0; i < args.length; i++) {
if (i == 0) {
values = values.concat(String.valueOf(args[i]));
} else {
values = values.concat(FORMAT_SEPARATOR + String.valueOf(args[i]));
}
}
result = format;
if (!values.isEmpty()) {
result = result.concat(FORMAT_SEPARATOR + values);
}
sharedLog.warn(StringUtils.format(
"Format error. Review the format string and the number of values.\n Format String: %s" + FORMAT_SEPARATOR
+ "Values: %s", format, values));
}
return result;
}
/**
* @param input the string to test
* @return true if the string is null or empty
*/
public static boolean isNullorEmpty(String input) {
return input == null || input.isEmpty();
}
}