/*
* JBoss, Home of Professional Open Source.
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership. Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
*
* Copyright (c) 2000, 2008 IBM Corporation and others.
* All rights reserved.
* This code is made available under the terms of the Eclipse Public
* License, version 1.0.
*/
package org.teiid.core.util;
import java.lang.reflect.Array;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
/**
* This is a common place to put String utility methods.
*/
public final class StringUtil {
@SuppressWarnings( {"nls", "javadoc"} )
public interface Constants {
char CARRIAGE_RETURN_CHAR = '\r';
char LINE_FEED_CHAR = '\n';
char NEW_LINE_CHAR = LINE_FEED_CHAR;
char SPACE_CHAR = ' ';
char DOT_CHAR = '.';
char TAB_CHAR = '\t';
char SPEECH_MARK_CHAR = '"';
char QUOTE_CHAR = '\'';
char STAR_CHAR = '*';
char QMARK_CHAR = '?';
char FORWARD_SLASH_CHAR = '/';
char LBRACE_CHAR = '{';
char RBRACE_CHAR = '}';
char PLUS_CHAR = '+';
char PIPE_CHAR = '|';
String CARRIAGE_RETURN = String.valueOf(CARRIAGE_RETURN_CHAR);
String EMPTY_STRING = "";
String DBL_SPACE = " ";
String LINE_FEED = String.valueOf(LINE_FEED_CHAR);
String NEW_LINE = String.valueOf(NEW_LINE_CHAR);
String SPACE = String.valueOf(SPACE_CHAR);
String DOT = String.valueOf(DOT_CHAR);
String TAB = String.valueOf(TAB_CHAR);
String SPEECH_MARK = String.valueOf(SPEECH_MARK_CHAR);
String QUOTE = String.valueOf(QUOTE_CHAR);
String STAR = String.valueOf(STAR_CHAR);
String FORWARD_SLASH = String.valueOf(FORWARD_SLASH_CHAR);
String LBRACE = String.valueOf(LBRACE_CHAR);
String RBRACE = String.valueOf(RBRACE_CHAR);
String PLUS = String.valueOf(PLUS_CHAR);
String PIPE = String.valueOf(PIPE_CHAR);
String ESCAPE = "\\";
String REGEX_ESCAPE = "\\\\";
String QMARK = String.valueOf(QMARK_CHAR);
String[] EMPTY_STRING_ARRAY = new String[0];
}
/*
* Replace a single occurrence of the search string with the replace string
* in the source string. If any of the strings is null or the search string
* is zero length, the source string is returned.
* @param source the source string whose contents will be altered
* @param search the string to search for in source
* @param replace the string to substitute for search if present
* @return source string with the *first* occurrence of the search string
* replaced with the replace string
*/
public static String replace(String source, String search, String replace) {
if (source != null && search != null && search.length() > 0 && replace != null) {
int start = source.indexOf(search);
if (start > -1) {
return new StringBuffer(source).replace(start, start + search.length(), replace).toString();
}
}
return source;
}
/*
* Replace all occurrences of the search string with the replace string
* in the source string. If any of the strings is null or the search string
* is zero length, the source string is returned.
* @param source the source string whose contents will be altered
* @param search the string to search for in source
* @param replace the string to substitute for search if present
* @return source string with *all* occurrences of the search string
* replaced with the replace string
*/
public static String replaceAll(String source, String search, String replace) {
if (source == null || search == null || search.length() == 0 || replace == null) {
return source;
}
int start = source.indexOf(search);
if (start > -1) {
StringBuffer newString = new StringBuffer(source);
while (start > -1) {
int end = start + search.length();
newString.replace(start, end, replace);
start = newString.indexOf(search, start + replace.length());
}
return newString.toString();
}
return source;
}
/**
* Join string pieces and separate with a delimiter. Similar to the perl function of
* the same name. If strings or delimiter are null, null is returned. Otherwise, at
* least an empty string will be returned.
* @see #split
*
* @param strings String pieces to join
* @param delimiter Delimiter to put between string pieces
* @return One merged string
*/
public static String join(Collection<String> strings, String delimiter) {
if(strings == null || delimiter == null) {
return null;
}
StringBuffer str = new StringBuffer();
Iterator<String> iter = strings.iterator();
while (iter.hasNext()) {
str.append(iter.next());
if (iter.hasNext()) {
str.append(delimiter);
}
}
return str.toString();
}
/**
* Return a stringified version of the array.
* @param array the array
* @param delim the delimiter to use between array components
* @return the string form of the array
*/
public static String toString( final Object[] array, final String delim ) {
return toString(array, delim, true);
}
/**
* Return a stringified version of the array.
* @param array the array
* @param delim the delimiter to use between array components
* @return the string form of the array
*/
public static String toString( final Object[] array, final String delim, boolean includeBrackets) {
if ( array == null ) {
return ""; //$NON-NLS-1$
}
final StringBuffer sb = new StringBuffer();
if (includeBrackets) {
sb.append('[');
}
for (int i = 0; i < array.length; ++i) {
if ( i != 0 ) {
sb.append(delim);
}
sb.append(array[i]);
}
if (includeBrackets) {
sb.append(']');
}
return sb.toString();
}
/**
* Return a stringified version of the array, using a ',' as a delimiter
* @param array the array
* @return the string form of the array
* @see #toString(Object[], String)
*/
public static String toString( final Object[] array ) {
return toString(array, ",", true); //$NON-NLS-1$
}
/**
* Split a string into pieces based on delimiters. Similar to the perl function of
* the same name. The delimiters are not included in the returned strings.
* @see #join
*
* @param str Full string
* @param splitter Characters to split on
* @return List of String pieces from full string
*/
public static List<String> split(String str, String splitter) {
StringTokenizer tokens = new StringTokenizer(str, splitter);
ArrayList<String> l = new ArrayList<String>(tokens.countTokens());
while(tokens.hasMoreTokens()) {
l.add(tokens.nextToken());
}
return l;
}
/**
* Return the last token in the string.
*
* @param str String to be tokenized
* @param delimiter Characters which are delimit tokens
* @return the last token contained in the tokenized string
*/
public static String getLastToken(String str, String delimiter) {
if (str == null) {
return Constants.EMPTY_STRING;
}
int beginIndex = 0;
if (str.lastIndexOf(delimiter) > 0) {
beginIndex = str.lastIndexOf(delimiter)+1;
}
return str.substring(beginIndex,str.length());
}
/**
* Return the first token in the string.
*
* @param str String to be tokenized
* @param delimiter Characters which are delimit tokens
* @return the first token contained in the tokenized string
*/
public static String getFirstToken(String str, String delimiter) {
if (str == null) {
return Constants.EMPTY_STRING;
}
int endIndex = str.indexOf(delimiter);
if (endIndex < 0) {
endIndex = str.length();
}
return str.substring(0,endIndex);
}
/**
* Tests if the string starts with the specified prefix.
*
* @param text the string to test.
* @param prefix the prefix.
* @return <code>true</code> if the character sequence represented by the
* argument is a prefix of the character sequence represented by
* this string; <code>false</code> otherwise.
* Note also that <code>true</code> will be returned if the
* prefix is an empty string or is equal to the text
* <code>String</code> object as determined by the
* {@link #equals(Object)} method. If the text or
* prefix argument is null <code>false</code> is returned.
* @since JDK1. 0
*/
public static boolean startsWithIgnoreCase(final String text, final String prefix) {
if (text == null || prefix == null) {
return false;
}
return text.regionMatches(true, 0, prefix, 0, prefix.length());
}
/**
* Tests if the string ends with the specified suffix.
*
* @param text the string to test.
* @param suffix the suffix.
* @return <code>true</code> if the character sequence represented by the
* argument is a suffix of the character sequence represented by
* this object; <code>false</code> otherwise. Note that the
* result will be <code>true</code> if the suffix is the
* empty string or is equal to this <code>String</code> object
* as determined by the {@link #equals(Object)} method. If the text or
* suffix argument is null <code>false</code> is returned.
*/
public static boolean endsWithIgnoreCase(final String text, final String suffix) {
if (text == null || suffix == null) {
return false;
}
return text.regionMatches(true, text.length() - suffix.length(), suffix, 0, suffix.length());
}
public static boolean isLetter(char c) {
return isBasicLatinLetter(c) || Character.isLetter(c);
}
public static boolean isDigit(char c) {
return isBasicLatinDigit(c) || Character.isDigit(c);
}
public static boolean isLetterOrDigit(char c) {
return isBasicLatinLetter(c) || isBasicLatinDigit(c) || Character.isLetterOrDigit(c);
}
public static boolean isValid(String str) {
return (!(str == null || str.trim().length() == 0));
}
private static boolean isBasicLatinLetter(char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}
private static boolean isBasicLatinDigit(char c) {
return c >= '0' && c <= '9';
}
private static int parseNumericValue(CharSequence string, StringBuilder sb, int i, int value, int possibleDigits, int radixExp) {
for (int j = 0; j < possibleDigits; j++) {
if (i + 1 == string.length()) {
break;
}
char digit = string.charAt(i + 1);
int val = Character.digit(digit, 1 << radixExp);
if (val == -1) {
break;
}
i++;
value = (value << radixExp) + val;
}
sb.append((char)value);
return i;
}
/**
* Convert the given value to specified type.
* @param value
* @param type
* @return
*/
@SuppressWarnings("unchecked")
public static <T> T valueOf(String value, Class type){
if (value == null) {
return null;
}
if(type == String.class) {
return (T) value;
}
else if(type == Boolean.class || type == Boolean.TYPE) {
return (T) Boolean.valueOf(value);
}
else if (type == Integer.class || type == Integer.TYPE) {
return (T) Integer.decode(value);
}
else if (type == Float.class || type == Float.TYPE) {
return (T) Float.valueOf(value);
}
else if (type == Double.class || type == Double.TYPE) {
return (T) Double.valueOf(value);
}
else if (type == Long.class || type == Long.TYPE) {
return (T) Long.decode(value);
}
else if (type == Short.class || type == Short.TYPE) {
return (T) Short.decode(value);
}
else if (type.isAssignableFrom(List.class)) {
return (T)new ArrayList<String>(Arrays.asList(value.split(","))); //$NON-NLS-1$
}
else if (type.isAssignableFrom(Set.class)) {
return (T)new HashSet<String>(Arrays.asList(value.split(","))); //$NON-NLS-1$
}
else if (type.isArray()) {
String[] values = value.split(","); //$NON-NLS-1$
Object array = Array.newInstance(type.getComponentType(), values.length);
for (int i = 0; i < values.length; i++) {
Array.set(array, i, valueOf(values[i], type.getComponentType()));
}
return (T)array;
}
else if (type == Void.class) {
return null;
}
else if (type.isEnum()) {
return (T)Enum.valueOf(type, value);
}
else if (type == URL.class) {
try {
return (T)new URL(value);
} catch (MalformedURLException e) {
// fall through and end up in error
}
}
else if (type.isAssignableFrom(Map.class)) {
List<String> l = Arrays.asList(value.split(",")); //$NON-NLS-1$
Map m = new HashMap<String, String>();
for(String key: l) {
int index = key.indexOf('=');
if (index != -1) {
m.put(key.substring(0, index), key.substring(index+1));
}
}
return (T)m;
}
throw new IllegalArgumentException("Conversion from String to "+ type.getName() + " is not supported"); //$NON-NLS-1$ //$NON-NLS-2$
}
/**
* @param enumType
* @param name
* @return enum given by name and type where the name is case insensitive
*/
public static <T extends Enum<T>> T caseInsensitiveValueOf(Class<T> enumType, String name) {
try {
return Enum.valueOf(enumType, name);
} catch (IllegalArgumentException e) {
T[] vals = enumType.getEnumConstants();
for (T t : vals) {
if (name.equalsIgnoreCase(t.name())) {
return t;
}
}
throw e;
}
}
public static List<String> tokenize(String str, char delim) {
ArrayList<String> result = new ArrayList<String>();
StringBuilder current = new StringBuilder();
boolean escaped = false;
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (c == delim) {
if (escaped) {
current.append(c);
escaped = false;
} else {
escaped = true;
}
} else {
if (escaped && current.length() > 0) {
result.add(current.toString());
current.setLength(0);
escaped = false;
}
current.append(c);
}
}
if (current.length()>0) {
result.add(current.toString());
}
return result;
}
/**
* Unescape the given string
* @param string
* @param quoteChar
* @param useAsciiExcapes
* @param sb a scratch buffer to use
* @return
*/
public static String unescape(CharSequence string, int quoteChar, boolean useAsciiEscapes, StringBuilder sb) {
boolean escaped = false;
for (int i = 0; i < string.length(); i++) {
char c = string.charAt(i);
if (escaped) {
switch (c) {
case 'b':
sb.append('\b');
break;
case 't':
sb.append('\t');
break;
case 'n':
sb.append('\n');
break;
case 'f':
sb.append('\f');
break;
case 'r':
sb.append('\r');
break;
case 'u':
i = parseNumericValue(string, sb, i, 0, 4, 4);
//TODO: this should probably be strict about needing 4 digits
break;
default:
if (c == quoteChar) {
sb.append(quoteChar);
} else if (useAsciiEscapes) {
int value = Character.digit(c, 8);
if (value == -1) {
sb.append(c);
} else {
int possibleDigits = value < 3 ? 2:1;
int radixExp = 3;
i = parseNumericValue(string, sb, i, value, possibleDigits, radixExp);
}
}
}
escaped = false;
} else {
if (c == '\\') {
escaped = true;
} else if (c == quoteChar) {
break;
} else {
sb.append(c);
}
}
}
//TODO: should this be strict?
//if (escaped) {
//throw new FunctionExecutionException();
//}
return sb.toString();
}
/**
* @param haystack
* @param needle
* @return the number of occurrences of needle in haystack
*/
public static int countOccurrences(String haystack, char needle) {
int count = 0;
for (int i = 0; i < haystack.length(); i++) {
if (haystack.charAt(i) == needle) {
count++;
}
}
return count;
}
}