StringUtilsHelper.java
package org.andromda.utils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.andromda.utils.inflector.EnglishInflector;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils;
import org.apache.log4j.Logger;
/**
* A utility object for doing string manipulation operations that are commonly
* needed by the code generation templates.
*
* @author Matthias Bohlen
* @author Chris Shaw
* @author Chad Brandon
* @author Wouter Zoons
* @author Bob Fields
*/
public class StringUtilsHelper
extends StringUtils
{
/**
* The logger instance.
*/
private static final Logger logger = Logger.getLogger(StringUtilsHelper.class);
/**
* <p> Replaces a given suffix of the source string with a new one. If the
* suffix isn't present, the string is returned unmodified.
* </p>
*
* @param src the <code>String</code> for which the suffix should be
* replaced
* @param suffixOld a <code>String</code> with the suffix that should be
* replaced
* @param suffixNew a <code>String</code> with the new suffix
* @return a <code>String</code> with the given suffix replaced or
* unmodified if the suffix isn't present
*/
public static String replaceSuffix(
final String src,
final String suffixOld,
final String suffixNew)
{
if (src.endsWith(suffixOld))
{
return src.substring(0, src.length() - suffixOld.length()) + suffixNew;
}
return src;
}
/**
* <p> Returns the argument string as a camel cased name beginning with an
* uppercased letter.
* </p>
* <p> Non word characters be removed and the letter following such a
* character will be uppercased.
* </p>
*
* @param string any string
* @return the string converted to a camel cased name beginning with a lower
* cased letter.
*/
public static String upperCamelCaseName(final String string)
{
if (StringUtils.isEmpty(string))
{
return string;
}
final String[] parts = splitAtNonWordCharacters(string);
final StringBuilder conversionBuffer = new StringBuilder();
for (String part : parts)
{
if (part.length() < 2)
{
conversionBuffer.append(part.toUpperCase());
}
else
{
conversionBuffer.append(part.substring(0, 1).toUpperCase());
conversionBuffer.append(part.substring(1));
}
}
return conversionBuffer.toString();
}
/**
* Removes the last occurrence of the oldValue found within the string.
*
* @param string the String to remove the <code>value</code> from.
* @param value the value to remove.
* @return String the resulting string.
*/
public static String removeLastOccurrence(
String string,
final String value)
{
if (string != null && value != null)
{
StringBuilder buf = new StringBuilder();
int index = string.lastIndexOf(value);
if (index != -1)
{
buf.append(string.substring(0, index));
buf.append(string.substring(
index + value.length(),
string.length()));
string = buf.toString();
}
}
return string;
}
/**
* <p> Returns the argument string as a camel cased name beginning with a
* lowercased letter.
* </p>
* <p> Non word characters be removed and the letter following such a
* character will be uppercased.
* </p>
*
* @param string any string
* @return the string converted to a camel cased name beginning with a lower
* cased letter.
*/
public static String lowerCamelCaseName(final String string)
{
return uncapitalize(upperCamelCaseName(string));
}
/**
* Returns true if the input string starts with a lowercase letter.
* Used for validations of property/operation names against naming conventions.
*
* @param string any string
* @return true/false, null if null input
*/
public static Boolean startsWithLowercaseLetter(final String string)
{
if (string==null || string.length()<1)
{
return null;
}
final String start = string.substring(0, 1);
return isAllLowerCase(start) && isAlpha(start);
}
/**
* Returns true if the input string starts with an uppercase letter.
* Used for validations of Class names against naming conventions.
*
* @param string any string
* @return true/false, null if null input
*/
public static Boolean startsWithUppercaseLetter(final String string)
{
if (string==null)
{
return null;
}
final String start = string.substring(0, 1);
return isAllUpperCase(start) && isAlpha(start);
}
/**
* Converts the argument into a message key in a properties resource bundle,
* all lowercase characters, words are separated by dots.
*
* @param string any string
* @return the string converted to a value that would be well-suited for a
* message key
*/
public static String toResourceMessageKey(final String string)
{
return separate(StringUtils.trimToEmpty(string), ".").toLowerCase();
}
/**
* Converts into a string suitable as a human readable phrase, First
* character is uppercase (the rest is left unchanged), words are separated
* by a space.
*
* @param string any string
* @return the string converted to a value that would be well-suited for a
* human readable phrase
*/
public static String toPhrase(final String string)
{
return capitalize(separate(string, " "));
}
/**
* Converts the argument to lowercase, removes all non-word characters, and
* replaces each of those sequences by the separator.
* @param string
* @param separator
* @return separated string
*/
public static String separate(
final String string,
final String separator)
{
if (StringUtils.isBlank(string))
{
return string;
}
final String[] parts = splitAtNonWordCharacters(string);
final StringBuilder buffer = new StringBuilder();
for (int i = 0; i < parts.length - 1; i++)
{
if (StringUtils.isNotBlank(parts[i]))
{
buffer.append(parts[i]).append(separator);
}
}
return buffer.append(parts[parts.length - 1]).toString();
}
/**
* Splits at each sequence of non-word characters. Sequences of capitals
* will be left untouched.
*/
private static String[] splitAtNonWordCharacters(final String string)
{
final Pattern capitalSequencePattern = Pattern.compile("[A-Z]+");
final Matcher matcher = capitalSequencePattern.matcher(StringUtils.trimToEmpty(string));
final StringBuffer buffer = new StringBuffer();
while (matcher.find())
{
matcher.appendReplacement(buffer, ' ' + matcher.group());
}
matcher.appendTail(buffer);
// split on all non-word characters: make sure we send the good parts
return buffer.toString().split("[^A-Za-z0-9]+");
}
/**
* Suffixes each line with the argument suffix.
*
* @param multiLines A String, optionally containing many lines
* @param suffix The suffix to append to the end of each line
* @return String The input String with the suffix appended at the end of
* each line
*/
public static String suffixLines(
final String multiLines,
final String suffix)
{
final String[] lines = StringUtils.trimToEmpty(multiLines).split(LINE_SEPARATOR);
final StringBuilder linesBuffer = new StringBuilder();
for (String line : lines)
{
linesBuffer.append(line);
linesBuffer.append(suffix);
linesBuffer.append(LINE_SEPARATOR);
}
return linesBuffer.toString();
}
/**
* Converts any multi-line String into a version that is suitable to be
* included as-is in properties resource bundle.
*
* @param multiLines A String, optionally containing many lines
* @return String The input String with a backslash appended at the end of
* each line, or <code>null</code> if the input String was blank.
*/
public static String toResourceMessage(String multiLines)
{
String resourceMessage = null;
if (StringUtils.isNotBlank(multiLines))
{
final String suffix = "\\";
multiLines = suffixLines(multiLines, ' ' + suffix).trim();
while (multiLines.endsWith(suffix))
{
multiLines = multiLines.substring(
0,
multiLines.lastIndexOf(suffix)).trim();
}
resourceMessage = multiLines;
}
return resourceMessage;
}
/**
* Takes an English word as input and prefixes it with 'a ' or 'an '
* depending on the first character of the argument String. <p> The
* characters 'a', 'e', 'i' and 'o' will yield the 'an' predicate while all
* the others will yield the 'a' predicate.
* </p>
*
* @param word the word needing the predicate
* @return the argument prefixed with the predicate
*/
public static String prefixWithAPredicate(final String word)
{
// todo: this method could be implemented with better logic, for example to support 'an r' and 'a rattlesnake'
final StringBuilder formattedBuffer = new StringBuilder();
formattedBuffer.append("a ");
formattedBuffer.append(word);
char firstChar = word.charAt(0);
switch (firstChar)
{
case 'a': // fall-through
case 'e': // fall-through
case 'i': // fall-through
case 'o':
formattedBuffer.insert(1, 'n');
break;
default:
}
return formattedBuffer.toString();
}
/**
* Converts multi-line text into a single line, normalizing whitespace in the
* process. This means whitespace characters will not follow each other
* directly. The resulting String will be trimmed. If the
* input String is null the return value will be an empty string.
*
* @param string A String, may be null
* @return The argument in a single line
*/
public static String toSingleLine(String string)
{
// remove anything that is greater than 1 space.
return (string == null) ? "" : string.replaceAll("[$\\s]+", " ").trim();
}
/**
* Linguistically pluralizes a singular noun.
* <ul>
* <li><code>noun</code> becomes <code>nouns</code></li>
* <li><code>key</code> becomes <code>keys</code></li>
* <li><code>word</code> becomes <code>words</code></li>
* <li><code>property</code> becomes <code>properties</code></li>
* <li><code>bus</code> becomes <code>busses</code></li>
* <li><code>boss</code> becomes <code>bosses</code></li>
* </ul>
* <p> Whitespace as well as <code>null</code> arguments will return an
* empty String.
* </p>
*
* @param singularNoun A singular noun to pluralize
* @return The plural of the argument singularNoun or the empty String if the argument is
* <code>null</code> or blank.
*/
public static String pluralize(final String singularNoun)
{
final String plural = EnglishInflector.pluralize(singularNoun);
return plural == null ? "" : plural.trim();
}
/**
* Formats the argument string without any indentation, the text will be
* wrapped at the default column.
* @param plainText
* @return formatted string
*
* @see #format(String, String)
*/
public static String format(final String plainText)
{
return format(plainText, "");
}
/**
* Formats the given argument with the specified indentation, wrapping the
* text at a 64 column margin.
* @param plainText
* @param indentation
* @return formatted string
*
* @see #format(String, String, int)
*/
public static String format(
final String plainText,
final String indentation)
{
return format(plainText, indentation, 100 - indentation.length());
}
/**
* Formats the given argument with the specified indentation, wrapping the
* text at the desired column margin. The returned String will not be suited
* for display in HTML environments.
* @param plainText
* @param indentation
* @param wrapAtColumn
* @return formatted string
*
* @see #format(String, String, int, boolean)
*/
public static String format(
final String plainText,
final String indentation,
final int wrapAtColumn)
{
return format(plainText, indentation, wrapAtColumn, true);
}
/**
* <p>
* Formats the given argument with the specified indentation, wrapping the
* text at the desired column margin.
* </p>
* <p>
* When enabling <em>htmlStyle</em> the returned text will be suitable for
* display in HTML environments such as JavaDoc, all newlines will be
* replaced by paragraphs.
* </p>
* <p>
* This method trims the input text: all leading and trailing whitespace
* will be removed.
* </p>
* <p>
* If for some reason this method would fail it will return the
* <em>plainText</em> argument.
* </p>
*
* @param plainText the text to format, the empty string will be returned in
* case this argument is <code>null</code>; long words will be
* placed on a newline but will never be wrapped
* @param indentation the empty string will be used if this argument would
* be <code>null</code>
* @param wrapAtColumn does not take into account the length of the
* indentation, needs to be strictly positive
* @param htmlStyle whether or not to make sure the returned string is
* suited for display in HTML environments such as JavaDoc
* @return a String instance which represents the formatted input, never
* <code>null</code>
* @throws IllegalArgumentException when the <em>wrapAtColumn</em>
* argument is not strictly positive
*/
public static String format(
final String plainText,
String indentation,
final int wrapAtColumn,
final boolean htmlStyle)
{
// - we cannot wrap at a column index less than 1
if (wrapAtColumn < 1)
{
throw new IllegalArgumentException("Cannot wrap at column less than 1: " + wrapAtColumn);
}
// unspecified indentation will use the empty string
if (indentation == null)
{
indentation = "";
}
// - null plaintext will yield the empty string
if (StringUtils.isBlank(plainText))
{
return indentation;
}
final String lineSeparator = LINE_SEPARATOR;
String format;
try
{
// - this buffer will contain the formatted text
final StringBuilder formattedText = new StringBuilder();
// - we'll be reading lines from this reader
final BufferedReader reader = new BufferedReader(new StringReader(plainText));
String line = reader.readLine();
// - test whether or not we reached the end of the stream
while (line != null)
{
if (StringUtils.isNotBlank(line))
{
// Remove leading/trailing whitespace before adding indentation and html formatting.
//line = line.trim();
// - in HTML mode we start each new line on a paragraph
if (htmlStyle)
{
formattedText.append(indentation);
formattedText.append("<p>");
formattedText.append(lineSeparator);
}
// - WordUtils.wrap never indents the first line so we do it
// here
formattedText.append(indentation);
// - append the wrapped text, the indentation is prefixed
// with a newline
formattedText.append(WordUtils.wrap(
line.trim(),
wrapAtColumn,
lineSeparator + indentation,
false));
// - in HTML mode we need to close the paragraph
if (htmlStyle)
{
formattedText.append(lineSeparator);
formattedText.append(indentation);
formattedText.append("</p>");
}
}
// - read the next line
line = reader.readLine();
// - only add a newline when the next line is not empty and some
// string have already been added
if (formattedText.length() > 0 && StringUtils.isNotBlank(line))
{
formattedText.append(lineSeparator);
}
}
// - close the reader as there is nothing more to read
reader.close();
// - set the return value
format = formattedText.toString();
}
catch (final IOException ioException)
{
logger.error("Could not format text: " + plainText, ioException);
format = plainText;
}
return format;
}
/**
* The line separator.
*/
private static final String LINE_SEPARATOR = "\n";
/**
* Gets the line separator.
*
* @return the line separator.
*/
public static String getLineSeparator()
{
// - for reasons of platform compatibility we do not use the 'line.separator' property
// since this will break the build on different platforms (for example
// when comparing cartridge output zips)
return LINE_SEPARATOR;
}
}