blob: cae7130f0f073f56ae6984e2c6c552ca9bf36395 [file] [log] [blame]
// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*
*******************************************************************************
* Copyright (C) 2005-2016 International Business Machines Corporation and
* others. All Rights Reserved.
*******************************************************************************
*/
package com.ibm.icu.text;
import static com.ibm.icu.impl.CharacterIteration.DONE32;
import static com.ibm.icu.impl.CharacterIteration.next32;
import static com.ibm.icu.impl.CharacterIteration.nextTrail32;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.text.CharacterIterator;
import java.util.ArrayList;
import java.util.List;
import com.ibm.icu.impl.CharacterIteration;
import com.ibm.icu.impl.ICUBinary;
import com.ibm.icu.impl.ICUDebug;
import com.ibm.icu.impl.RBBIDataWrapper;
import com.ibm.icu.lang.UCharacter;
import com.ibm.icu.lang.UProperty;
import com.ibm.icu.lang.UScript;
import com.ibm.icu.util.CodePointTrie;
/**
* Rule Based Break Iterator
* This is a port of the C++ class RuleBasedBreakIterator from ICU4C.
*
* @stable ICU 2.0
*/
public class RuleBasedBreakIterator extends BreakIterator {
//=======================================================================
// Constructors & Factories
//=======================================================================
/**
* private constructor
*/
private RuleBasedBreakIterator() {
fDictionaryCharCount = 0;
synchronized(gAllBreakEngines) {
fBreakEngines = new ArrayList<>(gAllBreakEngines);
}
}
/**
* Create a break iterator from a precompiled set of break rules.
*
* Creating a break iterator from the binary rules is much faster than
* creating one from source rules.
*
* The binary rules are generated by the RuleBasedBreakIterator.compileRules() function.
* Binary break iterator rules are not guaranteed to be compatible between
* different versions of ICU.
*
* @param is an input stream supplying the compiled binary rules.
* @throws IOException if there is an error while reading the rules from the InputStream.
* @see #compileRules(String, OutputStream)
* @stable ICU 4.8
*/
public static RuleBasedBreakIterator getInstanceFromCompiledRules(InputStream is) throws IOException {
RuleBasedBreakIterator This = new RuleBasedBreakIterator();
This.fRData = RBBIDataWrapper.get(ICUBinary.getByteBufferFromInputStreamAndCloseStream(is));
This.fLookAheadMatches = new int[This.fRData.fFTable.fLookAheadResultsSize];
return This;
}
/**
* Create a break iterator from a precompiled set of break rules.
*
* Creating a break iterator from the binary rules is much faster than
* creating one from source rules.
*
* The binary rules are generated by the RuleBasedBreakIterator.compileRules() function.
* Binary break iterator rules are not guaranteed to be compatible between
* different versions of ICU.
*
* @param bytes a buffer supplying the compiled binary rules.
* @throws IOException if there is an error while reading the rules from the buffer.
* @see #compileRules(String, OutputStream)
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public static RuleBasedBreakIterator getInstanceFromCompiledRules(ByteBuffer bytes) throws IOException {
RuleBasedBreakIterator This = new RuleBasedBreakIterator();
This.fRData = RBBIDataWrapper.get(bytes);
This.fLookAheadMatches = new int[This.fRData.fFTable.fLookAheadResultsSize];
return This;
}
/**
* Construct a RuleBasedBreakIterator from a set of rules supplied as a string.
* @param rules The break rules to be used.
* @stable ICU 2.2
*/
public RuleBasedBreakIterator(String rules) {
this();
try {
ByteArrayOutputStream ruleOS = new ByteArrayOutputStream();
compileRules(rules, ruleOS);
fRData = RBBIDataWrapper.get(ByteBuffer.wrap(ruleOS.toByteArray()));
fLookAheadMatches = new int[fRData.fFTable.fLookAheadResultsSize];
} catch (IOException e) {
///CLOVER:OFF
// An IO exception can only arrive here if there is a bug in the RBBI Rule compiler,
// causing bogus compiled rules to be produced, but with no compile error raised.
RuntimeException rte = new RuntimeException("RuleBasedBreakIterator rule compilation internal error: "
+ e.getMessage());
throw rte;
///CLOVER:ON
}
}
//=======================================================================
// Boilerplate
//=======================================================================
/**
* Clones this iterator.
* @return A newly-constructed RuleBasedBreakIterator with the same
* behavior as this one.
* @stable ICU 2.0
*/
@Override
public Object clone() {
RuleBasedBreakIterator result;
result = (RuleBasedBreakIterator)super.clone();
if (fText != null) {
result.fText = (CharacterIterator)(fText.clone());
}
synchronized (gAllBreakEngines) {
result.fBreakEngines = new ArrayList<>(gAllBreakEngines);
}
result.fLookAheadMatches = new int[fRData.fFTable.fLookAheadResultsSize];
result.fBreakCache = result.new BreakCache(fBreakCache);
result.fDictionaryCache = result.new DictionaryCache(fDictionaryCache);
return result;
}
/**
* Returns true if both BreakIterators are of the same class, have the same
* rules, and iterate over the same text.
* @stable ICU 2.0
*/
@Override
public boolean equals(Object that) {
if (that == null) {
return false;
}
if (this == that) {
return true;
}
try {
RuleBasedBreakIterator other = (RuleBasedBreakIterator) that;
if (fRData != other.fRData && (fRData == null || other.fRData == null)) {
return false;
}
if (fRData != null && other.fRData != null &&
(!fRData.fRuleSource.equals(other.fRData.fRuleSource))) {
return false;
}
if (fText == null && other.fText == null) {
return true;
}
if (fText == null || other.fText == null || !fText.equals(other.fText)) {
return false;
}
return fPosition == other.fPosition;
}
catch(ClassCastException e) {
return false;
}
}
/**
* Returns the description (rules) used to create this iterator.
* (In ICU4C, the same function is RuleBasedBreakIterator::getRules())
* @stable ICU 2.0
*/
@Override
public String toString() {
String retStr = "";
if (fRData != null) {
retStr = fRData.fRuleSource;
}
return retStr;
}
/**
* Compute a hashcode for this BreakIterator
* @return A hash code
* @stable ICU 2.0
*/
@Override
public int hashCode()
{
return fRData.fRuleSource.hashCode();
}
private static final int START_STATE = 1; // The state number of the starting state
private static final int STOP_STATE = 0; // The state-transition value indicating "stop"
// RBBIRunMode - the state machine runs an extra iteration at the beginning and end
// of user text. A variable with this enum type keeps track of where we
// are. The state machine only fetches user text input while in RUN mode.
private static final int RBBI_START = 0;
private static final int RBBI_RUN = 1;
private static final int RBBI_END = 2;
/**
* The character iterator through which this BreakIterator accesses the text.
*/
private CharacterIterator fText = new java.text.StringCharacterIterator("");
/**
* The rule data for this BreakIterator instance.
* Not intended for public use. Declared public for testing purposes only.
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public RBBIDataWrapper fRData;
/**
* The iteration state - current position, rule status for the current position,
* and whether the iterator ran off the end, yielding UBRK_DONE.
* Current position is pinned to be 0 < position <= text.length.
* Current position is always set to a boundary.
*
* The current position of the iterator. Pinned, 0 < fPosition <= text.length.
* Never has the value UBRK_DONE (-1).
*/
private int fPosition;
/**
* Index of the Rule {tag} values for the most recent match.
*/
private int fRuleStatusIndex;
/**
* True when iteration has run off the end, and iterator functions should return UBRK_DONE.
*/
private boolean fDone;
/**
* Array of look-ahead tentative results.
*/
private int[] fLookAheadMatches;
/**
* Cache of previously determined boundary positions.
*/
private BreakCache fBreakCache = new BreakCache();
/**
* Counter for the number of characters encountered with the "dictionary"
* flag set. Normal RBBI iterators don't use it, although the code
* for updating it is live. Dictionary Based break iterators (a subclass
* of us) access this field directly.
* @internal
*/
private int fDictionaryCharCount;
private DictionaryCache fDictionaryCache = new DictionaryCache();
/**
* ICU debug argument name for RBBI
*/
private static final String RBBI_DEBUG_ARG = "rbbi";
/**
* Debugging flag. Trace operation of state machine when true.
*/
private static final boolean TRACE = ICUDebug.enabled(RBBI_DEBUG_ARG)
&& ICUDebug.value(RBBI_DEBUG_ARG).indexOf("trace") >= 0;
/**
* The "default" break engine - just skips over ranges of dictionary words,
* producing no breaks. Should only be used if characters need to be handled
* by a dictionary but we have no dictionary implementation for them.
*
* Only one instance; shared by all break iterators.
*/
private static final UnhandledBreakEngine gUnhandledBreakEngine;
/**
* List of all known break engines, common for all break iterators.
* Lazily updated as break engines are needed, because instantiation of
* break engines is expensive.
*
* Because gAllBreakEngines can be referenced concurrently from different
* BreakIterator instances, all access is synchronized.
*/
private static final List<LanguageBreakEngine> gAllBreakEngines;
static {
gUnhandledBreakEngine = new UnhandledBreakEngine();
gAllBreakEngines = new ArrayList<>();
gAllBreakEngines.add(gUnhandledBreakEngine);
}
/**
* List of all known break engines. Similar to gAllBreakEngines, but local to a
* break iterator, allowing it to be used without synchronization.
*/
private List<LanguageBreakEngine> fBreakEngines;
/**
* Dump the contents of the state table and character classes for this break iterator.
* For debugging only.
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public void dump(java.io.PrintStream out) {
if (out == null) {
out = System.out;
}
this.fRData.dump(out);
}
/**
* Compile a set of source break rules into the binary state tables used
* by the break iterator engine. Creating a break iterator from precompiled
* rules is much faster than creating one from source rules.
*
* Binary break rules are not guaranteed to be compatible between different
* versions of ICU.
*
*
* @param rules The source form of the break rules
* @param ruleBinary An output stream to receive the compiled rules.
* @throws IOException If there is an error writing the output.
* @see #getInstanceFromCompiledRules(InputStream)
* @stable ICU 4.8
*/
public static void compileRules(String rules, OutputStream ruleBinary) throws IOException {
RBBIRuleBuilder.compileRules(rules, ruleBinary);
}
//=======================================================================
// BreakIterator overrides
//=======================================================================
/**
* Sets the current iteration position to the beginning of the text.
* (i.e., the CharacterIterator's starting offset).
* @return The offset of the beginning of the text.
* @stable ICU 2.0
*/
@Override
public int first() {
if (fText == null) {
return BreakIterator.DONE;
}
fText.first();
int start = fText.getIndex();
if (!fBreakCache.seek(start)) {
fBreakCache.populateNear(start);
}
fBreakCache.current();
assert(fPosition == start);
return fPosition;
}
/**
* Sets the current iteration position to the end of the text.
* (i.e., the CharacterIterator's ending offset).
* @return The text's past-the-end offset.
* @stable ICU 2.0
*/
@Override
public int last() {
if (fText == null) {
return BreakIterator.DONE;
}
int endPos = fText.getEndIndex();
boolean endShouldBeBoundary = isBoundary(endPos); // Has side effect of setting iterator position.
assert(endShouldBeBoundary);
if (fPosition != endPos) {
assert(fPosition == endPos);
}
return endPos;
}
/**
* Advances the iterator either forward or backward the specified number of steps.
* Negative values move backward, and positive values move forward. This is
* equivalent to repeatedly calling next() or previous().
* @param n The number of steps to move. The sign indicates the direction
* (negative is backwards, and positive is forwards).
* @return The character offset of the boundary position n boundaries away from
* the current one.
* @stable ICU 2.0
*/
@Override
public int next(int n) {
int result = 0;
if (n > 0) {
for (; n > 0 && result != DONE; --n) {
result = next();
}
} else if (n < 0) {
for (; n < 0 && result != DONE; ++n) {
result = previous();
}
} else {
result = current();
}
return result;
}
/**
* Advances the iterator to the next boundary position.
* @return The position of the first boundary after this one.
* @stable ICU 2.0
*/
@Override
public int next() {
fBreakCache.next();
return fDone ? DONE : fPosition;
}
/**
* Moves the iterator backwards, to the boundary preceding the current one.
* @return The position of the boundary position immediately preceding the starting position.
* @stable ICU 2.0
*/
@Override
public int previous() {
fBreakCache.previous();
return fDone ? DONE : fPosition;
}
/**
* Sets the iterator to refer to the first boundary position following
* the specified position.
* @param startPos The position from which to begin searching for a break position.
* @return The position of the first break after the current position.
* @stable ICU 2.0
*/
@Override
public int following(int startPos) {
// if the supplied position is before the beginning, return the
// text's starting offset
if (startPos < fText.getBeginIndex()) {
return first();
}
// Move requested offset to a code point start. It might be between a lead and trail surrogate.
// Or it may be beyond the end of the text.
startPos = CISetIndex32(fText, startPos);
fBreakCache.following(startPos);
return fDone ? DONE : fPosition;
}
/**
* Sets the iterator to refer to the last boundary position before the
* specified position.
* @param offset The position to begin searching for a break from.
* @return The position of the last boundary before the starting position.
* @stable ICU 2.0
*/
@Override
public int preceding(int offset) {
if (fText == null || offset > fText.getEndIndex()) {
return last();
} else if (offset < fText.getBeginIndex()) {
return first();
}
// Move requested offset to a code point start. It might be between a lead and trail surrogate.
// int adjustedOffset = CISetIndex32(fText, offset); // TODO: restore to match ICU4C behavior.
int adjustedOffset = offset;
fBreakCache.preceding(adjustedOffset);
return fDone ? DONE : fPosition;
}
/**
* Throw IllegalArgumentException unless begin &lt;= offset &lt; end.
* @stable ICU 2.0
*/
protected static final void checkOffset(int offset, CharacterIterator text) {
if (offset < text.getBeginIndex() || offset > text.getEndIndex()) {
throw new IllegalArgumentException("offset out of bounds");
}
}
/**
* Returns true if the specified position is a boundary position. As a side
* effect, leaves the iterator pointing to the first boundary position at
* or after "offset".
* @param offset the offset to check.
* @return True if "offset" is a boundary position.
* @stable ICU 2.0
*/
@Override
public boolean isBoundary(int offset) {
// TODO: behavior difference with ICU4C, which considers out-of-range offsets
// to not be boundaries, and to not be errors.
checkOffset(offset, fText);
// Adjust offset to be on a code point boundary and not beyond the end of the text.
// Note that isBoundary() is always false for offsets that are not on code point boundaries.
// But we still need the side effect of leaving iteration at the following boundary.
int adjustedOffset = CISetIndex32(fText, offset);
boolean result = false;
if (fBreakCache.seek(adjustedOffset) || fBreakCache.populateNear(adjustedOffset)) {
result = (fBreakCache.current() == offset);
}
if (!result) {
// Not on a boundary. isBoundary() must leave iterator on the following boundary.
// fBreakCache.seek(), above, left us on the preceding boundary, so advance one.
next();
}
return result;
}
/**
* Returns the current iteration position. Note that DONE is never
* returned from this function; if iteration has run to the end of a
* string, current() will return the length of the string while
* next() will return BreakIterator.DONE).
* @return The current iteration position.
* @stable ICU 2.0
*/
@Override
public int current() {
return (fText != null) ? fPosition : BreakIterator.DONE;
}
/**
* Return the status tag from the break rule that determined the boundary at
* the current iteration position. The values appear in the rule source
* within brackets, {123}, for example. For rules that do not specify a
* status, a default value of 0 is returned. If more than one rule applies,
* the numerically largest of the possible status values is returned.
* <p>
* Of the standard types of ICU break iterators, only the word and line break
* iterator provides status values. The values are defined in
* class RuleBasedBreakIterator, and allow distinguishing between words
* that contain alphabetic letters, "words" that appear to be numbers,
* punctuation and spaces, words containing ideographic characters, and
* more. Call <code>getRuleStatus</code> after obtaining a boundary
* position from <code>next()</code>, <code>previous()</code>, or
* any other break iterator functions that returns a boundary position.
* <p>
* Note that <code>getRuleStatus()</code> returns the value corresponding to
* <code>current()</code> index even after <code>next()</code> has returned DONE.
* <p>
* @return the status from the break rule that determined the boundary
* at the current iteration position.
*
* @stable ICU 60
*/
@Override
public int getRuleStatus() {
// Status records have this form:
// Count N <-- fLastRuleStatusIndex points here.
// Status val 0
// Status val 1
// ...
// Status val N-1 <-- the value we need to return
// The status values are sorted in ascending order.
// This function returns the last (largest) of the array of status values.
int idx = fRuleStatusIndex + fRData.fStatusTable[fRuleStatusIndex];
int tagVal = fRData.fStatusTable[idx];
return tagVal;
}
/**
* Get the status (tag) values from the break rule(s) that determined the boundary
* at the current iteration position. The values appear in the rule source
* within brackets, {123}, for example. The default status value for rules
* that do not explicitly provide one is zero.
* <p>
* The status values used by the standard ICU break rules are defined
* as public constants in class RuleBasedBreakIterator.
* <p>
* If the size of the output array is insufficient to hold the data,
* the output will be truncated to the available length. No exception
* will be thrown.
*
* @param fillInArray an array to be filled in with the status values.
* @return The number of rule status values from the rules that determined
* the boundary at the current iteration position.
* In the event that the array is too small, the return value
* is the total number of status values that were available,
* not the reduced number that were actually returned.
* @stable ICU 60
*/
@Override
public int getRuleStatusVec(int[] fillInArray) {
int numStatusVals = fRData.fStatusTable[fRuleStatusIndex];
if (fillInArray != null) {
int numToCopy = Math.min(numStatusVals, fillInArray.length);
for (int i=0; i<numToCopy; i++) {
fillInArray[i] = fRData.fStatusTable[fRuleStatusIndex + i + 1];
}
}
return numStatusVals;
}
/**
* Returns a CharacterIterator over the text being analyzed.
* <p>
* <b><i>Caution:</i></b>The state of the returned CharacterIterator
* must not be modified in any way while the BreakIterator is still in use.
* Doing so will lead to undefined behavior of the BreakIterator.
* Clone the returned CharacterIterator first and work with that.
* <p>
* The returned CharacterIterator is a reference
* to the <b>actual iterator being used</b> by the BreakIterator.
* No guarantees are made about the current position
* of this iterator when it is returned; it may differ from the
* BreakIterators current position. If you need to move that
* position to examine the text, clone this function's return value first.
* @return An iterator over the text being analyzed.
* @stable ICU 2.0
*/
@Override
public CharacterIterator getText() {
return fText;
}
/**
* Set the iterator to analyze a new piece of text. This function resets
* the current iteration position to the beginning of the text.
* (The old iterator is dropped.)
* <p>
* <b><i>Caution:</i></b> The supplied CharacterIterator is used
* directly by the BreakIterator, and must not be altered in any
* way by code outside of the BreakIterator.
* Doing so will lead to undefined behavior of the BreakIterator.
*
* @param newText An iterator over the text to analyze.
* @stable ICU 2.0
*/
@Override
public void setText(CharacterIterator newText) {
if (newText != null) {
fBreakCache.reset(newText.getBeginIndex(), 0);
} else {
fBreakCache.reset();
}
fDictionaryCache.reset();
fText = newText;
this.first();
}
/**
* Control debug, trace and dump options.
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
public static final String fDebugEnv = ICUDebug.enabled(RBBI_DEBUG_ARG) ?
ICUDebug.value(RBBI_DEBUG_ARG) : null;
private LanguageBreakEngine getLanguageBreakEngine(int c) {
// We have a dictionary character.
// Does an already instantiated break engine handle it?
for (LanguageBreakEngine candidate : fBreakEngines) {
if (candidate.handles(c)) {
return candidate;
}
}
synchronized (gAllBreakEngines) {
// This break iterator's list of break engines didn't handle the character.
// Check the global list, another break iterator may have instantiated the
// desired engine.
for (LanguageBreakEngine candidate : gAllBreakEngines) {
if (candidate.handles(c)) {
fBreakEngines.add(candidate);
return candidate;
}
}
// The global list doesn't have an existing engine, build one.
int script = UCharacter.getIntPropertyValue(c, UProperty.SCRIPT);
if (script == UScript.KATAKANA || script == UScript.HIRAGANA) {
// Katakana, Hiragana and Han are handled by the same dictionary engine.
// Fold them together for mapping from script -> engine.
script = UScript.HAN;
}
LanguageBreakEngine eng;
try {
switch (script) {
case UScript.THAI:
eng = new ThaiBreakEngine();
break;
case UScript.LAO:
eng = new LaoBreakEngine();
break;
case UScript.MYANMAR:
eng = new BurmeseBreakEngine();
break;
case UScript.KHMER:
eng = new KhmerBreakEngine();
break;
case UScript.HAN:
eng = new CjkBreakEngine(false);
break;
case UScript.HANGUL:
eng = new CjkBreakEngine(true);
break;
default:
gUnhandledBreakEngine.handleChar(c);
eng = gUnhandledBreakEngine;
break;
}
} catch (IOException e) {
eng = null;
}
if (eng != null && eng != gUnhandledBreakEngine) {
gAllBreakEngines.add(eng);
fBreakEngines.add(eng);
}
return eng;
} // end synchronized(gAllBreakEngines)
}
/**
* The State Machine Engine for moving forward is here.
* This function is the heart of the RBBI run time engine.
*
* Input
* fPosition, the position in the text to begin from.
* Output
* fPosition: the boundary following the starting position.
* fDictionaryCharCount the number of dictionary characters encountered.
* If > 0, the segment will be further subdivided
* fRuleStatusIndex Info from the state table indicating which rules caused the boundary.
*
* @return the new iterator position
*
* A note on supplementary characters and the position of underlying
* Java CharacterIterator: Normally, a character iterator is positioned at
* the char most recently returned by next(). Within this function, when
* a supplementary char is being processed, the char iterator is left
* sitting on the trail surrogate, in the middle of the code point.
* This is different from everywhere else, where an iterator always
* points at the lead surrogate of a supplementary.
*/
private int handleNext() {
if (TRACE) {
System.out.println("Handle Next pos char state category");
}
// handleNext always sets the break tag value.
// Set the default for it.
fRuleStatusIndex = 0;
fDictionaryCharCount = 0;
// caches for quicker access
CharacterIterator text = fText;
CodePointTrie trie = fRData.fTrie;
char[] stateTable = fRData.fFTable.fTable;
int initialPosition = fPosition;
text.setIndex(initialPosition);
int result = initialPosition;
// Set up the starting char
int c = text.current();
if (c >= UTF16.LEAD_SURROGATE_MIN_VALUE) {
c = nextTrail32(text, c);
if (c == DONE32) {
fDone = true;
return BreakIterator.DONE;
}
}
// Set the initial state for the state machine
int state = START_STATE;
int row = fRData.getRowIndex(state);
short category = 3;
int flagsState = fRData.fFTable.fFlags;
int dictStart = fRData.fFTable.fDictCategoriesStart;
int mode = RBBI_RUN;
if ((flagsState & RBBIDataWrapper.RBBI_BOF_REQUIRED) != 0) {
category = 2;
mode = RBBI_START;
if (TRACE) {
System.out.print(" " + RBBIDataWrapper.intToString(text.getIndex(), 5));
System.out.print(RBBIDataWrapper.intToHexString(c, 10));
System.out.println(RBBIDataWrapper.intToString(state,7) + RBBIDataWrapper.intToString(category,6));
}
}
// loop until we reach the end of the text or transition to state 0
while (state != STOP_STATE) {
if (c == DONE32) {
// Reached end of input string.
if (mode == RBBI_END) {
// We have already run the loop one last time with the
// character set to the pseudo {eof} value. Now it is time
// to unconditionally bail out.
break;
}
// Run the loop one last time with the fake end-of-input character category
mode = RBBI_END;
category = 1;
}
else if (mode == RBBI_RUN) {
// Get the char category. An incoming category of 1 or 2 mens that
// we are preset for doing the beginning or end of input, and
// that we shouldn't get a category from an actual text input character.
//
// look up the current character's character category, which tells us
// which column in the state table to look at.
//
category = (short) trie.get(c);
// Check for categories that require word dictionary handling.
if (category >= dictStart) {
fDictionaryCharCount++;
}
if (TRACE) {
System.out.print(" " + RBBIDataWrapper.intToString(text.getIndex(), 5));
System.out.print(RBBIDataWrapper.intToHexString(c, 10));
System.out.println(RBBIDataWrapper.intToString(state,7) + RBBIDataWrapper.intToString(category,6));
}
// Advance to the next character.
// If this is a beginning-of-input loop iteration, don't advance.
// The next iteration will be processing the first real input character.
c = text.next();
if (c >= UTF16.LEAD_SURROGATE_MIN_VALUE) {
c = nextTrail32(text, c);
}
}
else {
mode = RBBI_RUN;
}
// look up a state transition in the state table
state = stateTable[row + RBBIDataWrapper.NEXTSTATES + category];
row = fRData.getRowIndex(state);
int accepting = stateTable[row + RBBIDataWrapper.ACCEPTING];
if (accepting == RBBIDataWrapper.ACCEPTING_UNCONDITIONAL) {
// Match found, common case
result = text.getIndex();
if (c >= UTF16.SUPPLEMENTARY_MIN_VALUE && c <= UTF16.CODEPOINT_MAX_VALUE) {
// The iterator has been left in the middle of a surrogate pair.
// We want the start of it.
result--;
}
// Remember the break status (tag) values.
fRuleStatusIndex = stateTable[row + RBBIDataWrapper.TAGSIDX];
} else if (accepting > RBBIDataWrapper.ACCEPTING_UNCONDITIONAL) {
// Lookahead match is completed
int lookaheadResult = fLookAheadMatches[accepting];
if (lookaheadResult >= 0) {
fRuleStatusIndex = stateTable[row + RBBIDataWrapper.TAGSIDX];
fPosition = lookaheadResult;
return lookaheadResult;
}
}
// If we are at the position of the '/' in a look-ahead (hard break) rule;
// record the current position, to be returned later, if the full rule matches.
// TODO: Move this check before the previous check of fAccepting.
// This would enable hard-break rules with no following context.
// But there are line break test failures when trying this. Investigate.
// Issue ICU-20837
int rule = stateTable[row + RBBIDataWrapper.LOOKAHEAD];
if (rule != 0) {
int pos = text.getIndex();
if (c >= UTF16.SUPPLEMENTARY_MIN_VALUE && c <= UTF16.CODEPOINT_MAX_VALUE) {
// The iterator has been left in the middle of a surrogate pair.
// We want the beginning of it.
pos--;
}
fLookAheadMatches[rule] = pos;
}
} // End of state machine main loop
// The state machine is done. Check whether it found a match...
// If the iterator failed to advance in the match engine force it ahead by one.
// This indicates a defect in the break rules, which should always match
// at least one character.
if (result == initialPosition) {
if (TRACE) {
System.out.println("Iterator did not move. Advancing by 1.");
}
text.setIndex(initialPosition);
next32(text);
result = text.getIndex();
fRuleStatusIndex = 0;
}
// Leave the iterator at our result position.
// (we may have advanced beyond the last accepting position chasing after
// longer matches that never completed.)
fPosition = result;
if (TRACE) {
System.out.println("result = " + result);
}
return result;
}
/**
* Iterate backwards from an arbitrary position in the input text using the Safe Reverse rules.
* This locates a "Safe Position" from which the forward break rules
* will operate correctly. A Safe Position is not necessarily a boundary itself.
*
* The logic of this function is very similar to handleNext(), above, but simpler
* because the safe table does not require as many options.
*
* @param fromPosition the position in the input text to begin the iteration.
* @internal
*/
private int handleSafePrevious(int fromPosition) {
char state;
short category = 0;
int result = 0;
// caches for quicker access
CharacterIterator text = fText;
CodePointTrie trie = fRData.fTrie;
char[] stateTable = fRData.fRTable.fTable;
CISetIndex32(text, fromPosition);
if (TRACE) {
System.out.print("Handle Previous pos char state category");
}
// if we're already at the start of the text, return DONE.
if (text.getIndex() == text.getBeginIndex()) {
return BreakIterator.DONE;
}
// Set the initial state for the state machine
int c = CharacterIteration.previous32(text);
state = START_STATE;
int row = fRData.getRowIndex(state);
// loop until we reach the start of the text or transition to state 0
//
for (; c != DONE32; c = CharacterIteration.previous32(text)) {
// look up the current character's character category, which tells us
// which column in the state table to look at.
//
// And off the dictionary flag bit. For reverse iteration it is not used.
category = (short) trie.get(c);
if (TRACE) {
System.out.print(" " + RBBIDataWrapper.intToString(text.getIndex(), 5));
System.out.print(RBBIDataWrapper.intToHexString(c, 10));
System.out.println(RBBIDataWrapper.intToString(state,7) + RBBIDataWrapper.intToString(category,6));
}
// State Transition - move machine to its next state
//
assert(category < fRData.fHeader.fCatCount);
state = stateTable[row + RBBIDataWrapper.NEXTSTATES + category];
row = fRData.getRowIndex(state);
if (state == STOP_STATE) {
// This is the normal exit from the lookup state machine.
// Transition to state zero means we have found a safe point.
break;
}
}
// The state machine is done.
result = text.getIndex();
if (TRACE) {
System.out.println("result = " + result);
}
return result;
}
/**
* Set the index of a CharacterIterator.
* Pin the index to the valid range range of BeginIndex <= index <= EndIndex.
* If the index points to a trail surrogate of a supplementary character, adjust it
* to the start (lead surrogate) index.
*
* @param ci A CharacterIterator to set
* @param index the index to set
* @return the resulting index, possibly pinned or adjusted.
*/
private static int CISetIndex32(CharacterIterator ci, int index) {
if (index <= ci.getBeginIndex()) {
ci.first();
} else if (index >= ci.getEndIndex()) {
ci.setIndex(ci.getEndIndex());
} else if (Character.isLowSurrogate(ci.setIndex(index))) {
if (!Character.isHighSurrogate(ci.previous())) {
ci.next();
}
}
return ci.getIndex();
}
/** DictionaryCache stores the boundaries obtained from a run of dictionary characters.
* Dictionary boundaries are moved first to this cache, then from here
* to the main BreakCache, where they may inter-leave with non-dictionary
* boundaries. The public BreakIterator API always fetches directly
* from the main BreakCache, not from here.
*
* In common situations, the number of boundaries in a single dictionary run
* should be quite small, it will be terminated by punctuation, spaces,
* or any other non-dictionary characters. The main BreakCache may end
* up with boundaries from multiple dictionary based runs.
*
* The boundaries are stored in a simple ArrayList (vector), with the
* assumption that they will be accessed sequentially.
*/
class DictionaryCache {
void reset() {
fPositionInCache = -1;
fStart = 0;
fLimit = 0;
fFirstRuleStatusIndex = 0;
fOtherRuleStatusIndex = 0;
fBreaks.removeAllElements();
};
boolean following(int fromPos) {
if (fromPos >= fLimit || fromPos < fStart) {
fPositionInCache = -1;
return false;
}
// Sequential iteration, move from previous boundary to the following
int r = 0;
if (fPositionInCache >= 0 && fPositionInCache < fBreaks.size() && fBreaks.elementAt(fPositionInCache) == fromPos) {
++fPositionInCache;
if (fPositionInCache >= fBreaks.size()) {
fPositionInCache = -1;
return false;
}
r = fBreaks.elementAt(fPositionInCache);
assert(r > fromPos);
fBoundary = r;
fStatusIndex = fOtherRuleStatusIndex;
return true;
}
// Random indexing. Linear search for the boundary following the given position.
for (fPositionInCache = 0; fPositionInCache < fBreaks.size(); ++fPositionInCache) {
r= fBreaks.elementAt(fPositionInCache);
if (r > fromPos) {
fBoundary = r;
fStatusIndex = fOtherRuleStatusIndex;
return true;
}
}
// Internal error. fStart <= fromPos < fLimit, but no cached boundary.
assert(false);
fPositionInCache = -1;
return false;
};
boolean preceding(int fromPos) {
if (fromPos <= fStart || fromPos > fLimit) {
fPositionInCache = -1;
return false;
}
if (fromPos == fLimit) {
fPositionInCache = fBreaks.size() - 1;
if (fPositionInCache >= 0) {
assert(fBreaks.elementAt(fPositionInCache) == fromPos);
}
}
int r;
if (fPositionInCache > 0 && fPositionInCache < fBreaks.size() && fBreaks.elementAt(fPositionInCache) == fromPos) {
--fPositionInCache;
r = fBreaks.elementAt(fPositionInCache);
assert(r < fromPos);
fBoundary = r;
fStatusIndex = ( r== fStart) ? fFirstRuleStatusIndex : fOtherRuleStatusIndex;
return true;
}
if (fPositionInCache == 0) {
fPositionInCache = -1;
return false;
}
for (fPositionInCache = fBreaks.size()-1; fPositionInCache >= 0; --fPositionInCache) {
r = fBreaks.elementAt(fPositionInCache);
if (r < fromPos) {
fBoundary = r;
fStatusIndex = ( r == fStart) ? fFirstRuleStatusIndex : fOtherRuleStatusIndex;
return true;
}
}
assert(false);
fPositionInCache = -1;
return false;
};
/**
* Populate the cache with the dictionary based boundaries within a region of text.
* @param startPos The start position of a range of text
* @param endPos The end position of a range of text
* @param firstRuleStatus The rule status index that applies to the break at startPos
* @param otherRuleStatus The rule status index that applies to boundaries other than startPos
* @internal
*/
void populateDictionary(int startPos, int endPos,
int firstRuleStatus, int otherRuleStatus) {
if ((endPos - startPos) <= 1) {
return;
}
reset();
fFirstRuleStatusIndex = firstRuleStatus;
fOtherRuleStatusIndex = otherRuleStatus;
int rangeStart = startPos;
int rangeEnd = endPos;
int category;
int current;
int foundBreakCount = 0;
// Loop through the text, looking for ranges of dictionary characters.
// For each span, find the appropriate break engine, and ask it to find
// any breaks within the span.
fText.setIndex(rangeStart);
int c = CharacterIteration.current32(fText);
category = (short)fRData.fTrie.get(c);
int dictStart = fRData.fFTable.fDictCategoriesStart;
while(true) {
while((current = fText.getIndex()) < rangeEnd && (category < dictStart)) {
c = CharacterIteration.next32(fText); // pre-increment
category = (short)fRData.fTrie.get(c);
}
if (current >= rangeEnd) {
break;
}
// We now have a dictionary character. Get the appropriate language object
// to deal with it.
LanguageBreakEngine lbe = getLanguageBreakEngine(c);
// Ask the language object if there are any breaks. It will add them to the cache and
// leave the text pointer on the other side of its range, ready to search for the next one.
if (lbe != null) {
foundBreakCount += lbe.findBreaks(fText, rangeStart, rangeEnd, fBreaks);
}
// Reload the loop variables for the next go-round
c = CharacterIteration.current32(fText);
category = (short)fRData.fTrie.get(c);
}
// If we found breaks, ensure that the first and last entries are
// the original starting and ending position. And initialize the
// cache iteration position to the first entry.
// System.out.printf("foundBreakCount = %d%n", foundBreakCount);
if (foundBreakCount > 0) {
assert(foundBreakCount == fBreaks.size());
if (startPos < fBreaks.elementAt(0)) {
// The dictionary did not place a boundary at the start of the segment of text.
// Add one now. This should not commonly happen, but it would be easy for interactions
// of the rules for dictionary segments and the break engine implementations to
// inadvertently cause it. Cover it here, just in case.
fBreaks.offer(startPos);
}
if (endPos > fBreaks.peek()) {
fBreaks.push(endPos);
}
fPositionInCache = 0;
// Note: Dictionary matching may extend beyond the original limit.
fStart = fBreaks.elementAt(0);
fLimit = fBreaks.peek();
} else {
// there were no language-based breaks, even though the segment contained
// dictionary characters. Subsequent attempts to fetch boundaries from the dictionary cache
// for this range will fail, and the calling code will fall back to the rule based boundaries.
}
};
DictionaryCache() {
fPositionInCache = -1;
fBreaks = new DictionaryBreakEngine.DequeI();
}
/**
* copy constructor. Used by RuleBasedBreakIterator.clone().
*
* @param src the source object to be copied.
*/
DictionaryCache(DictionaryCache src) {
try {
fBreaks = (DictionaryBreakEngine.DequeI)src.fBreaks.clone();
}
catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
fPositionInCache = src.fPositionInCache;
fStart = src.fStart;
fLimit = src.fLimit;
fFirstRuleStatusIndex = src.fFirstRuleStatusIndex;
fOtherRuleStatusIndex = src.fOtherRuleStatusIndex;
fBoundary = src.fBoundary;
fStatusIndex = src.fStatusIndex;
}
// A data structure containing the boundaries themselves. Essentially a vector of raw ints.
DictionaryBreakEngine.DequeI fBreaks;
int fPositionInCache; // Index in fBreaks of last boundary returned by following()
// // or preceding(). Optimizes sequential access.
int fStart; // Text position of first boundary in cache.
int fLimit; // Last boundary in cache. Which is the limit of the
// // text segment being handled by the dictionary.
int fFirstRuleStatusIndex; // Rule status info for first boundary.
int fOtherRuleStatusIndex; // Rule status info for 2nd through last boundaries.
int fBoundary; // Current boundary. Set by preceding(), following().
int fStatusIndex; // Current rule status index. Set by preceding, following().
};
/*
* class BreakCache
*
* Cache of break boundary positions and rule status values.
* Break iterator API functions, next(), previous(), etc., will use cached results
* when possible, and otherwise cache new results as they are obtained.
*
* Uniformly caches both dictionary and rule based (non-dictionary) boundaries.
*
* The cache is implemented as a single circular buffer.
*/
/*
* size of the circular cache buffer.
*/
class BreakCache {
BreakCache() {
reset();
};
void reset(int pos, int ruleStatus) {
fStartBufIdx = 0;
fEndBufIdx = 0;
fTextIdx = pos;
fBufIdx = 0;
fBoundaries[0] = pos;
fStatuses[0] = (short)ruleStatus;
}
void reset() {reset(0, 0); };
void next() {
if (fBufIdx == fEndBufIdx) {
fDone = !populateFollowing();
fPosition = fTextIdx;
fRuleStatusIndex = fStatuses[fBufIdx];
} else {
fBufIdx = modChunkSize(fBufIdx + 1);
fTextIdx = fPosition = fBoundaries[fBufIdx];
fRuleStatusIndex = fStatuses[fBufIdx];
}
};
void previous() {
int initialBufIdx = fBufIdx;
if (fBufIdx == fStartBufIdx) {
// At start of cache. Prepend to it.
populatePreceding();
} else {
// Cache already holds the next boundary
fBufIdx = modChunkSize(fBufIdx - 1);
fTextIdx = fBoundaries[fBufIdx];
}
fDone = (fBufIdx == initialBufIdx);
fPosition = fTextIdx;
fRuleStatusIndex = fStatuses[fBufIdx];
return;
};
// Move the iteration state to the position following the startPosition.
// Input position must be pinned to the input length.
void following(int startPos) {
if (startPos == fTextIdx || seek(startPos) || populateNear(startPos)) {
// startPos is in the cache. Do a next() from that position.
// TODO: an awkward set of interactions with bi->fDone
// seek() does not clear it; it can't because of interactions with populateNear().
// next() does not clear it in the fast-path case, where everything matters. Maybe it should.
// So clear it here, for the case where seek() succeeded on an iterator that had previously run off the end.
fDone = false;
next();
}
};
void preceding(int startPos) {
if (startPos == fTextIdx || seek(startPos) || populateNear(startPos)) {
if (startPos == fTextIdx) {
previous();
} else {
// seek() leaves the BreakCache positioned at the preceding boundary
// if the requested position is between two bounaries.
// current() pushes the BreakCache position out to the BreakIterator itself.
assert(startPos > fTextIdx);
current();
}
}
return;
};
/**
* Update the state of the public BreakIterator (fBI) to reflect the
* current state of the break iterator cache (this).
*/
int current() {
fPosition = fTextIdx;
fRuleStatusIndex = fStatuses[fBufIdx];
fDone = false;
return fTextIdx;
};
/**
* Add boundaries to the cache near the specified position.
* The given position need not be a boundary itself.
* The input position must be within the range of the text, and
* on a code point boundary.
* If the requested position is a break boundary, leave the iteration
* position on it.
* If the requested position is not a boundary, leave the iteration
* position on the preceding boundary and include both the the
* preceding and following boundaries in the cache.
* Additional boundaries, either preceding or following, may be added
* to the cache as a side effect.
*
* Return false if the operation failed.
*/
boolean populateNear(int position) {
assert(position < fBoundaries[fStartBufIdx] || position > fBoundaries[fEndBufIdx]);
// Find a boundary somewhere in the vicinity of the requested position.
// Depending on the safe rules and the text data, it could be either before, at, or after
// the requested position.
// If the requested position is not near already cached positions, clear the existing cache,
// find a near-by boundary and begin new cache contents there.
if ((position < fBoundaries[fStartBufIdx] - 15) || position > (fBoundaries[fEndBufIdx] + 15)) {
int aBoundary = fText.getBeginIndex();
int ruleStatusIndex = 0;
if (position > aBoundary + 20) {
int backupPos = handleSafePrevious(position);
if (backupPos > aBoundary) {
// Advance to the boundary following the backup position.
// There is a complication: the safe reverse rules identify pairs of code points
// that are safe. If advancing from the safe point moves forwards by less than
// two code points, we need to advance one more time to ensure that the boundary
// is good, including a correct rules status value.
//
fPosition = backupPos;
aBoundary = handleNext();
if (aBoundary == backupPos + 1 ||
(aBoundary == backupPos + 2 &&
Character.isHighSurrogate(fText.setIndex(backupPos)) &&
Character.isLowSurrogate(fText.next()))) {
// The initial handleNext() only advanced by a single code point. Go again.
// Safe rules identify safe pairs.
aBoundary = handleNext();
}
}
ruleStatusIndex = fRuleStatusIndex;
}
reset(aBoundary, ruleStatusIndex); // Reset cache to hold aBoundary as a single starting point.
}
// Fill in boundaries between existing cache content and the new requested position.
if (fBoundaries[fEndBufIdx] < position) {
// The last position in the cache precedes the requested position.
// Add following position(s) to the cache.
while (fBoundaries[fEndBufIdx] < position) {
if (!populateFollowing()) {
assert false;
return false;
}
}
fBufIdx = fEndBufIdx; // Set iterator position to the end of the buffer.
fTextIdx = fBoundaries[fBufIdx]; // Required because populateFollowing may add extra boundaries.
while (fTextIdx > position) { // Move backwards to a position at or preceding the requested pos.
previous();
}
return true;
}
if (fBoundaries[fStartBufIdx] > position) {
// The first position in the cache is beyond the requested position.
// back up more until we get a boundary <= the requested position.
while (fBoundaries[fStartBufIdx] > position) {
populatePreceding();
}
fBufIdx = fStartBufIdx; // Set iterator position to the start of the buffer.
fTextIdx = fBoundaries[fBufIdx]; // Required because populatePreceding may add extra boundaries.
while (fTextIdx < position) { // Move forwards to a position at or following the requested pos.
next();
}
if (fTextIdx > position) {
// If position is not itself a boundary, the next() loop above will overshoot.
// Back up one, leaving cache position at the boundary preceding the requested position.
previous();
}
return true;
}
assert fTextIdx == position;
return true;
};
/**
* Add boundary(s) to the cache following the current last boundary.
* Return false if at the end of the text, and no more boundaries can be added.
* Leave iteration position at the first newly added boundary, or unchanged if no boundary was added.
*/
boolean populateFollowing() {
int fromPosition = fBoundaries[fEndBufIdx];
int fromRuleStatusIdx = fStatuses[fEndBufIdx];
int pos = 0;
int ruleStatusIdx = 0;
if (fDictionaryCache.following(fromPosition)) {
addFollowing(fDictionaryCache.fBoundary, fDictionaryCache.fStatusIndex, UpdateCachePosition);
return true;
}
fPosition = fromPosition;
pos = handleNext();
if (pos == BreakIterator.DONE) {
return false;
}
ruleStatusIdx = fRuleStatusIndex;
if (fDictionaryCharCount > 0) {
// The text segment obtained from the rules includes dictionary characters.
// Subdivide it, with subdivided results going into the dictionary cache.
fDictionaryCache.populateDictionary(fromPosition, pos, fromRuleStatusIdx, ruleStatusIdx);
if (fDictionaryCache.following(fromPosition)) {
addFollowing(fDictionaryCache.fBoundary, fDictionaryCache.fStatusIndex, UpdateCachePosition);
return true;
// TODO: may want to move a sizable chunk of the dictionary cache to the break cache at this point.
// But be careful with interactions with populateNear().
}
}
// Rule based segment did not include dictionary characters.
// Or, it did contain dictionary chars, but the dictionary segmenter didn't handle them,
// meaning that we didn't take the return, above.
// Add its end point to the cache.
addFollowing(pos, ruleStatusIdx, UpdateCachePosition);
// Add several non-dictionary boundaries at this point, to optimize straight forward iteration.
// (subsequent calls to BreakIterator::next() will take the fast path, getting cached results.
//
for (int count=0; count<6; ++count) {
pos = handleNext();
if (pos == BreakIterator.DONE || fDictionaryCharCount > 0) {
break;
}
addFollowing(pos, fRuleStatusIndex, RetainCachePosition);
}
return true;
};
/**
* Add one or more boundaries to the cache preceding the first currently cached boundary.
* Leave the iteration position on the first added boundary.
* Return false if no boundaries could be added (if at the start of the text.)
*/
boolean populatePreceding() {
int textBegin = fText.getBeginIndex();
int fromPosition = fBoundaries[fStartBufIdx];
if (fromPosition == textBegin) {
return false;
}
int position = textBegin;
int positionStatusIdx = 0;
if (fDictionaryCache.preceding(fromPosition)) {
addPreceding(fDictionaryCache.fBoundary, fDictionaryCache.fStatusIndex, UpdateCachePosition);
return true;
}
int backupPosition = fromPosition;
// Find a boundary somewhere preceding the first already-cached boundary
do {
backupPosition = backupPosition - 30;
if (backupPosition <= textBegin) {
backupPosition = textBegin;
} else {
backupPosition = handleSafePrevious(backupPosition);
}
if (backupPosition == BreakIterator.DONE || backupPosition == textBegin) {
position = textBegin;
positionStatusIdx = 0;
} else {
// Advance to the boundary following the backup position.
// There is a complication: the safe reverse rules identify pairs of code points
// that are safe. If advancing from the safe point moves forwards by less than
// two code points, we need to advance one more time to ensure that the boundary
// is good, including a correct rules status value.
//
fPosition = backupPosition; // TODO: pass starting position in a clearer way.
position = handleNext();
if (position == backupPosition + 1 ||
(position == backupPosition + 2 &&
Character.isHighSurrogate(fText.setIndex(backupPosition)) &&
Character.isLowSurrogate(fText.next()))) {
// The initial handleNext() only advanced by a single code point. Go again.
// Safe rules identify safe pairs.
position = handleNext();
}
positionStatusIdx = fRuleStatusIndex;
}
} while (position >= fromPosition);
// Find boundaries between the one we just located and the first already-cached boundary
// Put them in a side buffer, because we don't yet know where they will fall in the circular cache buffer.
fSideBuffer.removeAllElements();
fSideBuffer.push(position);
fSideBuffer.push(positionStatusIdx);
do {
int prevPosition = fPosition = position;
int prevStatusIdx = positionStatusIdx;
position = handleNext();
positionStatusIdx = fRuleStatusIndex;
if (position == BreakIterator.DONE) {
break;
}
boolean segmentHandledByDictionary = false;
if (fDictionaryCharCount != 0) {
// Segment from the rules includes dictionary characters.
// Subdivide it, with subdivided results going into the dictionary cache.
int dictSegEndPosition = position;
fDictionaryCache.populateDictionary(prevPosition, dictSegEndPosition, prevStatusIdx, positionStatusIdx);
while (fDictionaryCache.following(prevPosition)) {
position = fDictionaryCache.fBoundary;
positionStatusIdx = fDictionaryCache.fStatusIndex;
segmentHandledByDictionary = true;
assert(position > prevPosition);
if (position >= fromPosition) {
break;
}
assert(position <= dictSegEndPosition);
fSideBuffer.push(position);
fSideBuffer.push(positionStatusIdx);
prevPosition = position;
}
assert(position==dictSegEndPosition || position>=fromPosition);
}
if (!segmentHandledByDictionary && position < fromPosition) {
fSideBuffer.push(position);
fSideBuffer.push(positionStatusIdx);
}
} while (position < fromPosition);
// Move boundaries from the side buffer to the main circular buffer.
boolean success = false;
if (!fSideBuffer.isEmpty()) {
positionStatusIdx = fSideBuffer.pop();
position = fSideBuffer.pop();
addPreceding(position, positionStatusIdx, UpdateCachePosition);
success = true;
}
while (!fSideBuffer.isEmpty()) {
positionStatusIdx = fSideBuffer.pop();
position = fSideBuffer.pop();
if (!addPreceding(position, positionStatusIdx, RetainCachePosition)) {
// No space in circular buffer to hold a new preceding result while
// also retaining the current cache (iteration) position.
// Bailing out is safe; the cache will refill again if needed.
break;
}
}
return success;
};
static final boolean RetainCachePosition = false;
static final boolean UpdateCachePosition = true;
/**
* Add the boundary following the current position.
* The current position can be left as it was, or changed to the newly added boundary,
* as specified by the update parameter.
*/
void addFollowing(int position, int ruleStatusIdx, boolean update) {
assert(position > fBoundaries[fEndBufIdx]);
assert(ruleStatusIdx <= Short.MAX_VALUE);
int nextIdx = modChunkSize(fEndBufIdx + 1);
if (nextIdx == fStartBufIdx) {
fStartBufIdx = modChunkSize(fStartBufIdx + 6); // TODO: experiment. Probably revert to 1.
}
fBoundaries[nextIdx] = position;
fStatuses[nextIdx] = (short)ruleStatusIdx;
fEndBufIdx = nextIdx;
if (update == UpdateCachePosition) {
// Set current position to the newly added boundary.
fBufIdx = nextIdx;
fTextIdx = position;
} else {
// Retaining the original cache position.
// Check if the added boundary wraps around the buffer, and would over-write the original position.
// It's the responsibility of callers of this function to not add too many.
assert(nextIdx != fBufIdx);
}
};
/**
* Add the boundary preceding the current position.
* The current position can be left as it was, or changed to the newly added boundary,
* as specified by the update parameter.
*/
boolean addPreceding(int position, int ruleStatusIdx, boolean update) {
assert(position < fBoundaries[fStartBufIdx]);
assert(ruleStatusIdx <= Short.MAX_VALUE);
int nextIdx = modChunkSize(fStartBufIdx - 1);
if (nextIdx == fEndBufIdx) {
if (fBufIdx == fEndBufIdx && update == RetainCachePosition) {
// Failure. The insertion of the new boundary would claim the buffer position that is the
// current iteration position. And we also want to retain the current iteration position.
// (The buffer is already completely full of entries that precede the iteration position.)
return false;
}
fEndBufIdx = modChunkSize(fEndBufIdx - 1);
}
fBoundaries[nextIdx] = position;
fStatuses[nextIdx] = (short)ruleStatusIdx;
fStartBufIdx = nextIdx;
if (update == UpdateCachePosition) {
fBufIdx = nextIdx;
fTextIdx = position;
}
return true;
};
/**
* Set the cache position to the specified position, or, if the position
* falls between to cached boundaries, to the preceding boundary.
* Fails if the requested position is outside of the range of boundaries currently held by the cache.
* The startPosition must be on a code point boundary.
*
* Return true if successful, false if the specified position is after
* the last cached boundary or before the first.
*/
boolean seek(int pos) {
if (pos < fBoundaries[fStartBufIdx] || pos > fBoundaries[fEndBufIdx]) {
return false;
}
if (pos == fBoundaries[fStartBufIdx]) {
// Common case: seek(0), from BreakIterator::first()
fBufIdx = fStartBufIdx;
fTextIdx = fBoundaries[fBufIdx];
return true;
}
if (pos == fBoundaries[fEndBufIdx]) {
fBufIdx = fEndBufIdx;
fTextIdx = fBoundaries[fBufIdx];
return true;
}
int min = fStartBufIdx;
int max = fEndBufIdx;
while (min != max) {
int probe = (min + max + (min>max ? CACHE_SIZE : 0)) / 2;
probe = modChunkSize(probe);
if (fBoundaries[probe] > pos) {
max = probe;
} else {
min = modChunkSize(probe + 1);
}
}
assert(fBoundaries[max] > pos);
fBufIdx = modChunkSize(max - 1);
fTextIdx = fBoundaries[fBufIdx];
assert(fTextIdx <= pos);
return true;
};
/**
* copy constructor, used from RuleBasedBreakIterator.clone().
*
* @param src
*/
BreakCache(BreakCache src) {
fStartBufIdx = src.fStartBufIdx;
fEndBufIdx = src.fEndBufIdx;
fTextIdx = src.fTextIdx;
fBufIdx = src.fBufIdx;
fBoundaries = src.fBoundaries.clone();
fStatuses = src.fStatuses.clone();
fSideBuffer = new DictionaryBreakEngine.DequeI(); // Transient, no need to clone contents.
}
void dumpCache() {
System.out.printf("fTextIdx:%d fBufIdx:%d%n", fTextIdx, fBufIdx);
for (int i=fStartBufIdx; ; i=modChunkSize(i+1)) {
System.out.printf("%d %d%n", i, fBoundaries[i]);
if (i == fEndBufIdx) {
break;
}
}
};
private final int modChunkSize(int index) { return index & (CACHE_SIZE - 1); };
static final int CACHE_SIZE = 128;
// static_assert((CACHE_SIZE & (CACHE_SIZE-1)) == 0, "CACHE_SIZE must be power of two.");
int fStartBufIdx;
int fEndBufIdx; // inclusive
int fTextIdx;
int fBufIdx;
int[] fBoundaries = new int[CACHE_SIZE];
short[] fStatuses = new short[CACHE_SIZE];
DictionaryBreakEngine.DequeI fSideBuffer = new DictionaryBreakEngine.DequeI();
};
}