
info.freelibrary.util.StringUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of freelib-utils Show documentation
Show all versions of freelib-utils Show documentation
A small collection of utility classes
package info.freelibrary.util;
import static info.freelibrary.util.Constants.EOL;
import static info.freelibrary.util.Constants.SPACE;
import static java.util.stream.Collectors.joining;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import info.freelibrary.util.warnings.PMD;
/**
* Provides a few convenience methods for working with strings.
*/
@SuppressWarnings({ PMD.TOO_MANY_METHODS, PMD.CYCLOMATIC_COMPLEXITY, PMD.GOD_CLASS })
public final class StringUtils {
/** A double space constant. */
private static final String DOUBLE_SPACE = " ";
/** The logger used by the string utilities. */
private static final Logger LOGGER = LoggerFactory.getLogger(StringUtils.class, MessageCodes.BUNDLE);
/** A message format regex pattern. */
private static final Pattern PATTERN = Pattern.compile("\\{\\}");
/** A range delimiter constant. */
private static final String RANGE_DELIMETER = "-";
/**
* Creates a class of string utilities.
*/
private StringUtils() {
// This is intentionally left empty
}
/**
* Adds line numbers to any string. This is useful when I need to debug an XQuery outside the context of an IDE
* (i.e., in debugging output).
*
* @param aMessage Text to which line numbers should be added
* @return The supplied text with line numbers at the first of each line
*/
public static String addLineNumbers(final String aMessage) {
final StringBuilder buffer = new StringBuilder();
final String[] lines = aMessage.split(EOL);
int lineCount = 1; // Error messages start with line 1
for (final String line : lines) {
buffer.append(lineCount).append(' ').append(line);
lineCount++;
buffer.append(EOL);
}
final int length = buffer.length();
buffer.delete(length - EOL.length(), length);
return buffer.toString();
}
/**
* Formats the supplied message using the supplied details. The string form of the detail comes from the object's
* toString()
method.
*
* @param aMessage A message to format
* @param aDetail Additional details to integrate into the message
* @return The formatted message
*/
public static String format(final String aMessage, final Object... aDetail) {
return format(aMessage, toStrings(aDetail));
}
/**
* Takes a String
in the form "This is {} text {}" and replaces the {}
s with values from
* the supplied String[]
. The number of curly braces should be the same as the number of strings in the
* string array.
*
* @param aMessage A string that contains curly braces in the form {}
* @param aDetails Strings that should be put in place of the curly braces in the message string.
* @return The formatted string
*/
public static String format(final String aMessage, final String... aDetails) {
final StringBuilder builder = new StringBuilder();
final Matcher matcher = PATTERN.matcher(aMessage);
int index = checkBracketCount(aMessage, aDetails);
while (matcher.find()) {
matcher.appendReplacement(builder, "");
builder.append(aDetails[index]);
index++;
}
return matcher.appendTail(builder).toString();
}
/**
* Indents a multi-line string using spaces.
*
* @param aString A string to indent
* @param anIndentationCount The number of spaces to indent
* @return An indented string
*/
public static String indent(final String aString, final int anIndentationCount) {
final String indent = SPACE.repeat(anIndentationCount);
return aString.lines().map(line -> indent + line).collect(joining(EOL));
}
/**
* Returns true if the supplied string is null, empty, or contains nothing but whitespace.
*
* @param aString A string to test to see if it is null, empty or contains nothing but whitespace
* @return True if the supplied string is empty; else, false
*/
public static boolean isEmpty(final String aString) {
boolean result = true;
if (aString != null) {
for (int index = 0; index < aString.length(); index++) {
if (!Character.isWhitespace(aString.charAt(index))) {
result = false;
break;
}
}
}
return result;
}
/**
* Turns the keys in a map into a character delimited string. The order is only consistent if the map is sorted.
*
* @param aMap The map from which to pull the keys
* @param aSeparator The character separator for the construction of the string
* @return A string constructed from the keys in the map
*/
public static String joinKeys(final Map aMap, final char aSeparator) {
if (aMap.isEmpty()) {
return "";
}
final Iterator iterator = aMap.keySet().iterator();
final StringBuilder buffer = new StringBuilder();
while (iterator.hasNext()) {
buffer.append(iterator.next()).append(aSeparator);
}
final int length = buffer.length() - 1;
return buffer.charAt(length) == aSeparator ? buffer.substring(0, length) : buffer.toString();
}
/**
* Normalizes white space in the message value.
*
* @param aMessage A message
* @return The message with white space normalized
*/
public static String normalizeWS(final String aMessage) {
return aMessage.replaceAll("\\s+", " ");
}
/**
* Pads the end of a supplied string with the repetition of a supplied value.
*
* @param aString The string to pad
* @param aPadding The string to be repeated as the padding
* @param aRepeatCount How many times to repeat the padding
* @return The end padded string
*/
public static String padEnd(final String aString, final String aPadding, final int aRepeatCount) {
if (aRepeatCount != 0) {
final StringBuilder buffer = new StringBuilder(aString);
for (int index = 0; index < aRepeatCount; index++) {
buffer.append(aPadding);
}
return buffer.toString();
}
return aString;
}
/**
* Pads the beginning of a supplied string with the repetition of a supplied value.
*
* @param aString The string to pad
* @param aPadding The string to be repeated as the padding
* @param aRepeatCount How many times to repeat the padding
* @return The front padded string
*/
public static String padStart(final String aString, final String aPadding, final int aRepeatCount) {
if (aRepeatCount != 0) {
final StringBuilder buffer = new StringBuilder();
for (int index = 0; index < aRepeatCount; index++) {
buffer.append(aPadding);
}
return buffer.append(aString).toString();
}
return aString;
}
/**
* Parses strings with an integer range (e.g., 2-5) and returns an expanded integer array {2, 3, 4, 5} with those
* values.
*
* @param aIntRange A string representation of a range of integers
* @return An int array with the expanded values of the string representation
* @throws NumberFormatException if the supplied string isn't an integer range
*/
@SuppressWarnings(PMD.AVOID_LITERALS_IN_IF_CONDITION)
public static int[] parseIntRange(final String aIntRange) {
final String[] range = aIntRange.split(RANGE_DELIMETER);
final int[] ints;
if (range.length == 1) {
ints = new int[range.length];
ints[0] = Integer.parseInt(aIntRange);
} else {
final int start = Integer.parseInt(range[0]);
final int end = Integer.parseInt(range[1]);
if (end < start) {
throw new NumberFormatException(LOGGER.getI18n(MessageCodes.UTIL_045, start, RANGE_DELIMETER, end));
}
int position = 0;
final int size = end - start;
ints = new int[size + 1]; // because we're zero-based
for (int index = start; index <= end; index++) {
ints[position] = index;
position++;
}
}
return ints;
}
/**
* Reads the contents of a file into a string using the UTF-8 character set encoding.
*
* @param aFile The file from which to read
* @return The information read from the file
* @throws IOException If the supplied file could not be read
*/
public static String read(final File aFile) throws IOException {
String string = new String(readBytes(aFile), StandardCharsets.UTF_8.name());
if (string.endsWith(EOL)) {
string = string.substring(0, string.length() - 1);
}
return string;
}
/**
* Reads the contents of a file using the supplied {@link Charset}; the default system charset may vary across
* systems so can't be trusted.
*
* @param aFile The file from which to read
* @param aCharset The character set of the file to be read
* @return The information read from the file
* @throws IOException If the supplied file could not be read
*/
public static String read(final File aFile, final Charset aCharset) throws IOException {
String string = new String(readBytes(aFile), aCharset);
if (string.endsWith(EOL)) {
string = string.substring(0, string.length() - 1);
}
return string;
}
/**
* Reads the contents of a file using the supplied {@link Charset}; the default system charset may vary across
* systems so can't be trusted.
*
* @param aFile The file from which to read
* @param aCharsetName The name of the character set of the file to be read
* @return The information read from the file
* @throws IOException If the supplied file could not be read
*/
public static String read(final File aFile, final String aCharsetName) throws IOException {
return read(aFile, Charset.forName(aCharsetName));
}
/**
* Creates a new string from the repetition of a supplied char.
*
* @param aChar The char to repeat, creating a new string
* @param aRepeatCount The number of times to repeat the supplied value
* @return The new string containing the supplied value repeated the specified number of times
*/
public static String repeat(final char aChar, final int aRepeatCount) {
final StringBuilder buffer = new StringBuilder();
for (int index = 0; index < aRepeatCount; index++) {
buffer.append(aChar);
}
return buffer.toString();
}
/**
* Creates a new string from the repetition of a supplied value.
*
* @param aValue The string to repeat, creating a new string
* @param aRepeatCount The number of times to repeat the supplied value
* @return The new string containing the supplied value repeated the specified number of times
*/
public static String repeat(final String aValue, final int aRepeatCount) {
final StringBuilder buffer = new StringBuilder();
for (int index = 0; index < aRepeatCount; index++) {
buffer.append(aValue);
}
return buffer.toString();
}
/**
* Reverses the characters in a string.
*
* @param aString A string whose characters are to be reversed
* @return A string with the supplied string reversed
*/
public static String reverse(final String aString) {
return new StringBuffer(aString).reverse().toString();
}
/**
* Formats a string with or without line breaks into a string with lines with less than a supplied number of
* characters per line.
*
* @param aString A string to format
* @param aCount A number of characters to allow per line
* @return A string formatted using the supplied count
*/
public static String toCharCount(final String aString, final int aCount) {
final StringBuilder builder = new StringBuilder();
final String[] words = aString.split("\\s");
int count = 0;
for (final String word : words) {
count += word.length();
if (count < aCount) {
builder.append(word);
if ((count += 1) < aCount) {
builder.append(' ');
} else {
builder.append(EOL).append(DOUBLE_SPACE);
count = 2;
}
} else {
builder.append(EOL).append(DOUBLE_SPACE).append(word);
count = word.length() + 2; // two spaces at start of line
}
}
return builder.toString();
}
/**
* A convenience method for toString(Object[], char) to add varargs support.
*
* @param aPadChar A padding character
* @param aVarargs A varargs into which to insert the padding character
* @return A string form of the varargs with padding added
*/
public static String toString(final char aPadChar, final Object... aVarargs) {
return toString(aVarargs, aPadChar);
}
/**
* Returns a human-friendly, locale dependent, string representation of the supplied int; for instance, "1" becomes
* "first", "2" becomes "second", etc.
*
* @param aInt An int to convert into a string
* @return The string form of the supplied int
*/
public static String toString(final int aInt) {
return toUpcaseString(aInt).toLowerCase(Locale.getDefault());
}
/**
* Provides a toString() method for maps that have string keys and string array values. The regular map toString()
* works fine for string keys and string values but, since a string array doesn't have a toString(), the map's
* toString() method doesn't produce a useful output. This fixes that.
*
* @param aMap A map of string keys and string array values to turn into a single string representation of the map
* @return A concatenation of the supplied map's string values
*/
public static String toString(final Map aMap) {
final Set> set = aMap.entrySet();
final StringBuilder buffer = new StringBuilder();
for (final Entry entry : set) {
final Object[] values = entry.getValue();
buffer.append(entry.getKey()).append('=');
for (final Object value : values) {
buffer.append('{').append(value).append('}');
}
buffer.append('&');
}
if (buffer.length() > 0 && buffer.charAt(buffer.length() - 1) == '&') {
buffer.deleteCharAt(buffer.length() - 1);
}
return buffer.toString();
}
/**
* Concatenates the string representations of a series of objects (by calling their Object.toString()
* method). Concatenated strings are separated using the supplied 'padding' character.
*
* @param aObjArray An array of objects whose toString()
representations should be concatenated
* @param aPadChar The character used to separate concatenated strings
* @return A concatenation of the supplied objects' string representations
*/
@SuppressWarnings(PMD.AVOID_LITERALS_IN_IF_CONDITION)
public static String toString(final Object[] aObjArray, final char aPadChar) {
if (aObjArray == null || aObjArray.length == 0) {
return "";
}
if (aObjArray.length == 1) {
return aObjArray[0].toString();
}
final StringBuilder buffer = new StringBuilder();
for (final Object obj : aObjArray) {
buffer.append(obj).append(aPadChar);
}
return buffer.deleteCharAt(buffer.length() - 1).toString();
}
/**
* Converts a varargs into an array of strings by calling toString()
on each object.
*
* @param aVarargs A varargs of objects
* @return An array of strings
*/
public static String[] toStrings(final Object... aVarargs) {
final String[] strings = new String[aVarargs.length];
for (int index = 0; index < strings.length; index++) {
strings[index] = aVarargs[index].toString();
}
return strings;
}
/**
* Returns an up-cased human-friendly string representation for the supplied int; for instance, "1" becomes "First",
* "2" becomes "Second", etc.
*
* @param aInt An integer to convert into a string
* @return The string form of the supplied integer
* @throws UnsupportedOperationException If the supplied integer isn't one that is supported
*/
@SuppressWarnings(PMD.CYCLOMATIC_COMPLEXITY)
public static String toUpcaseString(final int aInt) {
return switch (aInt) {
case 1 -> "First";
case 2 -> "Second";
case 3 -> "Third";
case 4 -> "Fourth";
case 5 -> "Fifth";
case 6 -> "Sixth";
case 7 -> "Seventh";
case 8 -> "Eighth";
case 9 -> "Ninth";
case 10 -> "Tenth";
case 11 -> "Eleventh";
case 12 -> "Twelveth";
case 13 -> "Thirteenth";
case 14 -> "Fourteenth";
case 15 -> "Fifthteenth";
case 16 -> "Sixteenth";
case 17 -> "Seventeenth";
case 18 -> "Eighteenth";
case 19 -> "Nineteenth";
case 20 -> "Twentieth";
default -> throw new UnsupportedOperationException(LOGGER.getI18n(MessageCodes.UTIL_046, aInt));
};
}
/**
* Removes empty and null strings from a string array.
*
* @param aStringArray A varargs that may contain empty or null strings
* @return A string array without empty or null strings
*/
public static String[] trim(final String... aStringArray) {
final List list = new ArrayList<>();
for (final String string : aStringArray) {
if (string != null && !"".equals(string)) {
list.add(string);
}
}
return list.toArray(new String[0]);
}
/**
* Trims a string; if there is nothing left after the trimming, returns whatever the default value passed in is.
*
* @param aString The string to be trimmed
* @param aDefault A default string to return if a null string is passed in
* @return The trimmed string or the default value if string is empty
*/
public static String trimTo(final String aString, final String aDefault) {
if (aString == null) {
return aDefault;
}
final String trimmed = aString.trim();
return trimmed.length() == 0 ? aDefault : trimmed;
}
/**
* Trims a string object down into a boolean and has the ability to define what the default value should be. We only
* offer the method with the default value because most times a boolean with either exist or not (and in the case of
* not a default should be specified).
*
* @param aString A boolean in string form
* @param aBool A default boolean value
* @return The boolean representation of the string value or the default value
*/
public static boolean trimToBool(final String aString, final boolean aBool) {
final String boolString = trimTo(aString, Boolean.toString(aBool));
return Boolean.parseBoolean(boolString);
}
/**
* Trims a string; if there is nothing left after the trimming, returns null. If the passed in object is not a
* string, a cast exception will be thrown.
*
* @param aString The string to be trimmed
* @return The trimmed string or null if string is empty
*/
public static String trimToNull(final String aString) {
return trimTo(aString, null);
}
/**
* Upcases a string.
*
* @param aString A string to upcase
* @return The upcased string
*/
public static String upcase(final String aString) {
return aString.substring(0, 1).toUpperCase(Locale.getDefault()) + aString.substring(1);
}
/**
* Checks a string for the number of brackets compared to the number of arguments and throws an exception if the two
* numbers aren't equal.
*
* @param aMessage A message string
* @param aDetails An array of additional string details
* @return The starting index position if the counts matched
* @throws IndexOutOfBoundsException If the number of details doesn't match the number of slots
*/
private static int checkBracketCount(final String aMessage, final String... aDetails) {
int index = 0;
int count = 0;
while ((index = aMessage.indexOf("{}", index)) != -1) {
index += 1;
count += 1;
}
if (count != aDetails.length) {
throw new IndexOutOfBoundsException(LOGGER.getI18n(MessageCodes.UTIL_043, count, aDetails.length));
}
return 0;
}
/**
* Reads the contents of a file into a byte array.
*
* @param aFile The file from which to read
* @return The bytes read from the file
* @throws IOException If the supplied file could not be read in its entirety
*/
@SuppressWarnings(PMD.AVOID_FILE_STREAM)
private static byte[] readBytes(final File aFile) throws IOException {
try (FileInputStream fileStream = new FileInputStream(aFile)) {
final ByteBuffer buf = ByteBuffer.allocate((int) aFile.length());
final int read = fileStream.getChannel().read(buf);
if (read != aFile.length()) {
throw new IOException(LOGGER.getI18n(MessageCodes.UTIL_044, aFile));
}
return buf.array();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy