Path: blob/master/src/java.base/share/classes/java/text/CollationElementIterator.java
41152 views
/*1* Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425/*26* (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved27* (C) Copyright IBM Corp. 1996-1998 - All Rights Reserved28*29* The original version of this source code and documentation is copyrighted30* and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These31* materials are provided under terms of a License Agreement between Taligent32* and Sun. This technology is protected by multiple US and International33* patents. This notice and attribution to Taligent may not be removed.34* Taligent is a registered trademark of Taligent, Inc.35*36*/3738package java.text;3940import java.lang.Character;41import java.util.Vector;42import sun.text.CollatorUtilities;43import jdk.internal.icu.text.NormalizerBase;4445/**46* The {@code CollationElementIterator} class is used as an iterator47* to walk through each character of an international string. Use the iterator48* to return the ordering priority of the positioned character. The ordering49* priority of a character, which we refer to as a key, defines how a character50* is collated in the given collation object.51*52* <p>53* For example, consider the following in Spanish:54* <blockquote>55* <pre>56* "ca" → the first key is key('c') and second key is key('a').57* "cha" → the first key is key('ch') and second key is key('a').58* </pre>59* </blockquote>60* And in German,61* <blockquote>62* <pre>63* "\u00e4b" → the first key is key('a'), the second key is key('e'), and64* the third key is key('b').65* </pre>66* </blockquote>67* The key of a character is an integer composed of primary order(short),68* secondary order(byte), and tertiary order(byte). Java strictly defines69* the size and signedness of its primitive data types. Therefore, the static70* functions {@code primaryOrder}, {@code secondaryOrder}, and71* {@code tertiaryOrder} return {@code int}, {@code short},72* and {@code short} respectively to ensure the correctness of the key73* value.74*75* <p>76* Example of the iterator usage,77* <blockquote>78* <pre>79*80* String testString = "This is a test";81* Collator col = Collator.getInstance();82* if (col instanceof RuleBasedCollator) {83* RuleBasedCollator ruleBasedCollator = (RuleBasedCollator)col;84* CollationElementIterator collationElementIterator = ruleBasedCollator.getCollationElementIterator(testString);85* int primaryOrder = CollationElementIterator.primaryOrder(collationElementIterator.next());86* :87* }88* </pre>89* </blockquote>90*91* <p>92* {@code CollationElementIterator.next} returns the collation order93* of the next character. A collation order consists of primary order,94* secondary order and tertiary order. The data type of the collation95* order is <strong>int</strong>. The first 16 bits of a collation order96* is its primary order; the next 8 bits is the secondary order and the97* last 8 bits is the tertiary order.98*99* <p><b>Note:</b> {@code CollationElementIterator} is a part of100* {@code RuleBasedCollator} implementation. It is only usable101* with {@code RuleBasedCollator} instances.102*103* @see Collator104* @see RuleBasedCollator105* @author Helena Shih, Laura Werner, Richard Gillam106* @since 1.1107*/108public final class CollationElementIterator109{110/**111* Null order which indicates the end of string is reached by the112* cursor.113*/114public static final int NULLORDER = 0xffffffff;115116/**117* CollationElementIterator constructor. This takes the source string and118* the collation object. The cursor will walk thru the source string based119* on the predefined collation rules. If the source string is empty,120* NULLORDER will be returned on the calls to next().121* @param sourceText the source string.122* @param owner the collation object.123*/124CollationElementIterator(String sourceText, RuleBasedCollator owner) {125this.owner = owner;126ordering = owner.getTables();127if (!sourceText.isEmpty()) {128NormalizerBase.Mode mode =129CollatorUtilities.toNormalizerMode(owner.getDecomposition());130text = new NormalizerBase(sourceText, mode);131}132}133134/**135* CollationElementIterator constructor. This takes the source string and136* the collation object. The cursor will walk thru the source string based137* on the predefined collation rules. If the source string is empty,138* NULLORDER will be returned on the calls to next().139* @param sourceText the source string.140* @param owner the collation object.141*/142CollationElementIterator(CharacterIterator sourceText, RuleBasedCollator owner) {143this.owner = owner;144ordering = owner.getTables();145NormalizerBase.Mode mode =146CollatorUtilities.toNormalizerMode(owner.getDecomposition());147text = new NormalizerBase(sourceText, mode);148}149150/**151* Resets the cursor to the beginning of the string. The next call152* to next() will return the first collation element in the string.153*/154public void reset()155{156if (text != null) {157text.reset();158NormalizerBase.Mode mode =159CollatorUtilities.toNormalizerMode(owner.getDecomposition());160text.setMode(mode);161}162buffer = null;163expIndex = 0;164swapOrder = 0;165}166167/**168* Get the next collation element in the string. <p>This iterator iterates169* over a sequence of collation elements that were built from the string.170* Because there isn't necessarily a one-to-one mapping from characters to171* collation elements, this doesn't mean the same thing as "return the172* collation element [or ordering priority] of the next character in the173* string".</p>174* <p>This function returns the collation element that the iterator is currently175* pointing to and then updates the internal pointer to point to the next element.176* previous() updates the pointer first and then returns the element. This177* means that when you change direction while iterating (i.e., call next() and178* then call previous(), or call previous() and then call next()), you'll get179* back the same element twice.</p>180*181* @return the next collation element182*/183public int next()184{185if (text == null) {186return NULLORDER;187}188NormalizerBase.Mode textMode = text.getMode();189// convert the owner's mode to something the Normalizer understands190NormalizerBase.Mode ownerMode =191CollatorUtilities.toNormalizerMode(owner.getDecomposition());192if (textMode != ownerMode) {193text.setMode(ownerMode);194}195196// if buffer contains any decomposed char values197// return their strength orders before continuing in198// the Normalizer's CharacterIterator.199if (buffer != null) {200if (expIndex < buffer.length) {201return strengthOrder(buffer[expIndex++]);202} else {203buffer = null;204expIndex = 0;205}206} else if (swapOrder != 0) {207if (Character.isSupplementaryCodePoint(swapOrder)) {208char[] chars = Character.toChars(swapOrder);209swapOrder = chars[1];210return chars[0] << 16;211}212int order = swapOrder << 16;213swapOrder = 0;214return order;215}216int ch = text.next();217218// are we at the end of Normalizer's text?219if (ch == NormalizerBase.DONE) {220return NULLORDER;221}222223int value = ordering.getUnicodeOrder(ch);224if (value == RuleBasedCollator.UNMAPPED) {225swapOrder = ch;226return UNMAPPEDCHARVALUE;227}228else if (value >= RuleBasedCollator.CONTRACTCHARINDEX) {229value = nextContractChar(ch);230}231if (value >= RuleBasedCollator.EXPANDCHARINDEX) {232buffer = ordering.getExpandValueList(value);233expIndex = 0;234value = buffer[expIndex++];235}236237if (ordering.isSEAsianSwapping()) {238int consonant;239if (isThaiPreVowel(ch)) {240consonant = text.next();241if (isThaiBaseConsonant(consonant)) {242buffer = makeReorderedBuffer(consonant, value, buffer, true);243value = buffer[0];244expIndex = 1;245} else if (consonant != NormalizerBase.DONE) {246text.previous();247}248}249if (isLaoPreVowel(ch)) {250consonant = text.next();251if (isLaoBaseConsonant(consonant)) {252buffer = makeReorderedBuffer(consonant, value, buffer, true);253value = buffer[0];254expIndex = 1;255} else if (consonant != NormalizerBase.DONE) {256text.previous();257}258}259}260261return strengthOrder(value);262}263264/**265* Get the previous collation element in the string. <p>This iterator iterates266* over a sequence of collation elements that were built from the string.267* Because there isn't necessarily a one-to-one mapping from characters to268* collation elements, this doesn't mean the same thing as "return the269* collation element [or ordering priority] of the previous character in the270* string".</p>271* <p>This function updates the iterator's internal pointer to point to the272* collation element preceding the one it's currently pointing to and then273* returns that element, while next() returns the current element and then274* updates the pointer. This means that when you change direction while275* iterating (i.e., call next() and then call previous(), or call previous()276* and then call next()), you'll get back the same element twice.</p>277*278* @return the previous collation element279* @since 1.2280*/281public int previous()282{283if (text == null) {284return NULLORDER;285}286NormalizerBase.Mode textMode = text.getMode();287// convert the owner's mode to something the Normalizer understands288NormalizerBase.Mode ownerMode =289CollatorUtilities.toNormalizerMode(owner.getDecomposition());290if (textMode != ownerMode) {291text.setMode(ownerMode);292}293if (buffer != null) {294if (expIndex > 0) {295return strengthOrder(buffer[--expIndex]);296} else {297buffer = null;298expIndex = 0;299}300} else if (swapOrder != 0) {301if (Character.isSupplementaryCodePoint(swapOrder)) {302char[] chars = Character.toChars(swapOrder);303swapOrder = chars[1];304return chars[0] << 16;305}306int order = swapOrder << 16;307swapOrder = 0;308return order;309}310int ch = text.previous();311if (ch == NormalizerBase.DONE) {312return NULLORDER;313}314315int value = ordering.getUnicodeOrder(ch);316317if (value == RuleBasedCollator.UNMAPPED) {318swapOrder = UNMAPPEDCHARVALUE;319return ch;320} else if (value >= RuleBasedCollator.CONTRACTCHARINDEX) {321value = prevContractChar(ch);322}323if (value >= RuleBasedCollator.EXPANDCHARINDEX) {324buffer = ordering.getExpandValueList(value);325expIndex = buffer.length;326value = buffer[--expIndex];327}328329if (ordering.isSEAsianSwapping()) {330int vowel;331if (isThaiBaseConsonant(ch)) {332vowel = text.previous();333if (isThaiPreVowel(vowel)) {334buffer = makeReorderedBuffer(vowel, value, buffer, false);335expIndex = buffer.length - 1;336value = buffer[expIndex];337} else {338text.next();339}340}341if (isLaoBaseConsonant(ch)) {342vowel = text.previous();343if (isLaoPreVowel(vowel)) {344buffer = makeReorderedBuffer(vowel, value, buffer, false);345expIndex = buffer.length - 1;346value = buffer[expIndex];347} else {348text.next();349}350}351}352353return strengthOrder(value);354}355356/**357* Return the primary component of a collation element.358* @param order the collation element359* @return the element's primary component360*/361public static final int primaryOrder(int order)362{363order &= RBCollationTables.PRIMARYORDERMASK;364return (order >>> RBCollationTables.PRIMARYORDERSHIFT);365}366/**367* Return the secondary component of a collation element.368* @param order the collation element369* @return the element's secondary component370*/371public static final short secondaryOrder(int order)372{373order = order & RBCollationTables.SECONDARYORDERMASK;374return ((short)(order >> RBCollationTables.SECONDARYORDERSHIFT));375}376/**377* Return the tertiary component of a collation element.378* @param order the collation element379* @return the element's tertiary component380*/381public static final short tertiaryOrder(int order)382{383return ((short)(order &= RBCollationTables.TERTIARYORDERMASK));384}385386/**387* Get the comparison order in the desired strength. Ignore the other388* differences.389* @param order The order value390*/391final int strengthOrder(int order)392{393int s = owner.getStrength();394if (s == Collator.PRIMARY)395{396order &= RBCollationTables.PRIMARYDIFFERENCEONLY;397} else if (s == Collator.SECONDARY)398{399order &= RBCollationTables.SECONDARYDIFFERENCEONLY;400}401return order;402}403404/**405* Sets the iterator to point to the collation element corresponding to406* the specified character (the parameter is a CHARACTER offset in the407* original string, not an offset into its corresponding sequence of408* collation elements). The value returned by the next call to next()409* will be the collation element corresponding to the specified position410* in the text. If that position is in the middle of a contracting411* character sequence, the result of the next call to next() is the412* collation element for that sequence. This means that getOffset()413* is not guaranteed to return the same value as was passed to a preceding414* call to setOffset().415*416* @param newOffset The new character offset into the original text.417* @since 1.2418*/419@SuppressWarnings("deprecation") // getBeginIndex, getEndIndex and setIndex are deprecated420public void setOffset(int newOffset)421{422if (text != null) {423if (newOffset < text.getBeginIndex()424|| newOffset >= text.getEndIndex()) {425text.setIndexOnly(newOffset);426} else {427int c = text.setIndex(newOffset);428429// if the desired character isn't used in a contracting character430// sequence, bypass all the backing-up logic-- we're sitting on431// the right character already432if (ordering.usedInContractSeq(c)) {433// walk backwards through the string until we see a character434// that DOESN'T participate in a contracting character sequence435while (ordering.usedInContractSeq(c)) {436c = text.previous();437}438// now walk forward using this object's next() method until439// we pass the starting point and set our current position440// to the beginning of the last "character" before or at441// our starting position442int last = text.getIndex();443while (text.getIndex() <= newOffset) {444last = text.getIndex();445next();446}447text.setIndexOnly(last);448// we don't need this, since last is the last index449// that is the starting of the contraction which encompass450// newOffset451// text.previous();452}453}454}455buffer = null;456expIndex = 0;457swapOrder = 0;458}459460/**461* Returns the character offset in the original text corresponding to the next462* collation element. (That is, getOffset() returns the position in the text463* corresponding to the collation element that will be returned by the next464* call to next().) This value will always be the index of the FIRST character465* corresponding to the collation element (a contracting character sequence is466* when two or more characters all correspond to the same collation element).467* This means if you do setOffset(x) followed immediately by getOffset(), getOffset()468* won't necessarily return x.469*470* @return The character offset in the original text corresponding to the collation471* element that will be returned by the next call to next().472* @since 1.2473*/474public int getOffset()475{476return (text != null) ? text.getIndex() : 0;477}478479480/**481* Return the maximum length of any expansion sequences that end482* with the specified comparison order.483* @param order a collation order returned by previous or next.484* @return the maximum length of any expansion sequences ending485* with the specified order.486* @since 1.2487*/488public int getMaxExpansion(int order)489{490return ordering.getMaxExpansion(order);491}492493/**494* Set a new string over which to iterate.495*496* @param source the new source text497* @since 1.2498*/499public void setText(String source)500{501buffer = null;502swapOrder = 0;503expIndex = 0;504NormalizerBase.Mode mode =505CollatorUtilities.toNormalizerMode(owner.getDecomposition());506if (text == null) {507text = new NormalizerBase(source, mode);508} else {509text.setMode(mode);510text.setText(source);511}512}513514/**515* Set a new string over which to iterate.516*517* @param source the new source text.518* @since 1.2519*/520public void setText(CharacterIterator source)521{522buffer = null;523swapOrder = 0;524expIndex = 0;525NormalizerBase.Mode mode =526CollatorUtilities.toNormalizerMode(owner.getDecomposition());527if (text == null) {528text = new NormalizerBase(source, mode);529} else {530text.setMode(mode);531text.setText(source);532}533}534535//============================================================536// privates537//============================================================538539/**540* Determine if a character is a Thai vowel (which sorts after541* its base consonant).542*/543private static final boolean isThaiPreVowel(int ch) {544return (ch >= 0x0e40) && (ch <= 0x0e44);545}546547/**548* Determine if a character is a Thai base consonant549*/550private static final boolean isThaiBaseConsonant(int ch) {551return (ch >= 0x0e01) && (ch <= 0x0e2e);552}553554/**555* Determine if a character is a Lao vowel (which sorts after556* its base consonant).557*/558private static final boolean isLaoPreVowel(int ch) {559return (ch >= 0x0ec0) && (ch <= 0x0ec4);560}561562/**563* Determine if a character is a Lao base consonant564*/565private static final boolean isLaoBaseConsonant(int ch) {566return (ch >= 0x0e81) && (ch <= 0x0eae);567}568569/**570* This method produces a buffer which contains the collation571* elements for the two characters, with colFirst's values preceding572* another character's. Presumably, the other character precedes colFirst573* in logical order (otherwise you wouldn't need this method would you?).574* The assumption is that the other char's value(s) have already been575* computed. If this char has a single element it is passed to this576* method as lastValue, and lastExpansion is null. If it has an577* expansion it is passed in lastExpansion, and colLastValue is ignored.578*/579private int[] makeReorderedBuffer(int colFirst,580int lastValue,581int[] lastExpansion,582boolean forward) {583584int[] result;585586int firstValue = ordering.getUnicodeOrder(colFirst);587if (firstValue >= RuleBasedCollator.CONTRACTCHARINDEX) {588firstValue = forward? nextContractChar(colFirst) : prevContractChar(colFirst);589}590591int[] firstExpansion = null;592if (firstValue >= RuleBasedCollator.EXPANDCHARINDEX) {593firstExpansion = ordering.getExpandValueList(firstValue);594}595596if (!forward) {597int temp1 = firstValue;598firstValue = lastValue;599lastValue = temp1;600int[] temp2 = firstExpansion;601firstExpansion = lastExpansion;602lastExpansion = temp2;603}604605if (firstExpansion == null && lastExpansion == null) {606result = new int [2];607result[0] = firstValue;608result[1] = lastValue;609}610else {611int firstLength = firstExpansion==null? 1 : firstExpansion.length;612int lastLength = lastExpansion==null? 1 : lastExpansion.length;613result = new int[firstLength + lastLength];614615if (firstExpansion == null) {616result[0] = firstValue;617}618else {619System.arraycopy(firstExpansion, 0, result, 0, firstLength);620}621622if (lastExpansion == null) {623result[firstLength] = lastValue;624}625else {626System.arraycopy(lastExpansion, 0, result, firstLength, lastLength);627}628}629630return result;631}632633/**634* Check if a comparison order is ignorable.635* @return true if a character is ignorable, false otherwise.636*/637static final boolean isIgnorable(int order)638{639return ((primaryOrder(order) == 0) ? true : false);640}641642/**643* Get the ordering priority of the next contracting character in the644* string.645* @param ch the starting character of a contracting character token646* @return the next contracting character's ordering. Returns NULLORDER647* if the end of string is reached.648*/649private int nextContractChar(int ch)650{651// First get the ordering of this single character,652// which is always the first element in the list653Vector<EntryPair> list = ordering.getContractValues(ch);654EntryPair pair = list.firstElement();655int order = pair.value;656657// find out the length of the longest contracting character sequence in the list.658// There's logic in the builder code to make sure the longest sequence is always659// the last.660pair = list.lastElement();661int maxLength = pair.entryName.length();662663// (the Normalizer is cloned here so that the seeking we do in the next loop664// won't affect our real position in the text)665NormalizerBase tempText = (NormalizerBase)text.clone();666667// extract the next maxLength characters in the string (we have to do this using the668// Normalizer to ensure that our offsets correspond to those the rest of the669// iterator is using) and store it in "fragment".670tempText.previous();671key.setLength(0);672int c = tempText.next();673while (maxLength > 0 && c != NormalizerBase.DONE) {674if (Character.isSupplementaryCodePoint(c)) {675key.append(Character.toChars(c));676maxLength -= 2;677} else {678key.append((char)c);679--maxLength;680}681c = tempText.next();682}683String fragment = key.toString();684// now that we have that fragment, iterate through this list looking for the685// longest sequence that matches the characters in the actual text. (maxLength686// is used here to keep track of the length of the longest sequence)687// Upon exit from this loop, maxLength will contain the length of the matching688// sequence and order will contain the collation-element value corresponding689// to this sequence690maxLength = 1;691for (int i = list.size() - 1; i > 0; i--) {692pair = list.elementAt(i);693if (!pair.fwd)694continue;695696if (fragment.startsWith(pair.entryName) && pair.entryName.length()697> maxLength) {698maxLength = pair.entryName.length();699order = pair.value;700}701}702703// seek our current iteration position to the end of the matching sequence704// and return the appropriate collation-element value (if there was no matching705// sequence, we're already seeked to the right position and order already contains706// the correct collation-element value for the single character)707while (maxLength > 1) {708c = text.next();709maxLength -= Character.charCount(c);710}711return order;712}713714/**715* Get the ordering priority of the previous contracting character in the716* string.717* @param ch the starting character of a contracting character token718* @return the next contracting character's ordering. Returns NULLORDER719* if the end of string is reached.720*/721private int prevContractChar(int ch)722{723// This function is identical to nextContractChar(), except that we've724// switched things so that the next() and previous() calls on the Normalizer725// are switched and so that we skip entry pairs with the fwd flag turned on726// rather than off. Notice that we still use append() and startsWith() when727// working on the fragment. This is because the entry pairs that are used728// in reverse iteration have their names reversed already.729Vector<EntryPair> list = ordering.getContractValues(ch);730EntryPair pair = list.firstElement();731int order = pair.value;732733pair = list.lastElement();734int maxLength = pair.entryName.length();735736NormalizerBase tempText = (NormalizerBase)text.clone();737738tempText.next();739key.setLength(0);740int c = tempText.previous();741while (maxLength > 0 && c != NormalizerBase.DONE) {742if (Character.isSupplementaryCodePoint(c)) {743key.append(Character.toChars(c));744maxLength -= 2;745} else {746key.append((char)c);747--maxLength;748}749c = tempText.previous();750}751String fragment = key.toString();752753maxLength = 1;754for (int i = list.size() - 1; i > 0; i--) {755pair = list.elementAt(i);756if (pair.fwd)757continue;758759if (fragment.startsWith(pair.entryName) && pair.entryName.length()760> maxLength) {761maxLength = pair.entryName.length();762order = pair.value;763}764}765766while (maxLength > 1) {767c = text.previous();768maxLength -= Character.charCount(c);769}770return order;771}772773static final int UNMAPPEDCHARVALUE = 0x7FFF0000;774775private NormalizerBase text = null;776private int[] buffer = null;777private int expIndex = 0;778private StringBuffer key = new StringBuffer(5);779private int swapOrder = 0;780private RBCollationTables ordering;781private RuleBasedCollator owner;782}783784785