package com.icentris.util;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.text.CharacterIterator;
import java.text.NumberFormat;
import java.text.StringCharacterIterator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* StringUtil handles html escaping and other string manipulation.
*/
public class StringUtil {
/**
* Logger for this class
*/
private static final Log logger = LogFactory.getLog(StringUtil.class);
private static final boolean truncateIfMissingDefault = false;
/**
* @param string1
* The first passed String
* @param string2
* The second passed String
* @return The first passed String if not empty, otherwise the second String.
*/
public static String checkEmpty(String string1, String string2) {
String retString = "";
if (isEmpty(string1) == false) {
retString = string1;
}
else if (string2 != null) {
retString = string2;
}
return retString;
}
/**
* @param string1
* The passed String
* @return The passed String if not null, otherwise an empty non-null String
*/
public static String checkNull(String string1) {
String retString = "";
if (string1 != null) {
retString = string1;
}
return retString;
}
/**
* @param string1
* The first passed String
* @param string2
* The second passed String
* @return The first passed String if not null, otherwise the second String.
*/
public static String checkNull(String string1, String string2) {
String retString = "";
if (string1 != null) {
retString = string1;
}
else if (string2 != null) {
retString = string2;
}
return retString;
}
public static String cleanNull(String cstr) {
if (cstr == null) {
cstr = "";
return cstr;
}
else if (cstr.equals("null")) {
cstr = "";
return cstr;
}
return cstr;
}
public static String formatNumberTwoDecimals(String toFormat, Locale locale) {
String value = "0";
// double origValue =
// drbConnection.getResults().getDouble(column.getName());
double origValue = Double.parseDouble(toFormat);
NumberFormat nfNumber = NumberFormat.getNumberInstance(locale);
nfNumber.setMinimumFractionDigits(2);
nfNumber.setMaximumFractionDigits(2);
if (origValue > -1) {
try {
value = nfNumber.format(origValue);
}
catch (NumberFormatException nfe) {
logger.error("formatNumberTwoDecimals NumberFormatException:", nfe);
}
}
return value;
}
public static String formatPhone(String origValue) {
StringBuffer sb = new StringBuffer();
if (origValue != null && origValue.length() > 6) {
sb.append("(");
sb.append(origValue.substring(0, 3));
sb.append(") ");
sb.append(origValue.substring(3, 6));
sb.append("-");
sb.append(origValue.substring(6, origValue.length()));
}
return sb.toString();
}
/**
* Adds a dash after the 5th char in a US postal code if it is a plus 4 postal code.
* @param postalCode - The postal code to be formatted.
* @return - A formatted value if it is a plus 4 postal code, the original value if it is not.
*/
public static String formatPostalCode(String postalCode) {
String retValue = "";
if (postalCode != null && postalCode.length() > 5) {
StringBuffer sb = new StringBuffer();
sb.append(postalCode.substring(0, 5));
sb.append("-");
sb.append(postalCode.substring(5, postalCode.length()));
retValue = sb.toString();
} else {
retValue = postalCode;
}
return retValue;
}
/**
* Return an array as a string with each element delimited by the delimiter passed.
*
* @param array[]
* Array to return as String
* @param delimiter
* String used to delimit each element of the array
* @return returns a String of elements delimited by the delimiter character
*/
public static String getArrayAsString(Object[] array, String delimiter) {
StringBuffer string = new StringBuffer();
if (array != null) {
for (int i = 0; i < array.length; i++) {
if (i > 0) {
string.append(delimiter);
}
string.append(array[i].toString());
}
}
else {
return null;
}
return string.toString();
}
/**
* Object version of htmlEscape(Object)
*/
public static String htmlEscape(Object o) {
return htmlEscape(o.toString());
}
public static String htmlEscape(String s) {
// give them back what they gave me if they gave me a null instead of a string
// avoids null pointer error below
if (s == null)
return s;
StringBuffer sb = new StringBuffer();
// loop through each character of the string and look at it
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
// replace html-special characters with html-escaped characters
switch (c) {
case '&':
sb.append("&");
break;
case '\"':
sb.append(""");
break;
case '<':
sb.append("<");
break;
case '>':
sb.append(">");
break;
case '\'':
sb.append("'");
break;
case '\n':
sb.append("
");
break;
case '\r':
sb.append("
");
break;
default:
sb.append(c);
}
}
return sb.toString();
}
/**
* Within a String looks for substring and inserts another string after the substring.
*/
public static String insertString(String s, String stringToLookFor, String stringToInsert) {
int tmpInt = s.indexOf(stringToLookFor);
StringBuffer sb1 = new StringBuffer(s);
sb1.insert(tmpInt + stringToLookFor.length(), stringToInsert);
return sb1.toString();
}
/**
* Does embeded variable expansion (interpolation) on passed string value by replacing embeded variables of the form ${variableName} with value of the
* property whos key is "variableName" in props.
*
* @param value
* string on which to do interpolation
* @param props
* properties object containing keys matching embeded variable names and values used as variable replacements
*/
public static String interpolate(String value, Properties props) {
return interpolate(value, props, truncateIfMissingDefault);
}
/**
* Does embeded variable expansion (interpolation) on passed string value by replacing embeded variables of the form <code>${variableName}</code> with
* value of the property whos key is <code>variableName</code> in props. For example:
*
* <pre>
* Properties props = new Properties();
* props.setProperty("hello", "Good Morning");
* String str = "${hello}, Mr. Toad!";
* String greeting = StringUtil.interpolate(str, props);
* </pre>
*
* The value of <code>greeting</code> is "Good Morning, Mr. Toad!"
*
* Advanced example:
*
* <pre>
* Properties props = new Properties();
* int hour = (new Calendar()).get(Calendar.HOUR_OF_DAY);
* props.setProperty("partOfDay", (hour < 12 ? "Morning" : "Afternoon"));
* props.setProperty("hello", "Good ${partOfDay}");
* String str = "${hello}, Mr. Toad!";
* String greeting = StringUtil.interpolate(str, props);
* </pre>
*
* The value of <code>greeting</code> is "Good Morning, Mr. Toad!" or "Good Afternoon, Mr. Toad!" depending on the time of day.
*
* Extra Advanced features:
* <ul>
* <li>embeded variables like ${${something}.color}
* <li>recursion detection to avoid infinite loops when someone accidentally sets a=a or a=b, b=c, c=a in a properties file or object
* </ul>
*
* @param value
* string on which to do interpolation
* @param props
* properties object containing keys matching embeded variable names and values used as variable replacements
* @param truncateIfMissing
* if true, truncates variables not found to "" (empty string); if false, passes through unchanged any variables not found in props
*/
public static String interpolate(String value, Properties props, boolean truncateIfMissing) {
return interpolate(value, props, truncateIfMissing, null);
}
/** This method returns the interpolatable keys for a given string.
* NOTE: This function does not handle nested keys.
*
* For Example:
* given: "${x} of ${y}"
*
* this method returns an ArrayList wrapping an array like: { "x", "y" }
*
* @param value to be checked for keys
*
* @return a String array of keys.
*/
public static String[] getKeysToInterpolate(String value) { return getKeysToInterpolate(value, "\\$"); }
public static String[] getKeysToInterpolate(String value, String keyIdentifier) {
ArrayList<String> keys = new ArrayList<String>();
Pattern pattern = Pattern.compile( keyIdentifier+"\\{[^}]*}");
Matcher matcher = pattern.matcher(value);
while (matcher.find()) {
int startKeyName = matcher.start()+2;
int endKeyName = matcher.end()-1;
keys.add( value.substring(startKeyName, endKeyName) );
}
return keys.toArray(new String[0]);
}
private static String interpolate(String value, Properties props, boolean truncateIfMissing, ArrayList<String> beenThere) {
if (props == null || value == null)
return value;
else {
// System.out.println("[StringUtil] beenThere=[" + beenThere + "]");
}
int start = value.indexOf("${");
if ( start <= -1 ) {
return value; // Return here to avoid creation of ArrayList() in event no work is being done here
}
if (beenThere == null) {
beenThere = new ArrayList<String>();
}
while (start > -1) {
int end = value.indexOf("}", (start + 2));
if (end > start + 2) {
String keyToExpand = value.substring((start + 2), end);
int nestedStart = keyToExpand.indexOf("${");
while (nestedStart > -1) {
end = value.indexOf("}", (end + 1));
if (end > -1) {
keyToExpand = value.substring((start + 2), end);
nestedStart = keyToExpand.indexOf("${", (nestedStart + 2));
}
else {
// System.err.println("[StringUtil] Malformed value! [" + value + "] " +
// "contained unbalanced start (${) and end (}) characters");
return value;
}
}
// if this key needs to be interpolated itself
if (keyToExpand.indexOf("${") > -1) {
// System.out.println("[StringUtil] recursing! keyToExpand=[" + keyToExpand + "]");
beenThere.add(keyToExpand);
keyToExpand = interpolate(keyToExpand, props, truncateIfMissing, beenThere);
}
if (beenThere.contains(keyToExpand)) {
//beenThere.add(keyToExpand);
// System.err.println("[StringUtil] Recursion attempt detected! Property:[" + beenThere.get(0) + "] " +
// "recursively included property:[" + keyToExpand + "]");
// System.err.println("[StringUtil] Recursion attempt path:" + beenThere);
return value;
}
else {
String expandValue = props.getProperty(keyToExpand);
if (expandValue != null) { // Key found! Let's interpolate!
// if this value needs to be interpolated itself
if (expandValue.indexOf("${") > -1) {
// System.out.println("[StringUtil] recursing! key=[" + keyToExpand + "] expandValue=[" + expandValue + "]");
beenThere.add(keyToExpand);
expandValue = interpolate(expandValue, props, truncateIfMissing, beenThere);
beenThere.remove(keyToExpand);
}
value = value.substring(0, start) + expandValue + value.substring(end + 1);
end = start + expandValue.length();
}
else { // Key not found! (expandValue == null)
if (truncateIfMissing == true) {
value = value.substring(0, start) + value.substring(end + 1);
end = start;
}
}
}
}
else {
// System.err.println("[StringUtil] Value [" + value + "] starts but does end variable");
return value;
}
start = value.indexOf("${", end);
}
return value;
}
/**
* Check whether string is empty.
*
* @param string1
* The passed String
* @return "true" if the string is null or empty, else "false"
*/
public static boolean isEmpty(String string1) {
return ((string1 == null) || (string1.length() == 0));
}
/**
* javascriptEscape(String) escapes the one js(\) character and replaces them with entity equivalents.
*/
public static String javascriptEscape(String s) {
// give them back what they gave me if they gave me a null instead of a string
// avoids null pointer error below
if (s == null)
return s;
StringBuffer sb = new StringBuffer();
// loop through each character of the string and look at it
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
// replace html-special characters with html-escaped characters
switch (c) {
case '\\':
sb.append("\\\\");
break;
case '\r':
break;
case '\n':
sb.append("\\n");
break;
case '\"':
sb.append(""");
break;
case '\'':
sb.append("\\'");
break;
default:
sb.append(c);
}
}
return sb.toString();
}
/**
* @return if both have length > 0; first and second separated by sep; if only one has length > 0, just that one; otherwise (both are empty or null), ""
*/
public static String joinWithSeparatorIfNeeded(String first, String second, String sep) {
if (first != null && first.length() > 0) {
if (second != null && second.length() > 0) {
return first + sep + second;
}
else {
return first;
}
}
else {
if (second != null && second.length() > 0) {
return second;
}
else {
return "";
}
}
}
public static String makeTheFirstLetterOfEachWordUpperCase(String string) {
return makeTheFirstLetterOfEachWordUpperCase(string, " ");
}
public static String makeTheFirstLetterOfEachWordUpperCase(String string, String delimiter) {
StringBuffer returnBuffer = new StringBuffer();
if (returnBuffer == null)
returnBuffer = new StringBuffer();
if (string != null) {
StringTokenizer tokens = new StringTokenizer(string, delimiter, true);
while (tokens.hasMoreTokens()) {
returnBuffer.append(makeTheFirstLetterUpperCase(tokens.nextToken()));
}
}
else {
return null;
}
return returnBuffer.toString();
}
public static String makeTheFirstLetterUpperCase(String string) {
StringBuffer sb = new StringBuffer();
if (string != null) {
int stringLength = string.length();
if (stringLength > 0) {
sb.append(Character.toUpperCase(string.charAt(0)));
if (stringLength > 1) {
string = string.substring(1).toLowerCase();
sb.append(string);
}
}
}
else {
return null;
}
return sb.toString();
}
/**
* Do MD5 then convert binary to String using zero-padded base16. Like this:
*
* <pre>
* MessageDigest md = MessageDigest.getInstance("MD5");
* byte[] digest = md.digest(plainText);
* StringBuffer sb = new StringBuffer();
* for (int i = 0; i < digest.length; i++) {
* int oneByte = digest[i];
* if (oneByte < 0)
* oneByte += 256;
* String hexString = Integer.toHexString(oneByte);
* if (hexString.length() == 1)
* hexString = "0" + hexString;
* sb.append(hexString);
* }
* return sb.toString();
* </pre>
*
* @param plainText
* Any ASCII text (only char numbers 0-127)
*/
public static String md5Hash(String plainText) {
try {
java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");
byte[] digest = md.digest(plainText.getBytes("US-ASCII"));
StringBuffer sb = new StringBuffer();
for (int i = 0; i < digest.length; i++) {
int oneByte = digest[i];
if (oneByte < 0)
oneByte += 256;
String hexString = Integer.toHexString(oneByte);
if (hexString.length() == 1)
hexString = "0" + hexString;
sb.append(hexString);
}
return sb.toString();
}
catch (java.security.NoSuchAlgorithmException e) {
throw new java.lang.reflect.UndeclaredThrowableException(e);
}
catch (java.io.UnsupportedEncodingException e) {
throw new java.lang.reflect.UndeclaredThrowableException(e);
}
}
/**
* Removes HTML code from a passed in String
*/
public static String removeHtml(String s) {
if (s == null)
return null;
int start = (s.indexOf("<"));
while (start > -1) {
int end = s.indexOf(">", start);
if (end == -1) {
break;
}
else {
s = s.substring(0, start) + s.substring(end + 1);
start = s.indexOf("<", start);
}
}
return s;
}
public static String repeatString(String repeat, int times) {
StringBuffer sb = new StringBuffer();
for (int i = 1; i <= times; i++) {
sb.append(repeat);
}
sb.append(" ");
return sb.toString();
}
/**
* Searches for a specific string and replaces it with another. Case sensitive.
*/
public static String replace(String s, String find, String replace) {
if (s == null || find == null || find.length() == 0)
return s;
StringBuffer sb = new StringBuffer();
int index = -1;
int lastIndex = 0;
index = s.indexOf(find);
while (index != -1) {
sb.append(s.substring(lastIndex, index));
sb.append(replace);
lastIndex = index + find.length();
index = s.indexOf(find, lastIndex);
}
sb.append(s.substring(lastIndex));
return sb.toString();
}
/**
* Searches for a specific string and replaces it with another. Case insensitive.
*/
public static String replaceNoCase(String s, String find, String replace) {
if (s == null || find == null || find.length() == 0)
return s;
StringBuffer sb = new StringBuffer();
String upperCaseS = s.toUpperCase();
String upperCaseFind = find.toUpperCase();
int index = -1;
int lastIndex = 0;
index = upperCaseS.indexOf(upperCaseFind);
while (index != -1) {
sb.append(s.substring(lastIndex, index));
sb.append(replace);
lastIndex = index + find.length();
index = upperCaseS.indexOf(upperCaseFind, lastIndex);
}
sb.append(s.substring(lastIndex));
return sb.toString();
}
/**
* Breaks a delimiter-separated string into a String[] array. The delimiter can be embeded if it is escaped with a backslash (\) which will of course need to
* be double-escaped in java when between double quotes. e.g. String myString = "c\\:/Program Files:c\\:/dev";
*/
public static String[] split(String s, char delimiter) {
if (s == null || s.length() == 0)
return new String[] {};
String delim = String.valueOf(delimiter);
ArrayList<String> values = new ArrayList<String>();
StringBuffer value = new StringBuffer();
int begin = 0;
int end = -1;
boolean delimeterWasFound = s.indexOf(delimiter) != -1;
if (delimeterWasFound) {
while ((end = s.indexOf(delimiter, begin)) > -1) {
String nextValue = s.substring(begin, end);
begin = end + 1;
value.append(nextValue);
// check for escaped delimiters, and don't use them yet
if (nextValue.endsWith("\\") == true) {
// replace the trailing \ with the delimiter
value.replace(value.length() - 1, value.length(), delim);
}
else {
values.add(value.toString());
value.delete(0, value.length());
}
// if we ended on a comma
if (begin == s.length()) {
values.add("");
}
}
if (begin < (s.length() - 1)) {
if (s.charAt(begin) == delimiter) {
values.add(s.substring(begin + 1));
}
else {
values.add(s.substring(begin));
}
}
}
else { // No delimiter was found, so pass back the string in the array
values.add(s);
}
// there should be no leftovers
if (value.length() > 0) {
throw new IllegalStateException("Failed creating array for string=[" + s + "]. I have leftovers, how did this happen?");
}
return (String[]) values.toArray(new String[0]);
}
/**
* Same as insertString() except it replaces what it finds.
*/
public static String swapStrings(String s, String from, String to) {
return replace(s, from, to);
}
/**
* Replaces all newlines (\n) or CR-newlines (\r\n) with html <br>
* 's
*/
public static String textBreak2HtmlBreak(String s) {
s = swapStrings(s, "\r\n", "<br>");
s = swapStrings(s, "\n", "<br>");
return s;
}
/**
* Truncates a String to the given amount of characters, but will not allow a word to be cut off, will go back to the first previous non-LetterOrDigit
* (usually space or punctuation) character
*/
public static String truncate(int maxChars, String s) {
if (maxChars < 0 || s == null || s.length() <= maxChars)
return s;
StringCharacterIterator stringIterator = new StringCharacterIterator(s, maxChars);
for (char c = stringIterator.current(); c != CharacterIterator.DONE; c = stringIterator.previous()) {
if (!Character.isLetterOrDigit(c)) {
return s.substring(0, stringIterator.getIndex());
}
}
// we didn't find a non-LetterOrDigit character prior to maxChars
return "";
}
/**
* Truncates a String to the given amount of characters, but will not allow a word to be cut off,
* will go back to the first previous non-LetterOrDigit (usually space or punctuation) character.
*
* Unlike truncate(), if the entire String is longer than the lowerLimit without any spaces, it will
* return a substring of that cut off at the lowerLimit.
*
* @param lowerLimit Minimum point to truncate the String at.
* @param appendToEnd Will be appended to the truncated String, like ... or whatever.
* @param theVictim The String to be truncated.
* @return A truncated String.
*/
public static String truncateNicely(int lowerLimit,String appendToEnd,String theVictim) {
if (lowerLimit < 0 || theVictim == null || theVictim.length() <= lowerLimit) {
return theVictim;
}
String retValue = truncate(lowerLimit,theVictim);
if(retValue.length() == 0) {
retValue = theVictim.substring(0,lowerLimit + 1);
}
return retValue + appendToEnd;
}
/**
* Calls truncateNicely(int,String,String) and passes ... as the appendToEnd value.
*
* @param lowerLimit Minimum point to truncate the String at.
* @param theVictim The String to be truncated.
* @return A truncated String.
*/
public static String truncateNicely(int lowerLimit,String theVictim) {
return truncateNicely(lowerLimit,"...",theVictim);
}
/**
* Escapes text that will appear in a URL so it doesn't get broken apart by the parsing of the url
*
* @param urlText
* The text to escape
* @return The escaped text
*/
public static String urlEscape(String urlText) {
try {
return URLEncoder.encode(urlText, "UTF-8");
}
catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
public static String urlUnescape(String escapedText) {
try {
return URLDecoder.decode(escapedText, "UTF-8");
}
catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
/**
* Verifies an email address and ensures that it is a valid format.
*
* @param emailAddress
* The email address to be verified.
* @return true if the email is in the proper format, false if otherwise.
*/
public static boolean verifyEmailAddress(String emailAddress) {
java.util.regex.Pattern p = java.util.regex.Pattern.compile("^.*@.*\\..{2,3}$");
java.util.regex.Matcher m = p.matcher(emailAddress);
boolean b = m.matches();
return b;
}
/*
* Take a "full name" String and split it into two Strings, one for first name, one for last name.
* There are different ways to do this, but I choose to have everything up to the last whitespace be
* the "first" name. What is left after the last whitespace is the "last" name.
*/
public static String[] splitName(String fullName) {
String[] splitName = new String[2];
if(fullName != null && fullName.length() > 0) {
String[] fullNameArray = StringUtil.split(fullName, ' '); // split up by whitespace
StringBuilder firstName = new StringBuilder("");;
for(int i = 0; i < (fullNameArray.length - 1); i++){
firstName.append(fullNameArray[i]);
}
splitName[0] = firstName.toString();
splitName[1] = fullNameArray[fullNameArray.length-1];
}
return splitName;
}
public static String listToHtmlBulletedList(List<String> list) {
StringBuilder sb = new StringBuilder();
for ( String element : list ) {
if ( !isEmpty(element) ) {
sb.append("<li>").append(element).append("</li>");
}
}
return sb.toString();
}
public static String camelCase(String src){
return StringUtils.isNotEmpty(src) ? src.substring(0, 1).toLowerCase() + src.substring(1, src.length()) : src;
}
public static String defaultToString(Object o) {
return o == null ? "" : StringUtils.defaultString(o.toString());
}
/**
* Builds on top of CommonsLang StringUtil.join method.
*
* Instead of using toString() on each member of the collection, specify a property
* to use instead of toString().
*
* @param c
* @param propertyName
* @return
*/
public static String join( Collection c, String propertyName ) {
return StringUtil.join( c, propertyName, "," );
}
/**
* Builds on top of CommonsLang StringUtil.join method.
*
* Instead of using toString() on each member of the collection, specify a property
* to use instead of toString().
*
* @param c
* @param propertyName
* @return
*/
public static String join( Collection c, String propertyName, String delimiter ) {
List<String> valueList = new ArrayList<String>();
for ( Object o : c ) {
Object value = null;
try {
if ( o != null ) {
value = PropertyUtils.getProperty( o, propertyName);
}
} catch (Exception e) {
e.printStackTrace();
value = "";
logger.error( "Unable to access property ["+ propertyName +"] on object ["+ o +"]");
}
valueList.add( (value == null) ? "" : value.toString() );
}
return StringUtils.join( valueList, delimiter );
}
public static String listOf(int size, char ch, char delimiter) {
StringBuffer sb = new StringBuffer();
for ( int i = 0; i < size; i++ ) {
if ( i > 0 ) {
sb.append(delimiter);
}
sb.append(ch);
}
return sb.toString();
}
public static String listOfBindings(int size) {
return listOf(size, '?', ',');
}
}