package net.mypapit.java;

public class StringTokenizer implements java.util.Enumeration{

	protected String text;
	protected int strLength;
	protected String nontokenDelims;
	protected String tokenDelims;
	protected int position;
	protected boolean emptyReturned;
	protected char maxDelimChar;
	protected boolean returnEmptyTokens;
	protected int delimsChangedPosition;
	protected int tokenCount;

	public StringTokenizer(String text, String nontokenDelims, String tokenDelims){
		this(text, nontokenDelims, tokenDelims, false);
	}

	public StringTokenizer(String text, String nontokenDelims, String tokenDelims, boolean returnEmptyTokens){
		setDelims(nontokenDelims, tokenDelims);
		setText(text);
		setReturnEmptyTokens(returnEmptyTokens);
	}

	public StringTokenizer(String text, String delims, boolean delimsAreTokens){
		this(text, (delimsAreTokens ? null : delims), (delimsAreTokens ? delims : null));
	}

	public StringTokenizer(String text, String nontokenDelims){
		this(text, nontokenDelims, null);
	}

	public StringTokenizer(String text){
		this(text, " \t\n\r\f", null);
	}

	public void setText(String text){
		if (text == null){
			throw new NullPointerException();
		}
		this.text = text;
		strLength = text.length();
		emptyReturned = false;
		position = (strLength > 0 ? 0: -1);
		delimsChangedPosition = 0;
		tokenCount = -1;
	}

	private void setDelims(String nontokenDelims, String tokenDelims){
		this.nontokenDelims = nontokenDelims;
		this.tokenDelims = tokenDelims;
		delimsChangedPosition = (position != -1 ? position : strLength);
		maxDelimChar = 0;
		for (int i=0; nontokenDelims != null && i < nontokenDelims.length(); i++){
			if (maxDelimChar < nontokenDelims.charAt(i)){
				maxDelimChar = nontokenDelims.charAt(i);
			}
		}
		for (int i=0; tokenDelims != null && i < tokenDelims.length(); i++){
			if (maxDelimChar < tokenDelims.charAt(i)){
				maxDelimChar = tokenDelims.charAt(i);
			}
		}
		tokenCount = -1;
	}

	public boolean hasMoreTokens(){
		if (tokenCount == 0){
			return false;
		} else if (tokenCount > 0){
			return true;
		}
		
		int savedPosition = position;
		boolean savedEmptyReturned = emptyReturned;

		int workingPosition = position;
		boolean workingEmptyReturned = emptyReturned;
		boolean onToken = advancePosition();
		while(position != workingPosition ||
			emptyReturned != workingEmptyReturned){
			if (onToken){
				position = savedPosition;
				emptyReturned = savedEmptyReturned;
				return true;
			}
			workingPosition = position;
			workingEmptyReturned = emptyReturned;
			onToken = advancePosition();
		}

		position = savedPosition;
		emptyReturned = savedEmptyReturned;
		return false;
	}

	public String nextToken(){
		int workingPosition = position;
		boolean workingEmptyReturned = emptyReturned;
		boolean onToken = advancePosition();
		while(position != workingPosition ||
			emptyReturned != workingEmptyReturned){
			if (onToken){
				tokenCount--;
				return (emptyReturned ? "" : text.substring(workingPosition, (position != -1) ? position : strLength));
			}
			workingPosition = position;
			workingEmptyReturned = emptyReturned;
			onToken = advancePosition();
		}
		throw new java.util.NoSuchElementException();
	}

	public boolean skipDelimiters(){
		int workingPosition = position;
		boolean workingEmptyReturned = emptyReturned;
		boolean onToken = advancePosition();
		tokenCount = -1;

		while(position != workingPosition ||
			emptyReturned != workingEmptyReturned){
			if (onToken){
				position = workingPosition;
				emptyReturned = workingEmptyReturned;
				return true;
			}
			workingPosition = position;
			workingEmptyReturned = emptyReturned;
			onToken = advancePosition();
		}
		return false;
	}

	public int countTokens(){

		if (this.tokenCount >=0){
			return this.tokenCount;
		}

		int tokenCount = 0;

		int savedPosition = position;
		boolean savedEmptyReturned = emptyReturned;

		int workingPosition = position;
		boolean workingEmptyReturned = emptyReturned;
		boolean onToken = advancePosition();
		while(position != workingPosition ||
			emptyReturned != workingEmptyReturned){
			if (onToken){
				tokenCount++;
			}
			workingPosition = position;
			workingEmptyReturned = emptyReturned;
			onToken = advancePosition();
		}

		// restore object state
		position = savedPosition;
		emptyReturned = savedEmptyReturned;

		// Save the token count in case this is called again
		// so we wouldn't have to do so much work.
		this.tokenCount = tokenCount;

		return tokenCount;
	}

	/**
	 * Set the delimiters used to this set of (nontoken) delimiters.
	 *
	 * @param delims the new set of nontoken delimiters (the set of token delimiters will be empty).
	 *
	 * @since ostermillerutils 1.00.00
	 */
	public void setDelimiters(String delims){
		setDelims(delims, null);
	}

	/**
	 * Set the delimiters used to this set of delimiters.
	 *
	 * @param delims the new set of delimiters.
	 * @param delimsAreTokens flag indicating whether the first parameter specifies
	 *    token or nontoken delimiters: false -- the first parameter specifies nontoken
	 *    delimiters, the set of token delimiters is empty; true -- the first parameter
	 *    specifies token delimiters, the set of nontoken delimiters is empty.
	 *
	 * @since ostermillerutils 1.00.00
	 */
	public void setDelimiters(String delims, boolean delimsAreTokens){
		setDelims((delimsAreTokens ? null : delims), (delimsAreTokens ? delims : null));
	}

	/**
	 * Set the delimiters used to this set of delimiters.
	 *
	 * @param nontokenDelims the new set of nontoken delimiters.
	 * @param tokenDelims the new set of token delimiters.
	 *
	 * @since ostermillerutils 1.00.00
	 */
	public void setDelimiters(String nontokenDelims, String tokenDelims){
		setDelims(nontokenDelims, tokenDelims);
	}

	/**
	 * Set the delimiters used to this set of delimiters.
	 *
	 * @param nontokenDelims the new set of nontoken delimiters.
	 * @param tokenDelims the new set of token delimiters.
	 * @param returnEmptyTokens true if empty tokens may be returned; false otherwise.
	 *
	 * @since ostermillerutils 1.00.00
	 */
	public void setDelimiters(String nontokenDelims, String tokenDelims, boolean returnEmptyTokens){
		setDelims(nontokenDelims, tokenDelims);
		setReturnEmptyTokens(returnEmptyTokens);
	}

	/**
	 * Calculates the number of times that this tokenizer's <code>nextToken</code>
	 * method can be called before it generates an exception using the given set of
	 * (nontoken) delimiters.  The delimiters given will be used for future calls to
	 * nextToken() unless new delimiters are given. The current position
	 * is not advanced.
	 *
	 * @param delims the new set of nontoken delimiters (the set of token delimiters will be empty).
	 * @return the number of tokens remaining in the string using the new
	 *    delimiter set.
	 *
	 * @see #countTokens()
	 * @since ostermillerutils 1.00.00
	 */
	public int countTokens(String delims){
		setDelims(delims, null);
		return countTokens();
	}

	/**
	 * Calculates the number of times that this tokenizer's <code>nextToken</code>
	 * method can be called before it generates an exception using the given set of
	 * delimiters.  The delimiters given will be used for future calls to
	 * nextToken() unless new delimiters are given. The current position
	 * is not advanced.
	 *
	 * @param delims the new set of delimiters.
	 * @param delimsAreTokens flag indicating whether the first parameter specifies
	 *    token or nontoken delimiters: false -- the first parameter specifies nontoken
	 *    delimiters, the set of token delimiters is empty; true -- the first parameter
	 *    specifies token delimiters, the set of nontoken delimiters is empty.
	 * @return the number of tokens remaining in the string using the new
	 *    delimiter set.
	 *
	 * @see #countTokens()
	 * @since ostermillerutils 1.00.00
	 */
	public int countTokens(String delims, boolean delimsAreTokens){
		setDelims((delimsAreTokens ? null : delims), (delimsAreTokens ? delims : null));
		return countTokens();
	}

	/**
	 * Calculates the number of times that this tokenizer's <code>nextToken</code>
	 * method can be called before it generates an exception using the given set of
	 * delimiters.  The delimiters given will be used for future calls to
	 * nextToken() unless new delimiters are given. The current position
	 * is not advanced.
	 *
	 * @param nontokenDelims the new set of nontoken delimiters.
	 * @param tokenDelims the new set of token delimiters.
	 * @return the number of tokens remaining in the string using the new
	 *    delimiter set.
	 *
	 * @see #countTokens()
	 * @since ostermillerutils 1.00.00
	 */
	public int countTokens(String nontokenDelims, String tokenDelims){
		setDelims(nontokenDelims, tokenDelims);
		return countTokens();
	}

	/**
	 * Calculates the number of times that this tokenizer's <code>nextToken</code>
	 * method can be called before it generates an exception using the given set of
	 * delimiters.  The delimiters given will be used for future calls to
	 * nextToken() unless new delimiters are given. The current position
	 * is not advanced.
	 *
	 * @param nontokenDelims the new set of nontoken delimiters.
	 * @param tokenDelims the new set of token delimiters.
	 * @param returnEmptyTokens true if empty tokens may be returned; false otherwise.
	 * @return the number of tokens remaining in the string using the new
	 *    delimiter set.
	 *
	 * @see #countTokens()
	 * @since ostermillerutils 1.00.00
	 */
	public int countTokens(String nontokenDelims, String tokenDelims, boolean returnEmptyTokens){
		setDelims(nontokenDelims, tokenDelims);
		setReturnEmptyTokens(returnEmptyTokens);
		return countTokens();
	}

	/**
	 * Advances the state of the tokenizer to the next token or delimiter.  This method only
	 * modifies the class variables position, and emptyReturned.  The type of token that
	 * should be emitted can be deduced by examining the changes to these two variables.
	 * If there are no more tokens, the state of these variables does not change at all.
	 *
	 * @return true if we are at a juncture at which a token may be emitted, false otherwise.
	 *
	 * @since ostermillerutils 1.00.00
	 */
	private boolean advancePosition(){
		// if we are returning empty tokens, we are just starting to tokenizer
		// and there is a delimiter at the beginning of the string or the string
		// is empty we need to indicate that there is an empty token at the beginning.
		// The beginning is defined as where the delimiters were last changed.
		if (returnEmptyTokens && !emptyReturned &&
			(delimsChangedPosition == position ||
			(position == -1 && strLength == delimsChangedPosition))){
			if (strLength == delimsChangedPosition){
				// Case in which the string (since delim change)
				// is empty, but because we are returning empty
				// tokens, a single empty token should be returned.
				emptyReturned = true;
				/*System.out.println("Empty token for empty string.");*/
				return true;
			} else {
				char c = text.charAt(position);
				if (c <= maxDelimChar &&
					(nontokenDelims != null && nontokenDelims.indexOf(c) != -1) ||
					(tokenDelims != null && tokenDelims.indexOf(c) != -1)){
					// There is delimiter at the very start of the string
					// so we must return an empty token at the beginning.
					emptyReturned = true;
					/*System.out.println("Empty token at beginning.");*/
					return true;
				}
			}
		}
		// The main loop
		// Do this as long as parts of the string have yet to be examined
		while (position != -1){
			char c = text.charAt(position);
			if (returnEmptyTokens && !emptyReturned && position > delimsChangedPosition){
				char c1 = text.charAt(position - 1);
				// Examine the current character and the one before it.
				// If both of them are delimiters, then we need to return
				// an empty delimiter.  Note that characters that were examined
				// before the delimiters changed should not be reexamined.
				if (c <= maxDelimChar && c1 <= maxDelimChar &&
					((nontokenDelims != null && nontokenDelims.indexOf(c) != -1) ||
					(tokenDelims != null && tokenDelims.indexOf(c) != -1)) &&
					((nontokenDelims != null && nontokenDelims.indexOf(c1) != -1) ||
					(tokenDelims != null && tokenDelims.indexOf(c1) != -1))){
					emptyReturned = true;
					/*System.out.println("Empty token.");*/
					return true;
				}
			}

			int nextDelimiter = (position < strLength - 1 ? indexOfNextDelimiter(position + 1) : -1);
			if (c > maxDelimChar ||
				((nontokenDelims == null || nontokenDelims.indexOf(c) == -1) &&
				(tokenDelims == null || tokenDelims.indexOf(c) == -1))){
				// token found
				/*System.out.println("Token: '" +
					text.substring(position, (nextDelimiter == -1 ? strLength : nextDelimiter)) +
					"' at " + position + ".");*/
				position = nextDelimiter;
				emptyReturned = false;
				return true;
			} else if (tokenDelims != null && tokenDelims.indexOf(c) != -1) {
				// delimiter that can be returned as a token found
				emptyReturned = false;
				/*System.out.println("Delimiter: '" + c + "' at " + position + ".");*/
				position = (position < strLength -1 ? position +1 : -1);
				return true;
			} else {
				// delimiter that is not a token found.
				emptyReturned = false;
				position = (position < strLength -1 ? position +1 : -1);
				return false;
			}
		}
		// handle the case that a token is at the end of the string and we should
		// return empty tokens.
		if (returnEmptyTokens && !emptyReturned && strLength > 0){
			char c = text.charAt(strLength - 1);
			if (c <= maxDelimChar &&
				(nontokenDelims != null && nontokenDelims.indexOf(c) != -1) ||
				(tokenDelims != null && tokenDelims.indexOf(c) != -1)){
				// empty token at the end of the string found.
				emptyReturned = true;
				/*System.out.println("Empty token at end.");*/
				return true;
			}
		}
		return false;
	}

	/**
	 * Returns the next token in this string tokenizer's string.
	 * <p>
	 * First, the sets of token and nontoken delimiters are changed to be the
	 * <code>tokenDelims</code> and <code>nontokenDelims</code>, respectively.
	 * Then the next token (with respect to new delimiters) in the string after the
	 * current position is returned.
	 * <p>
	 * The current position is set after the token returned.
	 * <p>
	 * The new delimiter sets remains the used ones after this call.
	 *
	 * @param nontokenDelims the new set of nontoken delimiters.
	 * @param tokenDelims the new set of token delimiters.
	 * @return the next token, after switching to the new delimiter set.
	 * @throws NoSuchElementException if there are no more tokens in this tokenizer's string.
	 * @see #nextToken()
	 *
	 * @since ostermillerutils 1.00.00
	 */
	public String nextToken(String nontokenDelims, String tokenDelims){
		setDelims(nontokenDelims, tokenDelims);
		return nextToken();
	}

	/**
	 * Returns the next token in this string tokenizer's string.
	 * <p>
	 * First, the sets of token and nontoken delimiters are changed to be the
	 * <code>tokenDelims</code> and <code>nontokenDelims</code>, respectively;
	 * and whether or not to return empty tokens is set.
	 * Then the next token (with respect to new delimiters) in the string after the
	 * current position is returned.
	 * <p>
	 * The current position is set after the token returned.
	 * <p>
	 * The new delimiter set remains the one used for this call and empty tokens are
	 * returned in the future as they are in this call.
	 *
	 * @param nontokenDelims the new set of nontoken delimiters.
	 * @param tokenDelims the new set of token delimiters.
	 * @param returnEmptyTokens true if empty tokens may be returned; false otherwise.
	 * @return the next token, after switching to the new delimiter set.
	 * @throws NoSuchElementException if there are no more tokens in this tokenizer's string.
	 * @see #nextToken()
	 *
	 * @since ostermillerutils 1.00.00
	 */
	public String nextToken(String nontokenDelims, String tokenDelims, boolean returnEmptyTokens){
		setDelims(nontokenDelims, tokenDelims);
		setReturnEmptyTokens(returnEmptyTokens);
		return nextToken();
	}

	/**
	 * Returns the next token in this string tokenizer's string.
	 * <p>
	 * Is equivalent to:
	 * <ul>
	 * <li> If the second parameter is <code>false</code> --
	 *      <code>nextToken(delims, null)</code>
	 * <li> If the second parameter is <code>true</code> --
	 *      <code>nextToken(null ,delims)</code>
	 * </ul>
	 * <p>
	 * @param delims the new set of token or nontoken delimiters.
	 * @param delimsAreTokens
	 *     flag indicating whether the first parameter specifies token or
	 *     nontoken delimiters: <code>false</code> -- the first parameter
	 *     specifies nontoken delimiters, the set of token delimiters is
	 *     empty; <code>true</code> -- the first parameter specifies token
	 *     delimiters, the set of nontoken delimiters is empty.
	 * @return the next token, after switching to the new delimiter set.
	 * @throws NoSuchElementException if there are no more tokens in this tokenizer's string.
	 *
	 * @see #nextToken(String,String)
	 * @since ostermillerutils 1.00.00
	 */
	public String nextToken(String delims, boolean delimsAreTokens){
		return (delimsAreTokens ? nextToken(null, delims) : nextToken(delims, null));
	}

	/**
	 * Returns the next token in this string tokenizer's string.
	 * <p>
	 * Is equivalent to <code>nextToken(delims, null)</code>.
	 *
	 * @param nontokenDelims the new set of nontoken delimiters (the set of
	 *     token delimiters will be empty).
	 * @return the next token, after switching to the new delimiter set.
	 * @throws NoSuchElementException if there are no more tokens in this
	 *     tokenizer's string.
	 *
	 * @see #nextToken(String,String)
	 * @since ostermillerutils 1.00.00
	 */
	public String nextToken(String nontokenDelims){
		return nextToken(nontokenDelims, null);
	}

	/**
	 * Similar to String.indexOf(int, String) but will look for
	 * any character from string rather than the entire string.
	 *
	 * @param start index in text at which to begin the search
	 * @return index of the first delimiter from the start index (inclusive), or -1
	 *     if there are no more delimiters in the string
	 *
	 * @since ostermillerutils 1.00.00
	 */
	private int indexOfNextDelimiter(int start){
		char c;
		int next;
		for (next = start; (c = text.charAt(next)) > maxDelimChar ||
			((nontokenDelims == null || nontokenDelims.indexOf(c) == -1) &&
			(tokenDelims == null || tokenDelims.indexOf(c) == -1)); next++){
			if (next == strLength - 1){
				// we have reached the end of the string without
				// finding a delimiter
				return (-1);
			}
		}
		return next;
	}

	/**
	 * Returns the same value as the <code>hasMoreTokens()</code> method. It exists
	 * so that this class can implement the <code>Enumeration</code> interface.
	 *
	 * @return <code>true</code> if there are more tokens;
	 *    <code>false</code> otherwise.
	 *
	 * @see java.util.Enumeration
	 * @see #hasMoreTokens()
	 * @since ostermillerutils 1.00.00
	 */
	public boolean hasMoreElements(){
		return hasMoreTokens();
	}

	/**
	 * Returns the same value as the <code>nextToken()</code> method, except that
	 * its declared return value is <code>Object</code> rather than
	 * <code>String</code>. It exists so that this class can implement the
	 * <code>Enumeration</code> interface.
	 *
	 * @return the next token in the string.
	 * @throws NoSuchElementException if there are no more tokens in this tokenizer's string.
	 *
	 * @see java.util.Enumeration
	 * @see #nextToken()
	 * @since ostermillerutils 1.00.00
	 */
	public Object nextElement(){
		return nextToken();
	}

	/**
	 * Returns the same value as the <code>hasMoreTokens()</code> method. It exists
	 * so that this class can implement the <code>Iterator</code> interface.
	 *
	 * @return <code>true</code> if there are more tokens;
	 *     <code>false</code> otherwise.
	 *
	 * @see java.util.Iterator
	 * @see #hasMoreTokens()
	 * @since ostermillerutils 1.00.00
	 */
	public boolean hasNext(){
		return hasMoreTokens();
	}

	/**
	 * Returns the same value as the <code>nextToken()</code> method, except that
	 * its declared return value is <code>Object</code> rather than
	 * <code>String</code>. It exists so that this class can implement the
	 * <code>Iterator</code> interface.
	 *
	 * @return the next token in the string.
	 * @throws NoSuchElementException if there are no more tokens in this tokenizer's string.
	 *
	 * @see java.util.Iterator
	 * @see #nextToken()
	 * @since ostermillerutils 1.00.00
	 */
	public Object next(){
		return nextToken();
	}

	/**
	 * This implementation always throws <code>UnsupportedOperationException</code>.
	 * It exists so that this class can implement the <code>Iterator</code> interface.
	 *
	 * @throws UnsupportedOperationException always is thrown.
	 *
	 * @see java.util.Iterator
	 * @since ostermillerutils 1.00.00
	 */
	public void remove(){
		throw new UnsupportedOperationException();
	}

	/**
	 * Set whether empty tokens should be returned from this point in
	 * in the tokenizing process onward.
	 * <P>
	 * Empty tokens occur when two delimiters are next to each other
	 * or a delimiter occurs at the beginning or end of a string. If
	 * empty tokens are set to be returned, and a comma is the non token
	 * delimiter, the following table shows how many tokens are in each
	 * string.<br>
	 * <table><tr><th>String<th><th>Number of tokens<th></tr>
	 * <tr><td align=right>"one,two"<td><td>2 - normal case with no empty tokens.<td></tr>
	 * <tr><td align=right>"one,,three"<td><td>3 including the empty token in the middle.<td></tr>
	 * <tr><td align=right>"one,"<td><td>2 including the empty token at the end.<td></tr>
	 * <tr><td align=right>",two"<td><td>2 including the empty token at the beginning.<td></tr>
	 * <tr><td align=right>","<td><td>2 including the empty tokens at the beginning and the ends.<td></tr>
	 * <tr><td align=right>""<td><td>1 - all strings will have at least one token if empty tokens are returned.<td></tr></table>
	 *
	 * @param returnEmptyTokens true iff empty tokens should be returned.
	 *
	 * @since ostermillerutils 1.00.00
	 */
	public void setReturnEmptyTokens(boolean returnEmptyTokens){
		// this could effect the number of tokens
		tokenCount = -1;
		this.returnEmptyTokens = returnEmptyTokens;
	}

	/**
	 * Get the the index of the character immediately
	 * following the end of the last token.  This is the position at which this tokenizer will begin looking
	 * for the next token when a <code>nextToken()</code> method is invoked.
	 *
	 * @return the current position or -1 if the entire string has been tokenized.
	 *
	 * @since ostermillerutils 1.00.00
	 */
	public int getCurrentPosition(){
		return this.position;
	}

	/**
	 * Retrieve all of the remaining tokens in a String array.
	 * This method uses the options that are currently set for
	 * the tokenizer and will advance the state of the tokenizer
	 * such that <code>hasMoreTokens()</code> will return false.
	 *
	 * @return an array of tokens from this tokenizer.
	 *
	 * @since ostermillerutils 1.00.00
	 */
	public String[] toArray(){
		String[] tokenArray = new String[countTokens()];
		for(int i=0; hasMoreTokens(); i++) {
			tokenArray[i] = nextToken();
		}
		return tokenArray;
	}

	/**
	 * Retrieves the rest of the text as a single token.
	 * After calling this method hasMoreTokens() will always return false.
	 *
	 * @return any part of the text that has not yet been tokenized.
	 *
	 * @since ostermillerutils 1.00.00
	 */
	public String restOfText(){
		return nextToken(null, null);
	}

	/**
	 * Returns the same value as nextToken() but does not alter
	 * the internal state of the Tokenizer.  Subsequent calls
	 * to peek() or a call to nextToken() will return the same
	 * token again.
	 *
	 * @return the next token from this string tokenizer.
	 * @throws NoSuchElementException if there are no more tokens in this tokenizer's string.
	 *
	 * @since ostermillerutils 1.00.00
	 */
	public String peek(){
		// copy over state variables from the class to local
		// variables so that the state of this object can be
		// restored to the state that it was in before this
		// method was called.
		int savedPosition = position;
		boolean savedEmptyReturned = emptyReturned;
		int savedtokenCount = tokenCount;

		// get the next token
		String retval = nextToken();

		// restore the state
		position = savedPosition;
		emptyReturned = savedEmptyReturned;
		tokenCount = savedtokenCount;

		// return the nextToken;
		return(retval);
	}
}

class UnsupportedOperationException extends RuntimeException {

}
