// ===========================================================================
// CONTENT  : CLASS StringUtil
// AUTHOR   : Manfred Duchrow
// VERSION  : 2.0 - 21/03/2003
// HISTORY  :
//  10/07/1999 	duma  CREATED
//	09/12/1999	duma	added		->	SPACE, repeat()
//										moved		->	from package com.mdcs.util
//	25/01/2000	duma	moved		->	from package com.mdcs.text
//  09/02/2000  duma  changed ->  renamed SPACE to CH_SPACE
//                    added   ->  CH_CR, CH_TAB, ..., STR_SPACE, STR_NEWLINE, ...
//  11/01/2002  duma  added   ->	indexOf(), indexOfIgnoreCase(), contains(), containsIgnoreCase()
//	17/05/2002	duma	added		->	copyFrom()
//	03/07/2002	duma	added		->	cutHead(), prefix(), suffix()
//	06/07/2002	duma	added		->	indexOf() and contains() for StringPattern and reverse()
//	15/08/2002	duma	added		->	upTo(), startingFrom(), asMap()
//	29/09/2002	duma	added		->	allParts() and allSubstrings() that don't skip empty elements
//	06/03/2003	duma	changed	->	append() now uses System.arraycopy()
//										added		->	remove( String[], String[] ), remove( String[], String )
//																removeNull( String[] )
//	21/03/2003	duma	added		->	leftPad(), leftPadCh(), rightPad(), rightPadCh() for int values
//
// Copyright (c) 1999-2003, by Manfred Duchrow. All rights reserved.
// ===========================================================================
package org.pf.text;

// ===========================================================================
// IMPORT
// ===========================================================================
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.ArrayList;
import java.io.PrintWriter;
import java.io.StringWriter;

/**
 * The sole instance of this class provides several convienience methods
 * for string manipulation such as substring replacement or character repetition.
 *
 * @author Manfred Duchrow
 * @version 2.0
 */
public class StringUtil
{
  // =========================================================================
  // CONSTANTS
  // =========================================================================
  /** Constant for the space character **/
	public static final char CH_SPACE					= '\u0020' ;
	/** Constant for the new line character **/
	public static final char CH_NEWLINE  			= '\n' ;
	/** Constant for the carriage return character **/
	public static final char CH_CR  			    = '\r' ;
	/** Constant for the tabulator character **/
	public static final char CH_TAB     			= '\t' ;

	/** Constant for the String representation of the space character **/
	public static final String STR_SPACE			= "\u0020" ;
	/** Constant for the String representation of the new line character **/
	public static final String STR_NEWLINE  	= "\n" ;
	/** Constant for the String representation of the carriage return character **/
	public static final String STR_CR  			  = "\r" ;
	/** Constant for the String representation of the tabulator character **/
	public static final String STR_TAB     		= "\t" ;

  private static final String WORD_DELIM    = STR_SPACE + STR_TAB
											+ STR_NEWLINE + STR_CR ;

  // =========================================================================
  // CLASS VARIABLES
  // =========================================================================
  private static StringUtil singleton = null ;
  private static StringUtil getSingleton() { return singleton ; }
  private static void setSingleton( StringUtil inst ) { singleton = inst ; }

  // =========================================================================
  // PUBLIC CLASS METHODS
  // =========================================================================
  /**
   * Returns the one and only instance of this class.
   */
  public static StringUtil current()
  {
    if ( getSingleton() == null )
      setSingleton( new StringUtil() ) ;
    return getSingleton() ;
  } // current()

  // =========================================================================
  // PUBLIC INSTANCE METHODS
  // =========================================================================
  /**
   * Returns the given string with all found oldSubStr replaced by newSubStr.   <br>
   *
   * Example: StringUtil.current().replaceAll( "Seven of ten", "even", "ix" ) ;<br>
   * results in: "Six of ten"
   *
   * @param sourceStr The string that should be checked for occurrences of oldSubStr
   * @param oldSubStr The string that is searched for in sourceStr
   * @param newSubStr The new string that is placed everywhere the oldSubStr was found
   * @return The original string with all found substrings replaced by new strings
   */
  public String replaceAll( String sourceStr, String oldSubStr, String newSubStr )
  {
    String part     = null ;
    String result   = "" ;
    int index       = -1 ;
    int subLen      = 0 ;

    subLen = oldSubStr.length() ;
    part = sourceStr ;
    while ( ( part.length() > 0 ) && ( subLen > 0 ) )
    {
      index = part.indexOf( oldSubStr ) ;
      if ( index >= 0 )
      {
        result = result + part.substring( 0, index ) + newSubStr ;
        part = part.substring( index + subLen ) ;
      }
      else
      {
        result = result + part ;
        part = "" ;
      }
    } // while

    return result ;
  } // replaceAll()

  // -------------------------------------------------------------------------

	/**
	 * Returns a string with size of count and all characters initialized with ch.   <br>
	 *
	 * @param ch the character to be repeated in the result string.
	 * @param count the number of times the given character should occur in the result string.
	 * @return A string containing <i>count</i> characters <i>ch</i>.
	 */
	public String repeat( char ch, int count )
	{
		StringBuffer buffer		= null ;

		buffer = new StringBuffer( count ) ;
		for ( int i = 1 ; i <= count ; i++ )
		{
			buffer.append( ch ) ;
		}

		return ( buffer.toString() ) ;
	} // repeat()

  // -------------------------------------------------------------------------

  /**
   * Returns an array of substrings of the given text.    <br>
   * The delimiters between the substrings are the whitespace characters
   * SPACE, NEWLINE, CR and TAB.
   *
   * @see #parts(String, String)
   * @param text The string that should be splitted into whitespace separated words
   * @return An array of substrings of the given text
   */
  public String[] words( String text )
  {
  	return this.parts( text, WORD_DELIM ) ;
  } // words()

  // -------------------------------------------------------------------------

  /**
   * Returns an array of substrings of the given text.    <br>
   * The separators between the substrings are the given delimiters.
   * Each character in the delimiter string is treated as a separator.
   * <br>
   * All consecutive delimiters are treated as one delimiter, that is there
   * will be no empty strings in the result.
   *
   * @see #allParts(String, String)
   * @see #substrings(String, String)
   * @see #allSubstrings(String, String)
   * @param text The string that should be splitted into substrings
   * @param delimiters All characters that should be recognized as a separator or substrings
   * @return An array of substrings of the given text
   */
  public String[] parts( String text, String delimiters )
  {
    return this.parts( text, delimiters, false ) ;
  } // parts()

  // -------------------------------------------------------------------------

  /**
   * Returns an array of substrings of the given text.    <br>
   * The separators between the substrings are the given delimiters.
   * Each character in the delimiter string is treated as a separator.
   * <br>
   * For each delimiter that is followed immediately by another delimiter an
   * empty string will be added to the result. There are no empty strings added
   * to the result for a delimiter at the very beginning of at the very end.
   * <p>
   * Examples:
   * <p>
   * allParts( "/A/B//", "/" )  --> { "A", "B", "" }     <br>
   * allParts( "/A,B/C;D", ",;/" )  --> { "A", "B", "C", "D" }     <br>
   * allParts( "A/B,C/D", "," )  --> { "A/B", "C/D" }     <br>
   *
   * @see #parts(String, String)
   * @see #substrings(String, String)
   * @see #allSubstrings(String, String)
   * @param text The string that should be splitted into substrings
   * @param delimiters All characters that should be recognized as a separator or substrings
   * @return An array of substrings of the given text
   */
  public String[] allParts( String text, String delimiters )
  {
    return this.parts( text, delimiters, true ) ;
  } // allParts()

  // -------------------------------------------------------------------------

	/**
	 * Returns the given text split up into an array of strings, at
	 * the occurrances of the separator string.
	 * 
	 * In contrary to method parts() the separator is a one or many
	 * character sequence delimiter. That is, only the exact sequence 
	 * of the characters in separator identifies the end of a substring.
	 * Subsequent occurences of separator will be skipped. Therefore
	 * no empty strings ("") will be in the result array.
	 * 
	 * @see #allSubstrings(String, String)
	 * @see #parts(String, String)
	 * @see #allParts(String, String)
	 * @param text The text to be split up
	 * @param separator The string that separates the substrings
	 * @return An array of substrings not containing any separator anymore
	 */
  public String[] substrings( String text, String separator )
  {
		return this.substrings( text, separator, false ) ;
  } // substrings()

  // -------------------------------------------------------------------------

	/**
	 * Returns the given text split up into an array of strings, at
	 * the occurrances of the separator string.
	 * 
	 * In contrary to method allParts() the separator is a one or many
	 * character sequence delimiter. That is, only the exact sequence 
	 * of the characters in separator identifies the end of a substring.
	 * Subsequent occurences of separator are not skipped. They are added
	 * as empty strings to the result.
	 * 
	 * @see #substrings(String, String)
	 * @see #parts(String, String)
	 * @see #allParts(String, String)
	 * @param text The text to be split up
	 * @param separator The string that separates the substrings
	 * @return An array of substrings not containing any separator anymore
	 */
  public String[] allSubstrings( String text, String separator )
  {
		return this.substrings( text, separator, true ) ;
  } // allSubstrings()

  // -------------------------------------------------------------------------

  /**
   * Returns the first substring that is enclosed by the specified delimiters.  
   * <br>
   * The delimiters are not included in the return string.
   * <p>
   * Example:<br>
   * getDelimitedSubstring( "This {placeholder} belongs to me", "{", "}" )
   * --> returns "placeholder"
   *
   * @param text The input string that contains the delimited part
   * @param startDelimiter The start delimiter of the substring
   * @param endDelimiter The end delimiter of the substring
   * @return The substring or an empty string, if no delimiters are found.
   */
  public String getDelimitedSubstring( String text, String startDelimiter,
  																			String endDelimiter )
  {
    int start ;
    int stop ;
    String subStr   = "" ;

    if ( ( text != null ) && ( startDelimiter != null ) 
    		&& ( endDelimiter != null ) )
    {
      start = text.indexOf( startDelimiter ) ;
      if ( start >= 0 )
      {
	      stop = text.indexOf( endDelimiter, start + 1 ) ;
	      if ( stop > start )
	        subStr = text.substring( start + 1, stop ) ;
      }
    }

    return subStr ;
  } // getDelimitedSubstring()

  // -------------------------------------------------------------------------

  /**
   * Returns the first substring that is enclosed by the specified delimiter.  <br>
   * The delimiters are not included in the return string.
   * <p>
   * Example:<br>
   * getDelimitedSubstring( "File 'text.txt' not found.", "'", "'" )
   * --> returns "text.txt"
   *
   * @param text The input string that contains the delimited part
   * @param delimiter The start and end delimiter of the substring
   * @return The substring or an empty string, if no delimiters are found.
   */
  public String getDelimitedSubstring( String text, String delimiter )
  {
    return this.getDelimitedSubstring( text, delimiter, delimiter ) ;
  } // getDelimitedSubstring()

  // -------------------------------------------------------------------------

  /**
   * Prints the stack trace of the specified throwable to a
   * string and returns it.
   */
  public String stackTrace( Throwable throwable )
  {
    StringWriter sw   = new StringWriter() ;
    PrintWriter pw    = new PrintWriter( sw ) ;
    throwable.printStackTrace( pw ) ;
    pw.close() ;
    return sw.toString() ;
  } // stackTrace()

  // -------------------------------------------------------------------------

  /**
   * Returns the given string filled (on the left) up to the specified
   * length with the given character.     <br>
   * Example: leftPadCh( "12", 6, '0' ) --> "000012"
   */
  public String leftPadCh( String str, int len, char ch )
  {
    return this.padCh( str, len, ch, true ) ;
  } // leftPadCh()

  // -------------------------------------------------------------------------

  /**
   * Returns the given string filled (on the left) up to the specified
   * length with spaces.     <br>
   * Example: leftPad( "XX", 4 ) --> "  XX"
   * 
   * @param str The string that has to be filled up to the specified length
   * @param len The length of the result string
   */
  public String leftPad( String str, int len )
  {
    return this.leftPadCh( str, len, CH_SPACE ) ;
  } // leftPad()

  // -------------------------------------------------------------------------

  /**
   * Returns the given integer as string filled (on the left) up to the 
   * specified length with the given fill character.     <br>
   * Example: leftPad( 24, 5, '*' ) --> "***24"
   */
  public String leftPadCh( int value, int len, char fillChar )
  {
    return this.leftPadCh( Integer.toString(value), len, fillChar ) ;
  } // leftPadCh()

  // -------------------------------------------------------------------------

  /**
   * Returns the given integer as string filled (on the left) up to the 
   * specified length with zeroes.     <br>
   * Example: leftPad( 12, 4 ) --> "0012"
   */
  public String leftPad( int value, int len )
  {
    return this.leftPadCh( value, len, '0' ) ;
  } // leftPad()

  // -------------------------------------------------------------------------

  /**
   * Returns the given string filled (on the right) up to the specified
   * length with the given character.     <br>
   * Example: rightPadCh( "34", 5, 'X' ) --> "34XXX"
   */
  public String rightPadCh( String str, int len, char ch )
  {
    return this.padCh( str, len, ch, false ) ;
  } // rightPadCh()

  // -------------------------------------------------------------------------

  /**
   * Returns the given string filled (on the right) up to the specified
   * length with spaces.     <br>
   * Example: rightPad( "88", 6 ) --> "88    "
   */
  public String rightPad( String str, int len )
  {
    return this.rightPadCh( str, len, CH_SPACE ) ;
  } // rightPad()

  // -------------------------------------------------------------------------

  /**
   * Returns the given integer as string filled (on the right) up to the 
   * specified length with the given character.     <br>
   * Example: rightPad( "32", 4, '#' ) --> "32##"
   */
  public String rightPadCh( int value, int len, char fillChar )
  {
    return this.rightPadCh( Integer.toString(value), len, fillChar ) ;
  } // rightPad()

  // -------------------------------------------------------------------------

  /**
   * Returns the given integer as string filled (on the right) up to the 
   * specified length with spaces.     <br>
   * Example: rightPad( "17", 5 ) --> "17   "
   */
  public String rightPad( int value, int len )
  {
    return this.rightPadCh( value, len, CH_SPACE ) ;
  } // rightPad()

  // -------------------------------------------------------------------------

  /**
   * Returns the given string filled equally left and right
   * up to the specified length with the given character.     <br>
   * Example: centerCh( "A", 5, '_' ) --> "__A__"  <br>
   * Example: centerCh( "XX", 7, '+' ) --> "++XX+++"
   */
  public String centerCh( String str, int len, char ch )
  {
    String buffer   = null ;
    int missing     = len - str.length() ;
    int half        = 0 ;

    if ( missing <= 0 )
      return str ;

    half = missing / 2 ;
    buffer = this.rightPadCh( str, len-half, ch ) ;
    return this.leftPadCh( buffer, len, ch ) ;
  } // centerCh()

  // -------------------------------------------------------------------------

  /**
   * Returns the given string filled (on the right and right)
   * up to the specified length with spaces.     <br>
   * Example: center( "Mike", 10 ) --> "   Mike   "
   */
  public String center( String str, int len )
  {
    return this.centerCh( str, len, CH_SPACE ) ;
  } // center()

  // -------------------------------------------------------------------------

  /**
   * Returns the given string array extended by one element
   * that hold the specified string.
   */
  public String[] append( String[] strings, String string )
  {
    String[] appStr = { string } ;
    return this.append( strings, appStr ) ;
  } // append()
  
  // -------------------------------------------------------------------------

  /**
   * Returns an array of strings that contains all strings given
   * by the first and second string array. The strings from the 
   * second array will be added at the end of the first array.
   * 
   * @param strings The array of string to which to append
   * @param appendStrings The string to be appended to the first array
   */
  public String[] append( String[] strings, String[] appendStrings )
  {
    String[] newStrings   = null ;
    
    if ( strings == null )
      return appendStrings ;
    
    if ( appendStrings == null )
      return strings ;
    
    newStrings   = new String[strings.length + appendStrings.length] ;
    System.arraycopy( strings, 0, newStrings, 0, strings.length ) ;
    System.arraycopy( appendStrings, 0, newStrings, strings.length, appendStrings.length ) ;

    return newStrings ;
  } // append()
  
  // -------------------------------------------------------------------------

  /**
   * Returns an array of strings that contains all strings given
   * in the first plus the specified string to append, if it is not
   * already in the given array.
   */
  public String[] appendIfNotThere( String[] strings, String appendString )
  {
    if ( this.contains( strings, appendString ) )
      return strings ;
    else
      return this.append( strings, appendString ) ;
  } // appendIfNotThere()
  
  // -------------------------------------------------------------------------

  /**
   * Returns an array of strings that contains all strings given
   * in the first plus all strings of the second array that are not
   * already in the first array.
   */
  public String[] appendIfNotThere( String[] strings, String[] appendStrings )
  {
    String[] newStrings = strings ;
    
    if ( appendStrings == null )
      return newStrings ;
    
    for ( int i = 0 ; i < appendStrings.length ; i++ )
    {
      newStrings = this.appendIfNotThere( newStrings, appendStrings[i] ) ;
    }
    return newStrings ;
  } // appendIfNotThere()
  
  // -------------------------------------------------------------------------

	/**
	 * Removes all string of the second array from the first array.
	 * Returns a new array of string that contains all remaining strings of the
	 * original strings array.
	 * 
	 * @param strings The array from which to remove the strings
	 * @param removeStrings The strings to be removed
	 */
	public String[] remove( String[] strings, String[] removeStrings )
	{
		if ( ( strings == null ) || ( removeStrings == null ) 
				|| ( strings.length == 0 ) || ( removeStrings.length == 0 ) )		
		{
			return strings ;
		}	
		
		return this.removeFromStringArray( strings, removeStrings ) ;
	} // remove()

	// -------------------------------------------------------------------------

	/**
	 * Removes the given string from the specified string array.
	 * Returns a new array of string that contains all remaining strings of the
	 * original strings array.
	 * 
	 * @param strings The array from which to remove the string
	 * @param removeString The string to be removed
	 */
	public String[] remove( String[] strings, String removeString )
	{
		String[] removeStrings = { removeString } ;
		
		return this.remove( strings, removeStrings ) ;
	} // remove()

	// -------------------------------------------------------------------------

	/**
	 * Removes all null values from the given string array.
	 * Returns a new string array that contains all none null values of the 
	 * input array.
	 * 
	 * @param strings The array to be cleared of null values
	 */
	public String[] removeNull( String[] strings )
	{
		if ( strings == null )
			return strings ;
		
		return this.removeFromStringArray( strings, null ) ;
	} // removeNull()

	// -------------------------------------------------------------------------

  /**
   * Returns a string that contains all given strings concatenated
   * and separated by the specified separator.
   *
   * @param strings The array of strings that should be concatenated
   * @param separator The separator between the strings
   * @return One string containing the concatenated strings separated by separator
   */
  public String asString ( String[] strings, String separator )
  {
    StringBuffer buffer   = null ;
    
    buffer = new StringBuffer( strings.length * 20 ) ;
    if ( strings.length > 0 )
    {
      buffer.append( strings[0].toString() ) ;
      for ( int i = 1 ; i < strings.length ; i++ )
      {
        buffer.append( separator ) ;
        if ( strings[i] != null )
          buffer.append( strings[i] ) ;
      }
    }
    return buffer.toString() ;
  } // asString()
  
  // -------------------------------------------------------------------------
  
  /**
   * Returns a string that contains all given strings concatenated
   * and separated by comma.
   *
   * @param strings The array of strings that should be concatenated
   * @return One string containing the concatenated strings separated by comma (",")
   */
  public String asString ( String[] strings )
  {
    return this.asString( strings, "," ) ;
  } // asString()
  
  // -------------------------------------------------------------------------

  /**
   * Returns the index of the first string in the given string array that
   * matches the specified string pattern.
   * If no string is found in the array the result is -1.
   *
   * @param strArray An array of string (may contain null elements)
   * @param pattern The pattern the searched string must match
   * @return The index of the matching string in the array or -1 if not found
   */
  public int indexOf( String[] strArray, StringPattern pattern )
  {
    if ( ( strArray == null ) || ( strArray.length == 0 ) )
      return -1 ;
    
    boolean found = false ;
    for ( int i = 0 ; i < strArray.length ; i++ )
    {
      if ( strArray[i] == null )
      {
        if ( pattern == null )
          found = true ;
      }
      else
      {
        if ( pattern != null )
         found = pattern.matches( strArray[i] ) ;
      }
      if ( found )
        return i ;
    }
    return -1 ;	
  } // indexOf()
  
  // -------------------------------------------------------------------------
  
  /**
   * Returns the index of the specified string in the given string array.
   * It returns the index of the first occurrence of the string.
   * If the string is not found in the array the result is -1.
   * The comparison of the strings is case-sensitive!
   *
   * @param strArray An array of string (may contain null elements)
   * @param searchStr The string to be looked up in the array (null allowed)
   * @return The index of the string in the array or -1 if not found
   */
  public int indexOf( String[] strArray, String searchStr )
  {
    return this.indexOfString( strArray, searchStr, false ) ;
  } // indexOf()
  
  // -------------------------------------------------------------------------
  
  /**
   * Returns the index of the specified string in the given string array.
   * It returns the index of the first occurrence of the string.
   * If the string is not found in the array the result is -1.
   * The comparison of the strings is case-insensitive!
   *
   * @param strArray An array of string (may contain null elements)
   * @param searchStr The string to be looked up in the array (null allowed)
   * @return The index of the string in the array or -1 if not found
   */
  public int indexOfIgnoreCase( String[] strArray, String searchStr )
  {
    return this.indexOfString( strArray, searchStr, true ) ;
  } // indexOfIgnoreCase()
  
  // -------------------------------------------------------------------------
  
  /**
   * Returns whether or not the specified string can be found
   * in the given string array.
   *
   * @param strArray An array of string (may contain null elements)
   * @param searchStr The string to be looked up in the array (null allowed)
   * @param ignoreCase Defines whether or not the comparison is case-sensitive.
   * @return true, if the specified array contains the given string
   */
  public boolean contains( String[] strArray, String searchStr, boolean ignoreCase )
  {
  	if ( ignoreCase )
	    return this.containsIgnoreCase( strArray, searchStr ) ;
	  else
	    return this.contains( strArray, searchStr ) ;
  } // contains()
  
  // -------------------------------------------------------------------------
  
  /**
   * Returns whether or not a string can be found in the given string array
   * that matches the specified string pattern.
   *
   * @param strArray An array of string (may contain null elements)
   * @param pattern The string pattern to match against in the array (null allowed)
   * @return true, if the specified array contains a string matching the pattern
   */
  public boolean contains( String[] strArray, StringPattern pattern)
  {
    return ( this.indexOf( strArray, pattern ) >= 0 ) ;
  } // contains()
  
  // -------------------------------------------------------------------------
  
  /**
   * Returns whether or not the specified string can be found
   * in the given string array.
   * The comparison of the strings is case-sensitive!
   *
   * @param strArray An array of string (may contain null elements)
   * @param searchStr The string to be looked up in the array (null allowed)
   * @return true, if the specified array contains the given string
   */
  public boolean contains( String[] strArray, String searchStr )
  {
    return ( this.indexOf( strArray, searchStr ) >= 0 ) ;
  } // contains()
  
  // -------------------------------------------------------------------------
  
  /**
   * Returns whether or not the specified string can be found
   * in the given string array.
   * The comparison of the strings is case-insensitive!
   *
   * @param strArray An array of string (may contain null elements)
   * @param searchStr The string to be looked up in the array (null allowed)
   * @return true, if the specified array contains the given string
   */
  public boolean containsIgnoreCase( String[] strArray, String searchStr )
  {
    return ( this.indexOfIgnoreCase( strArray, searchStr ) >= 0 ) ;
  } // containsIgnoreCase()
  
  // -------------------------------------------------------------------------
  
	/**
	 * Returns all elements of string array <i>from</i> in a new array
	 * from index start up to the end.
	 * If start index is larger than the array's length, an empty array will be
	 * returned.
	 * 
	 * @param from The string array the elements should be copied from
	 * @param start Index of the first element to copy
	 */
	public String[] copyFrom( String[] from, int start )
	{
		if ( from == null )
			return null ;

		return this.copyFrom( from, start, from.length - 1 ) ;
	} // copyFrom()
  
  // -------------------------------------------------------------------------

	/**
	 * Returns all elements of string array <i>from</i> in a new array
	 * from index start up to index end (inclusive).
	 * If end is larger than the last valid index, it will be reduced
	 * to the last index.
	 * If end index is less than start index, an empty array will be
	 * returned.
	 * 
	 * @param from The string array the elements should be copied from
	 * @param start Index of the first element to copy
	 * @param end Index of last element to be copied
	 */
	public String[] copyFrom( String[] from, int start, int end )
	{
		String[] result ;
		int count ;
		int stop = end ;
		
		if ( from == null )
			return null ;
		
		if ( stop > ( from.length - 1 ) )
			stop = from.length - 1 ;
		
		count = stop - start + 1 ;
			
		if ( count < 1 )
			return new String[0] ;	
			
		result = new String[count] ;
		
		System.arraycopy( from, start, result, 0, count ) ;
			
		return result ;	
	} // copyFrom()
  
  // -------------------------------------------------------------------------
  
  /**
   * Returns the portion of the given string that comes before the last
   * occurance of the specified separator.    <br>
   * If the separator could not be found in the given string, then the
   * string is returned unchanged.
   * <p>
   * Examples:
   * <p>
   * cutTail( "A/B/C", "/" ) ;   // returns "A/B"
   * <br>
   * cutTail( "A/B/C", "," ) ;   // returns "A/B/C"
   * <p>
	 * @see #prefix( String, String )
	 * @see #suffix( String, String )
	 * @see #cutHead( String, String )
	 * @see #startingFrom( String, String )
	 * @see #upTo( String, String )
   * @param text The string from which to cut off the tail
   * @param separator The separator from where to cut off
   * @return the string without the separator and without the characters after the separator
   */
	public String cutTail( String text, String separator )
	{
		int index ;
		
		if ( ( text == null ) || ( separator == null ) )
			return text ;
			
		index = text.lastIndexOf( separator ) ;
		if ( index < 0 )
			return text ;
			
		return text.substring( 0, index ) ;	
	} // cutTail()

	// ------------------------------------------------------------------------

  /**
   * Returns the portion of the given string that stands after the last
   * occurance of the specified separator.    <br>
   * If the separator could not be found in the given string, then the
   * string is returned unchanged.
   * <p>
   * Examples:
   * <p>
   * cutHead( "A/B/C", "/" ) ;   // returns "C"
   * <br>
   * cutHead( "A/B/C", "," ) ;   // returns "A/B/C"
   * <p>
	 * @see #prefix( String, String )
	 * @see #cutTail( String, String )
	 * @see #suffix( String, String )
	 * @see #startingFrom( String, String )
	 * @see #upTo( String, String )
   * @param text The string from which to cut off the head
   * @param separator The separator up to which to cut off
   * @return the string without the separator and without the characters before the separator
   */
	public String cutHead( String text, String separator )
	{
		int index ;
		
		if ( ( text == null ) || ( separator == null ) )
			return text ;
			
		index = text.lastIndexOf( separator ) ;
		if ( index < 0 )
			return text ;
			
		return text.substring( index + 1 ) ;	
	} // cutHead()

	// ------------------------------------------------------------------------

	/**
	 * Returns a string array with two elements where the first is the attribute
	 * name and the second is the attribute value.
	 * Splits the given string at the first occurance of separator and returns
	 * the piece before the separator in element 0 and the piece after the 
	 * separator in the returned array.
	 * If the separator is not found, the first element contains the full
	 * string and the second an empty string.
	 * 
	 * @param str The string that contains the name-value pair
	 * @param separator The separator between name and value
	 */
	public String[] splitNameValue( String str, String separator )
	{
		String[] result = { "", "" } ;
		int index	;
		
		if ( str != null )
		{
			index = str.indexOf( separator ) ;
			if ( index > 0 )
			{
				result[0] = str.substring( 0, index ) ;
				result[1] = str.substring( index + separator.length() ) ;
			}
			else
			{
				result[0] = str ;
			}
		}
		
		return result ;
	} // splitNameValue()

  // -------------------------------------------------------------------------

	/**
	 * Returns the substring of the given string that comes before the
	 * first occurance of the specified separator.
	 * If the string starts with a separator, the result will be an empty string.
	 * If the string doesn't contain the separator the method returns null.
   * <p>
   * Examples:
   * <p>
   * prefix( "A/B/C", "/" ) ;   // returns "A"
   * <br>
   * prefix( "A/B/C", "," ) ;   // returns null
   * <p>
	 * @see #suffix( String, String )
	 * @see #cutTail( String, String )
	 * @see #cutHead( String, String )
	 * @see #startingFrom( String, String )
	 * @see #upTo( String, String )
	 * @param str The string of which the prefix is desired
	 * @param separator Separates the prefix from the rest of the string
	 */
	public String prefix( String str, String separator )
	{
		return this.prefix( str, separator, true ) ;
	} // prefix()

  // -------------------------------------------------------------------------

	/**
	 * Returns the substring of the given string that comes after the
	 * first occurance of the specified separator.
	 * If the string ends with a separator, the result will be an empty string.
	 * If the string doesn't contain the separator the method returns null.
   * <p>
   * Examples:
   * <p>
   * suffix( "A/B/C", "/" ) ;   // returns "B/C"
   * <br>
   * suffix( "A/B/C", "," ) ;   // returns null
   * <p>
	 * @see #prefix( String, String )
	 * @see #cutTail( String, String )
	 * @see #cutHead( String, String )
	 * @see #startingFrom( String, String )
	 * @see #upTo( String, String )
	 * @param str The string of which the suffix is desired
	 * @param separator Separates the suffix from the rest of the string
	 */
	public String suffix( String str, String separator )
	{
		return this.suffix( str, separator, true ) ;
	} // suffix()

  // -------------------------------------------------------------------------

	/**
	 * Returns the substring of the given string that comes before the
	 * first occurance of the specified separator.
	 * If the string starts with a separator, the result will be an empty string.
	 * If the string doesn't contain the separator the method returns the
	 * whole string unchanged.
   * <p>
   * Examples:
   * <p>
   * upTo( "A/B/C", "/" ) ;   // returns "A"
   * <br>
   * upTo( "A/B/C", "," ) ;   // returns "A/B/C"
   * <br>
   * upTo( "/A/B/C", "/" ) ;   // returns ""
   * <p>
	 * @see #prefix( String, String )
	 * @see #cutTail( String, String )
	 * @see #cutHead( String, String )
	 * @see #startingFrom( String, String )
	 * @see #suffix( String, String )
	 * @param str The string of which the prefix is desired
	 * @param separator Separates the prefix from the rest of the string
	 */
	public String upTo( String str, String separator )
	{
		return this.prefix( str, separator, false ) ;
	} // upTo()

  // -------------------------------------------------------------------------

	/**
	 * Returns the substring of the given string that comes after the
	 * first occurance of the specified separator.
	 * If the string doesn't contain the separator the method returns the
	 * whole string unchanged.
   * <p>
   * Examples:
   * <p>
   * startingFrom( "A/B/C", "/" ) ;   // returns "B/C"
   * <br>
   * startingFrom( "A/B/C", "," ) ;   // returns "A/B/C"
   * <p>
	 * @see #prefix( String, String )
	 * @see #cutTail( String, String )
	 * @see #cutHead( String, String )
	 * @see #suffix( String, String )
	 * @see #upTo( String, String )
	 * @param str The string of which the suffix is desired
	 * @param separator Separates the suffix from the rest of the string
	 */
	public String startingFrom( String str, String separator )
	{
		return this.suffix( str, separator, false ) ;
	} // startingFrom()

  // -------------------------------------------------------------------------

	/**
	 * Returns a string that contains all characters of the given string in
	 * reverse order.
	 */
	public String reverse( String str )
	{
		if ( str == null )
			return null ;
			
		char[] newStr = new char[str.length()] ;
		StringCharacterIterator iterator = new StringCharacterIterator(str) ;
		int i = 0 ;
				
		for(char ch = iterator.last(); ch != CharacterIterator.DONE; ch = iterator.previous())
		{
			newStr[i] = ch ;
			i++ ;
		}
		return new String( newStr ) ;	
	} // reverse()

	// -------------------------------------------------------------------------

	/**
	 * Returns the given map with new entries from the specified String.
	 * If the specified map is null a new empty java.util.Hashtable will be 
	 * created.
	 * <br>
	 * The string is split up into elements separated by the elementSeparator
	 * parameter. If this parameter is null the default separator "," is used.
	 * <br>
	 * After that each part is split up to a key-value pair separated by the
	 * keyValueSeparator parameter. If this parameter is null the default "=" is
	 * used.
	 * <br>
	 * Then the key-value pairs are added to the map and the map is returned.
	 * <p>
	 * <b>Be aware that all leading and trailing whitespaces of keys and values
	 * will be removed!</b>
	 * 
	 * @param str The string that contains the list of key-value pairs
	 * @param elementSeparator The separator between the elements of the list
	 * @param keyValueSeparator The separator between the keys and values
	 * @param map The map to which the key-value pairs are added
	 */
	public Map toMap(	String str, String elementSeparator, 
										String keyValueSeparator, Map map )
	{
		Map result ;
		String elemSep ;
		String kvSep ;
		String[] assignments ;
		String[] nameValue ;
		
		if ( str == null )
			return map ;
			
		result = ( map == null ? new Hashtable() : map ) ;
		elemSep = ( elementSeparator == null ) ? "," : elementSeparator ;
		kvSep = ( keyValueSeparator == null ) ? "=" : keyValueSeparator ;
		
		assignments = this.parts( str, elemSep ) ;
		for ( int i = 0 ; i < assignments.length ; i++ )
		{
			nameValue = this.splitNameValue( assignments[i], kvSep ) ;
			nameValue[0] = nameValue[0].trim() ;
			nameValue[1] = nameValue[1].trim() ;
			if ( nameValue[0].length() > 0 )
				result.put( nameValue[0], nameValue[1] ) ;
		}
		
		return result ;
	} // asMap()

	// -------------------------------------------------------------------------
	
	/**
	 * Returns a new map object that contains all key-value pairs of the
	 * specified string. 
	 * <br>
	 * The separator between the elements is assumed to be ","
	 * and "=" between key and value.
	 * <p>
	 * Example:<br>
	 * "main=Fred,support1=John,support2=Stella,manager=Oscar"
	 * <p>
	 * <b>Be aware that all leading and trailing whitespaces of keys and values
	 * will be removed!</b>
	 * 
	 * @param str The string with the list of key-value pairs
	 */
	public Map asMap( String str )
	{
		return this.toMap( str, null, null, null ) ;
	} // asMap()

	// -------------------------------------------------------------------------

	/**
	 * Returns a new map object that contains all key-value pairs of the
	 * specified string. 
	 * <br>
	 * The separator between the keys and values is assumed to be "=".
	 * <p>
	 * <b>Be aware that all leading and trailing whitespaces of keys and values
	 * will be removed!</b>
	 * 
	 * @param str The string that contains the list of key-value pairs
	 * @param elementSeparator The separator between the elements of the list
	 */	
	public Map asMap( String str, String elementSeparator )
	{
		return this.toMap( str, elementSeparator, null, null ) ;		
	} // asMap()

	// -------------------------------------------------------------------------	
	
	/**
	 * Returns a new map object that contains all key-value pairs of the
	 * specified string. 
	 * <p>
	 * <b>Be aware that all leading and trailing whitespaces of keys and values
	 * will be removed!</b>
	 * 
	 * @param str The string that contains the list of key-value pairs
	 * @param elementSeparator The separator between the elements of the list
	 * @param keyValueSeparator The separator between the keys and values
	 */
	public Map asMap( String str, String elementSeparator, 
										String keyValueSeparator )
	{
		return this.toMap( str, elementSeparator, keyValueSeparator, null ) ;		
	} // asMap()

	// -------------------------------------------------------------------------	

	/**
	 * Returns the given map object with all key-value pairs of the
	 * specified string added to it.
	 * <br>
	 * The separator between the keys and values is assumed to be "=".
	 * <p>
	 * <b>Be aware that all leading and trailing whitespaces of keys and values
	 * will be removed!</b>
	 * 
	 * @param str The string that contains the list of key-value pairs
	 * @param elementSeparator The separator between the elements of the list
	 * @param map The map to which the key-value pairs are added
	 */	
	public Map toMap( String str, String elementSeparator, Map map )
	{
		return this.toMap( str, elementSeparator, null, map ) ;		
	} // toMap()

	// -------------------------------------------------------------------------	
	
	/**
	 * Adds all key-value pairs of the given string to the specified map.
	 * <br>
	 * The separator between the elements is assumed to be ","
	 * and "=" between key and value.
	 * <p>
	 * <b>Be aware that all leading and trailing whitespaces of keys and values
	 * will be removed!</b>
	 * 
	 * @param str The string that contains the list of key-value pairs
	 * @param map The map to which the key-value pairs are added
	 */
	public Map toMap( String str, Map map )
	{
		return this.toMap( str, null, null, map ) ;		
	} // toMap()

	// -------------------------------------------------------------------------	
	
	/**
	 * Adds all key-value pairs of the given string to a new properties object.
	 * <br>
	 * The separator between the elements is assumed to be ","
	 * and "=" between key and value.
	 * <p>
	 * <b>Be aware that all leading and trailing whitespaces of keys and values
	 * will be removed!</b>
	 * 
	 * @param str The string that contains the list of key-value pairs
	 */
	public Properties asProperties( String str )
	{
		return this.toProperties( str, null ) ;		
	} // asProperties()

	// -------------------------------------------------------------------------	

	/**
	 * Adds all key-value pairs of the given string to the specified properties.
	 * <br>
	 * The separator between the elements is assumed to be ","
	 * and "=" between key and value.
	 * <p>
	 * <b>Be aware that all leading and trailing whitespaces of keys and values
	 * will be removed!</b>
	 * 
	 * @param str The string that contains the list of key-value pairs
	 * @param properties The properties where the key-value pairs should be added
	 */
	public Properties toProperties( String str, Properties properties )
	{
		Properties props = ( properties == null ) ? new Properties() : properties ;
		return (Properties)this.toMap( str, null, null, props ) ;		
	} // toProperties()

	// -------------------------------------------------------------------------	

  // =========================================================================
  // PROTECTED INSTANCE METHODS
  // =========================================================================
	/**
	 * Cuts off all leading and trailing occurences of separator in text.
	 */
	protected String trimSeparator( String text, String separator )
	{
		int sepLen		= separator.length() ;
		
		while ( text.startsWith( separator ) )
			text = text.substring( separator.length() ) ;
			
		while ( text.endsWith( separator ) )
			text = text.substring( 0, text.length() - sepLen ) ;

		return text ;			
	} // trimSeparator()

  // -------------------------------------------------------------------------

  /**
   * Returns an array of substrings of the given text.    <br>
   * The separators between the substrings are the given delimiters.
   * Each character in the delimiter string is treated as a separator.
   *
   * @param text The string that should be splitted into substrings
   * @param delimiters All characters that should be recognized as a separator or substrings
   * @param all If true, empty elements will be returned, otherwise thye are skipped
   * @return An array of substrings of the given text
   */
  protected String[] parts( String text, String delimiters, boolean all )
  {
    ArrayList result          = null ;
    StringTokenizer tokenizer = null ;

    if ( text == null )
      return null ;

    if ( ( delimiters == null ) || ( delimiters.length() == 0 ) )
    {
      String[] resultArray = { text } ;
      return resultArray ;
    }

    if ( text.length() == 0 )
    {
      return new String[0] ;
    }
    else
    {
      result = new ArrayList() ;
      tokenizer = new StringTokenizer( text, delimiters, all ) ;

			if ( all )
				this.collectParts( result, tokenizer, delimiters ) ;
			else
				this.collectParts( result, tokenizer ) ;
    }
    return (String[])result.toArray( new String[0] ) ;
  } // parts()

  // -------------------------------------------------------------------------

	protected void collectParts( List list, StringTokenizer tokenizer )
	{
    while( tokenizer.hasMoreTokens() )
    {
      list.add( tokenizer.nextToken() ) ;
    }		
	} // collectParts()

	// -------------------------------------------------------------------------

	protected void collectParts( List list, StringTokenizer tokenizer, String delimiter )
	{
		String token ;
		boolean lastWasDelimiter = false ; 
		
    while( tokenizer.hasMoreTokens() )
    {
    	token = tokenizer.nextToken() ;
    	if ( delimiter.indexOf( token ) >= 0 )
    	{
    		if ( lastWasDelimiter )
    			list.add( "" ) ;
    		lastWasDelimiter = true ;
    	}
    	else
    	{
      	list.add( token ) ;
      	lastWasDelimiter = false ;
    	}	
    }		
	} // collectParts()

	// -------------------------------------------------------------------------

	/**
	 * Returns the given text split up into an array of strings, at
	 * the occurrances of the separator string.
	 * 
	 * In contrary to method parts() the separator is a one or many
	 * character sequence delimiter. That is, only the exact sequence 
	 * of the characters in separator identifies the end of a substring.
	 * Parameter all defines whether empty strings between consecutive
	 * separators are added to the result or not.
	 * 
	 * @see #parts(String, String, boolean)
	 * @param text The text to be split up
	 * @param separator The string that separates the substrings
	 * @param all If true, empty strings are added, otherwise skipped
	 * @return An array of substrings not containing any separator anymore
	 */
  protected String[] substrings( String text, String separator, boolean all )
  {
		int index						= 0 ;
		int start						= 0 ;
		int sepLen					= 0 ;
		int strLen					= 0 ;
		String str					= text ;
		ArrayList strings		= new ArrayList() ;
		
		if ( text == null )
			return new String[0] ;

    if ( ( separator == null ) || ( separator.length() == 0 ) )
    {
			if ( text.length() == 0 )
				return new String[0] ;
				
      String[] resultArray = { text } ;
      return resultArray ;
    }

		if ( ! all )
			str = this.trimSeparator( text, separator ) ;

		strLen = str.length() ;
		if ( strLen > 0 )
		{
			sepLen = separator.length() ;
	
			index = str.indexOf( separator, start ) ;
			while ( index >= 0 )
			{ 
				if ( all )
				{
					if ( index > 0 )
					{
						strings.add( str.substring( start, index ) ) ;
					}
				}
				else
				{
					if ( index > ( start + sepLen ) )
						strings.add( str.substring( start, index ) ) ;
				}
				start = index + sepLen ;
				index = str.indexOf( separator, start ) ;
			}
	
			if ( start < strLen )
				strings.add( str.substring( start ) ) ;
		}
		return (String[])strings.toArray( new String[0] ) ;
  } // substrings()

  // -------------------------------------------------------------------------

  protected String padCh( String str, int len, char ch, boolean left )
  {
    StringBuffer buffer = null ;
    int missing         = len - str.length() ;

    if ( missing <= 0 )
      return str ;

    buffer = new StringBuffer( len ) ;
    if ( ! left )
      buffer.append( str ) ;
    for ( int i = 1 ;  i <= missing ; i++ )
      buffer.append( ch ) ;
    if ( left )
      buffer.append( str ) ;
    return buffer.toString() ;
  } // padCh()

  // -------------------------------------------------------------------------

  protected int indexOfString( String[] strArray, String searchStr, boolean ignoreCase )
  {
    if ( ( strArray == null ) || ( strArray.length == 0 ) )
      return -1 ;
    
    boolean found = false ;
    for ( int i = 0 ; i < strArray.length ; i++ )
    {
      if ( strArray[i] == null )
      {
        if ( searchStr == null )
          found = true ;
      }
      else
      {
        if ( ignoreCase )
          found = strArray[i].equalsIgnoreCase( searchStr ) ;
        else
          found = strArray[i].equals( searchStr ) ;
      }
      if ( found )
        return i ;
    }
    return -1 ;
  } // indexOfString()
  
  // -------------------------------------------------------------------------

	/**
	 * Returns the substring of the given string that comes before the
	 * first occurance of the specified separator.
	 * If the string starts with a separator, the result will be an empty string.
	 * If the string doesn't contain the separator the method returns null or
	 * the whole string, depending on the returnNull flag.
	 * 
	 * @param str The string of which the prefix is desired
	 * @param separator Separates the prefix from the rest of the string
	 * @param returnNull Specifies if null will be returned if no separator is found
	 */
	protected String prefix( String str, String separator, boolean returnNull )
	{
		if ( str == null )
			return null ;
			
		if ( separator == null )
			return ( returnNull ? null : str  ) ;
			
		int index = str.indexOf( separator ) ;
		if ( index >= 0 )
			return str.substring( 0, index ) ;
		else
			return ( returnNull ? null : str  ) ;
	} // prefix()

  // -------------------------------------------------------------------------

	/**
	 * Returns the substring of the given string that comes after the
	 * first occurance of the specified separator.
	 * If the string ends with a separator, the result will be an empty string.
	 * If the string doesn't contain the separator the method returns null or
	 * the whole string, depending on the returnNull flag.
	 * 
	 * @param str The string of which the suffix is desired
	 * @param separator Separates the suffix from the rest of the string
	 * @param returnNull Specifies if null will be returned if no separator is found
	 */
	protected String suffix( String str, String separator, boolean returnNull )
	{
		if ( str == null )
			return null ;
			
		if ( separator == null )
			return ( returnNull ? null : str  ) ;
			
		int index = str.indexOf( separator ) ;
		if ( index >= 0 )
			return str.substring( index + separator.length() ) ;
		else
			return ( returnNull ? null : str  ) ;
	} // suffix()

  // -------------------------------------------------------------------------

	/**
	 * Removes the given strings from the array.
	 * If removeStrings is null it means that all null values are removed from
	 * the first array.
	 */
	protected String[] removeFromStringArray( String[] strings, String[] removeStrings )
	{
		List list ;
		boolean remains ;
		
		list = new ArrayList( strings.length ) ;
		for (int i = 0; i < strings.length; i++)
		{
			if ( removeStrings == null )
			{
				remains = strings[i] != null ;
			}
			else
			{
				remains = ! this.contains( removeStrings, strings[i] ) ;
			}
			if ( remains )
			{
				list.add( strings[i] ) ;
			}			
		}	
		return (String[])list.toArray( new String[list.size()] ) ;
	} // removeFromStringArray()

	// -------------------------------------------------------------------------

} // class StringUtil
