blob: 0794dd1e19d1fc2f436f789cfcb6e8fda7ffdcd5 [file] [log] [blame]
/*
*******************************************************************************
* Copyright (C) 1996-2000, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*
* $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/text/SearchIterator.java,v $
* $Date: 2002/07/12 21:59:22 $
* $Revision: 1.11 $
*
*****************************************************************************************
*/
package com.ibm.icu.text;
import java.text.CharacterIterator;
/**
* <p>SearchIterator is an abstract base class that defines a protocol
* for text searching. Subclasses provide concrete implementations of
* various search algorithms. A concrete subclass, StringSearch, is
* provided that implements language-sensitive pattern matching based
* on the comparison rules defined in a RuleBasedCollator
* object. Instances of SearchIterator maintain a current position and
* scan over the target text, returning the indices where a match is
* found and the length of each match. Generally, the sequence of forward
* matches will be equivalent to the sequence of backward matches.One
* case where this statement may not hold is when non-overlapping mode
* is set on and there are continuous repetitive patterns in the text.
* Consider the case searching for pattern "aba" in the text
* "ababababa", setting overlapping mode off will produce forward matches
* at offsets 0, 4. However when a backwards search is done, the
* results will be at offsets 6 and 2.</p>
*
* <p>If matches searched for have boundary restrictions. BreakIterators
* can be used to define the valid boundaries of such a match. Once a
* BreakIterator is set, potential matches will be tested against the
* BreakIterator to determine if the boundaries are valid and that all
* characters in the potential match are equivalent to the pattern
* searched for. For example, looking for the pattern "fox" in the text
* "foxy fox" will produce match results at offset 0 and 5 with length 3
* if no BreakIterators were set. However if a WordBreakIterator is set,
* the only match that would be found will be at the offset 5. Since,
* the SearchIterator guarantees that if a BreakIterator is set, all its
* matches will match the given pattern exactly, a potential match that
* passes the BreakIterator might still not produce a valid match. For
* instance the pattern "e" will not be found in the string
* "&#92;u00e9" (latin small letter e with acute) if a
* CharacterBreakIterator is used. Even though "e" is
* a part of the character "&#92;u00e9" and the potential match at
* offset 0 length 1 passes the CharacterBreakIterator test, "&#92;u00e9"
* is not equivalent to "e", hence the SearchIterator rejects the potential
* match. By default, the SearchIterator
* does not impose any boundary restriction on the matches, it will
* return all results that match the pattern. Illustrating with the
* above example, "e" will
* be found in the string "&#92;u00e9" if no BreakIterator is
* specified.</p>
*
* <p>SearchIterator also provides a means to handle overlapping
* matches via the API setOverlapping(boolean). For example, if
* overlapping mode is set, searching for the pattern "abab" in the
* text "ababab" will match at positions 0 and 2, whereas if
* overlapping is not set, SearchIterator will only match at position
* 0. By default, overlapping mode is not set.</p>
*
* <p>The APIs in SearchIterator are similar to that of other text
* iteration classes such as BreakIterator. Using this class, it is
* easy to scan through text looking for all occurances of a
* match.</p>
* <p>
* Example of use:<br>
* <pre>
* String target = "The quick brown fox jumped over the lazy fox";
* String pattern = "fox";
* SearchIterator iter = new StringSearch(pattern, target);
* for (int pos = iter.first(); pos != SearchIterator.DONE;
* pos = iter.next()) {
* // println matches at offset 16 and 41 with length 3
* System.out.println("Found match at " + pos + ", length is "
* + iter.getMatchLength());
* }
* target = "ababababa";
* pattern = "aba";
* iter.setTarget(new StringCharacterIterator(pattern));
* iter.setOverlapping(false);
* System.out.println("Overlapping mode set to false");
* System.out.println("Forward matches of pattern " + pattern + " in text "
* + text + ": ");
* for (int pos = iter.first(); pos != SearchIterator.DONE;
* pos = iter.next()) {
* // println matches at offset 0 and 4 with length 3
* System.out.println("offset " + pos + ", length "
* + iter.getMatchLength());
* }
* System.out.println("Backward matches of pattern " + pattern + " in text "
* + text + ": ");
* for (int pos = iter.last(); pos != SearchIterator.DONE;
* pos = iter.previous()) {
* // println matches at offset 6 and 2 with length 3
* System.out.println("offset " + pos + ", length "
* + iter.getMatchLength());
* }
* System.out.println("Overlapping mode set to true");
* System.out.println("Index set to 2");
* iter.setIndex(2);
* iter.setOverlapping(true);
* System.out.println("Forward matches of pattern " + pattern + " in text "
* + text + ": ");
* for (int pos = iter.first(); pos != SearchIterator.DONE;
* pos = iter.next()) {
* // println matches at offset 2, 4 and 6 with length 3
* System.out.println("offset " + pos + ", length "
* + iter.getMatchLength());
* }
* System.out.println("Index set to 2");
* iter.setIndex(2);
* System.out.println("Backward matches of pattern " + pattern + " in text "
* + text + ": ");
* for (int pos = iter.last(); pos != SearchIterator.DONE;
* pos = iter.previous()) {
* // println matches at offset 0 with length 3
* System.out.println("offset " + pos + ", length "
* + iter.getMatchLength());
* }
* </pre>
* </p>
* @author Laura Werner, synwee
* @since release 1.0
* @draft release 2.2
* @see BreakIterator
*/
public abstract class SearchIterator
{
// public data members -------------------------------------------------
/**
* DONE is returned by previous() and next() after all valid matches have
* been returned, and by first() and last() if there are no matches at all.
* @see #previous
* @see #next
*/
public static final int DONE = -1;
// public methods -----------------------------------------------------
// public setters -----------------------------------------------------
/**
* <p>
* Sets the position in the target text at which the next search will start.
* This method clears any previous match.
* </p>
* @param position position from which to start the next search
* @exception IndexOutOfBoundsException thrown if argument position is out
* of the target text range.
* @see #getIndex
* @draft release 2.2
*/
public void setIndex(int position) {
if (position < targetText.getBeginIndex()
|| position > targetText.getEndIndex()) {
throw new IndexOutOfBoundsException(
"setIndex(int) expected position to be between " +
targetText.getBeginIndex() + " and " + targetText.getEndIndex());
}
m_setOffset_ = position;
m_reset_ = false;
matchLength = 0;
}
/**
* <p>
* Determines whether overlapping matches are returned. See the class
* documentation for more information about overlapping matches.
* </p>
* <p>
* The default setting of this property is false
* </p>
* @param allowOverlap flag indicator if overlapping matches are allowed
* @see #isOverlapping
* @draft release 2.2
*/
public void setOverlapping(boolean allowOverlap)
{
m_isOverlap_ = allowOverlap;
}
/**
* Set the BreakIterator that is used to restrict the points at which
* matches are detected.
* Using <tt>null</tt> as the parameter is legal; it means that break
* detection should not be attempted.
* See class documentation for more information.
* @param breakiter A BreakIterator that will be used to restrict the
* points at which matches are detected.
* @see #getBreakIterator
* @see BreakIterator
*/
public void setBreakIterator(BreakIterator breakiter)
{
breakIterator = breakiter;
if (breakIterator != null) {
breakIterator.setText(targetText);
}
}
/**
* Set the target text to be searched. Text iteration will then begin at
* the start of the text string. This method is useful if you want to
* reuse an iterator to search within a different body of text.
* @param text new text iterator to look for match,
* @exception IllegalArgumentException thrown when text is null or has
* 0 length
* @see #getTarget
* @draft ICU 2.0
*/
public void setTarget(CharacterIterator text)
{
if (text == null || text.getEndIndex() == text.getIndex()) {
throw new IllegalArgumentException("Illegal null or empty text");
}
targetText = text;
targetText.setIndex(targetText.getBeginIndex());
matchLength = 0;
m_reset_ = true;
m_isForwardSearching_ = true;
if (breakIterator != null) {
breakIterator.setText(targetText);
}
}
// public getters ----------------------------------------------------
/**
* <p>
* Returns the index of the most recent match in the target text.
* This call returns a valid result only after a successful call to
* {@link #first}, {@link #next}, {@link #previous}, or {@link #last}.
* Just after construction, or after a searching method returns
* <tt>DONE</tt>, this method will return <tt>DONE</tt>.
* </p>
* <p>
* Use <tt>getMatchLength</tt> to get the length of the matched text.
* <tt>getMatchedText</tt> will return the subtext in the searched
* target text from index getMatchStart() with length getMatchLength().
* </p>
* @return index to a substring within the text string that is being
* searched.
* @see #getMatchLength
* @see #getMatchedText
* @see #first
* @see #next
* @see #previous
* @see #last
* @see #DONE
* @draft release 2.2
*/
public int getMatchStart()
{
return m_lastMatchStart_;
}
/**
* Return the index in the target text at which the iterator is currently
* positioned.
* If the iteration has gone past the end of the target text, or past
* the beginning for a backwards search, {@link #DONE} is returned.
* @return index in the target text at which the iterator is currently
* positioned.
* @draft release 2.2
* @see #first
* @see #next
* @see #previous
* @see #last
* @see #DONE
*/
public abstract int getIndex();
/**
* <p>
* Returns the length of the most recent match in the target text.
* This call returns a valid result only after a successful
* call to {@link #first}, {@link #next}, {@link #previous}, or
* {@link #last}.
* Just after construction, or after a searching method returns
* <tt>DONE</tt>, this method will return 0. See getMatchStart() for
* more details.
* </p>
* @return The length of the most recent match in the target text, or 0 if
* there is no match.
* @see #getMatchStart
* @see #getMatchedText
* @see #first
* @see #next
* @see #previous
* @see #last
* @see #DONE
*/
public int getMatchLength()
{
return matchLength;
}
/**
* Returns the BreakIterator that is used to restrict the indexes at which
* matches are detected. This will be the same object that was passed to
* the constructor or to <code>setBreakIterator</code>.
* If the BreakIterator has not been set, <tt>null</tt> will be returned.
* See setBreakIterator for more information.
* @return the BreakIterator set to restrict logic matches
* @see #setBreakIterator
* @see BreakIterator
*/
public BreakIterator getBreakIterator()
{
return breakIterator;
}
/**
* Return the target text that is being searched.
* @return target text being searched.
* @see #setTarget
*/
public CharacterIterator getTarget()
{
return targetText;
}
/**
* Returns the text that was matched by the most recent call to
* {@link #first}, {@link #next}, {@link #previous}, or {@link #last}.
* If the iterator is not pointing at a valid match, for instance just
* after construction or after <tt>DONE</tt> has been returned, an empty
* String will be returned. See getMatchStart for more information
* @see #getMatchStart
* @see #getMatchLength
* @see #first
* @see #next
* @see #previous
* @see #last
* @see #DONE
* @return the substring in the target text of the most recent match
*/
public String getMatchedText()
{
if (matchLength > 0) {
int limit = m_lastMatchStart_ + matchLength;
StringBuffer result = new StringBuffer(matchLength);
result.append(targetText.current());
targetText.next();
while (targetText.getIndex() < limit) {
result.append(targetText.current());
targetText.next();
}
targetText.setIndex(m_lastMatchStart_);
return result.toString();
}
return null;
}
// miscellaneous public methods -----------------------------------------
/**
* Search <b>forwards</b> in the target text for the next valid match,
* starting the search from the current iterator position. The iterator is
* adjusted so that its current index, as returned by {@link #getIndex},
* is the starting position of the match if one was found. If a match is
* found, the index of the match is returned, otherwise <tt>DONE</tt> is
* returned. If overlapping mode is set, the beginning of the found match
* can be before the end of the current match, if any.
* @return The starting index of the next forward match after the current
* iterator position, or
* <tt>DONE</tt> if there are no more matches.
* @see #getMatchStart
* @see #getMatchLength
* @see #getMatchedText
* @see #following
* @see #preceding
* @see #previous
* @see #first
* @see #last
* @see #DONE
*/
public int next()
{
int start = targetText.getIndex();
if (m_setOffset_ != DONE) {
start = m_setOffset_;
m_setOffset_ = DONE;
}
if (m_isForwardSearching_) {
if (!m_reset_ &&
start + matchLength >= targetText.getEndIndex()) {
// not enough characters to match
matchLength = 0;
targetText.setIndex(targetText.getEndIndex());
m_lastMatchStart_ = DONE;
return DONE;
}
m_reset_ = false;
}
else {
// switching direction.
// if matchedIndex == USEARCH_DONE, it means that either a
// setIndex has been called or that previous ran off the text
// string. the iterator would have been set to offset 0 if a
// match is not found.
m_isForwardSearching_ = true;
if (start != DONE) {
// there's no need to set the collation element iterator
// the next call to next will set the offset.
return start;
}
}
if (start == DONE) {
start = targetText.getBeginIndex();
}
m_lastMatchStart_ = handleNext(start);
return m_lastMatchStart_;
}
/**
* Search <b>backwards</b> in the target text for the next valid match,
* starting the search from the current iterator position. The iterator is
* adjusted so that its current index, as returned by {@link #getIndex},
* is the starting position of the match if one was found. If a match is
* found, the index is returned, otherwise <tt>DONE</tt> is returned. If
* overlapping mode is set, the end of the found match can be after the
* beginning of the previous match, if any.
* @return The starting index of the next backwards match after the current
* iterator position, or
* <tt>DONE</tt> if there are no more matches.
* @see #getMatchStart
* @see #getMatchLength
* @see #getMatchedText
* @see #following
* @see #preceding
* @see #next
* @see #first
* @see #last
* @see #DONE
*/
public int previous()
{
int start = targetText.getIndex();
if (m_setOffset_ != DONE) {
start = m_setOffset_;
m_setOffset_ = DONE;
}
if (m_reset_) {
m_isForwardSearching_ = false;
m_reset_ = false;
start = targetText.getEndIndex();;
}
if (m_isForwardSearching_ == true) {
// switching direction.
// if matchedIndex == USEARCH_DONE, it means that either a
// setIndex has been called or that next ran off the text
// string. the iterator would have been set to offset textLength if
// a match is not found.
m_isForwardSearching_ = false;
if (start != targetText.getEndIndex()) {
return start;
}
}
else {
if (start == targetText.getBeginIndex()) {
// not enough characters to match
matchLength = 0;
targetText.setIndex(targetText.getBeginIndex());
m_lastMatchStart_ = DONE;
return DONE;
}
}
m_lastMatchStart_ = handlePrevious(start);
return m_lastMatchStart_;
}
/**
* Return true if the overlapping property has been set.
* See setOverlapping(boolean) for more information.
* @see #setOverlapping
* @return true if the overlapping property has been set, false otherwise
* @draft release 2.2
*/
public boolean isOverlapping()
{
return m_isOverlap_;
}
/**
* <p>
* Resets the search iteration. All properties will be reset to their
* default values.
* </p>
* <p>
* If a forward iteration is initiated, the next search will begin at the
* start of the target text. Otherwise, if a backwards iteration is initiated,
* the next search will begin at the end of the target text.
* </p>
* @draft release 2.2
*/
public void reset()
{
// reset is setting the attributes that are already in string search
matchLength = 0;
setIndex(targetText.getBeginIndex());
m_isOverlap_ = false;
m_isForwardSearching_ = true;
m_reset_ = true;
m_setOffset_ = DONE;
}
/**
* Return the index of the first <b>forward</b> match in the target text.
* This method sets the iteration to begin at the start of the
* target text and searches forward from there.
* @return The index of the first forward match, or <code>DONE</code>
* if there are no matches.
* @see #getMatchStart
* @see #getMatchLength
* @see #getMatchedText
* @see #following
* @see #preceding
* @see #next
* @see #previous
* @see #last
* @see #DONE
*/
public final int first()
{
m_isForwardSearching_ = true;
setIndex(targetText.getBeginIndex());
return next();
}
/**
* Return the index of the first <b>forward</b> match in target text that
* is at or after argument <tt>position</tt>.
* This method sets the iteration to begin at the specified
* position in the the target text and searches forward from there.
* @return The index of the first forward match, or <code>DONE</code>
* if there are no matches.
* @see #getMatchStart
* @see #getMatchLength
* @see #getMatchedText
* @see #first
* @see #preceding
* @see #next
* @see #previous
* @see #last
* @see #DONE
*/
public final int following(int position)
{
m_isForwardSearching_ = true;
// position checked in usearch_setOffset
setIndex(position);
return next();
}
/**
* Return the index of the first <b>backward</b> match in target text.
* This method sets the iteration to begin at the end of the
* target text and searches backwards from there.
* @return The starting index of the first backward match, or
* <code>DONE</code> if there are no matches.
* @see #getMatchStart
* @see #getMatchLength
* @see #getMatchedText
* @see #first
* @see #preceding
* @see #next
* @see #previous
* @see #following
* @see #DONE
*/
public final int last()
{
m_isForwardSearching_ = false;
setIndex(targetText.getEndIndex());
return previous();
}
/**
* Return the index of the first <b>backwards</b> match in target
* text that ends at or before argument <tt>position</tt>.
* This method sets the iteration to begin at the argument
* position index of the target text and searches backwards from there.
* @return The starting index of the first backwards match, or
* <code>DONE</code>
* if there are no matches.
* @see #getMatchStart
* @see #getMatchLength
* @see #getMatchedText
* @see #first
* @see #following
* @see #next
* @see #previous
* @see #last
* @see #DONE
*/
public final int preceding(int position)
{
m_isForwardSearching_ = false;
// position checked in usearch_setOffset
setIndex(position);
return previous();
}
// protected data member ----------------------------------------------
/**
* The BreakIterator to define the boundaries of a logical match.
* This value can be a null.
* See class documentation for more information.
* @see #setBreakIterator(BreakIterator)
* @see #getBreakIterator
* @see BreakIterator
*/
protected BreakIterator breakIterator;
/**
* Target text for searching.
* @see #setTarget(CharacterIterator)
* @see #getTarget
*/
protected CharacterIterator targetText;
/**
* Length of the most current match in target text.
* Value 0 is the default value.
* @see #setMatchLength
* @see #getMatchLength
*/
protected int matchLength;
// protected constructor ----------------------------------------------
/**
* Protected constructor for use by subclasses.
* Initializes the iterator with the argument target text for searching
* and sets the BreakIterator.
* See class documentation for more details on the use of the target text
* and BreakIterator.
* @param target The target text to be searched.
* @param breaker A {@link BreakIterator} that is used to determine the
* boundaries of a logical match. This argument can be null.
* @exception IllegalArgumentException thrown when argument target is null,
* or of length 0
* @see BreakIterator
*/
protected SearchIterator(CharacterIterator target, BreakIterator breaker)
{
if (target == null
|| (target.getEndIndex() - target.getBeginIndex()) == 0) {
throw new IllegalArgumentException(
"Illegal argument target. " +
" Argument can not be null or of length 0");
}
targetText = target;
breakIterator = breaker;
if (breakIterator != null) {
breakIterator.setText(target);
}
matchLength = 0;
m_lastMatchStart_ = DONE;
m_isOverlap_ = false;
m_isForwardSearching_ = true;
m_reset_ = true;
m_setOffset_ = DONE;
}
// protected methods --------------------------------------------------
/**
* Sets the length of the most recent match in the target text.
* Subclasses' handleNext() and handlePrevious() methods should call this
* after they find a match in the target text.
* @param length new length to set
* @see #handleNext
* @see #handlePrevious
*/
protected void setMatchLength(int length)
{
matchLength = length;
}
/**
* <p>
* Abstract method that subclasses override to provide the mechanism
* for finding the next <b>forwards</b> match in the target text. This
* allows different subclasses to provide different search algorithms.
* </p>
* <p>
* If a match is found, this function must call setMatchLength(int) to
* set the length of the result match.
* The iterator is adjusted so that its current index, as returned by
* {@link #getIndex}, is the starting position of the match if one was
* found. If a match is not found, <tt>DONE</tt> will be returned.
* </p>
* @param start index in the target text at which the forwards search
* should begin.
* @return the starting index of the next forwards match if found, DONE
* otherwise
* @see #setMatchLength(int)
* @see #handlePrevious(int)
* @see #DONE
*/
protected abstract int handleNext(int start);
/**
* <p>
* Abstract method which subclasses override to provide the mechanism
* for finding the next <b>backwards</b> match in the target text.
* This allows different
* subclasses to provide different search algorithms.
* </p>
* <p>
* If a match is found, this function must call setMatchLength(int) to
* set the length of the result match.
* The iterator is adjusted so that its current index, as returned by
* {@link #getIndex}, is the starting position of the match if one was
* found. If a match is not found, <tt>DONE</tt> will be returned.
* </p>
* @param start index in the target text at which the backwards search
* should begin.
* @return the starting index of the next backwards match if found,
* DONE otherwise
* @see #setMatchLength(int)
* @see #handleNext(int)
* @see #DONE
*/
protected abstract int handlePrevious(int startAt);
// private data members ------------------------------------------------
/**
* Flag indicates if we are doing a forwards search
*/
private boolean m_isForwardSearching_;
/**
* Flag to indicate if overlapping search is to be done.
* E.g. looking for "aa" in "aaa" will yield matches at offset 0 and 1.
*/
private boolean m_isOverlap_;
/**
* Flag indicates if we are at the start of a string search.
* This indicates that we are in forward search and at the start of m_text.
*/
private boolean m_reset_;
/**
* Data member to store user defined position in setIndex().
* If setIndex() is not called, this value will be DONE.
*/
private int m_setOffset_;
/**
* Offset of the beginning of the last match
*/
private int m_lastMatchStart_;
}