editor.search.StringUtil Maven / Gradle / Ivy
package editor.search;
import java.awt.*;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class StringUtil
{
/**
* Split up a string into tokens delimited by the specified separator
* character. If the string is null or zero length, returns null.
*
* @param s The String to tokenize
* @param separator The character delimiting tokens
*
* @return An array of String tokens, or null is s is null or 0 length.
*/
public static String[] tokenize( String s, char separator )
{
List tokens = tokenizeToList( s, separator );
return tokens == null ? null : tokens.toArray( new String[tokens.size()] );
}
/**
* Split up a string into tokens delimited by the specified separator
* character. If the string is null or zero length, returns null.
*
* @param s The String to tokenize
* @param separator The character delimiting tokens
*
* @return A List of String tokens, or null is s is null or 0 length.
*/
public static List tokenizeToList( String s, char separator )
{
if( s == null || s.length() == 0 )
{
return null;
}
int start = 0;
int stop = 0;
ArrayList tokens = new ArrayList();
while( start <= s.length() )
{
stop = s.indexOf( separator, start );
if( stop == -1 )
{
stop = s.length();
}
String token = s.substring( start, stop );
tokens.add( token );
start = stop + 1;
}
return tokens;
}
/**
* Split up a string into tokens delimited by the specified separator
* character as long as that character is not escaped with the escape character.
* If the string is null or zero length, returns null.
*
* @param s The string to tokenize.
* @param separator Character in s
denoting separation between tokens.
* @param escape Hint character where the string to tokenize ends.
*
* @return Array of tokens from the input string.
*/
public static String[] tokenizeUnescaped( String s, char separator, char escape )
{
if( s == null || s.length() == 0 )
{
return null;
}
int start = 0;
int stop = 0;
int mid = 0;
ArrayList tokens = new ArrayList();
while( mid <= s.length() )
{
stop = s.indexOf( separator, mid );
if( stop == -1 )
{
stop = s.length();
}
else if( isEscaped( s, stop, escape ) )
{ // if character is escaped
// skip this one because it's escaped and move on to the next one
mid = stop + 1;
continue;
}
String token = s.substring( start, stop );
tokens.add( token );
start = stop + 1;
mid = start;
}
return (String[])tokens.toArray( new String[tokens.size()] );
}
/**
* Private helper method to determine if the char at pos is escaped or not
*/
private static boolean isEscaped( String s, int pos, char escape )
{
// count the number of escape characters before position.
// it's it is odd, then it's escaped!
// Ex) In '\\\\\a' , the 'a' is escaped.
int count = 0;
while( pos > 0 )
{
if( s.charAt( pos - 1 ) == escape )
{
count++;
pos--;
}
else
{
break;
}
}
return (count % 2 == 1); // true if count is odd (mod 2 = 1)
}
/**
* Removes "chars" from start of baseString if baseString starts with chars. For example
* calling removeStartChars("fooBar", "foo") results in "Bar". Does nothing if
* baseString does not start with chars.
*
* @param baseString Original string
* @param chars Characters to remove if found
*
* @return String Updated string
*/
public static String removeStartChars( String baseString, String chars )
{
if( baseString.startsWith( chars ) )
{
return baseString.substring( chars.length() );
}
else
{
return baseString;
}
}
/**
* Removes "chars" from end of baseString if baseString ends with chars. For example
* calling removeEndChars("hello world", "world") results in "hello ". Does nothing if
* baseString does not end with chars.
*
* @deprecated use StringUtils.chomp() instead
*/
public static String removeEndChars( String baseString, String chars )
{
if( baseString.endsWith( chars ) )
{
return baseString.substring( 0, baseString.length() - chars.length() );
}
else
{
return baseString;
}
}
/**
* Combination of removeStartChars and removeEndChars.
*/
public static String removeStartAndEndChars( String baseString, String startChars, String endChars )
{
return removeEndChars( removeStartChars( baseString, startChars ), endChars );
}
/**
* Interface used by {@link editor.search.StringUtil#substitute} for variable substitution
*/
public interface VariableMap
{
public String getValue( String variableName );
}
public static final int ANT_STYLE = 0; // ${...}
public static final int JAVADOC_LINK_STYLE = 0; // {@...}
/**
* Looks in the pattern string for any variables enclosed in ${}. Any such
* variables are replaced by looking up their names in the variable map.
* For example ${some.name} would be replaced by whatever
* variableMap.getValue("some.name"} returns. Single quotes are stripped
* out of the pattern, but substitution is disabled between the quotes.
* To include a single quote in the pattern string use two single quotes
* next to each other.
*
* @param pattern the pattern string, must not be null
* @param variableMap the variable map used to lookup variable values
*
* @return either the original string (if no ${} were found) or a new string
* with the variable names replaced by values from the variable map.
*/
public static String substitute( String pattern, VariableMap variableMap )
{
int quoteStart = -1;
int variableStart = -1;
StringBuffer resultBuffer = null;
for( int i = 0, n = pattern.length(); i < n; i++ )
{
char ch = pattern.charAt( i );
boolean withinQuotes = quoteStart >= 0;
boolean withinVariable = variableStart >= 0;
boolean isQuote = ch == '\'';
boolean isVariableStart
= !withinQuotes && ch == '{' && i > 0 && pattern.charAt( i - 1 ) == '$';
if( (isQuote || isVariableStart) && resultBuffer == null )
{
resultBuffer = new StringBuffer( pattern );
resultBuffer.setLength( i );
}
if( isVariableStart )
{
if( withinVariable )
{
throw new IllegalArgumentException( "Invalid pattern, ${ inside ${:" + pattern );
}
resultBuffer.setLength( resultBuffer.length() - 1 );
variableStart = i + 1;
}
else if( isQuote )
{
if( withinVariable )
{
throw new IllegalArgumentException( "Invalid pattern, ' inside ${:" + pattern );
}
if( withinQuotes )
{
// Just ignore quotes, unless get '' which is equivalent to just '
if( quoteStart == i - 1 )
{
resultBuffer.append( '\'' );
}
quoteStart = -1;
}
else
{
quoteStart = i;
}
}
else if( withinVariable )
{
if( ch == '}' )
{
String variableName = pattern.substring( variableStart, i );
String value = variableMap.getValue( variableName );
if( value != null )
{
resultBuffer.append( value );
}
variableStart = -1;
}
}
else
{
// Normal character
if( resultBuffer != null )
{
resultBuffer.append( ch );
}
}
}
if( variableStart >= 0 )
{
throw new IllegalArgumentException( "Invalid pattern, non terminated ${ variable:" + pattern );
}
else if( quoteStart >= 0 )
{
throw new IllegalArgumentException( "Invalid pattern, missing closing quote:" + pattern );
}
return resultBuffer != null ? resultBuffer.toString() : pattern;
}
public static void escapeForJava( Writer out, String string ) throws IOException
{
if( string == null || string.length() == 0 )
{
out.write( string );
}
for( int i = 0, length = string.length(); i < length; i++ )
{
char ch = string.charAt( i );
String escape = escapeForJava( ch );
if( escape != null )
{
out.write( escape );
}
else
{
out.write( ch );
}
}
}
/**
* Escape any special characters in the string, using the Java escape syntax.
* For example any tabs become \t, newlines become \n etc.
*
* @return the escaped string. Returns the original string unchanged if it
* contains no special characters.
*/
public static String escapeForJava( String string )
{
return process( string, new Escaper()
{
public String eacape( char ch )
{
return escapeForJava( ch );
}
} );
}
public static String escapeForGosuStringLiteral( String string )
{
return process( string, new Escaper()
{
public String eacape( char ch )
{
return escapeForGosuStringLiteral( ch );
}
} );
}
private static String process( String string, Escaper escaper )
{
if( string == null || string.length() == 0 )
{
return string;
}
StringBuffer resultBuffer = null;
for( int i = 0, length = string.length(); i < length; i++ )
{
char ch = string.charAt( i );
String escape = escaper.eacape( ch );
if( escape != null )
{
if( resultBuffer == null )
{
resultBuffer = new StringBuffer( string );
resultBuffer.setLength( i );
}
resultBuffer.append( escape );
}
else if( resultBuffer != null )
{
resultBuffer.append( ch );
}
}
return (resultBuffer != null) ? resultBuffer.toString() : string;
}
/**
* Like escapeForJava( String ), but escapes the stringbuffer in place.
* Uses less memory.
*/
public static void escapeForJava( StringBuffer sb )
{
if( sb == null || sb.length() == 0 )
{
return;
}
for( int i = 0; i < sb.length(); i++ )
{
String escape = escapeForJava( sb.charAt( i ) );
if( escape != null )
{
sb.replace( i, i + 1, escape );
i += escape.length() - 1;
}
}
}
/**
* Replaces any backslashes in a String with two backslashes, in case a
* replaceFirst() call is going to consume half of them.
*/
public static String doubleBackslashes( String string )
{
if( string == null || string.length() == 0 )
{
return string;
}
StringBuffer resultBuffer = null;
for( int i = 0, length = string.length(); i < length; i++ )
{
char ch = string.charAt( i );
String escape = (ch == '\\') ? "\\\\" : null;
if( escape != null )
{
if( resultBuffer == null )
{
resultBuffer = new StringBuffer( string );
resultBuffer.setLength( i );
}
resultBuffer.append( escape );
}
else if( resultBuffer != null )
{
resultBuffer.append( ch );
}
}
return (resultBuffer != null) ? resultBuffer.toString() : string;
}
/**
* Converts an escaped character code into a string literal expressing it, e.g. '\n' becomes "\\n".
*
* @param ch Escaped character code.
*
* @return The string expression of the character code, null if ch
is not an escaped character.
* Supports Unicode.
*/
public static String escapeForJava( char ch )
{
String escape = escapeForGosuStringLiteral( ch );
if( escape == null )
{
if( ch <= 31 || ch >= 127 )
{
escape = getUnicodeEscape( ch );
}
}
return escape;
}
private static String escapeForGosuStringLiteral( char ch )
{
String escape = null;
switch( ch )
{
case '\b':
escape = "\\b";
break;
case '\t':
escape = "\\t";
break;
case '\n':
escape = "\\n";
break;
case '\f':
escape = "\\f";
break;
case '\r':
escape = "\\r";
break;
case '\"':
escape = "\\\"";
break;
case '\'':
escape = "\\'";
break;
case '\\':
escape = "\\\\";
break;
default:
break;
}
return escape;
}
/**
* Escape a string by replacing all occurrences of special characters (such
* as > and <) by their corresponding XML entities. Also converts
* any illegal XML characters into sequences of characters: control
* characters are converted to ^@, ^A etc. Large unicode characters that are
* not valid XML characters are converted to U+xxxx where xxxx are hex digits.
* This is more thorough than the Apache commons StringEscapeUtils.escapeXML
* which only handles the four basic XML entities (gt, lt, quot and amp)
*
* @param string the string to be escaped
*
* @return the escaped string or, if the original string does not
* contain any special characters, the original string.
*/
public static String escapeForXML( String string )
{
if( string == null || string.length() == 0 )
{
return string;
}
StringBuffer resultBuffer = null;
for( int i = 0, length = string.length(); i < length; i++ )
{
String entity = null;
char ch = string.charAt( i );
if( !isAllowedXMLCharacter( ch ) )
{
entity = replaceNonXMLCharacter( ch );
}
else if( ch > 127 )
{
entity = "" + (int)ch + ";";
}
else
{
switch( ch )
{
case '<':
entity = "<";
break;
case '>':
entity = ">";
break;
case '&':
entity = "&";
break;
case '"':
entity = """;
break;
default:
break;
}
}
if( entity != null )
{
if( resultBuffer == null )
{
resultBuffer = new StringBuffer( string );
resultBuffer.setLength( i );
}
resultBuffer.append( entity );
}
else if( resultBuffer != null )
{
resultBuffer.append( ch );
}
}
return (resultBuffer != null) ? resultBuffer.toString() : string;
}
/**
* Check whether a given character is XML-processor-safe. This information
* is taken from XML Spec at http://www.w3.org/TR/REC-xml/#NT-Char
*
* @param ch the character to be checked
*
* @return true if the character is XML-processor-safe, false otherwise.
*/
public static boolean isAllowedXMLCharacter( char ch )
{
// Note: using the unicode form for \n, \r and \t seems to break the
// compiler, even if you do it inside a comment!
return ch == '\n' || ch == '\r' || ch == '\t'
|| ('\u0020' <= ch && ch <= '\uD7FF')
|| ('\uE000' <= ch && ch <= '\uFFFD');
}
/**
*/
public static String replaceNonXMLCharacter( char ch )
{
if( ch < ' ' )
{
return "^" + (char)(ch + '@'); // Control character
}
else
{
return "U+" + Integer.toHexString( ch ).toUpperCase(); // Large Unicode character
}
}
/**
* Encode a list of strings as a single CSV encoded string
*
* @param fields a non null list of non null objects; their toString method
* will be called to convert them to strings (harmless if they are strings)
*
* @return the fields encoded as a CSV string
*
* @throws NullPointerException if fields or any member of fields is null
*/
public static String encodeCSV( List fields )
{
return encodeCSV( fields.iterator() );
}
/**
* Encode a list of strings as a single CSV encoded string
*
* @param i a non null iterator which should return a series of non null
* objects; their toString method will be called to convert them to strings
* (harmless if they are strings)
*
* @return the objects encoded as a CSV string
*
* @throws NullPointerException if i or any object returned by i is null
*/
public static String encodeCSV( Iterator i )
{
StringBuilder resultBuffer = new StringBuilder();
boolean first = true;
while( i.hasNext() )
{
if( first )
{
first = false;
}
else
{
resultBuffer.append( ',' );
}
int fieldStart = resultBuffer.length();
boolean isQuoted = false;
String field = i.next().toString();
for( int j = 0, n = field.length(); j < n; j++ )
{
char ch = field.charAt( j );
if( (ch == ',' || ch == '"') && !isQuoted )
{
resultBuffer.insert( fieldStart, '"' );
isQuoted = true;
}
resultBuffer.append( ch );
if( ch == '"' )
{
resultBuffer.append( '"' );
}
}
if( isQuoted )
{
resultBuffer.append( '"' );
}
}
return resultBuffer.toString();
}
/**
* Decodes a string encoded by {@link #encodeCSV(java.util.Iterator)} and
* returns an array containing the individual string fields. An empty string
* is ambiguous - could be a zero length array or an array containing a single
* empty string. This implementation assumes it is an array containing a
* single empty string
*
* @param csvFields the encoded fields
*
* @return an array of decoded fields
*
* @throws NullPointerException if csvFields is null
* @throws IllegalArgumentException if csvFields is badly encoded - for example if it contains strings that
* contain quotes or commas and they have not been encoded correctly
*/
public static String[] decodeCSV( String csvFields )
{
List resultList = new ArrayList();
StringBuilder fieldBuffer = new StringBuilder();
boolean inQuotedField = false;
for( int i = 0, n = csvFields.length(); i < n; i++ )
{
char ch = csvFields.charAt( i );
if( ch == '"' )
{
if( inQuotedField )
{
boolean atEnd = i == n - 1;
char nextCh = atEnd ? 0 : csvFields.charAt( i + 1 );
if( nextCh == '"' )
{
fieldBuffer.append( '"' );
i++;
}
else if( atEnd || nextCh == ',' )
{
inQuotedField = false;
}
else
{
throw new IllegalArgumentException( "Misplaced double quote in CSV string: " + csvFields );
}
}
else if( fieldBuffer.length() == 0 )
{
inQuotedField = true;
}
else
{
throw new IllegalArgumentException( "Misplaced double quote in CSV string: " + csvFields );
}
}
else if( ch == ',' && !inQuotedField )
{
resultList.add( fieldBuffer.toString() );
fieldBuffer.setLength( 0 );
}
else
{
fieldBuffer.append( ch );
}
}
if( inQuotedField )
{
throw new IllegalArgumentException( "Missing quote at end of CSV string: " + csvFields );
}
resultList.add( fieldBuffer.toString() );
return (String[])resultList.toArray( new String[resultList.size()] );
}
/**
* Return a list of SearchLocation
s representing the occurrences of the
* specified pattern in the specified source string.
*
* @param strSource A string to search.
* @param strPattern A pattern to search for.
*
* @return A list of SearchLocations for each occurrence of the specified pattern.
* Returns an empty list for zero occurrences.
*/
public static List searchIgnoreCase( String strSource, String strPattern )
{
return search( strSource, strPattern, true );
}
public static List search( String strSource, String strPattern, boolean bIgnoreCase )
{
return search( strSource, strPattern, bIgnoreCase, 0 );
}
public static List search( String strSource, String strPattern, boolean bIgnoreCase, int iOffset )
{
return search( strSource, strPattern, bIgnoreCase, iOffset, false );
}
public static List search( String strSource, String strPattern, boolean bIgnoreCase, int iOffset, boolean backwards )
{
if( bIgnoreCase )
{
strSource = strSource.toLowerCase();
strPattern = strPattern.toLowerCase();
}
List list = new ArrayList();
int iIndex = backwards ? strSource.length() : 0;
int iIndex2 = backwards ? strSource.lastIndexOf( strPattern, iOffset - strPattern.length() - 1 ) : strSource.indexOf( strPattern, iOffset );
if( iIndex2 < 0 )
{
return list;
}
SearchLocation slBuffer = new SearchLocation();
while( true )
{
SearchLocation sl = new SearchLocation();
sl._iOffset = iIndex2;
getLocation( strSource, iIndex, iIndex2, slBuffer );
sl._iLine = slBuffer._iLine + 1;
sl._iColumn = slBuffer._iColumn + 1;
sl._iLineOffset = slBuffer._iLineOffset;
sl._iLength = strPattern.length();
list.add( sl );
iIndex = iIndex2;
iIndex2 = backwards ? strSource.lastIndexOf( strPattern, iIndex -= 1 ) : strSource.indexOf( strPattern, iIndex += strPattern.length() );
if( iIndex2 < 0 )
{
break;
}
}
return list;
}
/**
* Returns true
if and only if s
begins with
* the string prefix
, ignoring case. This method is not null
* safe on the second argument.
*/
public static boolean startsWithIgnoreCase( String s, String prefix )
{
if( s == null || s.length() < prefix.length() )
{
return false;
}
for( int i = 0; i < prefix.length(); i++ )
{
char c1Upper = Character.toUpperCase( s.charAt( i ) );
char c2Upper = Character.toUpperCase( prefix.charAt( i ) );
/* Unfortunately, conversion to uppercase does not work properly
* for the Georgian alphabet, which has strange rules about case
* conversion. So we need to make one last check before
* exiting. {@see java.lang.String#regionMatches}
*/
if( c1Upper != c2Upper && Character.toLowerCase( c1Upper ) != Character.toLowerCase( c2Upper ) )
{
return false;
}
}
return true;
}
/**
* Returns true
if and only if s
ends with
* the string prefix
, ignoring case. This method is not null
* safe on the second argument.
*/
public static boolean endsWithIgnoreCase( String s, String suffix )
{
if( s == null || s.length() < suffix.length() )
{
return false;
}
for( int i = 0; i < suffix.length(); i++ )
{
char c1Upper = Character.toUpperCase( s.charAt( s.length() - 1 - i ) );
char c2Upper = Character.toUpperCase( suffix.charAt( suffix.length() - 1 - i ) );
/* Unfortunately, conversion to uppercase does not work properly
* for the Georgian alphabet, which has strange rules about case
* conversion. So we need to make one last check before
* exiting. {@see java.lang.String#regionMatches}
*/
if( c1Upper != c2Upper && Character.toLowerCase( c1Upper ) != Character.toLowerCase( c2Upper ) )
{
return false;
}
}
return true;
}
/**
* Return true if string s contains the substring substr
*/
public static boolean contains( String s, String substr )
{
return s.indexOf( substr ) >= 0;
}
/**
* Return true if string s contains the substring substr (case-insensitive)
*/
public static boolean containsCaseInsensitive( String s, String substr )
{
return !searchIgnoreCase( s, substr ).isEmpty();
}
/**
*/
public static void getLocation( String strSource, int iFrom, int iTo, SearchLocation location )
{
if( strSource == null )
{
return;
}
if( iFrom > strSource.length() || iTo > strSource.length() )
{
throw new IllegalArgumentException( "String index out of bounds. Source: " + ", From: " + iFrom + ", To:" + iTo );
}
int iLineCount = 0;
int iLineStart = location._iLineOffset;
for( int i = iFrom; i < iTo; i++ )
{
char c = strSource.charAt( i );
if( c == '\n' )
{
iLineCount++;
iLineStart = i + 1;
}
}
location._iLine += iLineCount;
location._iColumn = iTo - iLineStart;
location._iLineOffset = iLineStart;
}
/**
* Simple utility to format an List of objects as a delimited String.
*
* @param list the list of items
* @param delimiter the delimiter to use between items
*
* @return a delimited list of items, or the empty string if the list is empty
*/
public static String formatObjectList( List list, String delimiter )
{
if( list.isEmpty() )
{
return "";
}
StringBuilder sb = new StringBuilder();
for( int i = 0; i < list.size(); i++ )
{
sb.append( list.get( i ) );
if( i < list.size() - 1 )
{
sb.append( delimiter );
}
}
return sb.toString();
}
/**
*/
public static String stackTraceToString( Throwable t )
{
StringWriter sw = new StringWriter();
t.printStackTrace( new PrintWriter( sw ) );
return sw.toString();
}
/**
* See TruncateFieldTest for examples of this method's usage
*
* @return The truncated field.
*/
public static String truncateField( String fieldVal, String delimiter, int maxLength )
{
if( fieldVal != null )
{
int firstDelimiter = -1;
if( delimiter != null )
{
firstDelimiter = fieldVal.indexOf( delimiter );
}
while( fieldVal.length() > maxLength )
{
if( firstDelimiter != -1 )
{
fieldVal = fieldVal.substring( firstDelimiter + delimiter.length() );
firstDelimiter = fieldVal.indexOf( delimiter );
}
else
{
fieldVal = fieldVal.substring( fieldVal.length() - maxLength );
}
}
}
return fieldVal;
}
private static String getUnicodeEscape( char ch )
{
String prefix = "\\u";
int length = prefix.length() + 4;
String hex = Integer.toHexString( ch );
StringBuilder resultBuffer = new StringBuilder( length );
resultBuffer.append( prefix );
for( int i = 0, n = length - (prefix.length() + hex.length()); i < n; i++ )
{
resultBuffer.append( '0' );
}
resultBuffer.append( hex );
return resultBuffer.toString();
}
/**
* Replaces all occurences of space with its escape-sequence "%20".
*
* @param strUrl - string to encode
*
* @return encoded string
*/
public static String encodeSpaces( String strUrl )
{
StringBuffer sb = new StringBuffer();
for( int i = 0; i < strUrl.length(); i++ )
{
char c = strUrl.charAt( i );
if( c == ' ' )
{
escapeUrlChar( sb, c );
}
else
{
sb.append( c );
}
}
return sb.toString();
}
/**
* Replaces all occurrences of characters which need to be escaped in a URL.
*
* @param strUrl string to encode
*
* @return encoded string
*/
public static String encodeURLCharacters( String strUrl )
{
StringBuffer sb = new StringBuffer();
for( int i = 0; i < strUrl.length(); i++ )
{
char c = strUrl.charAt( i );
escapeUrlChar( sb, c );
}
return sb.toString();
}
/**
* Replaces all occurrences of characters which need to be escaped in a URL.
*
* @param strUrl string to encode
*
* @return encoded string
*
* @deprecated Please use {@link #encodeURLCharacters} instead
*/
public static String encodeDocumentFilenameChars( String strUrl )
{
return encodeURLCharacters( strUrl );
}
private static void escapeUrlChar( StringBuffer sb, char c )
{
switch( c )
{
case ' ':
case '%':
case '#':
case '`':
case '~':
case '!':
case '@':
case '$':
case '^':
case '&':
case '(':
case ')':
case '{':
case '}':
case '+':
sb.append( '%' ).append( Integer.toHexString( 0x100 | (int)c ).substring( 1 ).toUpperCase() );
break;
default:
sb.append( c );
}
}
/**
* Compares two Strings, ignoring line separator differences. Acceptable line separators are
* - "\r\n"
* - "\n"
*
* A single "\r" is not considered to be a line separator.
*
* Examples:
*
* StringUtil.equalsIgnoreLineSeparatorDifferences(null, null) = true
* StringUtil.equalsIgnoreLineSeparatorDifferences(null, *) = false
* StringUtil.equalsIgnoreLineSeparatorDifferences(*, null) = false
* StringUtil.equalsIgnoreLineSeparatorDifferences("abc", "abc") = true
* StringUtil.equalsIgnoreLineSeparatorDifferences("abc", "def") = false
* StringUtil.equalsIgnoreLineSeparatorDifferences("\r\n", "\n") = true
* StringUtil.equalsIgnoreLineSeparatorDifferences("abc\r\ndef", "abc\ndef") = true
* StringUtil.equalsIgnoreLineSeparatorDifferences("abc\rdef", "abc\ndef") = false
* StringUtil.equalsIgnoreLineSeparatorDifferences("abc\r\ndef\nghi", "abc\ndef\r\nghi") = true
* StringUtil.equalsIgnoreLineSeparatorDifferences("abc\r\r\n\ndef", "abc\r\n\r\ndef") = true
*
*
* @return true if the strings are equivalent, otherwise this method returns false.
*/
public static boolean equalsIgnoreLineSeparatorDifferences( String s1, String s2 )
{
if( s1 == null )
{
return s2 == null;
}
if( s2 == null )
{
return false;
}
int len1 = s1.length();
int len2 = s2.length();
int index1 = 0;
int index2 = 0;
for(; index1 < len1 && index2 < len2; index1++, index2++ )
{
char c1 = s1.charAt( index1 );
char c2 = s2.charAt( index2 );
if( c1 != c2 )
{
if( c1 == '\r' && index1 < (len1 - 1) && s1.charAt( index1 + 1 ) == '\n' )
{ // Have we encountered a "\r\n" sequence?
index2--; // back up index2, so that we stay on the same char in s2 on the next iteration. In effect,
// we've skipped the '\r' because the following char is '\n'.
}
else if( c2 == '\r' && index2 < (len2 - 1) && s2.charAt( index2 + 1 ) == '\n' )
{
index1--;
}
else
{
return false;
}
}
}
// We now know that one string is a prefix of the other. Make sure we've reached the end of each string to know
// whether or not they're equal.
return (index1 == len1 && index2 == len2);
}
/**
* Takes a string 'glue' and array of strings, and returns a StringBuffer containing the strings joined with the glue
* between each of them. The strings are joined in order *
*
* @param glue The glue string
* @param strings The strings to join
*
* @return a StringBuffer
*/
public static StringBuffer join( String glue, String[] strings )
{
return join( glue, strings, 0, strings.length - 1 );
}
/**
* Takes a string 'glue' and array of strings, and returns a StringBuffer containing the strings
* between the specified indices (inclusive) joined with the glue between each of them. The strings are joined in order
*
* @param glue The glue string
* @param strings The strings to join
* @param first The index of the first string to join
* @param last The index of the last string to join
*
* @return a StringBuffer
*/
public static StringBuffer join( String glue, String[] strings, int first, int last )
{
StringBuffer buf = new StringBuffer();
for( int i = first; i <= last; i++ )
{
String s = strings[i];
if( i > first )
{
buf.append( glue );
}
buf.append( s );
}
return buf;
}
/**
* Takes a string 'glue' and collection of CharSequences, and returns a StringBuffer containing the CharSequences
* joined with the glue between each of them. They are joined in the order returned by the iterator of the colection
*
* @param glue The glue string
* @param charSequences The CharSequences to join
*
* @return a StringBuffer
*/
public static StringBuffer join( String glue, Collection charSequences )
{
StringBuffer buf = new StringBuffer();
int i = 0;
for( Object charSequence : charSequences )
{
if( i > 0 )
{
buf.append( glue );
}
buf.append( charSequence );
i++;
}
return buf;
}
/**
* Prefixes the given string with the given fillChar so that the string is at least minLength long
*
* @param fillChar The character to use as prefix
* @param minLength The minimum length of the string
* @param string The original string
*
* @return A StringBuffer that is at least of length minLength
*/
public static StringBuffer preFill( char fillChar, int minLength, String string )
{
if( string.length() >= minLength )
{
return new StringBuffer( string );
}
else
{
StringBuffer strbuf = new StringBuffer( minLength );
for( int i = 0; i < minLength - string.length(); i++ )
{
strbuf.append( fillChar );
}
strbuf.append( string );
return strbuf;
}
}
/**
* Suffixes the given string with the given fillChar so that the string is at least minLength long
*
* @param fillChar The character to use as prefix
* @param minLength The minimum length of the string
* @param string The original string
*
* @return A StringBuffer that is at least of length minLength
*/
public static StringBuffer postFill( char fillChar, int minLength, String string )
{
if( string.length() >= minLength )
{
return new StringBuffer( string );
}
else
{
StringBuffer strbuf = new StringBuffer( minLength );
strbuf.append( string );
for( int i = 0; i < minLength - string.length(); i++ )
{
strbuf.append( fillChar );
}
return strbuf;
}
}
/**
* Returns an unmodifiable set containing the given strings
*/
public static Set finalSetOfStrings( String[] members )
{
return Collections.unmodifiableSet( new HashSet( Arrays.asList( members ) ) );
}
/**
* Returns the toString() of the object or null if the object is null.
*
* @return The object as a String or null if the object is null.
*/
public static String asStringOrNull( Object object )
{
if( object == null )
{
return null;
}
else
{
return object.toString();
}
}
/**
* @param object the object
*
* @return Returns the toString() of the object or "null" string if the object is null.
*/
public static String asStringOrNullString( Object object )
{
if( object == null )
{
return "null";
}
else
{
return object.toString();
}
}
/**
* Returns the toString() of the object or "" if the object is null.
*
* @return The object as a String or an empty string if the object is null.
*/
public static String asStringOrEmpty( Object object )
{
if( object == null )
{
return "";
}
else
{
return object.toString();
}
}
/**
* Escape any special characters in the string, using the CSV escape syntax.
* Here are the rules for CSV escape:
* 1) If the string contains comma, then enclose the string with double quotes
* 2) If the string contains double quotes, then firstly escape each double quote by
* putting a double quote preceeding it. Secondly enclose the string with double quotes
*
* @return the escaped string. Returns the original string unchanged if it contains no sepcail char
*/
public static String escapeForCSV( String str )
{
if( str == null || str.length() == 0 )
{
return str;
}
StringBuffer resultBuffer = null;
for( int i = 0, length = str.length(); i < length; i++ )
{
char ch = str.charAt( i );
String escape = escapeForCSV( ch );
if( escape != null )
{
if( resultBuffer == null )
{
resultBuffer = new StringBuffer( "\"" + str );
resultBuffer.setLength( 1 + i );
}
resultBuffer.append( escape );
}
else if( resultBuffer != null )
{
resultBuffer.append( ch );
}
}
if( resultBuffer != null )
{
resultBuffer.append( '"' );
}
return (resultBuffer != null) ? resultBuffer.toString() : str;
}
/**
* Converts an escaped character code into a string literal expressing it, i.e. '"' becomes '""'
*
* @param ch Escaped character code
*
* @return The string expression of the character code, null if ch
is not an escaped character
*/
public static String escapeForCSV( char ch )
{
String escape = null;
switch( ch )
{
case '"':
escape = "\"\"";
break;
case ',':
escape = ","; //We are returning the same char, but this will notify the caller that escape is needed!
break;
}
return escape;
}
/**
* Make a copy of String before interning to make sure the interned string doesn't have an
* overly-large char buffer. Returns null for null arg.
*
* @return the interned String s
*/
public static String intern( String s )
{
if( s == null )
{
return null;
}
else
{
return new String( s ).intern();
}
}
private static final Map _internMap = new InternMap( 20000 );
/**
* Interns the given string, but unlike string.intern() the strings will be stored
* on the heap rather than in perm space, allowing us to later clear out the map if necessary.
*
* @return If s is null, returns null. Otherwise, this method returns a String from a pool of strings.
*/
public static String heapIntern( String s )
{
if( s == null )
{
return null;
}
if( _internMap.containsKey( s ) )
{
return (String)_internMap.get( s );
}
else
{
_internMap.put( s, s );
return s;
}
}
private static class InternMap extends ConcurrentHashMap
{
public InternMap( int initialCapacity )
{
super( initialCapacity );
}
}
public static String stripNewLinesAndExtraneousWhiteSpace( String s )
{
if( s == null )
{
return null;
}
StringBuilder result = new StringBuilder();
boolean hitNewLine = false;
boolean addedSpace = false;
for( int i = 0; i < s.length(); i++ )
{
char c = s.charAt( i );
if( c == '\n' )
{
hitNewLine = true;
}
else if( c == ' ' )
{
if( hitNewLine )
{
if( !addedSpace )
{
result.append( c );
addedSpace = true;
}
}
else
{
result.append( c );
}
}
else
{
hitNewLine = false;
addedSpace = false;
result.append( c );
}
}
return result.toString().trim();
}
/**
* Transforms a given name to a legal identifier suitable for Java or
* Gosu. Strips all illegal characters like spaces, punctuation, etc.
*
* @param name A name that may not already be a legal identifier
*
* @return A legal identifier formed from all legal characters in name.
*/
public static String makeIdentifier( CharSequence name )
{
StringBuilder sb = new StringBuilder();
for( int i = 0; i < name.length(); i++ )
{
char c = name.charAt( i );
if( (sb.length() == 0 && (c == '_' || Character.isLetter( c ))) ||
(sb.length() > 0 && (c == '_' || Character.isLetterOrDigit( c ))) )
{
sb.append( c );
}
}
return sb.toString();
}
public static String capitalizeFirstChar( String name )
{
if( name == null || name.length() == 0 )
{
return name;
}
if( name.startsWith( "_" ) )
{
return capitalizeFirstChar( name.substring( 1 ) );
}
char chars[] = name.toCharArray();
chars[0] = Character.toUpperCase( chars[0] );
return new String( chars );
}
public static String toRGBString( Color color )
{
StringBuilder str = new StringBuilder();
int red = color.getRed();
str.append( red < 16 ? "0" : "" ).append( Integer.toHexString( red ) );
int green = color.getGreen();
str.append( green < 16 ? "0" : "" ).append( Integer.toHexString( green ) );
int blue = color.getBlue();
str.append( blue < 16 ? "0" : "" ).append( Integer.toHexString( blue ) );
return str.toString();
}
public static String elide( String str, int maxLength )
{
if( str.length() <= maxLength )
{
return str;
}
return str.substring( 0, (maxLength - 3) / 2 ) + "..." + str.substring( str.length() - (maxLength - 3) / 2 );
}
/**
* Returns the index of the specified character in the char sequence.
*
* @returns the index of the specified character in the char sequence.
*/
public static int indexOf( CharSequence seq, char ch )
{
return indexOf( seq, ch, 0 );
}
/**
* Returns the index of the specified character in the char sequence starting at the given start position.
*
* @returns the index of the specified character in the char sequence.
*/
public static int indexOf( CharSequence seq, char ch, int startPos )
{
int max = seq.length();
for( int i = startPos; i < max; i++ )
{
if( seq.charAt( i ) == ch )
{
return i;
}
}
return -1;
}
/**
* Returns the index of the specified character in the char sequence.
*
* @returns the index of the specified character in the char sequence.
*/
public static int lastIndexOf( CharSequence seq, char ch )
{
return lastIndexOf( seq, ch, 0 );
}
/**
* Returns the index of the specified character in the char sequence starting at the given start position.
*
* @returns the index of the specified character in the char sequence.
*/
public static int lastIndexOf( CharSequence seq, char ch, int startPos )
{
for( int i = startPos - 1; i >= 0; i-- )
{
if( seq.charAt( i ) == ch )
{
return i;
}
}
return -1;
}
private static interface Escaper
{
public String eacape( char ch );
}
public static String htmlEncode( String text )
{
StringBuilder sb = new StringBuilder();
for( int i = 0; i < text.length(); i++ )
{
char ch = text.charAt( i );
switch( ch )
{
case '<':
sb.append( "<" );
break;
case '>':
sb.append( ">" );
break;
case '&':
sb.append( "&" );
break;
case '"':
sb.append( """ );
break;
default:
sb.append( ch );
}
}
return sb.toString();
}
public static String xmlEncode( String text )
{
StringBuilder sb = new StringBuilder();
for( int i = 0; i < text.length(); i++ )
{
char ch = text.charAt( i );
switch( ch )
{
case '<':
sb.append( "<" );
break;
case '>':
sb.append( ">" );
break;
case '&':
sb.append( "&" );
break;
case '"':
sb.append( """ );
break;
case '\'':
sb.append( "'" );
break;
default:
sb.append( ch );
}
}
return sb.toString();
}
/**
* Pretty print function for formatting a duration. E.g. 1010 milliseconds
* is printed as "1sec 10ms"
*
* @param millis Millis to format
*
* @return A nicely formatted description of the duration
*/
public static String formatDuration( long millis )
{
if( millis < 0 )
{
return "Negative";
}
int secs = (int)(millis / 1000);
int mins = (secs / 60) % 60;
int hrs = (secs / 3600);
secs = secs % 60;
millis = millis % 1000;
StringBuilder buf = new StringBuilder();
if( hrs > 0 )
{
buf.append( hrs ).append( ":" );
}
if( mins > 0 || hrs > 0 )
{
if( buf.length() > 0 && mins < 10 )
{
buf.append( "0" );
}
buf.append( mins ).append( ":" );
}
if( secs > 0 || mins > 0 || hrs > 0 )
{
if( buf.length() > 0 && secs < 10 )
{
buf.append( "0" );
}
buf.append( secs ).append( "." );
}
else
{
buf.append( "0." );
}
if( millis < 10 )
{
buf.append( "0" );
}
if( millis < 100 )
{
buf.append( "0" );
}
buf.append( millis );
return buf.toString();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy