/*
 *******************************************************************************
 * Copyright (C) 1996-2010, International Business Machines Corporation and    *
 * others. All Rights Reserved.                                                *
 *******************************************************************************
 */

package com.ibm.icu.text;

import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.Locale;

import com.ibm.icu.impl.CharacterIteratorWrapper;
import com.ibm.icu.impl.Norm2AllModes;
import com.ibm.icu.impl.Normalizer2Impl;
import com.ibm.icu.lang.UCharacter;
import com.ibm.icu.util.ULocale;

/**
 * <p>
 * <code>StringSearch</code> is the concrete subclass of 
 * <code>SearchIterator</code> that provides language-sensitive text searching 
 * based on the comparison rules defined in a {@link RuleBasedCollator} object.
 * </p>
 * <p>
 * <code>StringSearch</code> uses a version of the fast Boyer-Moore search
 * algorithm that has been adapted to work with the large character set of
 * Unicode. Refer to 
 * <a href="http://www.icu-project.org/docs/papers/efficient_text_searching_in_java.html">
 * "Efficient Text Searching in Java"</a>, published in the 
 * <i>Java Report</i> on February, 1999, for further information on the 
 * algorithm.
 * </p>
 * <p>
 * Users are also strongly encouraged to read the section on 
 * <a href="http://www.icu-project.org/userguide/searchString.html">
 * String Search</a> and 
 * <a href="http://www.icu-project.org/userguide/Collate_Intro.html">
 * Collation</a> in the user guide before attempting to use this class.
 * </p>
 * <p>
 * String searching becomes a little complicated when accents are encountered at
 * match boundaries. If a match is found and it has preceding or trailing 
 * accents not part of the match, the result returned will include the 
 * preceding accents up to the first base character, if the pattern searched 
 * for starts an accent. Likewise, 
 * if the pattern ends with an accent, all trailing accents up to the first
 * base character will be included in the result.
 * </p>
 * <p>
 * For example, if a match is found in target text "a&#92;u0325&#92;u0300" for 
 * the pattern
 * "a&#92;u0325", the result returned by StringSearch will be the index 0 and
 * length 3 &lt;0, 3&gt;. If a match is found in the target 
 * "a&#92;u0325&#92;u0300" 
 * for the pattern "&#92;u0300", then the result will be index 1 and length 2 
 * <1, 2>.
 * </p>
 * <p>
 * In the case where the decomposition mode is on for the RuleBasedCollator,
 * all matches that starts or ends with an accent will have its results include 
 * preceding or following accents respectively. For example, if pattern "a" is
 * looked for in the target text "&aacute;&#92;u0325", the result will be
 * index 0 and length 2 &lt;0, 2&gt;.
 * </p>
 * <p>
 * The StringSearch class provides two options to handle accent matching 
 * described below:
 * </p>
 * <p>
 * Let S' be the sub-string of a text string S between the offsets start and 
 * end &lt;start, end&gt;.
 * <br>
 * A pattern string P matches a text string S at the offsets &lt;start, 
 * length&gt; 
 * <br>
 * if
 * <pre> 
 * option 1. P matches some canonical equivalent string of S'. Suppose the 
 *           RuleBasedCollator used for searching has a collation strength of 
 *           TERTIARY, all accents are non-ignorable. If the pattern 
 *           "a&#92;u0300" is searched in the target text 
 *           "a&#92;u0325&#92;u0300", 
 *           a match will be found, since the target text is canonically 
 *           equivalent to "a&#92;u0300&#92;u0325"
 * option 2. P matches S' and if P starts or ends with a combining mark, 
 *           there exists no non-ignorable combining mark before or after S' 
 *           in S respectively. Following the example above, the pattern 
 *           "a&#92;u0300" will not find a match in "a&#92;u0325&#92;u0300", 
 *           since
 *           there exists a non-ignorable accent '&#92;u0325' in the middle of 
 *           'a' and '&#92;u0300'. Even with a target text of 
 *           "a&#92;u0300&#92;u0325" a match will not be found because of the 
 *           non-ignorable trailing accent &#92;u0325.
 * </pre>
 * Option 2. will be the default mode for dealing with boundary accents unless
 * specified via the API setCanonical(boolean).
 * One restriction is to be noted for option 1. Currently there are no 
 * composite characters that consists of a character with combining class > 0 
 * before a character with combining class == 0. However, if such a character 
 * exists in the future, the StringSearch may not work correctly with option 1
 * when such characters are encountered.
 * </p>
 * <p>
 * <tt>SearchIterator</tt> provides APIs to specify the starting position 
 * within the text string to be searched, e.g. <tt>setIndex</tt>,
 * <tt>preceding</tt> and <tt>following</tt>. Since the starting position will 
 * be set as it is specified, please take note that there are some dangerous 
 * positions which the search may render incorrect results:
 * <ul>
 * <li> The midst of a substring that requires decomposition.
 * <li> If the following match is to be found, the position should not be the
 *      second character which requires to be swapped with the preceding 
 *      character. Vice versa, if the preceding match is to be found, 
 *      position to search from should not be the first character which 
 *      requires to be swapped with the next character. E.g certain Thai and
 *      Lao characters require swapping.
 * <li> If a following pattern match is to be found, any position within a 
 *      contracting sequence except the first will fail. Vice versa if a 
 *      preceding pattern match is to be found, a invalid starting point 
 *      would be any character within a contracting sequence except the last.
 * </ul>
 * </p>
 * <p>
 * Though collator attributes will be taken into consideration while 
 * performing matches, there are no APIs provided in StringSearch for setting 
 * and getting the attributes. These attributes can be set by getting the 
 * collator from <tt>getCollator</tt> and using the APIs in 
 * <tt>com.ibm.icu.text.Collator</tt>. To update StringSearch to the new 
 * collator attributes, <tt>reset()</tt> or 
 * <tt>setCollator(RuleBasedCollator)</tt> has to be called.
 * </p>
 * <p>
 * Consult the 
 * <a href="http://www.icu-project.org/userguide/searchString.html">
 * String Search</a> user guide and the <code>SearchIterator</code> 
 * documentation for more information and examples of use.
 * </p>
 * <p>
 * This class is not subclassable
 * </p>
 * @see SearchIterator
 * @see RuleBasedCollator
 * @author Laura Werner, synwee
 * @stable ICU 2.0
 */
// internal notes: all methods do not guarantee the correct status of the 
// characteriterator. the caller has to maintain the original index position
// if necessary. methods could change the index position as it deems fit
public final class StringSearch extends SearchIterator
{
    
    // public constructors --------------------------------------------------
    
    /**
     * Initializes the iterator to use the language-specific rules defined in 
     * the argument collator to search for argument pattern in the argument 
     * target text. The argument breakiter is used to define logical matches.
     * See super class documentation for more details on the use of the target 
     * text and BreakIterator.
     * @param pattern text to look for.
     * @param target target text to search for pattern. 
     * @param collator RuleBasedCollator that defines the language rules
     * @param breakiter 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
     * @see RuleBasedCollator
     * @see SearchIterator
     * @stable ICU 2.0
     */
    public StringSearch(String pattern, CharacterIterator target,
                        RuleBasedCollator collator, BreakIterator breakiter) 
    {
        super(target, breakiter);
        m_textBeginOffset_ = targetText.getBeginIndex();
        m_textLimitOffset_ = targetText.getEndIndex();
        m_collator_ = collator;
        m_colEIter_ = m_collator_.getCollationElementIterator(target);
        m_utilColEIter_ = collator.getCollationElementIterator("");
        m_ceMask_ = getMask(m_collator_.getStrength());
        m_isCanonicalMatch_ = false;
        m_pattern_ = new Pattern(pattern);
        m_matchedIndex_ = DONE;
        m_charBreakIter_ = BreakIterator.getCharacterInstance(/*m_collator_.getLocale(ULocale.ACTUAL_LOCALE)*/);
        m_charBreakIter_.setText(target);
        initialize();
    }

    /**
     * Initializes the iterator to use the language-specific rules defined in 
     * the argument collator to search for argument pattern in the argument 
     * target text. No BreakIterators are set to test for logical matches.
     * @param pattern text to look for.
     * @param target target text to search for pattern. 
     * @param collator RuleBasedCollator that defines the language rules
     * @exception IllegalArgumentException thrown when argument target is null,
     *            or of length 0
     * @see RuleBasedCollator
     * @see SearchIterator
     * @stable ICU 2.0
     */
    public StringSearch(String pattern, CharacterIterator target,
                        RuleBasedCollator collator) 
    {
        this(pattern, target, collator, null/*BreakIterator.getCharacterInstance()*/);
    }

    /**
     * Initializes the iterator to use the language-specific rules and 
     * break iterator rules defined in the argument locale to search for 
     * argument pattern in the argument target text. 
     * See super class documentation for more details on the use of the target 
     * text and BreakIterator.
     * @param pattern text to look for.
     * @param target target text to search for pattern. 
     * @param locale locale to use for language and break iterator rules
     * @exception IllegalArgumentException thrown when argument target is null,
     *            or of length 0. ClassCastException thrown if the collator for 
     *            the specified locale is not a RuleBasedCollator.
     * @see BreakIterator
     * @see RuleBasedCollator
     * @see SearchIterator
     * @stable ICU 2.0
     */
    public StringSearch(String pattern, CharacterIterator target, Locale locale)
    {
        this(pattern, target, ULocale.forLocale(locale));
    }

    /**
     * Initializes the iterator to use the language-specific rules and 
     * break iterator rules defined in the argument locale to search for 
     * argument pattern in the argument target text. 
     * See super class documentation for more details on the use of the target 
     * text and BreakIterator.
     * @param pattern text to look for.
     * @param target target text to search for pattern. 
     * @param locale ulocale to use for language and break iterator rules
     * @exception IllegalArgumentException thrown when argument target is null,
     *            or of length 0. ClassCastException thrown if the collator for 
     *            the specified locale is not a RuleBasedCollator.
     * @see BreakIterator
     * @see RuleBasedCollator
     * @see SearchIterator
     * @stable ICU 3.2
     */
    public StringSearch(String pattern, CharacterIterator target, ULocale locale)
    {
        this(pattern, target, (RuleBasedCollator)Collator.getInstance(locale),
             null/*BreakIterator.getCharacterInstance(locale)*/);
    }

    /**
     * Initializes the iterator to use the language-specific rules and 
     * break iterator rules defined in the default locale to search for 
     * argument pattern in the argument target text. 
     * See super class documentation for more details on the use of the target 
     * text and BreakIterator.
     * @param pattern text to look for.
     * @param target target text to search for pattern. 
     * @exception IllegalArgumentException thrown when argument target is null,
     *            or of length 0. ClassCastException thrown if the collator for 
     *            the default locale is not a RuleBasedCollator.
     * @see BreakIterator
     * @see RuleBasedCollator
     * @see SearchIterator
     * @stable ICU 2.0
     */
    public StringSearch(String pattern, String target) 
    {
        this(pattern, new StringCharacterIterator(target),
             (RuleBasedCollator)Collator.getInstance(),
             null/*BreakIterator.getCharacterInstance()*/);
    }

    // public getters -----------------------------------------------------
    
    /**
     * <p>
     * Gets the RuleBasedCollator used for the language rules.
     * </p>
     * <p>
     * Since StringSearch depends on the returned RuleBasedCollator, any 
     * changes to the RuleBasedCollator result should follow with a call to 
     * either StringSearch.reset() or 
     * StringSearch.setCollator(RuleBasedCollator) to ensure the correct 
     * search behaviour.
     * </p>
     * @return RuleBasedCollator used by this StringSearch
     * @see RuleBasedCollator
     * @see #setCollator
     * @stable ICU 2.0
     */
    public RuleBasedCollator getCollator() 
    {
        return m_collator_;
    }
    
    /**
     * Returns the pattern for which StringSearch is searching for.
     * @return the pattern searched for
     * @stable ICU 2.0
     */
    public String getPattern() 
    {
        return m_pattern_.targetText;
    }
    
    /**
     * Return the index in the target text where the iterator is currently 
     * positioned at. 
     * 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 where the iterator is currently 
     *         positioned at
     * @stable ICU 2.8
     */
    public int getIndex() 
    {
        int result = m_colEIter_.getOffset();
        if (isOutOfBounds(m_textBeginOffset_, m_textLimitOffset_, result)) {
            return DONE;
        }
        return result;
    }
    
    /**
     * Determines whether canonical matches (option 1, as described in the 
     * class documentation) is set.
     * See setCanonical(boolean) for more information.
     * @see #setCanonical
     * @return true if canonical matches is set, false otherwise
     * @stable ICU 2.8
     */
    public boolean isCanonical() 
    {
        return m_isCanonicalMatch_;
    }
    
    // public setters -----------------------------------------------------
    
    /**
     * <p>
     * Sets the RuleBasedCollator to be used for language-specific searching.
     * </p>
     * <p>
     * This method causes internal data such as Boyer-Moore shift tables
     * to be recalculated, but the iterator's position is unchanged.
     * </p>
     * @param collator to use for this StringSearch
     * @exception IllegalArgumentException thrown when collator is null
     * @see #getCollator
     * @stable ICU 2.0
     */
    public void setCollator(RuleBasedCollator collator) 
    {
        if (collator == null) {
            throw new IllegalArgumentException("Collator can not be null");
        }
        m_collator_ = collator;
        m_ceMask_ = getMask(m_collator_.getStrength());
        // if status is a failure, ucol_getAttribute returns UCOL_DEFAULT
        initialize();
        m_colEIter_.setCollator(m_collator_);
        m_utilColEIter_.setCollator(m_collator_);
        m_charBreakIter_ = BreakIterator.getCharacterInstance(/*collator.getLocale(ULocale.VALID_LOCALE)*/);
        m_charBreakIter_.setText(targetText);
    }
    
    /**
     * <p>
     * Set the pattern to search for.  
     * </p>
     * <p>
     * This method causes internal data such as Boyer-Moore shift tables
     * to be recalculated, but the iterator's position is unchanged.
     * </p>
     * @param pattern for searching
     * @see #getPattern
     * @exception IllegalArgumentException thrown if pattern is null or of
     *               length 0
     * @stable ICU 2.0
     */
    public void setPattern(String pattern) 
    {
        if (pattern == null || pattern.length() <= 0) {
            throw new IllegalArgumentException(
                    "Pattern to search for can not be null or of length 0");
        }
        m_pattern_.targetText = pattern;
        initialize();
    }
    
    /**
      * Set the target text to be searched. Text iteration will hence begin at 
     * the start of the text string. This method is useful if you want to 
     * re-use 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
     * @stable ICU 2.8
     */
    public void setTarget(CharacterIterator text)
    {
        super.setTarget(text);
        m_textBeginOffset_ = targetText.getBeginIndex();
        m_textLimitOffset_ = targetText.getEndIndex();
        m_colEIter_.setText(targetText);
        m_charBreakIter_.setText(targetText);
    }
    
    /**
     * <p>
     * Sets the position in the target text which the next search will start 
     * from to the argument. This method clears all previous states.
     * </p>
     * <p>
     * This method takes the argument position and sets the position in the 
     * target text accordingly, without checking if position is pointing to a 
     * valid starting point to begin searching.
     * </p>
     * <p>
     * Search positions that may render incorrect results are highlighted in 
     * the class documentation.
     * </p>
     * @param position index to start next search from.
     * @exception IndexOutOfBoundsException thrown if argument position is out
     *            of the target text range.
     * @see #getIndex
     * @stable ICU 2.8
     */
    public void setIndex(int position)
    {
        super.setIndex(position);
        m_matchedIndex_ = DONE;
        m_colEIter_.setExactOffset(position);
    }
    
    /**
     * <p>
     * Set the canonical match mode. See class documentation for details.
     * The default setting for this property is false.
     * </p>
     * @param allowCanonical flag indicator if canonical matches are allowed
     * @see #isCanonical
     * @stable ICU 2.8
     */
    public void setCanonical(boolean allowCanonical)
    {
        m_isCanonicalMatch_ = allowCanonical;
        if (m_isCanonicalMatch_ == true) {
            if (m_canonicalPrefixAccents_ == null) {
                m_canonicalPrefixAccents_ = new StringBuilder();
            }
            else {
                m_canonicalPrefixAccents_.delete(0, 
                                            m_canonicalPrefixAccents_.length());
            }
            if (m_canonicalSuffixAccents_ == null) {
                m_canonicalSuffixAccents_ = new StringBuilder();
            }
            else {
                m_canonicalSuffixAccents_.delete(0, 
                                            m_canonicalSuffixAccents_.length());
            }
        }
    }
    
    // public miscellaneous methods -----------------------------------------
    
    /** 
     * <p>
     * Resets the search iteration. All properties will be reset to the 
     * default value.
     * </p>
     * <p>
     * Search will begin at the start of the target text if a forward iteration 
     * is initiated before a backwards iteration. Otherwise if a 
     * backwards iteration is initiated before a forwards iteration, the search 
     * will begin at the end of the target text.
     * </p>
     * <p>
     * Canonical match option will be reset to false, ie an exact match.
     * </p>
     * @stable ICU 2.8
     */
    public void reset()
    {
        // reset is setting the attributes that are already in string search, 
        // hence all attributes in the collator should be retrieved without any 
        // problems
        super.reset();
        m_isCanonicalMatch_ = false;
        m_ceMask_ = getMask(m_collator_.getStrength());
        // if status is a failure, ucol_getAttribute returns UCOL_DEFAULT
        initialize();
        m_colEIter_.setCollator(m_collator_);
        m_colEIter_.reset();
        m_utilColEIter_.setCollator(m_collator_);
    }

    // protected methods -----------------------------------------------------
    
    /**
     * <p>
     * Concrete method to provide the mechanism 
     * for finding the next <b>forwards</b> match in the target text.
     * See super class documentation for its use.
     * </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 #handlePrevious(int)
     * @see #DONE
     * @stable ICU 2.8
     */
    protected int handleNext(int start)
    {
        if (m_pattern_.m_CELength_ == 0) {
            matchLength = 0;
            if (m_matchedIndex_ == DONE && start == m_textBeginOffset_) {
                m_matchedIndex_ = start;
                return m_matchedIndex_;
            }
            
            targetText.setIndex(start);
            char ch = targetText.current();
            // ch can never be done, it is handled by next()
            char ch2 = targetText.next();
            if (ch2 == CharacterIterator.DONE) {
                m_matchedIndex_ = DONE;    
            }
            else {
                m_matchedIndex_ = targetText.getIndex();
            }
            if (UTF16.isLeadSurrogate(ch) && UTF16.isTrailSurrogate(ch2)) {
                targetText.next();
                m_matchedIndex_ = targetText.getIndex();
            }
        }
        else {
            if (matchLength <= 0) {
                // we must have reversed direction after we reached the start
                // of the target text
                // see SearchIterator next(), it checks the bounds and returns
                // if it exceeds the range. It does not allow setting of
                // m_matchedIndex
                if (start == m_textBeginOffset_) {
                    m_matchedIndex_ = DONE;
                }
                else {
                    // for boundary check purposes. this will ensure that the
                    // next match will not preceed the current offset
                    // note search->matchedIndex will always be set to something
                    // in the code
                    m_matchedIndex_ = start - 1;
                }
            }
    
            // status checked below
            if (m_isCanonicalMatch_) {
                // can't use exact here since extra accents are allowed.
                handleNextCanonical(start);
            }
            else {
                handleNextExact(start);
            }
        }
        if (m_matchedIndex_ == DONE) {
            targetText.setIndex(m_textLimitOffset_);
        }
        else {
            targetText.setIndex(m_matchedIndex_);
        }
        return m_matchedIndex_;
    }
    
    /**
     * <p>
     * Concrete method to provide the mechanism 
     * for finding the next <b>backwards</b> match in the target text.
     * See super class documentation for its use.
     * </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 #handleNext(int)
     * @see #DONE
     * @stable ICU 2.8
     */
    protected int handlePrevious(int start)
    {
        if (m_pattern_.m_CELength_ == 0) {
            matchLength = 0;
            // start can never be DONE or 0, it is handled in previous
            targetText.setIndex(start);
            char ch = targetText.previous();
            if (ch == CharacterIterator.DONE) {
                m_matchedIndex_ = DONE;
            }
            else {
                m_matchedIndex_ = targetText.getIndex();
                if (UTF16.isTrailSurrogate(ch)) {
                    if (UTF16.isLeadSurrogate(targetText.previous())) {
                        m_matchedIndex_ = targetText.getIndex();
                    }
                }
            }            
        }
        else {
            if (matchLength == 0) {
                // we must have reversed direction after we reached the end
                // of the target text
                // see SearchIterator next(), it checks the bounds and returns
                // if it exceeds the range. It does not allow setting of
                // m_matchedIndex
                m_matchedIndex_ = DONE;
            }
            if (m_isCanonicalMatch_) {
                // can't use exact here since extra accents are allowed.
                handlePreviousCanonical(start);
            }
            else {
                handlePreviousExact(start);
            }
        }

        if (m_matchedIndex_ == DONE) {
            targetText.setIndex(m_textBeginOffset_);
        }
        else {
            targetText.setIndex(m_matchedIndex_);
        }
        return m_matchedIndex_;
    }

    // private static inner classes ----------------------------------------
    
    private static class Pattern 
    {
        // protected methods -----------------------------------------------
        
        /**
         * Pattern string
         */
        protected String targetText;
        /**
         * Array containing the collation elements of targetText
         */
        protected int m_CE_[];
        /**
         * Number of collation elements in m_CE_
         */
        protected int m_CELength_; 
        /**
         * Flag indicator if targetText starts with an accent
         */
        protected boolean m_hasPrefixAccents_;
        /**
         * Flag indicator if targetText ends with an accent
         */
        protected boolean m_hasSuffixAccents_;
        /**
         * Default number of characters to shift for Boyer Moore
         */
        protected int m_defaultShiftSize_;
        /**
         * Number of characters to shift for Boyer Moore, depending on the
         * source text to search
         */
        protected char m_shift_[];
        /**
         * Number of characters to shift backwards for Boyer Moore, depending 
         * on the source text to search
         */
        protected char m_backShift_[];
        
        // protected constructors ------------------------------------------
        
        /**
         * Empty constructor 
         */
        protected Pattern(String pattern) 
        {
            targetText = pattern;
            m_CE_ = new int[INITIAL_ARRAY_SIZE_];    
            m_CELength_ = 0;
            m_hasPrefixAccents_ = false;
            m_hasSuffixAccents_ = false;
            m_defaultShiftSize_ = 1;        
            m_shift_ = new char[MAX_TABLE_SIZE_];
            m_backShift_ = new char[MAX_TABLE_SIZE_];
        }
    }


    // private data members ------------------------------------------------
    
    /**
     * target text begin offset. Each targetText has a valid contiguous region 
     * to iterate and this data member is the offset to the first such
     * character in the region.
     */
    private int m_textBeginOffset_;
    /**
     * target text limit offset. Each targetText has a valid contiguous region 
     * to iterate and this data member is the offset to 1 after the last such
     * character in the region.
     */
    private int m_textLimitOffset_;
    /**
     * Upon completion of a search, m_matchIndex_ will store starting offset in
     * m_text for the match. The Value DONE is the default value. 
     * If we are not at the start of the text or the end of the text and 
     * m_matchedIndex_ is DONE it means that we can find any more matches in 
     * that particular direction
     */
    private int m_matchedIndex_; 
    /**
     * Current pattern to search for
     */
    private Pattern m_pattern_;
    /**
     * Collator whose rules are used to perform the search
     */
    private RuleBasedCollator m_collator_;
    /** 
     * The collation element iterator for the text source.
     */
    private CollationElementIterator m_colEIter_;
    /** 
     * Utility collation element, used throughout program for temporary 
     * iteration.
     */
    private CollationElementIterator m_utilColEIter_;
    /**
     * The mask used on the collation elements to retrieve the valid strength
     * weight 
     */
    private int m_ceMask_;
    /**
     * Buffer storing accents during a canonical search
     */
    private StringBuilder m_canonicalPrefixAccents_;
    /**
     * Buffer storing accents during a canonical search
     */
    private StringBuilder m_canonicalSuffixAccents_;
    /**
     * Flag to indicate if canonical search is to be done.
     * E.g looking for "a\u0300" in "a\u0318\u0300" will yield the match at 0.
     */
    private boolean m_isCanonicalMatch_;
    /**
     * Character break iterator for boundary checking.
     */
    private BreakIterator m_charBreakIter_;
    private final Normalizer2Impl m_nfcImpl_ = Norm2AllModes.getNFCInstance().impl;
    /**
     * Size of the shift tables
     */
    private static final int MAX_TABLE_SIZE_ = 257; 
    /**
     * Initial array size
     */
    private static final int INITIAL_ARRAY_SIZE_ = 256;
    /**
     * Utility mask
     */
    private static final int SECOND_LAST_BYTE_SHIFT_ = 8;
    /**
     * Utility mask
     */
    private static final int LAST_BYTE_MASK_ = 0xff;
    /**
     * Utility buffer for return values and temporary storage
     */
    private int m_utilBuffer_[] = new int[2];
    /**
     *  Unsigned 32-Bit Integer Mask
     */
    private static final long UNSIGNED_32BIT_MASK = 0xffffffffL;

    // private methods -------------------------------------------------------

    /**
     * Hash a collation element from its full size (32 bits) down into a
     * value that can be used as an index into the shift tables.  Right
     * now we do a modulus by the size of the hash table.
     * @param ce collation element
     * @return collapsed version of the collation element
     */
    private static final int hash(int ce) 
    {
        // the old value UCOL_PRIMARYORDER(ce) % MAX_TABLE_SIZE_ does not work
        // well with the new collation where most of the latin 1 characters
        // are of the value xx000xxx. their hashes will most of the time be 0
        // to be discussed on the hash algo.
        return CollationElementIterator.primaryOrder(ce) % MAX_TABLE_SIZE_;
    }

    private final char getFCD(int c) {
        return (char)m_nfcImpl_.getFCD16(c);
    }
    /**
     * Gets the fcd value for a character at the argument index.
     * This method takes into accounts of the supplementary characters.
     * Note this method changes the offset in the character iterator.
     * @param str UTF16 string where character for fcd retrieval resides
     * @param offset position of the character whose fcd is to be retrieved
     * @return fcd value
     */
    private final char getFCD(CharacterIterator str, int offset)
    {
        char ch = str.setIndex(offset);
        int result = m_nfcImpl_.getFCD16FromSingleLead(ch);
        if (result != 0 && Character.isHighSurrogate(ch)) {
            char c2 = str.next();
            if (Character.isLowSurrogate(c2)) {
                result = m_nfcImpl_.getFCD16(Character.toCodePoint(ch, c2));
            } else {
                result = 0;
            }
        }
        return (char)result;
    }
    /**
     * Gets the FCD value for the code point before the input offset.
     * Modifies the iterator's index.
     * @param iter text iterator
     * @param offset index after the character to test
     * @return FCD value for the character before offset
     */
    private final int getFCDBefore(CharacterIterator iter, int offset) {
        int result;
        iter.setIndex(offset);
        char c = iter.previous();
        if (UTF16.isSurrogate(c)) {
            if (Normalizer2Impl.UTF16Plus.isSurrogateLead(c)) {
                result = 0;
            } else {
                char lead = iter.previous();
                if (Character.isHighSurrogate(lead)) {
                    result = m_nfcImpl_.getFCD16(Character.toCodePoint(lead, c));
                } else {
                    result = 0;
                }
            }
        } else {
            result = m_nfcImpl_.getFCD16FromSingleLead(c);
        }
        return result;
    }
    /**
     * Gets the fcd value for a character at the argument index.
     * This method takes into accounts of the supplementary characters.
     * @param str UTF16 string where character for fcd retrieval resides
     * @param offset position of the character whose fcd is to be retrieved
     * @return fcd value
     */
    private final char getFCD(String str, int offset)
    {
        char ch = str.charAt(offset);
        int result = m_nfcImpl_.getFCD16FromSingleLead(ch);
        if (result != 0 && Character.isHighSurrogate(ch)) {
            char c2;
            if (++offset < str.length() && Character.isLowSurrogate(c2 = str.charAt(offset))) {
                result = m_nfcImpl_.getFCD16(Character.toCodePoint(ch, c2));
            } else {
                result = 0;
            }
        }
        return (char)result;
    }

    /**
    * Getting the modified collation elements taking into account the collation 
    * attributes
    * @param ce 
    * @return the modified collation element
    */
    private final int getCE(int ce)
    {
        // note for tertiary we can't use the collator->tertiaryMask, that
        // is a preprocessed mask that takes into account case options. since
        // we are only concerned with exact matches, we don't need that.
        ce &= m_ceMask_;
        
        if (m_collator_.isAlternateHandlingShifted()) {
            // alternate handling here, since only the 16 most significant 
            // digits is only used, we can safely do a compare without masking
            // if the ce is a variable, we mask and get only the primary values
            // no shifting to quartenary is required since all primary values
            // less than variabletop will need to be masked off anyway.
            if (((m_collator_.m_variableTopValue_  << 16) & UNSIGNED_32BIT_MASK) > (ce & UNSIGNED_32BIT_MASK)) {
                if (m_collator_.getStrength() == Collator.QUATERNARY) {
                    ce = CollationElementIterator.primaryOrder(ce);
                }
                else { 
                    ce = CollationElementIterator.IGNORABLE;
                }
            }
        }
    
        return ce;
    }
    
    /**
     * Appends a int to a int array, increasing the size of the array when 
     * we are out of space.
     * @param offset in array to append to
     * @param value to append
     * @param array to append to
     * @return the array appended to, this could be a new and bigger array
     */
    private static final int[] append(int offset, int value, int array[])
    {
        if (offset >= array.length) {
            int temp[] = new int[offset + INITIAL_ARRAY_SIZE_];
            System.arraycopy(array, 0, temp, 0, array.length);
            array = temp;
        }
        array[offset] = value;
        return array;
    }
    
    /**
     * Initializing the ce table for a pattern. Stores non-ignorable collation 
     * keys. Table size will be estimated by the size of the pattern text. 
     * Table expansion will be perform as we go along. Adding 1 to ensure that 
     * the table size definitely increases.
     * Internal method, status assumed to be a success.
     * @return total number of expansions 
     */
    private final int initializePatternCETable()
    {
        m_utilColEIter_.setText(m_pattern_.targetText);
        
        int offset = 0;
        int result = 0;
        int ce = m_utilColEIter_.next();
    
        while (ce != CollationElementIterator.NULLORDER) {
            int newce = getCE(ce);
            if (newce != CollationElementIterator.IGNORABLE) {
                m_pattern_.m_CE_ = append(offset, newce, m_pattern_.m_CE_);
                offset ++;            
            }
            result += m_utilColEIter_.getMaxExpansion(ce) - 1;
            ce = m_utilColEIter_.next();
        }
    
        m_pattern_.m_CE_ = append(offset, 0, m_pattern_.m_CE_);
        m_pattern_.m_CELength_ = offset;
    
        return result;
    }
    
    /**
     * Initializes the pattern struct.
     * Internal method, status assumed to be success.
     * @return expansionsize the total expansion size of the pattern
     */ 
    private final int initializePattern()
    {
        if (m_collator_.getStrength() == Collator.PRIMARY) {
            m_pattern_.m_hasPrefixAccents_ = false;
            m_pattern_.m_hasSuffixAccents_ = false;
        } else {
            m_pattern_.m_hasPrefixAccents_ = (getFCD(m_pattern_.targetText, 0) 
                                                 >> SECOND_LAST_BYTE_SHIFT_) != 0;
            m_pattern_.m_hasSuffixAccents_ = (getFCD(m_pattern_.targetText.codePointBefore(
                                                        m_pattern_.targetText.length()))
                                                & LAST_BYTE_MASK_) != 0;
        }
        // since intializePattern is an internal method status is a success.
        return initializePatternCETable();   
    }
    
    /**
     * Initializing shift tables, with the default values.
     * If a corresponding default value is 0, the shift table is not set.
     * @param shift table for forwards shift 
     * @param backshift table for backwards shift
     * @param cetable table containing pattern ce
     * @param cesize size of the pattern ces
     * @param expansionsize total size of the expansions
     * @param defaultforward the default forward value
     * @param defaultbackward the default backward value
     */
     private final void setShiftTable(char shift[], 
                                                    char backshift[], 
                                                    int cetable[], int cesize, 
                                                      int expansionsize,
                                                    char defaultforward,
                                                      char defaultbackward)
    {
        // estimate the value to shift. to do that we estimate the smallest 
        // number of characters to give the relevant ces, ie approximately
        // the number of ces minus their expansion, since expansions can come 
        // from a character.
        for (int count = 0; count < MAX_TABLE_SIZE_; count ++) {
            shift[count] = defaultforward;
        }
        cesize --; // down to the last index
        for (int count = 0; count < cesize; count ++) {
            // number of ces from right of array to the count
            int temp = defaultforward - count - 1;
            shift[hash(cetable[count])] = temp > 1 ? ((char)temp) : 1;
        }
        shift[hash(cetable[cesize])] = 1;
        // for ignorables we just shift by one. see test examples.
        shift[hash(0)] = 1;
        
        for (int count = 0; count < MAX_TABLE_SIZE_; count ++) {
            backshift[count] = defaultbackward;
        }
        for (int count = cesize; count > 0; count --) {
            // the original value count does not seem to work
            backshift[hash(cetable[count])] = (char)(count > expansionsize ? 
                                                      count - expansionsize : 1);
        }
        backshift[hash(cetable[0])] = 1;
        backshift[hash(0)] = 1;
    }
    
    /**
     * <p>Building of the pattern collation element list and the Boyer Moore 
     * StringSearch table.</p>
     * <p>The canonical match will only be performed after the default match 
     * fails.</p>
     * <p>For both cases we need to remember the size of the composed and 
     * decomposed versions of the string. Since the Boyer-Moore shift 
     * calculations shifts by a number of characters in the text and tries to 
     * match the pattern from that offset, the shift value can not be too large 
     * in case we miss some characters. To choose a right shift size, we 
     * estimate the NFC form of the and use its size as a shift guide. The NFC 
     * form should be the small possible representation of the pattern. Anyways, 
     * we'll err on the smaller shift size. Hence the calculation for 
     * minlength. Canonical match will be performed slightly differently. We'll 
     * split the pattern into 3 parts, the prefix accents (PA), the middle 
     * string bounded by the first and last base character (MS), the ending 
     * accents (EA). Matches will be done on MS first, and only when we match 
     * MS then some processing will be required for the prefix and end accents 
     * in order to determine if they match PA and EA. Hence the default shift 
     * values for the canonical match will take the size of either end's accent 
     * into consideration. Forwards search will take the end accents into 
     * consideration for the default shift values and the backwards search will 
     * take the prefix accents into consideration.</p>
     * <p>If pattern has no non-ignorable ce, we return a illegal argument 
     * error.</p>
     */ 
    private final void initialize()
    {
        int expandlength  = initializePattern();   
        if (m_pattern_.m_CELength_ > 0) {
            char minlength = (char)(m_pattern_.m_CELength_ > expandlength 
                                ? m_pattern_.m_CELength_ - expandlength : 1);
            m_pattern_.m_defaultShiftSize_ = minlength;
            setShiftTable(m_pattern_.m_shift_, m_pattern_.m_backShift_, 
                          m_pattern_.m_CE_, m_pattern_.m_CELength_, 
                          expandlength, minlength, minlength);
        }
        else {
            m_pattern_.m_defaultShiftSize_ = 0;
        }
    }
    
    /**
     * Determine whether the search text bounded by the offset start and end is 
     * one or more whole units of text as determined by the breakiterator in 
     * StringSearch.
     * @param start target text start offset
     * @param end target text end offset
     */
    private final boolean isBreakUnit(int start, int end) 
    {
        if (breakIterator != null) {
            int startindex = breakIterator.first();
            int endindex   = breakIterator.last();
            
            // out-of-range indexes are never boundary positions
            if (start < startindex || start > endindex || end < startindex 
                || end > endindex) {
                return false;
            }
            // otherwise, we can use following() on the position before the 
            // specified one and return true of the position we get back is the 
            // one the user specified
            boolean result = (start == startindex 
                              || breakIterator.following(start - 1) == start) 
                             && (end == endindex 
                                  || breakIterator.following(end - 1) == end);
            if (result) {
                // iterates the individual ces
                m_utilColEIter_.setText(
                    new CharacterIteratorWrapper(targetText), start);
                for (int count = 0; count < m_pattern_.m_CELength_;
                     count ++) {
                    int ce = getCE(m_utilColEIter_.next());
                    if (ce == CollationElementIterator.IGNORABLE) {
                        count --;
                        continue;
                    }
                    if (ce != m_pattern_.m_CE_[count]) {
                        return false;
                    }
                }
                int nextce = m_utilColEIter_.next();
                while (m_utilColEIter_.getOffset() == end 
                       && getCE(nextce) == CollationElementIterator.IGNORABLE) {
                    nextce = m_utilColEIter_.next();       
                }
                if (nextce != CollationElementIterator.NULLORDER 
                    && m_utilColEIter_.getOffset() == end) {
                    // extra collation elements at the end of the match
                    return false;
                }
            }
            return result;
        }
        return true;
    }

    /**
     * Getting the next base character offset if current offset is an accent, 
     * or the current offset if the current character contains a base character. 
     * accents the following base character will be returned
     * @param text string
     * @param textoffset current offset
     * @param textlength length of text string
     * @return the next base character or the current offset
     *         if the current character is contains a base character.
     */
    private final int getNextBaseOffset(CharacterIterator text, int textoffset)
    {
        if (textoffset >= text.getEndIndex()) {
            return textoffset;
        }
        // iteration ends with reading CharacterIterator.DONE which has fcd==0
        char c = text.setIndex(textoffset);
        for (;;) {
            if ((m_nfcImpl_.getFCD16FromSingleLead(c) >> SECOND_LAST_BYTE_SHIFT_) == 0) {
                return textoffset;
            }
            char next = text.next();
            if (Character.isSurrogatePair(c, next)) {
                int fcd = m_nfcImpl_.getFCD16(Character.toCodePoint(c, next));
                if ((fcd >> SECOND_LAST_BYTE_SHIFT_) == 0) {
                    return textoffset;
                }
                next = text.next();
                textoffset += 2;
            } else {
                ++textoffset;
            }
            c = next;
        }
    }

    /**
     * Gets the next base character offset depending on the string search 
     * pattern data
     * @param textoffset one offset away from the last character
     *                   to search for.
     * @return start index of the next base character or the current offset
     *         if the current character is contains a base character.
     */
    private final int getNextBaseOffset(int textoffset)
    {
        if (m_pattern_.m_hasSuffixAccents_ && textoffset < m_textLimitOffset_) {
            if ((getFCDBefore(targetText, textoffset) & LAST_BYTE_MASK_) != 0) {
                return getNextBaseOffset(targetText, textoffset);
            }
        }
        return textoffset;
    }

    /**
     * Shifting the collation element iterator position forward to prepare for
     * a following match. If the last character is a unsafe character, we'll 
     * only shift by 1 to capture contractions, normalization etc.
     * Internal method, status assumed to be success.
     * @param textoffset start text position to do search
     * @param ce the text ce which failed the match.
     * @param patternceindex index of the ce within the pattern ce buffer which
     *        failed the match
     * @return final offset
     */
    private int shiftForward(int textoffset, int ce, int patternceindex)
                                    
    {
        if (ce != CollationElementIterator.NULLORDER) {
            int shift = m_pattern_.m_shift_[hash(ce)];
            // this is to adjust for characters in the middle of the 
            // substring for matching that failed.
            int adjust = m_pattern_.m_CELength_ - patternceindex;
            if (adjust > 1 && shift >= adjust) {
                shift -= adjust - 1;
            }
            textoffset += shift;
        }
        else {
            textoffset += m_pattern_.m_defaultShiftSize_;
        }
         
        textoffset = getNextBaseOffset(textoffset);
        // check for unsafe characters
        // * if it is the start or middle of a contraction: to be done after 
        //   a initial match is found
        // * thai or lao base consonant character: similar to contraction
        // * high surrogate character: similar to contraction
        // * next character is a accent: shift to the next base character
        return textoffset;
    }
    
    /**
     * Gets the offset to the next safe point in text.
     * ie. not the middle of a contraction, swappable characters or 
     * supplementary characters.
     * @param textoffset offset in string
     * @param end offset in string
     * @return offset to the next safe character
     */
    private final int getNextSafeOffset(int textoffset, int end)
    {
        int result = textoffset; // first contraction character
        targetText.setIndex(result);
        while (result != end && 
            m_collator_.isUnsafe(targetText.current())) {
               result ++;
               targetText.setIndex(result);
        }
        return result; 
    }
    
    /** 
     * This checks for accents in the potential match started with a composite 
     * character.
     * This is really painful... we have to check that composite character do 
     * not have any extra accents. We have to normalize the potential match and 
     * find the immediate decomposed character before the match.
     * The first composite character would have been taken care of by the fcd 
     * checks in checkForwardExactMatch.
     * This is the slow path after the fcd of the first character and 
     * the last character has been checked by checkForwardExactMatch and we 
     * determine that the potential match has extra non-ignorable preceding
     * ces.
     * E.g. looking for \u0301 acute in \u01FA A ring above and acute, 
     * checkExtraMatchAccent should fail since there is a middle ring in 
     * \u01FA Note here that accents checking are slow and cautioned in the API 
     * docs.
     * Internal method, status assumed to be a success, caller should check 
     * status before calling this method
     * @param start index of the potential unfriendly composite character
     * @param end index of the potential unfriendly composite character
     * @return true if there is non-ignorable accents before at the beginning
     *              of the match, false otherwise.
     */
    private final boolean checkExtraMatchAccents(int start, int end)
    {
        boolean result = false;
        if (m_pattern_.m_hasPrefixAccents_) {
            targetText.setIndex(start);
            
            if (UTF16.isLeadSurrogate(targetText.next())) {
                if (!UTF16.isTrailSurrogate(targetText.next())) {
                    targetText.previous();
                }
            }
            // we are only concerned with the first composite character
            String str = getString(targetText, start, end);
            if (Normalizer.quickCheck(str, Normalizer.NFD,0) 
                                                    == Normalizer.NO) {
                int safeoffset = getNextSafeOffset(start, end);
                if (safeoffset != end) {
                    safeoffset ++;
                }
                String decomp = Normalizer.decompose(
                                str.substring(0, safeoffset - start), false);
                m_utilColEIter_.setText(decomp);
                int firstce = m_pattern_.m_CE_[0];
                boolean ignorable = true;
                int ce = CollationElementIterator.IGNORABLE;
                int offset = 0;
                while (ce != firstce) {
                    offset = m_utilColEIter_.getOffset();
                    if (ce != firstce 
                        && ce != CollationElementIterator.IGNORABLE) {
                        ignorable = false;
                    }
                    ce = m_utilColEIter_.next();
                }
                m_utilColEIter_.setExactOffset(offset); // back up 1 to the 
                m_utilColEIter_.previous();             // right offset
                offset = m_utilColEIter_.getOffset();
                result = !ignorable && (UCharacter.getCombiningClass(
                                            UTF16.charAt(decomp, offset)) != 0);
            }
        }
    
        return result;
    }
    
    /**
    * Used by exact matches, checks if there are accents before the match. 
    * This is really painful... we have to check that composite characters at
    * the start of the matches have to not have any extra accents. 
    * We check the FCD of the character first, if it starts with an accent and 
    * the first pattern ce does not match the first ce of the character, we 
    * bail.
    * Otherwise we try normalizing the first composite 
    * character and find the immediate decomposed character before the match to 
    * see if it is an non-ignorable accent.
    * Now normalizing the first composite character is enough because we ensure 
    * that when the match is passed in here with extra beginning ces, the 
    * first or last ce that match has to occur within the first character.
    * E.g. looking for \u0301 acute in \u01FA A ring above and acute, 
    * checkExtraMatchAccent should fail since there is a middle ring in \u01FA
    * Note here that accents checking are slow and cautioned in the API docs.
    * @param start offset 
    * @param end offset
    * @return true if there are accents on either side of the match, 
    *         false otherwise
    */
    private final boolean hasAccentsBeforeMatch(int start, int end) 
    {
        if (m_pattern_.m_hasPrefixAccents_) {
            // we have been iterating forwards previously
            boolean ignorable = true;
            int firstce = m_pattern_.m_CE_[0];
            m_colEIter_.setExactOffset(start);
            int ce  = getCE(m_colEIter_.next());
            while (ce != firstce) {
                if (ce != CollationElementIterator.IGNORABLE) {
                    ignorable = false;
                }
                ce = getCE(m_colEIter_.next());
            }
            if (!ignorable && m_colEIter_.isInBuffer()) {
                // within normalization buffer, discontiguous handled here
                return true;
            }
    
            // within text
            boolean accent = (getFCD(targetText, start) >> SECOND_LAST_BYTE_SHIFT_)
                                                        != 0; 
            if (!accent) {
                return checkExtraMatchAccents(start, end);
            }
            if (!ignorable) {
                return true;
            }
            if (start > m_textBeginOffset_) {
                targetText.setIndex(start);
                targetText.previous();
                if ((getFCD(targetText, targetText.getIndex()) & LAST_BYTE_MASK_) 
                                                                        != 0) {
                    m_colEIter_.setExactOffset(start);
                    ce = m_colEIter_.previous();
                    if (ce != CollationElementIterator.NULLORDER 
                        && ce != CollationElementIterator.IGNORABLE) {
                        return true;
                    }
                }
            }
        }
      
        return false;
    }
    
    /**
     * Used by exact matches, checks if there are accents bounding the match.
     * Note this is the initial boundary check. If the potential match
     * starts or ends with composite characters, the accents in those
     * characters will be determined later.
     * Not doing backwards iteration here, since discontiguos contraction for 
     * backwards collation element iterator, use up too many characters.
     * E.g. looking for \u030A ring in \u01FA A ring above and acute, 
     * should fail since there is a acute at the end of \u01FA
     * Note here that accents checking are slow and cautioned in the API docs.
     * @param start offset of match
     * @param end end offset of the match
     * @return true if there are accents on either side of the match, 
     *         false otherwise
     */
    private final boolean hasAccentsAfterMatch(int start, int end) 
    {
        if (m_pattern_.m_hasSuffixAccents_) {
            targetText.setIndex(end);
            if (end > m_textBeginOffset_ 
                && UTF16.isTrailSurrogate(targetText.previous())) {
                if (targetText.getIndex() > m_textBeginOffset_ &&
                    !UTF16.isLeadSurrogate(targetText.previous())) {
                    targetText.next();
                }
            }
            if ((getFCD(targetText, targetText.getIndex()) & LAST_BYTE_MASK_) != 0) {
                int firstce  = m_pattern_.m_CE_[0];
                m_colEIter_.setExactOffset(start);
                while (getCE(m_colEIter_.next()) != firstce) {
                }
                int count = 1;
                while (count < m_pattern_.m_CELength_) {
                    if (getCE(m_colEIter_.next()) 
                        == CollationElementIterator.IGNORABLE) {
                        count --;
                    }
                    count ++;
                }
                //int ce = getCE(m_colEIter_.next());
                int ce = m_colEIter_.next();
                if (ce != CollationElementIterator.NULLORDER 
                        && ce != CollationElementIterator.IGNORABLE) {
                    ce = getCE(ce);
                }
                if (ce != CollationElementIterator.NULLORDER 
                            && ce != CollationElementIterator.IGNORABLE) {
                    if (m_colEIter_.getOffset() <= end) {
                        return true;
                    }
                    if ((getFCD(targetText, end) >> SECOND_LAST_BYTE_SHIFT_) 
                        != 0) {
                        return true;
                    }
                }
            }
        }
        return false;
    }
    
    /**
    * Checks if the offset runs out of the text string range
    * @param textstart offset of the first character in the range
    * @param textlimit limit offset of the text string range
    * @param offset to test
    * @return true if offset is out of bounds, false otherwise
    */
    private static final boolean isOutOfBounds(int textstart, int textlimit, 
                                                int offset)
    {
        return offset < textstart || offset > textlimit;
    }
    
    /**
     * Checks for identical match
     * @param strsrch string search data
     * @param start offset of possible match
     * @param end offset of possible match
     * @return true if identical match is found
     */
    private final boolean checkIdentical(int start, int end) 
    {
        if (m_collator_.getStrength() != Collator.IDENTICAL) {
            return true;
        }
    
        String textstr = getString(targetText, start, end - start);
        if (Normalizer.quickCheck(textstr, Normalizer.NFD,0) 
                                                    == Normalizer.NO) {
            textstr = Normalizer.decompose(textstr, false);
        }
        String patternstr = m_pattern_.targetText;
        if (Normalizer.quickCheck(patternstr, Normalizer.NFD,0) 
                                                    == Normalizer.NO) {
            patternstr = Normalizer.decompose(patternstr, false);
        }
        return textstr.equals(patternstr);
    }
    
    /**
     * Checks to see if the match is repeated
     * @param start new match start index
     * @param limit new match limit index
     * @return true if the the match is repeated, false otherwise
     */
    private final boolean checkRepeatedMatch(int start, int limit)
    {
        if (m_matchedIndex_ == DONE) {
            return false;
        }
        int end = limit - 1; // last character in the match
        int lastmatchend = m_matchedIndex_ + matchLength - 1; 
        if (!isOverlapping()) {
            return (start >= m_matchedIndex_ && start <= lastmatchend) 
                    || (end >= m_matchedIndex_ && end <= lastmatchend)
                    || (start <= m_matchedIndex_ && end >= lastmatchend);
                      
        }
        return start <= m_matchedIndex_ && end >= lastmatchend;
    }
    
    /**
     * Checks match for contraction. 
     * If the match ends with a partial contraction we fail.
     * If the match starts too far off (because of backwards iteration) we try 
     * to chip off the extra characters depending on whether a breakiterator 
     * has been used.
     * Temporary utility buffer used to return modified start and end.
     * @param start offset of potential match, to be modified if necessary
     * @param end offset of potential match, to be modified if necessary
     * @return true if match passes the contraction test, false otherwise.
     */
    private final boolean checkNextExactContractionMatch(int start, int end) 
    {
        // This part checks if either ends of the match contains potential 
        // contraction. If so we'll have to iterate through them
        char endchar = 0;
        if (end < m_textLimitOffset_) {
            targetText.setIndex(end);
            endchar = targetText.current();
        }
        char poststartchar = 0;
        if (start + 1 < m_textLimitOffset_) {
            targetText.setIndex(start + 1);
            poststartchar = targetText.current();
        }
        if (m_collator_.isUnsafe(endchar) 
            || m_collator_.isUnsafe(poststartchar)) {
            // expansion prefix, what's left to iterate
            int bufferedCEOffset = m_colEIter_.m_CEBufferOffset_;
            boolean hasBufferedCE = bufferedCEOffset > 0;
            m_colEIter_.setExactOffset(start);
            int temp = start;
            while (bufferedCEOffset > 0) {
                // getting rid of the redundant ce, caused by setOffset.
                // since backward contraction/expansion may have extra ces if 
                // we are in the normalization buffer, hasAccentsBeforeMatch 
                // would have taken care of it.
                // E.g. the character \u01FA will have an expansion of 3, but 
                // if we are only looking for acute and ring \u030A and \u0301, 
                // we'll have to skip the first ce in the expansion buffer.
                m_colEIter_.next();
                if (m_colEIter_.getOffset() != temp) {
                    start = temp;
                    temp  = m_colEIter_.getOffset();
                }
                bufferedCEOffset --;
            }
    
            int count = 0;
            while (count < m_pattern_.m_CELength_) {
                int ce = getCE(m_colEIter_.next());
                if (ce == CollationElementIterator.IGNORABLE) {
                    continue;
                }
                if (hasBufferedCE && count == 0 
                    && m_colEIter_.getOffset() != temp) {
                    start = temp;
                    temp   = m_colEIter_.getOffset();
                }
                if (ce != m_pattern_.m_CE_[count]) {
                    end ++;
                    end = getNextBaseOffset(end);  
                    m_utilBuffer_[0] = start;
                    m_utilBuffer_[1] = end;
                    return false;
                }
                count ++;
            }
        } 
        m_utilBuffer_[0] = start;
        m_utilBuffer_[1] = end;
        return true;
    }
    
    
    /**
     * Checks and sets the match information if found.
     * Checks 
     * <ul>
     * <li> the potential match does not repeat the previous match
     * <li> boundaries are correct
     * <li> exact matches has no extra accents
     * <li> identical matchesb
     * <li> potential match does not end in the middle of a contraction
     * </ul>
     * Otherwise the offset will be shifted to the next character.
     * The result m_matchIndex_ and m_matchLength_ will be set to the truncated
     * more fitting result value.
     * Uses the temporary utility buffer for storing the modified textoffset.
     * @param textoffset offset in the collation element text.
     * @return true if the match is valid, false otherwise
     */
    private final boolean checkNextExactMatch(int textoffset)
    {
        int start = m_colEIter_.getOffset();        
        if (!checkNextExactContractionMatch(start, textoffset)) {
            // returns the modified textoffset
            m_utilBuffer_[0] = m_utilBuffer_[1];
            return false;
        }
    
        start = m_utilBuffer_[0];
        textoffset = m_utilBuffer_[1];
        // this totally matches, however we need to check if it is repeating
        if (!isBreakUnit(start, textoffset) 
            || checkRepeatedMatch(start, textoffset) 
            || hasAccentsBeforeMatch(start, textoffset) 
            || !checkIdentical(start, textoffset) 
            || hasAccentsAfterMatch(start, textoffset)) {
            textoffset ++;
            textoffset = getNextBaseOffset(textoffset);  
            m_utilBuffer_[0] = textoffset;
            return false;
        }
        
        if (m_collator_.getStrength() == Collator.PRIMARY) {
            textoffset = checkBreakBoundary(textoffset);
        }
            
        // totally match, we will get rid of the ending ignorables.
        m_matchedIndex_  = start;
        matchLength = textoffset - start;
        return true;
    }
    
    /**
    * Getting the previous base character offset, or the current offset if the 
    * current character is a base character
    * @param text the source text to work on
    * @param textoffset one offset after the current character
    * @return the offset of the next character after the base character or the 
    *             first composed character with accents
    */
    private final int getPreviousBaseOffset(CharacterIterator text, 
                                            int textoffset)
    {
        if (textoffset > m_textBeginOffset_) {
            while (true) {
                int result = textoffset;
                text.setIndex(result);
                if (UTF16.isTrailSurrogate(text.previous())) {
                    if (text.getIndex() != text.getBeginIndex() &&
                        !UTF16.isLeadSurrogate(text.previous())) {
                        text.next();
                    }
                }
                textoffset = text.getIndex();
                char fcd = getFCD(text, textoffset);
                if ((fcd >> SECOND_LAST_BYTE_SHIFT_) == 0) {
                    if ((fcd & LAST_BYTE_MASK_) != 0) {
                        return textoffset;
                    }
                    return result;
                }
                if (textoffset == m_textBeginOffset_) {
                    return m_textBeginOffset_;
                }
            }
        }
        return textoffset;
    }
    
    /**
    * Getting the indexes of the accents that are not blocked in the argument
    * accent array
    * @param accents accents in nfd.
    * @param accentsindex array to store the indexes of accents in accents that 
    *         are not blocked
    * @return the length of populated accentsindex
    */
    private int getUnblockedAccentIndex(StringBuilder accents, 
                                        int accentsindex[])
    {
        int index = 0;
        int length = accents.length();
        int cclass = 0;
        int result = 0;
        while (index < length) {
            int codepoint = UTF16.charAt(accents, index);
            int tempclass = UCharacter.getCombiningClass(codepoint);
            if (tempclass != cclass) {
                cclass = tempclass;
                accentsindex[result] = index;
                result ++;
            }
            if (UCharacter.isSupplementary(codepoint)) {
                index += 2;
            }
            else {
                index ++;
            }
        }
        accentsindex[result] = length;
        return result;
    }

    /**
     * Appends 3 StringBuilder/CharacterIterator together into a destination 
     * string buffer.
     * @param source1 string buffer
     * @param source2 character iterator
     * @param start2 start of the character iterator to merge
     * @param end2 end of the character iterator to merge
     * @param source3 string buffer
     * @return appended string buffer
     */
    private static final StringBuilder merge(StringBuilder source1, 
                                             CharacterIterator source2,
                                             int start2, int end2,
                                             StringBuilder source3) 
    {
        StringBuilder result = new StringBuilder();    
        if (source1 != null && source1.length() != 0) {
            result.append(source1);
        }
        source2.setIndex(start2);
        while (source2.getIndex() < end2) {
            result.append(source2.current());
            source2.next();
        }
        if (source3 != null && source3.length() != 0) {
            result.append(source3);
        }
        return result;
    }
    
    /**
    * Running through a collation element iterator to see if the contents 
    * matches pattern in string search data
    * @param coleiter collation element iterator to test
    * @return true if a match if found, false otherwise
    */
    private final boolean checkCollationMatch(CollationElementIterator coleiter)
    {
        int patternceindex = m_pattern_.m_CELength_;
        int offset = 0;
        while (patternceindex > 0) {
            int ce = getCE(coleiter.next());
            if (ce == CollationElementIterator.IGNORABLE) {
                continue;
            }
            if (ce != m_pattern_.m_CE_[offset]) {
                return false;
            }
            offset ++;
            patternceindex --;
        }
        return true;
    }
    
    /**
     * Rearranges the front accents to try matching.
     * Prefix accents in the text will be grouped according to their combining 
     * class and the groups will be mixed and matched to try find the perfect 
     * match with the pattern.
     * So for instance looking for "\u0301" in "\u030A\u0301\u0325"
     * step 1: split "\u030A\u0301" into 6 other type of potential accent 
     *            substrings "\u030A", "\u0301", "\u0325", "\u030A\u0301", 
     *            "\u030A\u0325", "\u0301\u0325".
     * step 2: check if any of the generated substrings matches the pattern.
     * Internal method, status is assumed to be success, caller has to check 
     * status before calling this method.
     * @param start first offset of the accents to start searching
     * @param end start of the last accent set
     * @return DONE if a match is not found, otherwise return the starting
     *         offset of the match. Note this start includes all preceding 
     *            accents.
     */
    private int doNextCanonicalPrefixMatch(int start, int end)
    {
        if ((getFCD(targetText, start) & LAST_BYTE_MASK_) == 0) {
            // die... failed at a base character
            return DONE;
        }
    
        start = targetText.getIndex(); // index changed by fcd
        int offset = getNextBaseOffset(targetText, start);
        start = getPreviousBaseOffset(start);
    
        StringBuilder accents = new StringBuilder();
        String accentstr = getString(targetText, start, offset - start);
        // normalizing the offensive string
        if (Normalizer.quickCheck(accentstr, Normalizer.NFD,0) 
                                                    == Normalizer.NO) {
            accentstr = Normalizer.decompose(accentstr, false);
        }
        accents.append(accentstr);
            
        int accentsindex[] = new int[INITIAL_ARRAY_SIZE_];      
        int accentsize = getUnblockedAccentIndex(accents, accentsindex);
        int count = (2 << (accentsize - 1)) - 1;  
        while (count > 0) {
            // copy the base characters
            m_canonicalPrefixAccents_.delete(0, 
                                        m_canonicalPrefixAccents_.length());
            int k = 0;
            for (; k < accentsindex[0]; k ++) {
                m_canonicalPrefixAccents_.append(accents.charAt(k));
            }
            // forming all possible canonical rearrangement by dropping
            // sets of accents
            for (int i = 0; i <= accentsize - 1; i ++) {
                int mask = 1 << (accentsize - i - 1);
                if ((count & mask) != 0) {
                    for (int j = accentsindex[i]; j < accentsindex[i + 1]; 
                                                                        j ++) {
                        m_canonicalPrefixAccents_.append(accents.charAt(j));
                    }
                }
            }
            StringBuilder match = merge(m_canonicalPrefixAccents_,
                                       targetText, offset, end,
                                       m_canonicalSuffixAccents_);
                
            // if status is a failure, ucol_setText does nothing.
            // run the collator iterator through this match
            m_utilColEIter_.setText(match.toString());
            if (checkCollationMatch(m_utilColEIter_)) {
                 return start;
            }
            count --;
        }
        return DONE;
    }

    /**
    * Gets the offset to the safe point in text before textoffset.
    * ie. not the middle of a contraction, swappable characters or 
    * supplementary characters.
    * @param start offset in string
    * @param textoffset offset in string
    * @return offset to the previous safe character
    */
    private final int getPreviousSafeOffset(int start, int textoffset)
    {
        int result = textoffset; // first contraction character
        targetText.setIndex(textoffset);
        while (result >= start && m_collator_.isUnsafe(targetText.previous())) {
            result = targetText.getIndex();
        }
        if (result != start) {
            // the first contraction character is consider unsafe here
            result = targetText.getIndex(); // originally result --;
        }
        return result; 
    }

    /**
     * Take the rearranged end accents and tries matching. If match failed at
     * a seperate preceding set of accents (seperated from the rearranged on by
     * at least a base character) then we rearrange the preceding accents and 
     * tries matching again.
     * We allow skipping of the ends of the accent set if the ces do not match. 
     * However if the failure is found before the accent set, it fails.
     * Internal method, status assumed to be success, caller has to check 
     * status before calling this method.
     * @param textoffset of the start of the rearranged accent
     * @return DONE if a match is not found, otherwise return the starting
     *         offset of the match. Note this start includes all preceding 
     *         accents.
     */
    private int doNextCanonicalSuffixMatch(int textoffset)
    {
        int safelength = 0;
        StringBuilder safetext;
        int safeoffset = m_textBeginOffset_; 
        
        if (textoffset != m_textBeginOffset_ 
            && m_canonicalSuffixAccents_.length() > 0
            && m_collator_.isUnsafe(m_canonicalSuffixAccents_.charAt(0))) {
            safeoffset     = getPreviousSafeOffset(m_textBeginOffset_, 
                                                    textoffset);
            safelength     = textoffset - safeoffset;
            safetext       = merge(null, targetText, safeoffset, textoffset, 
                                   m_canonicalSuffixAccents_);
        }
        else {
            safetext = m_canonicalSuffixAccents_;
        }
    
        // if status is a failure, ucol_setText does nothing
        CollationElementIterator coleiter = m_utilColEIter_;
        coleiter.setText(safetext.toString());
        // status checked in loop below
    
        int ceindex = m_pattern_.m_CELength_ - 1;
        boolean isSafe = true; // indication flag for position in safe zone
        
        while (ceindex >= 0) {
            int textce = coleiter.previous();
            if (textce == CollationElementIterator.NULLORDER) {
                // check if we have passed the safe buffer
                if (coleiter == m_colEIter_) {
                    return DONE;
                }
                coleiter = m_colEIter_;
                if (safetext != m_canonicalSuffixAccents_) {
                    safetext.delete(0, safetext.length());
                }
                coleiter.setExactOffset(safeoffset);
                // status checked at the start of the loop
                isSafe = false;
                continue;
            }
            textce = getCE(textce);
            if (textce != CollationElementIterator.IGNORABLE 
                && textce != m_pattern_.m_CE_[ceindex]) {
                // do the beginning stuff
                int failedoffset = coleiter.getOffset();
                if (isSafe && failedoffset >= safelength) {
                    // alas... no hope. failed at rearranged accent set
                    return DONE;
                }
                else {
                    if (isSafe) {
                        failedoffset += safeoffset;
                    }
                    
                    // try rearranging the front accents
                    int result = doNextCanonicalPrefixMatch(failedoffset, 
                                                            textoffset);
                    if (result != DONE) {
                        // if status is a failure, ucol_setOffset does nothing
                        m_colEIter_.setExactOffset(result);
                    }
                    return result;
                }
            }
            if (textce == m_pattern_.m_CE_[ceindex]) {
                ceindex --;
            }
        }
        // set offset here
        if (isSafe) {
            int result = coleiter.getOffset();
            // sets the text iterator with the correct expansion and offset
            int leftoverces = coleiter.m_CEBufferOffset_;
            if (result >= safelength) { 
                result = textoffset;
            }
            else {
                result += safeoffset;
            }
            m_colEIter_.setExactOffset(result);
            m_colEIter_.m_CEBufferOffset_ = leftoverces;
            return result;
        }
        
        return coleiter.getOffset();              
    }
    
    /**
     * Trying out the substring and sees if it can be a canonical match.
     * This will try normalizing the end accents and arranging them into 
     * canonical equivalents and check their corresponding ces with the pattern 
     * ce.
     * Suffix accents in the text will be grouped according to their combining 
     * class and the groups will be mixed and matched to try find the perfect 
     * match with the pattern.
     * So for instance looking for "\u0301" in "\u030A\u0301\u0325"
     * step 1: split "\u030A\u0301" into 6 other type of potential accent 
     *         substrings
     *         "\u030A", "\u0301", "\u0325", "\u030A\u0301", "\u030A\u0325", 
     *         "\u0301\u0325".
     * step 2: check if any of the generated substrings matches the pattern.
     * @param textoffset end offset in the collation element text that ends with 
     *                   the accents to be rearranged
     * @return true if the match is valid, false otherwise
     */
    private boolean doNextCanonicalMatch(int textoffset)
    {
        int offset = m_colEIter_.getOffset();
        targetText.setIndex(textoffset);
        if (UTF16.isTrailSurrogate(targetText.previous()) 
            && targetText.getIndex() > m_textBeginOffset_) { 
            if (!UTF16.isLeadSurrogate(targetText.previous())) {
                targetText.next();
            }
        }
        if ((getFCD(targetText, targetText.getIndex()) & LAST_BYTE_MASK_) == 0) {
            if (m_pattern_.m_hasPrefixAccents_) {
                offset = doNextCanonicalPrefixMatch(offset, textoffset);
                if (offset != DONE) {
                    m_colEIter_.setExactOffset(offset);
                    return true;
                }
            }
            return false;
        }
    
        if (!m_pattern_.m_hasSuffixAccents_) {
            return false;
        }
    
        StringBuilder accents = new StringBuilder();
        // offset to the last base character in substring to search
        int baseoffset = getPreviousBaseOffset(targetText, textoffset);
        // normalizing the offensive string
        String accentstr = getString(targetText, baseoffset, 
                                     textoffset - baseoffset);
        if (Normalizer.quickCheck(accentstr, Normalizer.NFD,0) 
                                                    == Normalizer.NO) {
            accentstr = Normalizer.decompose(accentstr, false);
        }
        accents.append(accentstr);
        // status checked in loop below
            
        int accentsindex[] = new int[INITIAL_ARRAY_SIZE_];
        int size = getUnblockedAccentIndex(accents, accentsindex);
    
        // 2 power n - 1 plus the full set of accents
        int  count = (2 << (size - 1)) - 1;  
        while (count > 0) {
            m_canonicalSuffixAccents_.delete(0, 
                                           m_canonicalSuffixAccents_.length());
            // copy the base characters
            for (int k = 0; k < accentsindex[0]; k ++) {
                m_canonicalSuffixAccents_.append(accents.charAt(k));
            }
            // forming all possible canonical rearrangement by dropping
            // sets of accents
            for (int i = 0; i <= size - 1; i ++) {
                int mask = 1 << (size - i - 1);
                if ((count & mask) != 0) {
                    for (int j = accentsindex[i]; j < accentsindex[i + 1]; 
                        j ++) {
                        m_canonicalSuffixAccents_.append(accents.charAt(j));
                    }
                }
            }
            offset = doNextCanonicalSuffixMatch(baseoffset);
            if (offset != DONE) {
                return true; // match found
            }
            count --;
        }
        return false;
    }
    
    /**
     * Gets the previous base character offset depending on the string search 
     * pattern data
     * @param strsrch string search data
     * @param textoffset current offset, current character
     * @return the offset of the next character after this base character or 
     *             itself if it is a composed character with accents
     */
    private final int getPreviousBaseOffset(int textoffset)
    {
        if (m_pattern_.m_hasPrefixAccents_ && textoffset > m_textBeginOffset_) {
            int offset = textoffset;
            if ((getFCD(targetText, offset) >> SECOND_LAST_BYTE_SHIFT_) != 0) {
                return getPreviousBaseOffset(targetText, textoffset);
            }
        }
        return textoffset;
    }
    
    /**
     * Checks match for contraction. 
     * If the match ends with a partial contraction we fail.
     * If the match starts too far off (because of backwards iteration) we try 
     * to chip off the extra characters.
     * Uses the temporary util buffer for return values of the modified start
     * and end.
     * @param start offset of potential match, to be modified if necessary
     * @param end offset of potential match, to be modified if necessary
     * @return true if match passes the contraction test, false otherwise. 
     */
    private boolean checkNextCanonicalContractionMatch(int start, int end) 
    {
        // This part checks if either ends of the match contains potential 
        // contraction. If so we'll have to iterate through them
        char schar = 0;
        char echar = 0;
        if (end < m_textLimitOffset_) {
            targetText.setIndex(end);
            echar = targetText.current();
        }
        if (start < m_textLimitOffset_) {
            targetText.setIndex(start + 1);
            schar = targetText.current();
        }
        if (m_collator_.isUnsafe(echar) || m_collator_.isUnsafe(schar)) {
            int expansion  = m_colEIter_.m_CEBufferOffset_;
            boolean hasExpansion = expansion > 0;
            m_colEIter_.setExactOffset(start);
            int temp = start;
            while (expansion > 0) {
                // getting rid of the redundant ce, caused by setOffset.
                // since backward contraction/expansion may have extra ces if 
                // we are in the normalization buffer, hasAccentsBeforeMatch 
                // would have taken care of it.
                // E.g. the character \u01FA will have an expansion of 3, but 
                // if we are only looking for acute and ring \u030A and \u0301, 
                // we'll have to skip the first ce in the expansion buffer.
                m_colEIter_.next();
                if (m_colEIter_.getOffset() != temp) {
                    start = temp;
                    temp  = m_colEIter_.getOffset();
                }
                expansion --;
            }
    
            int count = 0;
            while (count < m_pattern_.m_CELength_) {
                int ce = getCE(m_colEIter_.next());
                // status checked below, note that if status is a failure
                // ucol_next returns UCOL_NULLORDER
                if (ce == CollationElementIterator.IGNORABLE) {
                    continue;
                }
                if (hasExpansion && count == 0 
                    && m_colEIter_.getOffset() != temp) {
                    start = temp;
                    temp = m_colEIter_.getOffset();
                }
    
                if (count == 0 && ce != m_pattern_.m_CE_[0]) {
                    // accents may have extra starting ces, this occurs when a 
                    // pure accent pattern is matched without rearrangement
                    // text \u0325\u0300 and looking for \u0300
                    int expected = m_pattern_.m_CE_[0]; 
                    if ((getFCD(targetText, start) & LAST_BYTE_MASK_) != 0) {
                        ce = getCE(m_colEIter_.next());
                        while (ce != expected 
                               && ce != CollationElementIterator.NULLORDER 
                               && m_colEIter_.getOffset() <= end) {
                            ce = getCE(m_colEIter_.next());
                        }
                    }
                }
                if (ce != m_pattern_.m_CE_[count]) {
                    end ++;
                    end = getNextBaseOffset(end);  
                    m_utilBuffer_[0] = start;
                    m_utilBuffer_[1] = end;
                    return false;
                }
                count ++;
            }
        } 
        m_utilBuffer_[0] = start;
        m_utilBuffer_[1] = end;
        return true;
    }

    /**
     * Checks and sets the match information if found.
     * Checks 
     * <ul>
     * <li> the potential match does not repeat the previous match
     * <li> boundaries are correct
     * <li> potential match does not end in the middle of a contraction
     * <li> identical matches
     * </ul>
     * Otherwise the offset will be shifted to the next character.
     * The result m_matchIndex_ and m_matchLength_ will be set to the truncated
     * more fitting result value.
     * Uses the temporary utility buffer for storing the modified textoffset.
     * @param textoffset offset in the collation element text.
     * @return true if the match is valid, false otherwise
     */
    private boolean checkNextCanonicalMatch(int textoffset)
    {
        // to ensure that the start and ends are not composite characters
        // if we have a canonical accent match
        if ((m_pattern_.m_hasSuffixAccents_ 
                && m_canonicalSuffixAccents_.length() != 0) || 
            (m_pattern_.m_hasPrefixAccents_ 
                && m_canonicalPrefixAccents_.length() != 0)) {
            m_matchedIndex_ = getPreviousBaseOffset(m_colEIter_.getOffset());
            matchLength = textoffset - m_matchedIndex_;
            return true;
        }
    
        int start = m_colEIter_.getOffset();
        if (!checkNextCanonicalContractionMatch(start, textoffset)) {
            // return the modified textoffset
            m_utilBuffer_[0] = m_utilBuffer_[1]; 
            return false;
        }
        start = m_utilBuffer_[0];
        textoffset = m_utilBuffer_[1];
        start = getPreviousBaseOffset(start);
        // this totally matches, however we need to check if it is repeating
        if (checkRepeatedMatch(start, textoffset) 
            || !isBreakUnit(start, textoffset) 
            || !checkIdentical(start, textoffset)) {
            textoffset ++;
            textoffset = getNextBaseOffset(targetText, textoffset);
            m_utilBuffer_[0] = textoffset;
            return false;
        }
        
        m_matchedIndex_  = start;
        matchLength = textoffset - start;
        return true;
    }
    
    /**
     * Shifting the collation element iterator position forward to prepare for
     * a preceding match. If the first character is a unsafe character, we'll 
     * only shift by 1 to capture contractions, normalization etc.
     * @param textoffset start text position to do search
     * @param ce the text ce which failed the match.
     * @param patternceindex index of the ce within the pattern ce buffer which
     *        failed the match
     * @return final offset
     */
    private int reverseShift(int textoffset, int ce, int patternceindex)
    {         
        if (isOverlapping()) {
            if (textoffset != m_textLimitOffset_) {
                textoffset --;
            }
            else {
                textoffset -= m_pattern_.m_defaultShiftSize_;
            }
        }
        else {
            if (ce != CollationElementIterator.NULLORDER) {
                int shift = m_pattern_.m_backShift_[hash(ce)];
                
                // this is to adjust for characters in the middle of the substring 
                // for matching that failed.
                int adjust = patternceindex;
                if (adjust > 1 && shift > adjust) {
                    shift -= adjust - 1;
                }
                textoffset -= shift;
            }
            else {
                textoffset -= m_pattern_.m_defaultShiftSize_;
            }
        }    
        
        textoffset = getPreviousBaseOffset(textoffset);
        return textoffset;
    }

    /**
     * Checks match for contraction. 
     * If the match starts with a partial contraction we fail.
     * Uses the temporary utility buffer to return the modified start and end.
     * @param start offset of potential match, to be modified if necessary
     * @param end offset of potential match, to be modified if necessary
     * @return true if match passes the contraction test, false otherwise.
     */
    private boolean checkPreviousExactContractionMatch(int start, int end) 
    {
        // This part checks if either ends of the match contains potential 
        // contraction. If so we'll have to iterate through them
        char echar = 0;
        if (end < m_textLimitOffset_) {
            targetText.setIndex(end);
            echar = targetText.current();
        }
        char schar = 0;
        if (start + 1 < m_textLimitOffset_) {
            targetText.setIndex(start + 1);
            schar = targetText.current();
        }
        if (m_collator_.isUnsafe(echar) || m_collator_.isUnsafe(schar)) {
            // expansion suffix, what's left to iterate
            int expansion = m_colEIter_.m_CEBufferSize_ 
                                            - m_colEIter_.m_CEBufferOffset_;
            boolean hasExpansion = expansion > 0;
            m_colEIter_.setExactOffset(end);
            int temp = end;
            while (expansion > 0) {
                // getting rid of the redundant ce
                // since forward contraction/expansion may have extra ces
                // if we are in the normalization buffer, hasAccentsBeforeMatch
                // would have taken care of it.
                // E.g. the character \u01FA will have an expansion of 3, but if
                // we are only looking for A ring A\u030A, we'll have to skip the 
                // last ce in the expansion buffer
                m_colEIter_.previous();
                if (m_colEIter_.getOffset() != temp) {
                    end = temp;
                    temp = m_colEIter_.getOffset();
                }
                expansion --;
            }
    
            int count = m_pattern_.m_CELength_;
            while (count > 0) {
                int ce = getCE(m_colEIter_.previous());
                // status checked below, note that if status is a failure
                // ucol_previous returns UCOL_NULLORDER
                if (ce == CollationElementIterator.IGNORABLE) {
                    continue;
                }
                if (hasExpansion && count == 0 
                    && m_colEIter_.getOffset() != temp) {
                    end = temp;
                    temp = m_colEIter_.getOffset();
                }
                if (ce != m_pattern_.m_CE_[count - 1]) {
                    start --;
                    start = getPreviousBaseOffset(targetText, start);
                    m_utilBuffer_[0] = start;
                    m_utilBuffer_[1] = end;
                    return false;
                }
                count --;
            }
        } 
        m_utilBuffer_[0] = start;
        m_utilBuffer_[1] = end;
        return true;
    }
    
    /**
     * Checks and sets the match information if found.
     * Checks 
     * <ul>
     * <li> the current match does not repeat the last match
     * <li> boundaries are correct
     * <li> exact matches has no extra accents
     * <li> identical matches
     * </ul>
     * Otherwise the offset will be shifted to the preceding character.
     * Uses the temporary utility buffer to store the modified textoffset.
     * @param textoffset offset in the collation element text. the returned value
     *        will be the truncated start offset of the match or the new start 
     *        search offset.
     * @return true if the match is valid, false otherwise
     */
    private final boolean checkPreviousExactMatch(int textoffset)
    {
        // to ensure that the start and ends are not composite characters
        int end = m_colEIter_.getOffset();        
        if (!checkPreviousExactContractionMatch(textoffset, end)) {
            return false;
        }
        textoffset = m_utilBuffer_[0];
        end = m_utilBuffer_[1];
            
        // this totally matches, however we need to check if it is repeating
        // the old match
        if (checkRepeatedMatch(textoffset, end) 
            || !isBreakUnit(textoffset, end) 
            || hasAccentsBeforeMatch(textoffset, end) 
            || !checkIdentical(textoffset, end) 
            || hasAccentsAfterMatch(textoffset, end)) {
            textoffset --;
            textoffset = getPreviousBaseOffset(targetText, textoffset);
            m_utilBuffer_[0] = textoffset;
            return false;
        }
        
        if (m_collator_.getStrength() == Collator.PRIMARY) {
            end = checkBreakBoundary(end);
        }
        
        m_matchedIndex_ = textoffset;
        matchLength = end - textoffset;
        return true;
    }

    /**
     * Rearranges the end accents to try matching.
     * Suffix accents in the text will be grouped according to their combining 
     * class and the groups will be mixed and matched to try find the perfect 
     * match with the pattern.
     * So for instance looking for "\u0301" in "\u030A\u0301\u0325"
     * step 1: split "\u030A\u0301" into 6 other type of potential accent 
     *             substrings
     *         "\u030A", "\u0301", "\u0325", "\u030A\u0301", "\u030A\u0325", 
     *         "\u0301\u0325".
     * step 2: check if any of the generated substrings matches the pattern.
     * @param start offset of the first base character
     * @param end start of the last accent set
     * @return DONE if a match is not found, otherwise return the ending
     *         offset of the match. Note this start includes all following 
     *         accents.
     */
    private int doPreviousCanonicalSuffixMatch(int start, int end)
    {
        targetText.setIndex(end);
        if (UTF16.isTrailSurrogate(targetText.previous()) 
            && targetText.getIndex() > m_textBeginOffset_) {
            if (!UTF16.isLeadSurrogate(targetText.previous())) {
                targetText.next();
            } 
        }
        if ((getFCD(targetText, targetText.getIndex()) & LAST_BYTE_MASK_) == 0) {
            // die... failed at a base character
            return DONE;
        }
        end = getNextBaseOffset(targetText, end);
    
        StringBuilder accents = new StringBuilder();
        int offset = getPreviousBaseOffset(targetText, end);
        // normalizing the offensive string
        String accentstr = getString(targetText, offset, end - offset);
        if (Normalizer.quickCheck(accentstr, Normalizer.NFD,0) 
                                                    == Normalizer.NO) {
            accentstr = Normalizer.decompose(accentstr, false);
        }
        accents.append(accentstr);    
            
        int accentsindex[] = new int[INITIAL_ARRAY_SIZE_];      
        int accentsize = getUnblockedAccentIndex(accents, accentsindex);
        int count = (2 << (accentsize - 1)) - 1;  
        while (count > 0) {
            m_canonicalSuffixAccents_.delete(0, 
                                           m_canonicalSuffixAccents_.length());
            // copy the base characters
            for (int k = 0; k < accentsindex[0]; k ++) {
                 m_canonicalSuffixAccents_.append(accents.charAt(k));
            }
            // forming all possible canonical rearrangement by dropping
            // sets of accents
            for (int i = 0; i <= accentsize - 1; i ++) {
                int mask = 1 << (accentsize - i - 1);
                if ((count & mask) != 0) {
                    for (int j = accentsindex[i]; j < accentsindex[i + 1]; 
                                                                        j ++) {
                        m_canonicalSuffixAccents_.append(accents.charAt(j));
                    }
                }
            }
            StringBuilder match = merge(m_canonicalPrefixAccents_, targetText,
                                        start, offset, 
                                        m_canonicalSuffixAccents_);
            // run the collator iterator through this match
            // if status is a failure ucol_setText does nothing
            m_utilColEIter_.setText(match.toString());
            if (checkCollationMatch(m_utilColEIter_)) {
                return end;
            }
            count --;
        }
        return DONE;
    }
    
    /**
     * Take the rearranged start accents and tries matching. If match failed at
     * a seperate following set of accents (seperated from the rearranged on by
     * at least a base character) then we rearrange the preceding accents and 
     * tries matching again.
     * We allow skipping of the ends of the accent set if the ces do not match. 
     * However if the failure is found before the accent set, it fails.
     * Internal method, status assumed to be success, caller has to check 
     * status before calling this method.
     * @param textoffset of the ends of the rearranged accent
     * @return DONE if a match is not found, otherwise return the ending offset 
     *             of the match. Note this start includes all following accents.
     */
    private int doPreviousCanonicalPrefixMatch(int textoffset)
    {
       // int safelength = 0;
        StringBuilder safetext;
        int safeoffset = textoffset;
    
        if (textoffset > m_textBeginOffset_
            && m_collator_.isUnsafe(m_canonicalPrefixAccents_.charAt(
                                    m_canonicalPrefixAccents_.length() - 1))) {
            safeoffset = getNextSafeOffset(textoffset, m_textLimitOffset_);
            //safelength = safeoffset - textoffset;
            safetext = merge(m_canonicalPrefixAccents_, targetText, textoffset, 
                             safeoffset, null);
        }
        else {
            safetext = m_canonicalPrefixAccents_;
        }
    
        // if status is a failure, ucol_setText does nothing
        CollationElementIterator coleiter = m_utilColEIter_;
        coleiter.setText(safetext.toString());
        // status checked in loop below
        
        int ceindex = 0;
        boolean isSafe = true; // safe zone indication flag for position
        int prefixlength = m_canonicalPrefixAccents_.length();
        
        while (ceindex < m_pattern_.m_CELength_) {
            int textce = coleiter.next();
            if (textce == CollationElementIterator.NULLORDER) {
                // check if we have passed the safe buffer
                if (coleiter == m_colEIter_) {
                    return DONE;
                }
                if (safetext != m_canonicalPrefixAccents_) {
                    safetext.delete(0, safetext.length());
                }
                coleiter = m_colEIter_;
                coleiter.setExactOffset(safeoffset);
                // status checked at the start of the loop
                isSafe = false;
                continue;
            }
            textce = getCE(textce);
            if (textce != CollationElementIterator.IGNORABLE 
                && textce != m_pattern_.m_CE_[ceindex]) {
                // do the beginning stuff
                int failedoffset = coleiter.getOffset();
                if (isSafe && failedoffset <= prefixlength) {
                    // alas... no hope. failed at rearranged accent set
                    return DONE;
                }
                else {
                    if (isSafe) {
                        failedoffset = safeoffset - failedoffset;
                        if (safetext != m_canonicalPrefixAccents_) {
                            safetext.delete(0, safetext.length());
                        }
                    }
                    
                    // try rearranging the end accents
                    int result = doPreviousCanonicalSuffixMatch(textoffset, 
                                                                failedoffset);
                    if (result != DONE) {
                        // if status is a failure, ucol_setOffset does nothing
                        m_colEIter_.setExactOffset(result);
                    }
                    return result;
                }
            }
            if (textce == m_pattern_.m_CE_[ceindex]) {
                ceindex ++;
            }
        }
        // set offset here
        if (isSafe) {
            int result = coleiter.getOffset();
            // sets the text iterator here with the correct expansion and offset
            int leftoverces = coleiter.m_CEBufferSize_ 
                                                - coleiter.m_CEBufferOffset_;
            if (result <= prefixlength) { 
                result = textoffset;
            }
            else {
                result = textoffset + (safeoffset - result);
            }
            m_colEIter_.setExactOffset(result);
            m_colEIter_.m_CEBufferOffset_ = m_colEIter_.m_CEBufferSize_ 
                                                                - leftoverces;
            return result;
        }
        
        return coleiter.getOffset();              
    }
    
    /**
     * Trying out the substring and sees if it can be a canonical match.
     * This will try normalizing the starting accents and arranging them into 
     * canonical equivalents and check their corresponding ces with the pattern 
     * ce.
     * Prefix accents in the text will be grouped according to their combining 
     * class and the groups will be mixed and matched to try find the perfect 
     * match with the pattern.
     * So for instance looking for "\u0301" in "\u030A\u0301\u0325"
     * step 1: split "\u030A\u0301" into 6 other type of potential accent 
     *            substrings
     *         "\u030A", "\u0301", "\u0325", "\u030A\u0301", "\u030A\u0325", 
     *         "\u0301\u0325".
     * step 2: check if any of the generated substrings matches the pattern.
     * @param textoffset start offset in the collation element text that starts 
     *                   with the accents to be rearranged
     * @return true if the match is valid, false otherwise
     */
    private boolean doPreviousCanonicalMatch(int textoffset)
    {
        int offset = m_colEIter_.getOffset();
        if ((getFCD(targetText, textoffset) >> SECOND_LAST_BYTE_SHIFT_) == 0) {
            if (m_pattern_.m_hasSuffixAccents_) {
                offset = doPreviousCanonicalSuffixMatch(textoffset, offset);
                if (offset != DONE) {
                    m_colEIter_.setExactOffset(offset);
                    return true;
                }
            }
            return false;
        }
    
        if (!m_pattern_.m_hasPrefixAccents_) {
            return false;
        }
    
        StringBuilder accents = new StringBuilder();
        // offset to the last base character in substring to search
        int baseoffset = getNextBaseOffset(targetText, textoffset);
        // normalizing the offensive string
        String textstr = getString(targetText, textoffset, 
                                                    baseoffset - textoffset);
        if (Normalizer.quickCheck(textstr, Normalizer.NFD,0) 
                                                    == Normalizer.NO) {
            textstr = Normalizer.decompose(textstr, false);
        }
        accents.append(textstr);
        // status checked in loop
            
        int accentsindex[] = new int[INITIAL_ARRAY_SIZE_];
        int size = getUnblockedAccentIndex(accents, accentsindex);
    
        // 2 power n - 1 plus the full set of accents
        int count = (2 << (size - 1)) - 1;  
        while (count > 0) {
            m_canonicalPrefixAccents_.delete(0, 
                                        m_canonicalPrefixAccents_.length());
            // copy the base characters
            for (int k = 0; k < accentsindex[0]; k ++) {
                m_canonicalPrefixAccents_.append(accents.charAt(k));
            }
            // forming all possible canonical rearrangement by dropping
            // sets of accents
            for (int i = 0; i <= size - 1; i ++) {
                int mask = 1 << (size - i - 1);
                if ((count & mask) != 0) {
                    for (int j = accentsindex[i]; j < accentsindex[i + 1]; 
                         j ++) {
                        m_canonicalPrefixAccents_.append(accents.charAt(j));
                    }
                }
            }
            offset = doPreviousCanonicalPrefixMatch(baseoffset);
            if (offset != DONE) {
                return true; // match found
            }
            count --;
        }
        return false;
    }
    
    /**
     * Checks match for contraction. 
     * If the match starts with a partial contraction we fail.
     * Uses the temporary utility buffer to return the modified start and end.
     * @param start offset of potential match, to be modified if necessary
     * @param end offset of potential match, to be modified if necessary
     * @return true if match passes the contraction test, false otherwise.
     */
    private boolean checkPreviousCanonicalContractionMatch(int start, int end) 
    {
        int temp = end;
        // This part checks if either ends of the match contains potential 
        // contraction. If so we'll have to iterate through them
        char echar = 0;
        char schar = 0;
        if (end < m_textLimitOffset_) {
            targetText.setIndex(end);
            echar = targetText.current();
        }
        if (start + 1 < m_textLimitOffset_) {
            targetText.setIndex(start + 1);
            schar = targetText.current();
        }
        if (m_collator_.isUnsafe(echar) || m_collator_.isUnsafe(schar)) {
            int expansion = m_colEIter_.m_CEBufferSize_ 
                                            - m_colEIter_.m_CEBufferOffset_;
            boolean hasExpansion = expansion > 0;
            m_colEIter_.setExactOffset(end);
            while (expansion > 0) {
                // getting rid of the redundant ce
                // since forward contraction/expansion may have extra ces
                // if we are in the normalization buffer, hasAccentsBeforeMatch
                // would have taken care of it.
                // E.g. the character \u01FA will have an expansion of 3, but 
                // if we are only looking for A ring A\u030A, we'll have to 
                // skip the last ce in the expansion buffer
                m_colEIter_.previous();
                if (m_colEIter_.getOffset() != temp) {
                    end = temp;
                    temp = m_colEIter_.getOffset();
                }
                expansion --;
            }
    
            int count = m_pattern_.m_CELength_;
            while (count > 0) {
                int ce = getCE(m_colEIter_.previous());
                // status checked below, note that if status is a failure
                // previous() returns NULLORDER
                if (ce == CollationElementIterator.IGNORABLE) {
                    continue;
                }
                if (hasExpansion && count == 0 
                    && m_colEIter_.getOffset() != temp) {
                    end = temp;
                    temp = m_colEIter_.getOffset();
                }
                if (count == m_pattern_.m_CELength_ 
                    && ce != m_pattern_.m_CE_[m_pattern_.m_CELength_ - 1]) {
                    // accents may have extra starting ces, this occurs when a 
                    // pure accent pattern is matched without rearrangement
                    int expected = m_pattern_.m_CE_[m_pattern_.m_CELength_ - 1];
                    targetText.setIndex(end);
                    if (UTF16.isTrailSurrogate(targetText.previous())) {
                        if (targetText.getIndex() > m_textBeginOffset_ &&
                            !UTF16.isLeadSurrogate(targetText.previous())) {
                            targetText.next();
                        }
                    }
                    end = targetText.getIndex();
                    if ((getFCD(targetText, end) & LAST_BYTE_MASK_) != 0) {
                        ce = getCE(m_colEIter_.previous());
                        while (ce != expected 
                                && ce != CollationElementIterator.NULLORDER 
                                && m_colEIter_.getOffset() <= start) {
                            ce = getCE(m_colEIter_.previous());
                        }
                    }
                }
                if (ce != m_pattern_.m_CE_[count - 1]) {
                    start --;
                    start = getPreviousBaseOffset(start);
                    m_utilBuffer_[0] = start;
                    m_utilBuffer_[1] = end;
                    return false;
                }
                count --;
            }
        } 
        m_utilBuffer_[0] = start;
        m_utilBuffer_[1] = end;
        return true;
    }
    
    /**
     * Checks and sets the match information if found.
     * Checks 
     * <ul>
     * <li> the potential match does not repeat the previous match
     * <li> boundaries are correct
     * <li> potential match does not end in the middle of a contraction
     * <li> identical matches
     * </ul>
     * Otherwise the offset will be shifted to the next character.
     * Uses the temporary utility buffer for storing the modified textoffset.
     * @param textoffset offset in the collation element text. the returned 
     *             value will be the truncated start offset of the match or the 
     *             new start search offset.
     * @return true if the match is valid, false otherwise
     */
    private boolean checkPreviousCanonicalMatch(int textoffset)
    {
        // to ensure that the start and ends are not composite characters
        // if we have a canonical accent match
        if (m_pattern_.m_hasSuffixAccents_ 
            && m_canonicalSuffixAccents_.length() != 0 
            || m_pattern_.m_hasPrefixAccents_ 
            && m_canonicalPrefixAccents_.length() != 0) {
            m_matchedIndex_ = textoffset;
            matchLength = getNextBaseOffset(m_colEIter_.getOffset()) 
                                                                - textoffset;
            return true;
        }
    
        int end = m_colEIter_.getOffset();
        if (!checkPreviousCanonicalContractionMatch(textoffset, end)) {
            // storing the modified textoffset
            return false;
        }
        textoffset = m_utilBuffer_[0];
        end = m_utilBuffer_[1];
        end = getNextBaseOffset(end);
        // this totally matches, however we need to check if it is repeating
        if (checkRepeatedMatch(textoffset, end) 
            || !isBreakUnit(textoffset, end) 
            || !checkIdentical(textoffset, end)) {
            textoffset --;
            textoffset = getPreviousBaseOffset(textoffset);
            m_utilBuffer_[0] = textoffset;
            return false;
        }
        
        m_matchedIndex_ = textoffset;
        matchLength = end - textoffset;
        return true;
    }
    
    /**
     * Method that does the next exact match
     * @param start the offset to start shifting from and performing the 
     *        next exact match
     */
    private void handleNextExact(int start)
    {
        int textoffset = shiftForward(start, 
                                         CollationElementIterator.NULLORDER,
                                         m_pattern_.m_CELength_);
        int targetce = CollationElementIterator.IGNORABLE;
        while (textoffset <= m_textLimitOffset_) {
            m_colEIter_.setExactOffset(textoffset);
            int patternceindex = m_pattern_.m_CELength_ - 1;
            boolean found = false;
            int lastce = CollationElementIterator.NULLORDER;
            
            while (true) {
                // finding the last pattern ce match, imagine composite 
                // characters. for example: search for pattern A in text \u00C0
                // we'll have to skip \u0300 the grave first before we get to A
                targetce = m_colEIter_.previous();
                if (targetce == CollationElementIterator.NULLORDER) {
                    found = false;
                    break;
                }
                targetce = getCE(targetce);
                if (targetce == CollationElementIterator.IGNORABLE && 
                    m_colEIter_.isInBuffer()) { 
                    // this is for the text \u0315\u0300 that requires 
                    // normalization and pattern \u0300, where \u0315 is ignorable
                    continue;
                }
                if (lastce == CollationElementIterator.NULLORDER 
                    || lastce == CollationElementIterator.IGNORABLE) {
                    lastce = targetce;
                }
                if (targetce == m_pattern_.m_CE_[patternceindex]) {
                    // the first ce can be a contraction
                    found = true;
                    break;
                }
                if (m_colEIter_.m_CEBufferOffset_ <= 0) {
                    found = false;
                    break;
                }
            }
    
            while (found && patternceindex > 0) {
                lastce = targetce;
                targetce = m_colEIter_.previous();
                if (targetce == CollationElementIterator.NULLORDER) {
                    found = false;
                    break;
                }
                targetce = getCE(targetce);
                if (targetce == CollationElementIterator.IGNORABLE) {
                    continue;
                }
    
                patternceindex --;
                found = found && targetce == m_pattern_.m_CE_[patternceindex]; 
            }
            
            targetce = lastce;
    
            if (!found) {
                textoffset = shiftForward(textoffset, lastce, patternceindex);
                // status checked at loop.
                patternceindex = m_pattern_.m_CELength_;
                continue;
            }
            
            if (checkNextExactMatch(textoffset)) {
                // status checked in ucol_setOffset
                return;
            }
            textoffset = m_utilBuffer_[0];
        }
        setMatchNotFound();
    }

    /**
     * Method that does the next canonical match
     * @param start the offset to start shifting from and performing the 
     *        next canonical match
     */
    private void handleNextCanonical(int start)
    {
        boolean hasPatternAccents = 
           m_pattern_.m_hasSuffixAccents_ || m_pattern_.m_hasPrefixAccents_;
              
        // shifting it check for setting offset
        // if setOffset is called previously or there was no previous match, we
        // leave the offset as it is.
        int textoffset = shiftForward(start, CollationElementIterator.NULLORDER, 
                                        m_pattern_.m_CELength_);
        m_canonicalPrefixAccents_.delete(0, m_canonicalPrefixAccents_.length());
        m_canonicalSuffixAccents_.delete(0, m_canonicalSuffixAccents_.length());
        int targetce = CollationElementIterator.IGNORABLE;
        
        while (textoffset <= m_textLimitOffset_)
        {
            m_colEIter_.setExactOffset(textoffset);
            int patternceindex = m_pattern_.m_CELength_ - 1;
            boolean found = false;
            int lastce = CollationElementIterator.NULLORDER;
            
            while (true) {
                // finding the last pattern ce match, imagine composite characters
                // for example: search for pattern A in text \u00C0
                // we'll have to skip \u0300 the grave first before we get to A
                targetce = m_colEIter_.previous();
                if (targetce == CollationElementIterator.NULLORDER) {
                    found = false;
                    break;
                }
                targetce = getCE(targetce);
                if (lastce == CollationElementIterator.NULLORDER 
                            || lastce == CollationElementIterator.IGNORABLE) {
                    lastce = targetce;
                }
                if (targetce == m_pattern_.m_CE_[patternceindex]) {
                    // the first ce can be a contraction
                    found = true;
                    break;
                }
                if (m_colEIter_.m_CEBufferOffset_ <= 0) {
                    found = false;
                    break;
                }
            }
            
            while (found && patternceindex > 0) {
                targetce    = m_colEIter_.previous();
                if (targetce == CollationElementIterator.NULLORDER) {
                    found = false;
                    break;
                }
                targetce    = getCE(targetce);
                if (targetce == CollationElementIterator.IGNORABLE) {
                    continue;
                }
    
                patternceindex --;
                found = found && targetce == m_pattern_.m_CE_[patternceindex]; 
            }
    
            // initializing the rearranged accent array
            if (hasPatternAccents && !found) {
                found = doNextCanonicalMatch(textoffset);
            }
    
            if (!found) {
                textoffset = shiftForward(textoffset, lastce, patternceindex);
                // status checked at loop
                patternceindex = m_pattern_.m_CELength_;
                continue;
            }
            
            if (checkNextCanonicalMatch(textoffset)) {
                return;
            }
            textoffset = m_utilBuffer_[0];
        }
        setMatchNotFound();
    }
    
    /**
     * Method that does the previous exact match
     * @param start the offset to start shifting from and performing the 
     *        previous exact match
     */
    private void handlePreviousExact(int start)
    {
        int textoffset = reverseShift(start, CollationElementIterator.NULLORDER, 
                                      m_pattern_.m_CELength_);
        while (textoffset >= m_textBeginOffset_)
        {
            m_colEIter_.setExactOffset(textoffset);
            int patternceindex = 1;
            int targetce = CollationElementIterator.IGNORABLE;
            boolean found = false;
            int firstce = CollationElementIterator.NULLORDER;
            
            while (true) {
                // finding the first pattern ce match, imagine composite 
                // characters. for example: search for pattern \u0300 in text 
                // \u00C0, we'll have to skip A first before we get to 
                // \u0300 the grave accent
                targetce = m_colEIter_.next();
                if (targetce == CollationElementIterator.NULLORDER) {
                    found = false;
                    break;
                }
                targetce = getCE(targetce);
                if (firstce == CollationElementIterator.NULLORDER 
                    || firstce == CollationElementIterator.IGNORABLE) {
                    firstce = targetce;
                }
                if (targetce == CollationElementIterator.IGNORABLE && m_collator_.getStrength() != Collator.PRIMARY) {
                    continue;
                }         
                if (targetce == m_pattern_.m_CE_[0]) {
                    found = true;
                    break;
                }
                if (m_colEIter_.m_CEBufferOffset_ == -1 
                    || m_colEIter_.m_CEBufferOffset_ 
                                            == m_colEIter_.m_CEBufferSize_) {
                    // checking for accents in composite character
                    found = false;
                    break;
                }
            }
    
            //targetce = firstce;
            
            while (found && patternceindex < m_pattern_.m_CELength_) {
                firstce = targetce;
                targetce = m_colEIter_.next();
                if (targetce == CollationElementIterator.NULLORDER) {
                    found = false;
                    break;
                }
                targetce = getCE(targetce);
                if (targetce == CollationElementIterator.IGNORABLE) {
                    continue;
                }
    
                found = found && targetce == m_pattern_.m_CE_[patternceindex]; 
                patternceindex ++;
            }
            
            targetce = firstce;
    
            if (!found) {
                textoffset = reverseShift(textoffset, targetce, patternceindex);
                patternceindex = 0;
                continue;
            }
            
            if (checkPreviousExactMatch(textoffset)) {
                return;
            }
            textoffset = m_utilBuffer_[0];
        }
        setMatchNotFound();
    }
    
    /**
     * Method that does the previous canonical match
     * @param start the offset to start shifting from and performing the 
     *        previous canonical match
     */
    private void handlePreviousCanonical(int start)
    {
        boolean hasPatternAccents = 
           m_pattern_.m_hasSuffixAccents_ || m_pattern_.m_hasPrefixAccents_;
              
        // shifting it check for setting offset
        // if setOffset is called previously or there was no previous match, we
        // leave the offset as it is.
        int textoffset = reverseShift(start, CollationElementIterator.NULLORDER, 
                                          m_pattern_.m_CELength_);
        m_canonicalPrefixAccents_.delete(0, m_canonicalPrefixAccents_.length());
        m_canonicalSuffixAccents_.delete(0, m_canonicalSuffixAccents_.length());
        
        while (textoffset >= m_textBeginOffset_)
        {
            m_colEIter_.setExactOffset(textoffset);
            int patternceindex = 1;
            int targetce = CollationElementIterator.IGNORABLE;
            boolean found = false;
            int firstce = CollationElementIterator.NULLORDER;
            
            while (true) {
                // finding the first pattern ce match, imagine composite 
                // characters. for example: search for pattern \u0300 in text 
                // \u00C0, we'll have to skip A first before we get to 
                // \u0300 the grave accent
                targetce = m_colEIter_.next();
                if (targetce == CollationElementIterator.NULLORDER) {
                    found = false;
                    break;
                }
                targetce = getCE(targetce);
                if (firstce == CollationElementIterator.NULLORDER 
                    || firstce == CollationElementIterator.IGNORABLE) {
                    firstce = targetce;
                }
                
                if (targetce == m_pattern_.m_CE_[0]) {
                    // the first ce can be a contraction
                    found = true;
                    break;
                }
                if (m_colEIter_.m_CEBufferOffset_ == -1 
                    || m_colEIter_.m_CEBufferOffset_ 
                                            == m_colEIter_.m_CEBufferSize_) {
                    // checking for accents in composite character
                    found = false;
                    break;
                }
            }
    
            targetce = firstce;
            
            while (found && patternceindex < m_pattern_.m_CELength_) {
                targetce = m_colEIter_.next();
                if (targetce == CollationElementIterator.NULLORDER) {
                    found = false;
                    break;
                }
                targetce = getCE(targetce);
                if (targetce == CollationElementIterator.IGNORABLE) {
                    continue;
                }
    
                found = found && targetce == m_pattern_.m_CE_[patternceindex]; 
                patternceindex ++;
            }
    
            // initializing the rearranged accent array
            if (hasPatternAccents && !found) {
                found = doPreviousCanonicalMatch(textoffset);
            }
    
            if (!found) {
                textoffset = reverseShift(textoffset, targetce, patternceindex);
                patternceindex = 0;
                continue;
            }
    
            if (checkPreviousCanonicalMatch(textoffset)) {
                return;
            }
            textoffset = m_utilBuffer_[0];
        }
        setMatchNotFound();
    }
    
    /**
     * Gets a substring out of a CharacterIterator
     * @param text CharacterIterator
     * @param start start offset
     * @param length of substring
     * @return substring from text starting at start and length length
     */
    private static final String getString(CharacterIterator text, int start,
                                            int length)
    {
        StringBuilder result = new StringBuilder(length);
        int offset = text.getIndex();
        text.setIndex(start);
        for (int i = 0; i < length; i ++) {
            result.append(text.current());
            text.next();
        }
        text.setIndex(offset);
        return result.toString();
    }
    
    /**
     * Getting the mask for collation strength
     * @param strength collation strength
      * @return collation element mask
     */
    private static final int getMask(int strength) 
    {
        switch (strength) 
        {
            case Collator.PRIMARY:
                return RuleBasedCollator.CE_PRIMARY_MASK_;
            case Collator.SECONDARY:
                return RuleBasedCollator.CE_SECONDARY_MASK_ 
                       | RuleBasedCollator.CE_PRIMARY_MASK_;
            default:
                return RuleBasedCollator.CE_TERTIARY_MASK_ 
                       | RuleBasedCollator.CE_SECONDARY_MASK_ 
                       | RuleBasedCollator.CE_PRIMARY_MASK_;
        }
    }
    
    /**
     * Sets match not found 
     */
    private void setMatchNotFound() 
    {
        // this method resets the match result regardless of the error status.
        m_matchedIndex_ = DONE;
        setMatchLength(0);
    }
    
    /**
     * Check the boundaries of the match.
     */
    private int checkBreakBoundary(int end) {
        if (!m_charBreakIter_.isBoundary(end)) {
            end = m_charBreakIter_.following(end);
        }
        return end;
    }
}
