Files
Brainstorm/Common/src/com/common/util/AdvancedTextParser.java
2014-05-30 10:31:51 -07:00

706 lines
31 KiB
Java

/*
* Copyright (c) 2003,2009 Declarative Engineering LLC.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Declarative Engineering LLC
* verson 1 which accompanies this distribution, and is available at
* http://declarativeengineering.com/legal/DE_Developer_License_v1.txt
*/
package com.common.util;
/**
* This string parser uses delimiter strings to parse a source character data into segements.
* While the string parser is not thread safe, the set of delimiters is since it cannot be modified once used. Therefore you may create multiple string parsers using the same delimiter set at the same time.<p>
* <p><b>WARNING: This class is NOT thread safe.</b></p>
* <p><b>WARNING: This class is NOT serializable.</b></p>
*/
public class AdvancedTextParser {
private String source = null; //The source string to parse.//
private int currentPosition = 0; //The current position in the parsed string. This index is of the next character in the string that has NOT been looked at.//
private int firstPosition = 0; //The first position in the parsed string.//
private int lastPosition = 0; //The last position in the parsed string.//
private boolean isLastDelimiter = false; //Whether the last element returned was a delimiter.//
private DelimiterSet delimiterSet = null; //The set of delimiters used by the parser.//
private final CharacterHolder characterHolder = new CharacterHolder(); //A simple holder for characters used when querying the delimiter tree. This saves on GC time and is allowed because this class is NOT thread safe.//
private DelimiterEndNode nextDelimiter = null; //The next known delimiter in the source. If there are no more delimiters this will be null and the nextDelimiterPosition will be -1; otherwise this will be null if the next delimiter has not yet be located.//
private int nextDelimiterPosition = 0; //The position of the next delimiter. This will only be -1 if there are no more delimiters. This is not valid if nextDelimiter is null.//
private int lastDelimiterIdentifier = 0; //The user assigned number associated with the last delimiter returned by this parser. Delimiters not returned by the parser are ignored. A value of zero indicates an unknown identifier.//
private int[] previousPositions = null; //The beginning position of the last parsed segment.//
public static class DelimiterSet {
private LiteHashMap rootMap = null; //The root map of character holders to delimiter nodes for the set.//
private boolean isImmutable = false; //Whether the set cannot be modified. This will be automatically set to true when the set is first used.//
private DelimiterSet() {
rootMap = new LiteHashMap(9);
}//DelimiterSet()//
}//DelimiterSet//
private static class CharacterHolder {
private char character;
private CharacterHolder() {
}//CharacterHolder()//
private CharacterHolder(char character) {
this.character = character;
}//CharacterHolder()//
public int hashCode() {
return (int) character;
}//hashCode()//
public boolean equals(Object object) {
return (object instanceof CharacterHolder) && (((CharacterHolder) object).character == character);
}//equals()//
public String toString() {
return "" + character;
}//toString()//
}//CharacterHolder//
private static class DelimiterNode {
private LiteHashMap characterMap = null;
private DelimiterEndNode endNode = null;
private DelimiterNode parent = null;
protected DelimiterNode() {
}//DelimiterNode()//
protected DelimiterNode(DelimiterNode parent) {
this.parent = parent;
}//DelimiterNode()//
protected DelimiterNode(DelimiterEndNode endNode) {
this.endNode = endNode;
}//DelimiterNode()//
private DelimiterNode(DelimiterNode parent, DelimiterEndNode endNode) {
this.parent = parent;
this.endNode = endNode;
}//DelimiterNode()//
}//DelimiterNode//
private static class DelimiterEndNode {
private String delimiter = null;
private int identifier = 0;
private boolean returnDelimiter = false;
private boolean ignoreCase = false;
private boolean requiresTerminationValidation = false; //Whether terminating characters must be verified.//
private boolean canOccurAtSourceEnd = false;
private LiteHashSet terminatingCharacters = null; //A map of characters that are allowed to terminate the delimiter (not included in the delimiter). This is null if it does not apply to this delimiter.//
private IList terminatingStrings = null; //A list of terminating strings that are allowed to terminate the delimiter. All strings will be longer than one character. This may be null if it does not apply.//
private DelimiterEndNode() {
}//DelimiterEndNode()//
private DelimiterEndNode(String delimiter, boolean returnDelimiter, boolean ignoreCase, IList terminators, int identifier) {
this.delimiter = delimiter;
this.returnDelimiter = returnDelimiter;
this.ignoreCase = ignoreCase;
this.identifier = identifier;
if((terminators != null) && (terminators.getSize() > 0)) {
IIterator iterator = terminators.iterator();
requiresTerminationValidation = true;
while(iterator.hasNext()) {
String next = (String) iterator.next();
if((next == null) || (next.length() == 0)) {
canOccurAtSourceEnd = true;
}//if//
else if(next.length() == 1) {
if(terminatingCharacters == null) {
terminatingCharacters = new LiteHashSet(7);
}//if//
terminatingCharacters.add(new CharacterHolder(next.charAt(0)));
}//if//
else {
if(terminatingStrings == null) {
terminatingStrings = new LiteList(5, 10);
}//if//
terminatingStrings.add(next);
}//else//
}//while//
}//if//
}//DelimiterEndNode()//
}//DelimiterEndNode//
/**
* Constructs a parser for the specified source.
* @param source The source to parse.
*/
public AdvancedTextParser(String source) {
this(source, 1);
}//AdvancedTextParser()//
/**
* Constructs a parser for the specified source.
* @param source The source to parse.
* @param previousPositionCount The number of previous positions to track. If this is 2 for example, then the last two positions in the source will be saved and will be retrievable.
*/
public AdvancedTextParser(String source, int previousPositionCount) {
setSource(source);
previousPositions = new int[previousPositionCount < 1 ? 1 : previousPositionCount];
}//AdvancedTextParser()//
/**
* Constructs a parser for the specified source.
* <p><b>WARNING: Once used, the delimiter set cannot be changed. All additions to the set must occur before using the set.</b>
* @param source The source to parse.
* @param previousPositionCount The number of previous positions to track. If this is 2 for example, then the last two positions in the source will be saved and will be retrievable.
* @param delimiterSet The set of all delimiters used to parse the source. This set may be changed at any time while parsing.
*/
public AdvancedTextParser(String source, int previousPositionCount, DelimiterSet delimiterSet) {
setSource(source);
setDelimiterSet(delimiterSet);
previousPositions = new int[previousPositionCount < 1 ? 1 : previousPositionCount];
}//AdvancedTextParser()//
/**
* Constructs a parser for the specified source.
* <p><b>WARNING: Once used, the delimiter set cannot be changed. All additions to the set must occur before using the set.</b>
* @param source The source to parse.
* @param delimiterSet The set of all delimiters used to parse the source. This set may be changed at any time while parsing.
*/
public AdvancedTextParser(String source, DelimiterSet delimiterSet) {
this(source, 1, delimiterSet);
}//AdvancedTextParser()//
/**
* Adds a delimiter to the collection.
* @param delimiterSet The set of delimiters to add the delimiter to. This may be null in which case a new set will be created.
* @param delimiter The delimiter string to add to the collection of delimiters.
* @param returnable Whether the delimiter should be returned by the parser when it is found. If this is false then this delimiter will be ignored and will mearly separate other content.
* @param terminators The collection of strings that can terminate the delimiter. This may be null if any character may terminate the delimiter. A null of empty terminator indicates that the delimiter can occur as the last characters in the source.
* @return The updated delimiter set. This should be used in place of the set passed to this method for future calls since it may or may not be the same object as the passed set.
*/
public static DelimiterSet addDelimiter(DelimiterSet delimiterSet, String delimiter, boolean returnable, boolean ignoreCase, IList terminators) {
if(delimiterSet == null) {
delimiterSet = new DelimiterSet();
}//if//
if(delimiterSet.isImmutable) {
throw new RuntimeException("Cannot modify an immutable delimiter set.");
}//if//
internalAddDelimiter(delimiterSet, delimiter, returnable, ignoreCase, terminators, 0);
return delimiterSet;
}//addDelimiter()//
/**
* Adds a delimiter to the collection.
* @param delimiterSet The set of delimiters to add the delimiter to. This may be null in which case a new set will be created.
* @param delimiter The delimiter string to add to the collection of delimiters.
* @param returnable Whether the delimiter should be returned by the parser when it is found. If this is false then this delimiter will be ignored and will mearly separate other content.
* @param terminators The collection of strings that can terminate the delimiter. This may be null if any character may terminate the delimiter. A null of empty terminator indicates that the delimiter can occur as the last characters in the source.
* @param identifier The identifier associated with the delimiter. This must be a value greater than zero (zero is used for unknown).
* @return The updated delimiter set. This should be used in place of the set passed to this method for future calls since it may or may not be the same object as the passed set.
*/
public static DelimiterSet addDelimiter(DelimiterSet delimiterSet, String delimiter, boolean returnable, boolean ignoreCase, IList terminators, int identifier) {
if(delimiterSet == null) {
delimiterSet = new DelimiterSet();
}//if//
if(delimiterSet.isImmutable) {
throw new RuntimeException("Cannot modify an immutable delimiter set.");
}//if//
internalAddDelimiter(delimiterSet, delimiter, returnable, ignoreCase, terminators, identifier);
return delimiterSet;
}//addDelimiter()//
/**
* Adds delimiters to the collection.
* @param delimiterSet The set of delimiters to add the delimiter to. This may be null in which case a new set will be created.
* @param delimiters The delimiter strings to add to the collection of delimiters.
* @param returnable Whether the delimiter should be returned by the parser when it is found. If this is false then this delimiter will be ignored and will mearly separate other content.
* @param terminators The collection of strings that can terminate the delimiter. This may be null if any character may terminate the delimiter. A null of empty terminator indicates that the delimiter can occur as the last characters in the source.
* @return The updated delimiter set. This should be used in place of the set passed to this method for future calls since it may or may not be the same object as the passed set.
*/
public static DelimiterSet addDelimiters(DelimiterSet delimiterSet, String[] delimiters, boolean returnable, boolean ignoreCase, IList terminators) {
if(delimiterSet == null) {
delimiterSet = new DelimiterSet();
}//if//
if(delimiterSet.isImmutable) {
throw new RuntimeException("Cannot modify an immutable delimiter set.");
}//if//
for(int index = 0; index < delimiters.length; index++) {
internalAddDelimiter(delimiterSet, delimiters[index], returnable, ignoreCase, terminators, 0);
}//for//
return delimiterSet;
}//addDelimiters()//
/**
* Adds delimiters to the collection.
* @param delimiterSet The set of delimiters to add the delimiter to. This may be null in which case a new set will be created.
* @param delimiters The delimiter strings to add to the collection of delimiters.
* @param returnable Whether the delimiter should be returned by the parser when it is found. If this is false then this delimiter will be ignored and will mearly separate other content.
* @param terminators The collection of strings that can terminate the delimiter. This may be null if any character may terminate the delimiter. A null of empty terminator indicates that the delimiter can occur as the last characters in the source.
* @return The updated delimiter set. This should be used in place of the set passed to this method for future calls since it may or may not be the same object as the passed set.
*/
public static DelimiterSet addDelimiters(DelimiterSet delimiterSet, IList delimiters, boolean returnable, boolean ignoreCase, IList terminators) {
IIterator iterator = delimiters.iterator();
if(delimiterSet == null) {
delimiterSet = new DelimiterSet();
}//if//
if(delimiterSet.isImmutable) {
throw new RuntimeException("Cannot modify an immutable delimiter set.");
}//if//
while(iterator.hasNext()) {
internalAddDelimiter(delimiterSet, (String) iterator.next(), returnable, ignoreCase, terminators, 0);
}//while//
return delimiterSet;
}//addDelimiters()//
/**
* Gets the parser's current position in the string.
* @return The current index in the string (points to the next character to be looked at).
*/
public int getCurrentPosition() {
return currentPosition;
}//getCurrentPosition()//
/**
* Gets the set of delimiters that is being used to parse the source from the current position.
* @return The immutable set of all delimiters used in parsing the source from the current source location (previous parsing may have already occured).
*/
public DelimiterSet getDelimiterSet() {
return delimiterSet;
}//getDelimiterSet()//
/**
* Gets the parser's first character position in the source string.
* @return The first read index in the source string.
*/
public int getFirstPosition() {
return firstPosition;
}//getFirstPosition()//
/**
* Gets the last delimiter's identifier.
* @return The user assigned identifier of the last returned delimiter. The value is zero if no delimiters have been retrieved yet or if the last delimiter was not assigned an identifier.
* @see #isLastDelimiter()
*/
public int getLastDelimiterIdentifier() {
return lastDelimiterIdentifier;
}//getLastDelimiterIdentifier()//
/**
* Gets the parser's last character position in the source string.
* @return The last read index in the source string.
*/
public int getLastPosition() {
return lastPosition;
}//getLastPosition()//
/**
* Gets the location of a previously returned source segment.
* The index will be of the first character in the segment.
* How many steps backward the user can go is dependant on the <code>previousPositionCount</code> parameter passed in the constructor (defaults to 1).
* @param stepCount The number of steps backward in the parser where a value of zero indicates the most recently parsed segement.
* @return The index of the first character in the most recently returned source segment. This will be zero if there is no previous segment.
*/
public int getPreviousPosition(int stepCount) {
return previousPositions[stepCount];
}//getPreviousPosition()//
/**
* Gets the source string being parsed.
* @return The string being parsed into segments.
*/
public String getSource() {
return source;
}//getSource()//
/**
* Determines whether there is a next segment.
* @return Whether there is another string segment to be returned by the next() method.
* @see #next()
*/
public boolean hasNext() {
//If the next delimiter is unkown then locate it.//
if((nextDelimiter == null) && (nextDelimiterPosition != -1)) {
locateNextDelimiter();
}//if//
//Ignore hidden delimiters.//
while((nextDelimiterPosition == currentPosition) && (!nextDelimiter.returnDelimiter)) {
currentPosition += nextDelimiter.delimiter.length();
locateNextDelimiter();
}//while//
//Determine whether there are more source characters left (that are not hidden).//
return currentPosition <= lastPosition;
}//hasNext()//
/**
* Recusivly builds the delimiter tree for the give end node.
* @param character The next character in the delimiter. This character should not be modified.
* @param characterMap The hashmap to place the delimiter within.
* @param previousNode The last node in the tree that was checked for the given end node.
* @param endNode The end node that requires placement.
* @param index The index of the next character in the end node's delimiter. This also is the depth within the tree.
* @param ignoreCase Whether the delimiter character case should be ignored. This should only be true if necessary since it can dramatically increase the size of the delimiter tree structure.
*/
private static void internalAddDelimiter(char character, LiteHashMap characterMap, DelimiterNode previousNode, DelimiterEndNode endNode, int index, boolean ignoreCase) {
DelimiterNode node = null;
CharacterHolder characterHolder = new CharacterHolder(character);
node = (DelimiterNode) characterMap.get(characterHolder);
if(node == null) {
//It does not matter how long the delimiter since there is currently only one with this starting character.//
characterMap.put(characterHolder, new DelimiterNode(previousNode, endNode));
}//if//
else {
//Shift the node's end node if necessary to a lower position in the tree.//
if((node.endNode != null) && (node.endNode.delimiter.length() > index)) {
internalAddDelimiter(node, node.endNode, index, ignoreCase);
node.endNode = null;
}//if//
//If the new end node has a length greater than the index then push it farther down in the tree.//
if(endNode.delimiter.length() > index) {
internalAddDelimiter(node, endNode, index, ignoreCase);
}//if//
else {
//Since the node has a length equal to index then we will place it here in the tree.//
node.endNode = endNode;
}//else//
}//else//
}//internalAddDelimiter()//
/**
* Recusivly builds the delimiter tree for the give end node.
* @param previousNode The last node in the tree that was checked for the given end node.
* @param endNode The end node that requires placement.
* @param index The index of the next character in the end node's delimiter. This also is the depth within the tree.
* @param ignoreCase Whether the delimiter character case should be ignored. This should only be true if necessary since it can dramatically increase the size of the delimiter tree structure.
*/
private static void internalAddDelimiter(DelimiterNode previousNode, DelimiterEndNode endNode, int index, boolean ignoreCase) {
char character;
if(previousNode.characterMap == null) {
previousNode.characterMap = new LiteHashMap(4);
}//if//
//Get the next character in the delimiter and increment the character index.//
character = endNode.delimiter.charAt(index++);
//Determine how to add the delimiter to the root mapping based on whether the case is ignored.//
if((ignoreCase) && (Character.isLetter(character))) {
internalAddDelimiter(Character.toUpperCase(character), previousNode.characterMap, previousNode, endNode, index, ignoreCase);
internalAddDelimiter(Character.toLowerCase(character), previousNode.characterMap, previousNode, endNode, index, ignoreCase);
}//if//
else {
internalAddDelimiter(character, previousNode.characterMap, previousNode, endNode, index, ignoreCase);
}//else//
}//internalAddDelimiter()//
/**
* Adds a delimiter to the collection.
* @param delimiterSet The set of delimiters to add the delimiter to.
* @param delimiter The delimiter string to add to the collection of delimiters.
* @param returnable Whether the delimiter should be returned by the parser when it is found. If this is false then this delimiter will be ignored and will mearly separate other content.
* @param ignoreCase Whether the delimiter character case should be ignored. This should only be true if necessary since it can dramatically increase the size of the delimiter tree structure.
* @param terminators The collection of strings that can terminate the delimiter. This may be null if any character may terminate the delimiter. A null of empty terminator indicates that the delimiter can occur as the last characters in the source.
* @param identifier The number assigned to this delimiter. A value of zero indicates an unknown delimiter. The value should be positive and should not overlap with other identifiers. It is up to the application to validate these values.
*/
private static void internalAddDelimiter(DelimiterSet delimiterSet, String delimiter, boolean returnable, boolean ignoreCase, IList terminators, int identifier) {
DelimiterEndNode endNode = new DelimiterEndNode(delimiter, returnable, ignoreCase, terminators, identifier);
char character;
//Check for programmer errors.//
if((delimiter == null) || (delimiter == "")) {
throw new RuntimeException("Invalid delimiter");
}//if//
character = delimiter.charAt(0);
//Determine how to add the delimiter to the root mapping based on whether the case is ignored.//
if((ignoreCase) && (Character.isLetter(character))) {
internalAddDelimiter(Character.toUpperCase(character), delimiterSet.rootMap, null, endNode, 1, ignoreCase);
internalAddDelimiter(Character.toLowerCase(character), delimiterSet.rootMap, null, endNode, 1, ignoreCase);
}//if//
else {
internalAddDelimiter(character, delimiterSet.rootMap, null, endNode, 1, ignoreCase);
}//else//
}//internalAddDelimiter()//
/**
* Determines whether the last element returned was a delimiter element.
* @return Whether the last element was a delimiter element.
*/
public boolean isLastDelimiter() {
return isLastDelimiter;
}//isLastDelimiter()//
/**
* Determines whether the next element returned is going to be a delimiter element.
* @return Whether the next element will be a delimiter element. This will always be false if all delimiters are not ever returned. This will also be false if hasNext returns false.
*/
public boolean isNextDelimiter() {
//If the next delimiter is unkown then locate it.//
if((nextDelimiter == null) && (nextDelimiterPosition != -1)) {
locateNextDelimiter();
}//if//
//Ignore hidden delimiters.//
while((nextDelimiterPosition == currentPosition) && (!nextDelimiter.returnDelimiter)) {
currentPosition += nextDelimiter.delimiter.length();
locateNextDelimiter();
}//while//
//Determine whether the next segment returned will be a delimiter.//
return nextDelimiterPosition == currentPosition;
}//isNextDelimiter()//
/**
* Locates the next delimiter in the source.
* If there are no more delimiters then <code>nextDelimiter</code> will be <code>null</code> and <code>nextDelimiterPosition</code> will be <code>-1</code>,
* otherwise the <code>nextDelimiterPosition</code> will be the absolute source index of the next delimiter and <code>nextDelimiter</code> will reference the DelimiterEndNode of that delimiter.
*/
private void locateNextDelimiter() {
DelimiterNode node = null;
DelimiterNode nextNode = null;
int positionOffset;
DelimiterEndNode nextDelimiter = null;
//Start at the current position.//
int nextPosition = currentPosition;
//Keep reading characters until a valid delimiter is found or the end of the source string is found.//
while((nextDelimiter == null) && (nextPosition <= lastPosition)) {
positionOffset = 0;
characterHolder.character = source.charAt(nextPosition);
nextNode = (DelimiterNode) delimiterSet.rootMap.get(characterHolder);
//Locate the longest valid delimiter if one or more match the string from the next position.//
while(nextNode != null) {
node = nextNode;
positionOffset++;
//Make sure there are enough source characters.//
if(lastPosition - nextPosition - positionOffset < 0) {
nextNode = null;
}//if//
else {
//Check the next source character.//
characterHolder.character = source.charAt(nextPosition + positionOffset);
nextNode = (DelimiterNode) (node.characterMap != null ? node.characterMap.get(characterHolder) : null);
}//else//
}//while//
//Search valid nodes for a valid delimiter.//
while(node != null) {
if(node.endNode != null) {
int difference = node.endNode.delimiter.length() - positionOffset;
//Start looking at this delimiter and all previous delimiters.//
if((difference == 0) || ((lastPosition >= difference + nextPosition + positionOffset) && (node.endNode.delimiter.regionMatches(node.endNode.ignoreCase, positionOffset, source, nextPosition + positionOffset, difference)))) {
if(validateDelimiter(node.endNode, nextPosition + node.endNode.delimiter.length())) {
nextDelimiter = node.endNode;
node = null;
continue;
}//if//
}//if//
}//if//
//Move up the node chain.//
node = node.parent;
}//while//
//If we did not find a delimiter beginning on the nextPosition index then increment the index.//
if(nextDelimiter == null) {
nextPosition++;
}//if//
}//while//
//We now have the position of the next delimiter: nextPosition; and we have the delimiter: nextDelimiter.//
if(nextDelimiter != null) {
this.nextDelimiter = nextDelimiter;
this.nextDelimiterPosition = nextPosition;
}//if//
else {
this.nextDelimiter = null;
this.nextDelimiterPosition = -1;
}//else//
}//locateNextDelimiter()//
/**
* Gets the next string segment in the parse operation.
* @return The next available string segment.
*/
public String next() {
String retVal = null;
//If the next delimiter is unkown then locate it.//
if((nextDelimiter == null) && (nextDelimiterPosition != -1)) {
locateNextDelimiter();
}//if//
//Ignore hidden delimiters.//
while((nextDelimiterPosition == currentPosition) && (!nextDelimiter.returnDelimiter)) {
currentPosition += nextDelimiter.delimiter.length();
locateNextDelimiter();
}//while//
//Get the next segment of the source.//
if(nextDelimiterPosition == currentPosition) {
retVal = nextDelimiter.delimiter;
isLastDelimiter = true;
lastDelimiterIdentifier = nextDelimiter.identifier;
nextDelimiter = null;
}//if//
else {
retVal = nextDelimiterPosition == -1 ? source.substring(currentPosition, lastPosition + 1) : source.substring(currentPosition, nextDelimiterPosition);
isLastDelimiter = false;
}//else//
//Save the previous position in the source.//
if(previousPositions != null) {
for(int index = previousPositions.length - 1; index > 0; index--) {
previousPositions[index] = previousPositions[index - 1];
}//for//
previousPositions[0] = currentPosition;
}//if//
//Increment the current position.//
currentPosition += retVal.length();
return retVal;
}//next()//
/**
* Resets the current position to the first position in the string.
*/
public void reset() {
resetTo(firstPosition);
}//reset()//
/**
* Resets the current position to the given position in the string.
* @param index The position to move to. The index will point to the first character to next be parsed.
*/
public void resetTo(int index) {
if((index < firstPosition) || (index > lastPosition + 1)) {
throw new IndexOutOfBoundsException("The index " + index + " must be greater than or equal to " + firstPosition + " and less than or equal to " + (lastPosition + 1) + ".");
}//if//
this.currentPosition = index;
this.nextDelimiter = null;
this.nextDelimiterPosition = 0;
//Save the previous position in the source.//
if(previousPositions != null) {
for(int index2 = previousPositions.length - 1; index2 >= 0; index2--) {
previousPositions[index2] = firstPosition;
}//for//
}//if//
}//resetTo()//
/**
* Resets the current position to the first position in the string.
*/
public void resetToFront() {
resetTo(firstPosition);
}//resetToFront()//
/**
* Sets the set of delimiters that will be used to parse the source from the current position.
* <p><b>WARNING: Once used, the delimiter set cannot be changed. All additions to the set must occur before using the set.</b>
* @param delimiterSet The immutable set of all delimiters used in parsing the source from the current source location (previous parsing may have already occured).
*/
public void setDelimiterSet(DelimiterSet delimiterSet) {
this.delimiterSet = delimiterSet;
delimiterSet.isImmutable = true;
}//setDelimiterSet()//
/**
* Sets the string to parse.
* @param source The string to parse into segments.
*/
public void setSource(String source) {
setSource(source, 0, source.length() - 1);
}//setString()//
/**
* Sets the string to parse.
* @param source The string to parse into segments.
* @param startIndex The first (inclusive) character index in the source to include when parsing.
* @param endIndex The last (inclusive) character index in the source to include when parsing.
*/
public void setSource(String source, int startIndex, int endIndex) {
this.source = source;
this.firstPosition = startIndex;
this.lastPosition = endIndex >= source.length() ? source.length() - 1 : endIndex;
this.currentPosition = 0;
this.nextDelimiter = null;
this.nextDelimiterPosition = 0;
//Save the previous position in the source.//
if(previousPositions != null) {
for(int index = previousPositions.length - 1; index >= 0; index--) {
previousPositions[index] = firstPosition;
}//for//
}//if//
}//setString()//
/**
* Skips the next <code>skipCount</code> characters in the source.
* @param skipCount The number of characters to be skipped.
*/
public void skipCharacters(char[] characters) {
String source = getSource();
int index = getCurrentPosition();
boolean done = false;
while(!done) {
char ch = source.charAt(index);
boolean contained = false;
for(int charIndex = 0; (!contained) && (charIndex < characters.length); charIndex++) {
if(ch == characters[charIndex]) {
contained = true;
}//if//
}//for//
if(!contained) {
done = true;
}//if//
else if(index == lastPosition) {
done = true;
index++;
}//else if//
else {
index++;
}//else//
}//while//
resetTo(index);
}//skipCharacters()//
/**
* Skips the next <code>skipCount</code> characters in the source.
* @param skipCount The number of characters to be skipped.
*/
public void skipCharacters(int skipCount) {
resetTo(getCurrentPosition() + skipCount);
}//skipCharacters()//
/**
*
* @param endNode The endNode being validated.
* @param sourcePosition The position in the source of the next valid character after the delimiter.
*/
private boolean validateDelimiter(DelimiterEndNode endNode, int sourcePosition) {
boolean isValid = false;
//Verify end conditions.//
if(endNode.requiresTerminationValidation) {
int remainingSourceCharacterCount = source.length() - sourcePosition;
//If this is the end of the source then check to see if the delimiter can end the source.//
if(remainingSourceCharacterCount == 0) {
isValid = endNode.canOccurAtSourceEnd;
}//if//
else {
//Check terminating characters.//
if(endNode.terminatingCharacters != null) {
characterHolder.character = source.charAt(sourcePosition);
if(endNode.terminatingCharacters.containsValue(characterHolder)) {
isValid = true;
}//if//
}//if//
//Check terminating strings.//
if((!isValid) && (endNode.terminatingStrings != null)) {
IIterator iterator = endNode.terminatingStrings.iterator();
while((!isValid) && (iterator.hasNext())) {
String terminatingString = (String) iterator.next();
isValid = (remainingSourceCharacterCount >= terminatingString.length()) && (source.regionMatches(false, sourcePosition, terminatingString, 0, terminatingString.length()));
}//while//
}//if//
}//else//
}//if//
else {
isValid = true;
}//else//
return isValid;
}//validateDelimiter()//
}//AdvancedTextParser//