/*!
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* Copyright (c) 2002-2013 Pentaho Corporation.. All rights reserved.
*/
package org.pentaho.reporting.libraries.base.util;
import java.text.Format;
import java.text.MessageFormat;
import java.util.HashSet;
import java.util.StringTokenizer;
/**
* String utilities.
*
* @author Thomas Morgner.
*/
public final class StringUtils {
// Constants used to convert a string to a boolean
private static final String TRUE = "true";
private static final String YES = "yes";
private static final String ON = "on";
/**
* Private constructor prevents object creation.
*/
private StringUtils() {
}
/**
* Helper functions to query a strings start portion. The comparison is case insensitive.
*
* @param base the base string.
* @param start the starting text.
* @return true, if the string starts with the given starting text.
*/
public static boolean startsWithIgnoreCase( final String base, final String start ) {
if ( base.length() < start.length() ) {
return false;
}
return base.regionMatches( true, 0, start, 0, start.length() );
}
/**
* Helper functions to query a strings end portion. The comparison is case insensitive.
*
* @param base the base string.
* @param end the ending text.
* @return true, if the string ends with the given ending text.
*/
public static boolean endsWithIgnoreCase( final String base, final String end ) {
if ( base.length() < end.length() ) {
return false;
}
return base.regionMatches( true, base.length() - end.length(), end, 0, end.length() );
}
/**
* Queries the system properties for the line separator. If access to the System properties is forbidden, the UNIX
* default is returned.
*
* @return the line separator.
* @noinspection AccessOfSystemProperties
*/
public static String getLineSeparator() {
try {
return System.getProperty( "line.separator", "\n" );
} catch ( Exception e ) {
return "\n";
}
}
/**
* Splits a given string on any whitespace character. Duplicate separators will be merged into a single separator
* occurance. This implementation provides the same functionality as the REGEXP-based String.split(..) operation but
* does not use Regular expressions and therefore it is faster and less memory consuming.
*
* @param string the text to be split.
* @return the text elements as array.
*/
public static String[] split( final String string ) {
return split( string, " \t\n\r\f" );
}
/**
* Splits a given string at the given separator string. Duplicate separators will be merged into a single separator
* occurance.
*
* @param string the text to be split.
* @param separator the separator chacters used for the split.
* @return the splitted array.
*/
public static String[] split( final String string, final String separator ) {
if ( separator == null ) {
throw new NullPointerException( "Separator characters must not be null." );
}
if ( string == null ) {
throw new NullPointerException( "String to be split must not be null." );
}
final StringTokenizer strtok = new StringTokenizer( string, separator, false );
final String[] tokens = new String[ strtok.countTokens() ];
int i = 0;
while ( strtok.hasMoreTokens() ) {
final String token = strtok.nextToken();
tokens[ i ] = ( token );
i += 1;
}
return tokens;
}
/**
* Splits a given string at the given separator string. Duplicate separators will be merged into a single separator
* occurance.
*
* @param string the text to be split.
* @param separator the separator chacters used for the split.
* @param quate the quoting character.
* @return the splitted array.
*/
public static String[] split( final String string, final String separator, final String quate ) {
final CSVTokenizer strtok = new CSVTokenizer( string, separator, quate, false );
final String[] tokens = new String[ strtok.countTokens() ];
int i = 0;
while ( strtok.hasMoreTokens() ) {
final String token = strtok.nextToken();
if ( token.length() > 0 ) {
tokens[ i ] = ( token );
i += 1;
}
}
if ( i == tokens.length ) {
return tokens;
}
final String[] retval = new String[ i ];
System.arraycopy( tokens, 0, retval, 0, i );
return retval;
}
/**
* Splits a given string at the given separator string. Duplicate separators will result in empty strings thus
* preserving the number of fields specified in the original string.
*
* @param string the text to be split.
* @param separator the separator chacters used for the split.
* @return the splitted array.
*/
public static String[] splitCSV( final String string, final String separator ) {
return splitCSV( string, separator, null );
}
/**
* Splits a given string at the given separator string. Duplicate separators will result in empty strings thus
* preserving the number of fields specified in the original string.
*
* @param string the text to be split.
* @param separator the separator chacters used for the split.
* @param quate the quoting character.
* @return the splitted array.
*/
public static String[] splitCSV( final String string, final String separator, final String quate ) {
final CSVTokenizer strtok = new CSVTokenizer( string, separator, quate, false );
final String[] tokens = new String[ strtok.countTokens() ];
int i = 0;
while ( strtok.hasMoreTokens() ) {
final String token = strtok.nextToken();
tokens[ i ] = ( token );
i += 1;
}
return tokens;
}
/**
* Computes a unique name using the given known-names array as filter. This method is not intended for large
* datasets.
*
* @param knownNames the list of known names.
* @param pattern the name pattern, which should have one integer slot to create derived names.
* @return the unique name or null, if no unqiue name could be created.
*/
public static String makeUniqueName( final String[] knownNames, final String pattern ) {
final HashSet<String> knownNamesSet = new HashSet<String>( knownNames.length );
for ( int i = 0; i < knownNames.length; i++ ) {
final String name = knownNames[ i ];
knownNamesSet.add( name );
}
final MessageFormat message = new MessageFormat( pattern );
final Object[] objects = { "" };
final String plain = message.format( objects );
if ( knownNamesSet.contains( plain ) == false ) {
return plain;
}
final Format[] formats = message.getFormats();
if ( formats.length == 0 ) {
// there is no variation in this name.
return null;
}
int count = 1;
while ( count < 2000000 ) {
objects[ 0 ] = String.valueOf( count );
final String testFile = message.format( objects );
if ( knownNamesSet.contains( testFile ) == false ) {
return testFile;
}
count += 1;
}
return null;
}
/**
* Merges the contents of the first and second array returning a array that contains only unique strings. The order of
* the returned array is undefined.
*
* @param first the first array to be merged.
* @param second the second array to be merged.
* @return the merged araray.
*/
public static String[] merge( final String[] first, final String[] second ) {
if ( first.length == 0 ) {
return second.clone();
}
if ( second.length == 0 ) {
return first.clone();
}
final HashSet<String> total = new HashSet<String>( first.length + second.length );
for ( int i = 0; i < first.length; i++ ) {
total.add( first[ i ] );
}
for ( int i = 0; i < second.length; i++ ) {
total.add( second[ i ] );
}
return total.toArray( new String[ total.size() ] );
}
/**
* Returns <code>true</code> if the source string evaulates (case insensative and trimmed) to <code>true</code>,
* <code>yes</code>, or <code>on</code>. It will return <code>false</code> otherwise (including <code>null</code>).
*
* @param source the string to check
* @return <code>true</code> if the source string evaulates to <code>true</code> or similar value, <code>false</code>
* otherwise.
*/
public static boolean toBoolean( final String source ) {
return toBoolean( source, false );
}
/**
* Returns <code>true</code> if the source string evaulates (case insensative and trimmed) to <code>true</code>,
* <code>yes</code>, or <code>on</code>. It will return <code>false otherwise. If the source string is
* <code>null</code>, it will return the value of the default.
*
* @param source the string to check
* @param nullDefault to value to return if the source string is <code>null</code>
* @return <code>true</code> if the source string evaulates to <code>true</code> or similar value, <code>false</code>
* otherwise.
*/
public static boolean toBoolean( String source, final boolean nullDefault ) {
// If the source is null, use the default
if ( source == null ) {
return nullDefault;
}
// Check for valid values
source = source.trim().toLowerCase();
return ( TRUE.equals( source ) || YES.equals( source ) || ON.equals( source ) );
}
/**
* Determines if the string is empty or <code>null</code>.
*
* @param source the string to check
* @return <code>true</code> if the source string is <code>null</code> or an emptry string, <code>false</code>
* otherwise.
*/
public static boolean isEmpty( final String source ) {
return isEmpty( source, true );
}
/**
* Determines if the string is empty or <code>null</code>. If the </code>trim</code> is <code>true</code>, the string
* will be trimmed before checking for an empty string.
*
* @param source the string to check
* @param trim indicates if the string should be trimmed before checking for an empty string.
* @return <code>true</code> if the source string is <code>null</code> or an emptry string, <code>false</code>
* otherwise.
*/
public static boolean isEmpty( final String source, final boolean trim ) {
if ( source == null ) {
return true;
}
if ( source.length() == 0 ) {
return true;
}
if ( trim == false ) {
return false;
}
final char[] chars = source.toCharArray();
for ( int i = 0; i < chars.length; i++ ) {
final char c = chars[ i ];
if ( c > ' ' ) {
return false;
}
}
return true;
}
/**
* Determines if the two Strings are equals (taking nulls into account).
*
* @param s1 the first string to compare.
* @param s2 the second string to compare.
* @return <code>true</code> if both string are null or the contain the same value, <code>false</code>otherwise
*/
public static boolean equals( final String s1, final String s2 ) {
return ( ( s1 == null && s2 == null ) || ( s1 != null && s1.equals( s2 ) ) );
}
/**
* Determines if the two Strings are equals ingnoring case sensitivity (taking nulls into account).
*
* @param s1 the first string to compare.
* @param s2 the second string to compare.
* @return <code>true</code> if both string are null or the contain the same case-insensitive value,
* <code>false</code>otherwise
*/
public static boolean equalsIgnoreCase( final String s1, final String s2 ) {
return ( ( s1 == null && s2 == null ) || ( s1 != null && s1.equalsIgnoreCase( s2 ) ) );
}
/**
* Checks whether or not a String consists only of spaces.
*
* @param str The string to check
* @return true if the string has nothing but spaces.
*/
public static final boolean onlySpaces( String str ) {
for ( int i = 0; i < str.length(); i++ ) {
if ( !isSpace( str.charAt( i ) ) ) {
return false;
}
}
return true;
}
/**
* Determines whether or not a character is considered a space. A character is considered a space in Kettle if it is a
* space, a tab, a newline or a cariage return.
*
* @param c The character to verify if it is a space.
* @return true if the character is a space. false otherwise.
*/
public static final boolean isSpace( char c ) {
return c == ' ' || c == '\t' || c == '\r' || c == '\n' || Character.isWhitespace( c );
}
/**
* Trims a string: removes the leading and trailing spaces of a String.
*
* @param str The string to trim
* @return The trimmed string.
*/
public static final String trim( String str ) {
if ( str == null ) {
return null;
}
int max = str.length() - 1;
int min = 0;
while ( min <= max && isSpace( str.charAt( min ) ) ) {
min++;
}
while ( max >= 0 && isSpace( str.charAt( max ) ) ) {
max--;
}
if ( max < min ) {
return "";
}
return str.substring( min, max + 1 );
}
}