1. /*
  2. * @(#)RuleBasedCollator.java 1.26 01/11/29
  3. *
  4. * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. /*
  8. * @(#)RuleBasedCollator.java 1.26 01/11/29
  9. *
  10. * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved
  11. * (C) Copyright IBM Corp. 1996-1998 - All Rights Reserved
  12. *
  13. * Portions copyright (c) 1997, 1998 Sun Microsystems, Inc. All Rights Reserved.
  14. *
  15. * The original version of this source code and documentation is copyrighted
  16. * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
  17. * materials are provided under terms of a License Agreement between Taligent
  18. * and Sun. This technology is protected by multiple US and International
  19. * patents. This notice and attribution to Taligent may not be removed.
  20. * Taligent is a registered trademark of Taligent, Inc.
  21. *
  22. * Permission to use, copy, modify, and distribute this software
  23. * and its documentation for NON-COMMERCIAL purposes and without
  24. * fee is hereby granted provided that this copyright notice
  25. * appears in all copies. Please refer to the file "copyright.html"
  26. * for further important copyright and licensing information.
  27. *
  28. * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
  29. * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
  30. * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  31. * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR
  32. * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
  33. * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
  34. *
  35. */
  36. package java.text;
  37. import java.util.Vector;
  38. /**
  39. * The <code>RuleBasedCollator</code> class is a concrete subclass of
  40. * <code>Collator</code> that provides a simple, data-driven, table
  41. * collator. With this class you can create a customized table-based
  42. * <code>Collator</code>. <code>RuleBasedCollator</code> maps
  43. * characters to sort keys.
  44. *
  45. * <p>
  46. * <code>RuleBasedCollator</code> has the following restrictions
  47. * for efficiency (other subclasses may be used for more complex languages) :
  48. * <ol>
  49. * <li>If a French secondary ordering is specified it applies to the
  50. * whole collator object.
  51. * <li>All non-mentioned Unicode characters are at the end of the
  52. * collation order.
  53. * </ol>
  54. *
  55. * <p>
  56. * The collation table is composed of a list of collation rules, where each
  57. * rule is of three forms:
  58. * <pre>
  59. * < modifier >
  60. * < relation > < text-argument >
  61. * < reset > < text-argument >
  62. * </pre>
  63. * The following demonstrates how to create your own collation rules:
  64. * <UL Type=disc>
  65. * <LI><strong>Text-Argument</strong>: A text-argument is any sequence of
  66. * characters, excluding special characters (that is, common
  67. * whitespace characters [0009-000D, 0020] and rule syntax characters
  68. * [0021-002F, 003A-0040, 005B-0060, 007B-007E]). If those
  69. * characters are desired, you can put them in single quotes
  70. * (e.g. ampersand => '&'). Note that unquoted white space characters
  71. * are ignored; e.g. <code>b c</code> is treated as <code>bc</code>.
  72. * <LI><strong>Modifier</strong>: There is a single modifier
  73. * which is used to specify that all accents (secondary differences) are
  74. * backwards.
  75. * <p>'@' : Indicates that accents are sorted backwards, as in French.
  76. * <LI><strong>Relation</strong>: The relations are the following:
  77. * <UL Type=square>
  78. * <LI>'<' : Greater, as a letter difference (primary)
  79. * <LI>';' : Greater, as an accent difference (secondary)
  80. * <LI>',' : Greater, as a case difference (tertiary)
  81. * <LI>'=' : Equal
  82. * </UL>
  83. * <LI><strong>Reset</strong>: There is a single reset
  84. * which is used primarily for contractions and expansions, but which
  85. * can also be used to add a modification at the end of a set of rules.
  86. * <p>'&' : Indicates that the next rule follows the position to where
  87. * the reset text-argument would be sorted.
  88. * </UL>
  89. *
  90. * <p>
  91. * This sounds more complicated than it is in practice. For example, the
  92. * following are equivalent ways of expressing the same thing:
  93. * <blockquote>
  94. * <pre>
  95. * a < b < c
  96. * a < b & b < c
  97. * a < c & a < b
  98. * </pre>
  99. * </blockquote>
  100. * Notice that the order is important, as the subsequent item goes immediately
  101. * after the text-argument. The following are not equivalent:
  102. * <blockquote>
  103. * <pre>
  104. * a < b & a < c
  105. * a < c & a < b
  106. * </pre>
  107. * </blockquote>
  108. * Either the text-argument must already be present in the sequence, or some
  109. * initial substring of the text-argument must be present. (e.g. "a < b & ae <
  110. * e" is valid since "a" is present in the sequence before "ae" is reset). In
  111. * this latter case, "ae" is not entered and treated as a single character;
  112. * instead, "e" is sorted as if it were expanded to two characters: "a"
  113. * followed by an "e". This difference appears in natural languages: in
  114. * traditional Spanish "ch" is treated as though it contracts to a single
  115. * character (expressed as "c < ch < d"), while in traditional German
  116. * "a" (a-umlaut) is treated as though it expanded to two characters
  117. * (expressed as "a,A < b,B ... & ae,a & AE,A").
  118. * <p>
  119. * <strong>Ignorable Characters</strong>
  120. * <p>
  121. * For ignorable characters, the first rule must start with a relation (the
  122. * examples we have used above are really fragments; "a < b" really should be
  123. * "< a < b"). If, however, the first relation is not "<", then all the all
  124. * text-arguments up to the first "<" are ignorable. For example, ", - < a < b"
  125. * makes "-" an ignorable character, as we saw earlier in the word
  126. * "black-birds". In the samples for different languages, you see that most
  127. * accents are ignorable.
  128. *
  129. * <p><strong>Normalization and Accents</strong>
  130. * <p>
  131. * <code>RuleBasedCollator</code> automatically processes its rule table to
  132. * include both pre-composed and combining-character versions of
  133. * accented characters. Even if the provided rule string contains only
  134. * base characters and separate combining accent characters, the pre-composed
  135. * accented characters matching all canonical combinations of characters from
  136. * the rule string will be entered in the table.
  137. * <p>
  138. * This allows you to use a RuleBasedCollator to compare accented strings
  139. * even when the collator is set to NO_DECOMPOSITION. There are two caveats,
  140. * however. First, if the strings to be collated contain combining
  141. * sequences that may not be in canonical order, you should set the collator to
  142. * CANONICAL_DECOMPOSITION or FULL_DECOMPOSITION to enable sorting of
  143. * combining sequences. Second, if the strings contain characters with
  144. * compatibility decompositions (such as full-width and half-width forms),
  145. * you must use FULL_DECOMPOSITION, since the rule tables only include
  146. * canonical mappings.
  147. * For more information, see
  148. * <A HREF="http://www.aw.com/devpress">The Unicode Standard, Version 2.0</A>.)
  149. *
  150. * <p><strong>Errors</strong>
  151. * <p>
  152. * The following are errors:
  153. * <UL Type=disc>
  154. * <LI>A text-argument contains unquoted punctuation symbols
  155. * (e.g. "a < b-c < d").
  156. * <LI>A relation or reset character not followed by a text-argument
  157. * (e.g. "a < , b").
  158. * <LI>A reset where the text-argument (or an initial substring of the
  159. * text-argument) is not already in the sequence.
  160. * (e.g. "a < b & e < f")
  161. * </UL>
  162. * If you produce one of these errors, a <code>RuleBasedCollator</code> throws
  163. * a <code>ParseException</code>.
  164. *
  165. * <p><strong>Examples</strong>
  166. * <p>Simple: "< a < b < c < d"
  167. * <p>Norwegian: "< a,A< b,B< c,C< d,D< e,E< f,F< g,G< h,H< i,I< j,J
  168. * < k,K< l,L< m,M< n,N< o,O< p,P< q,Q< r,R< s,S< t,T
  169. * < u,U< v,V< w,W< x,X< y,Y< z,Z
  170. * < \u00E5=a\u030A,\u00C5=A\u030A
  171. * ;aa,AA< \u00E6,\u00C6< \u00F8,\u00D8"
  172. *
  173. * <p>
  174. * Normally, to create a rule-based Collator object, you will use
  175. * <code>Collator</code>'s factory method <code>getInstance</code>.
  176. * However, to create a rule-based Collator object with specialized
  177. * rules tailored to your needs, you construct the <code>RuleBasedCollator</code>
  178. * with the rules contained in a <code>String</code> object. For example:
  179. * <blockquote>
  180. * <pre>
  181. * String Simple = "< a < b < c < d";
  182. * RuleBasedCollator mySimple = new RuleBasedCollator(Simple);
  183. * </pre>
  184. * </blockquote>
  185. * Or:
  186. * <blockquote>
  187. * <pre>
  188. * String Norwegian = "< a,A< b,B< c,C< d,D< e,E< f,F< g,G< h,H< i,I< j,J" +
  189. * "< k,K< l,L< m,M< n,N< o,O< p,P< q,Q< r,R< s,S< t,T" +
  190. * "< u,U< v,V< w,W< x,X< y,Y< z,Z" +
  191. * "< \u00E5=a\u030A,\u00C5=A\u030A" +
  192. * ";aa,AA< \u00E6,\u00C6< \u00F8,\u00D8";
  193. * RuleBasedCollator myNorwegian = new RuleBasedCollator(Norwegian);
  194. * </pre>
  195. * </blockquote>
  196. *
  197. * <p>
  198. * Combining <code>Collator</code>s is as simple as concatenating strings.
  199. * Here's an example that combines two <code>Collator</code>s from two
  200. * different locales:
  201. * <blockquote>
  202. * <pre>
  203. * // Create an en_US Collator object
  204. * RuleBasedCollator en_USCollator = (RuleBasedCollator)
  205. * Collator.getInstance(new Locale("en", "US", ""));
  206. * // Create a da_DK Collator object
  207. * RuleBasedCollator da_DKCollator = (RuleBasedCollator)
  208. * Collator.getInstance(new Locale("da", "DK", ""));
  209. * // Combine the two
  210. * // First, get the collation rules from en_USCollator
  211. * String en_USRules = en_USCollator.getRules();
  212. * // Second, get the collation rules from da_DKCollator
  213. * String da_DKRules = da_DKCollator.getRules();
  214. * RuleBasedCollator newCollator =
  215. * new RuleBasedCollator(en_USRules + da_DKRules);
  216. * // newCollator has the combined rules
  217. * </pre>
  218. * </blockquote>
  219. *
  220. * <p>
  221. * Another more interesting example would be to make changes on an existing
  222. * table to create a new <code>Collator</code> object. For example, add
  223. * "& C < ch, cH, Ch, CH" to the <code>en_USCollator</code> object to create
  224. * your own:
  225. * <blockquote>
  226. * <pre>
  227. * // Create a new Collator object with additional rules
  228. * String addRules = "& C < ch, cH, Ch, CH";
  229. * RuleBasedCollator myCollator =
  230. * new RuleBasedCollator(en_USCollator + addRules);
  231. * // myCollator contains the new rules
  232. * </pre>
  233. * </blockquote>
  234. *
  235. * <p>
  236. * The following example demonstrates how to change the order of
  237. * non-spacing accents,
  238. * <blockquote>
  239. * <pre>
  240. * // old rule
  241. * String oldRules = "=\u0301;\u0300;\u0302;\u0308" // main accents
  242. * + ";\u0327;\u0303;\u0304;\u0305" // main accents
  243. * + ";\u0306;\u0307;\u0309;\u030A" // main accents
  244. * + ";\u030B;\u030C;\u030D;\u030E" // main accents
  245. * + ";\u030F;\u0310;\u0311;\u0312" // main accents
  246. * + "< a , A ; ae, AE ; \u00e6 , \u00c6"
  247. * + "< b , B < c, C < e, E & C < d, D";
  248. * // change the order of accent characters
  249. * String addOn = "& \u0300 ; \u0308 ; \u0302";
  250. * RuleBasedCollator myCollator = new RuleBasedCollator(oldRules + addOn);
  251. * </pre>
  252. * </blockquote>
  253. *
  254. * <p>
  255. * The last example shows how to put new primary ordering in before the
  256. * default setting. For example, in Japanese <code>Collator</code>, you
  257. * can either sort English characters before or after Japanese characters,
  258. * <blockquote>
  259. * <pre>
  260. * // get en_US Collator rules
  261. * RuleBasedCollator en_USCollator = (RuleBasedCollator)Collator.getInstance(Locale.US);
  262. * // add a few Japanese character to sort before English characters
  263. * // suppose the last character before the first base letter 'a' in
  264. * // the English collation rule is \u2212
  265. * String jaString = "& \u2212 < \u3041, \u3042 < \u3043, \u3044";
  266. * RuleBasedCollator myJapaneseCollator = new
  267. * RuleBasedCollator(en_USCollator.getRules() + jaString);
  268. * </pre>
  269. * </blockquote>
  270. *
  271. * @see Collator
  272. * @see CollationElementIterator
  273. * @version 1.26 11/29/01
  274. * @author Helena Shih
  275. */
  276. public class RuleBasedCollator extends Collator{
  277. //===========================================================================================
  278. // The following diagram shows the data structure of the RuleBasedCollator object.
  279. // Suppose we have the rule, where 'o-umlaut' is the unicode char 0x00F6.
  280. // "a, A < b, B < c, C, ch, cH, Ch, CH < d, D ... < o, O; 'o-umlaut'/E, 'O-umlaut'/E ...".
  281. // What the rule says is, sorts 'ch'ligatures and 'c' only with tertiary difference and
  282. // sorts 'o-umlaut' as if it's always expanded with 'e'.
  283. //
  284. // mapping table contracting list expanding list
  285. // (contains all unicode char
  286. // entries) ___ ____________ _________________________
  287. // ________ +>|_*_|->|'c' |v('c') | +>|v('o')|v('umlaut')|v('e')|
  288. // |_\u0001_|-> v('\u0001') | |_:_| |------------| | |-------------------------|
  289. // |_\u0002_|-> v('\u0002') | |_:_| |'ch'|v('ch')| | | : |
  290. // |____:___| | |_:_| |------------| | |-------------------------|
  291. // |____:___| | |'cH'|v('cH')| | | : |
  292. // |__'a'___|-> v('a') | |------------| | |-------------------------|
  293. // |__'b'___|-> v('b') | |'Ch'|v('Ch')| | | : |
  294. // |____:___| | |------------| | |-------------------------|
  295. // |____:___| | |'CH'|v('CH')| | | : |
  296. // |___'c'__|---------------- ------------ | |-------------------------|
  297. // |____:___| | | : |
  298. // |o-umlaut|---------------------------------------- |_________________________|
  299. // |____:___|
  300. //
  301. // Noted by Helena Shih on 6/23/97
  302. //============================================================================================
  303. /**
  304. * RuleBasedCollator constructor. This takes the table rules and builds
  305. * a collation table out of them. Please see RuleBasedCollator class
  306. * description for more details on the collation rule syntax.
  307. * @see java.util.Locale
  308. * @param rules the collation rules to build the collation table from.
  309. * @exception ParseException A format exception
  310. * will be thrown if the build process of the rules fails. For
  311. * example, build rule "a < ? < d" will cause the constructor to
  312. * throw the ParseException because the '?' is not quoted.
  313. */
  314. public RuleBasedCollator(String rules) throws ParseException {
  315. setStrength(Collator.TERTIARY);
  316. build(rules);
  317. }
  318. /**
  319. * RuleBasedCollator constructor. This takes the table rules and builds
  320. * a collation table out of them. Please see RuleBasedCollator class
  321. * description for more details on the collation rule syntax.
  322. * @see java.util.Locale
  323. * @param rules the collation rules to build the collation table from.
  324. * @param decomp the decomposition strength used to build the
  325. * collation table and to perform comparisons.
  326. * @exception ParseException A format exception
  327. * will be thrown if the build process of the rules fails. For
  328. * example, build rule "a < ? < d" will cause the constructor to
  329. * throw the ParseException because the '?' is not quoted.
  330. */
  331. RuleBasedCollator(String rules, int decomp) throws ParseException {
  332. setStrength(Collator.TERTIARY);
  333. setDecomposition(decomp);
  334. build(rules);
  335. }
  336. /**
  337. * Gets the table-based rules for the collation object.
  338. * @return returns the collation rules that the table collation object
  339. * was created from.
  340. */
  341. public String getRules()
  342. {
  343. if (ruleTable == null) {
  344. ruleTable = mPattern.emitPattern();
  345. mPattern = null;
  346. }
  347. return ruleTable;
  348. }
  349. /**
  350. * Return a CollationElementIterator for the given String.
  351. * @see java.text.CollationElementIterator
  352. */
  353. public CollationElementIterator getCollationElementIterator(String source) {
  354. return new CollationElementIterator( source, this );
  355. }
  356. /**
  357. * Return a CollationElementIterator for the given String.
  358. * @see java.text.CollationElementIterator
  359. */
  360. public CollationElementIterator getCollationElementIterator(
  361. CharacterIterator source) {
  362. return new CollationElementIterator( source, this );
  363. }
  364. /**
  365. * Compares the character data stored in two different strings based on the
  366. * collation rules. Returns information about whether a string is less
  367. * than, greater than or equal to another string in a language.
  368. * This can be overriden in a subclass.
  369. */
  370. public int compare(String source, String target)
  371. {
  372. // The basic algorithm here is that we use CollationElementIterators
  373. // to step through both the source and target strings. We compare each
  374. // collation element in the source string against the corresponding one
  375. // in the target, checking for differences.
  376. //
  377. // If a difference is found, we set <result> to LESS or GREATER to
  378. // indicate whether the source string is less or greater than the target.
  379. //
  380. // However, it's not that simple. If we find a tertiary difference
  381. // (e.g. 'A' vs. 'a') near the beginning of a string, it can be
  382. // overridden by a primary difference (e.g. "A" vs. "B") later in
  383. // the string. For example, "AA" < "aB", even though 'A' > 'a'.
  384. //
  385. // To keep track of this, we use strengthResult to keep track of the
  386. // strength of the most significant difference that has been found
  387. // so far. When we find a difference whose strength is greater than
  388. // strengthResult, it overrides the last difference (if any) that
  389. // was found.
  390. int result = Collator.EQUAL;
  391. strengthResult = Collator.IDENTICAL;
  392. if (sourceCursor == null) {
  393. sourceCursor = getCollationElementIterator(source);
  394. } else {
  395. sourceCursor.setText(source);
  396. }
  397. if (targetCursor == null) {
  398. targetCursor = getCollationElementIterator(target);
  399. } else {
  400. targetCursor.setText(target);
  401. }
  402. int sOrder = 0, tOrder = 0;
  403. boolean initialCheckSecTer = getStrength() >= Collator.SECONDARY;
  404. boolean checkSecTer = initialCheckSecTer;
  405. boolean checkTertiary = getStrength() >= Collator.TERTIARY;
  406. boolean gets = true, gett = true;
  407. while(true) {
  408. // Get the next collation element in each of the strings, unless
  409. // we've been requested to skip it.
  410. if (gets) sOrder = sourceCursor.next(); else gets = true;
  411. if (gett) tOrder = targetCursor.next(); else gett = true;
  412. // If we've hit the end of one of the strings, jump out of the loop
  413. if ((sOrder == CollationElementIterator.NULLORDER)||
  414. (tOrder == CollationElementIterator.NULLORDER))
  415. break;
  416. // When we hit the end of one of the strings, we're going to need to remember
  417. // the last element in each string, in order to decide if there
  418. //savedSOrder = sOrder;
  419. //savedTOrder = tOrder;
  420. int pSOrder = CollationElementIterator.primaryOrder(sOrder);
  421. int pTOrder = CollationElementIterator.primaryOrder(tOrder);
  422. // If there's no difference at this position, we can skip it
  423. if (sOrder == tOrder) {
  424. if (isFrenchSec && pSOrder != 0) {
  425. if (!checkSecTer) {
  426. // in french, a secondary difference more to the right is stronger,
  427. // so accents have to be checked with each base element
  428. checkSecTer = initialCheckSecTer;
  429. // but tertiary differences are less important than the first
  430. // secondary difference, so checking tertiary remains disabled
  431. checkTertiary = false;
  432. }
  433. }
  434. continue;
  435. }
  436. // Compare primary differences first.
  437. if ( pSOrder != pTOrder )
  438. {
  439. if (sOrder == 0) {
  440. // The entire source element is ignorable.
  441. // Skip to the next source element, but don't fetch another target element.
  442. gett = false;
  443. continue;
  444. }
  445. if (tOrder == 0) {
  446. gets = false;
  447. continue;
  448. }
  449. // The source and target elements aren't ignorable, but it's still possible
  450. // for the primary component of one of the elements to be ignorable....
  451. if (pSOrder == 0) // primary order in source is ignorable
  452. {
  453. // The source's primary is ignorable, but the target's isn't. We treat ignorables
  454. // as a secondary difference, so remember that we found one.
  455. if (checkSecTer) {
  456. result = Collator.GREATER; // (strength is SECONDARY)
  457. checkSecTer = false;
  458. }
  459. // Skip to the next source element, but don't fetch another target element.
  460. gett = false;
  461. }
  462. else if (pTOrder == 0)
  463. {
  464. // record differences - see the comment above.
  465. if (checkSecTer) {
  466. result = Collator.LESS; // (strength is SECONDARY)
  467. checkSecTer = false;
  468. }
  469. // Skip to the next source element, but don't fetch another target element.
  470. gets = false;
  471. } else {
  472. // Neither of the orders is ignorable, and we already know that the primary
  473. // orders are different because of the (pSOrder != pTOrder) test above.
  474. // Record the difference and stop the comparison.
  475. if (pSOrder < pTOrder) {
  476. return Collator.LESS; // (strength is PRIMARY)
  477. } else {
  478. return Collator.GREATER; // (strength is PRIMARY)
  479. }
  480. }
  481. } else { // else of if ( pSOrder != pTOrder )
  482. // primary order is the same, but complete order is different. So there
  483. // are no base elements at this point, only ignorables (Since the strings are
  484. // normalized)
  485. if (checkSecTer) {
  486. // a secondary or tertiary difference may still matter
  487. short secSOrder = CollationElementIterator.secondaryOrder(sOrder);
  488. short secTOrder = CollationElementIterator.secondaryOrder(tOrder);
  489. if (secSOrder != secTOrder) {
  490. // there is a secondary difference
  491. result = (secSOrder < secTOrder) ? Collator.LESS : Collator.GREATER;
  492. // (strength is SECONDARY)
  493. checkSecTer = false;
  494. // (even in french, only the first secondary difference within
  495. // a base character matters)
  496. } else {
  497. if (checkTertiary) {
  498. // a tertiary difference may still matter
  499. short terSOrder = CollationElementIterator.tertiaryOrder(sOrder);
  500. short terTOrder = CollationElementIterator.tertiaryOrder(tOrder);
  501. if (terSOrder != terTOrder) {
  502. // there is a tertiary difference
  503. result = (terSOrder < terTOrder) ? Collator.LESS : Collator.GREATER;
  504. // (strength is TERTIARY)
  505. checkTertiary = false;
  506. }
  507. }
  508. }
  509. } // if (checkSecTer)
  510. } // if ( pSOrder != pTOrder )
  511. } // while()
  512. if (sOrder != CollationElementIterator.NULLORDER) {
  513. // (tOrder must be CollationElementIterator::NULLORDER,
  514. // since this point is only reached when sOrder or tOrder is NULLORDER.)
  515. // The source string has more elements, but the target string hasn't.
  516. do {
  517. if (CollationElementIterator.primaryOrder(sOrder) != 0) {
  518. // We found an additional non-ignorable base character in the source string.
  519. // This is a primary difference, so the source is greater
  520. return Collator.GREATER; // (strength is PRIMARY)
  521. }
  522. else if (CollationElementIterator.secondaryOrder(sOrder) != 0) {
  523. // Additional secondary elements mean the source string is greater
  524. if (checkSecTer) {
  525. result = Collator.GREATER; // (strength is SECONDARY)
  526. checkSecTer = false;
  527. }
  528. }
  529. } while ((sOrder = sourceCursor.next()) != CollationElementIterator.NULLORDER);
  530. }
  531. else if (tOrder != CollationElementIterator.NULLORDER) {
  532. // The target string has more elements, but the source string hasn't.
  533. do {
  534. if (CollationElementIterator.primaryOrder(tOrder) != 0)
  535. // We found an additional non-ignorable base character in the target string.
  536. // This is a primary difference, so the source is less
  537. return Collator.LESS; // (strength is PRIMARY)
  538. else if (CollationElementIterator.secondaryOrder(tOrder) != 0) {
  539. // Additional secondary elements in the target mean the source string is less
  540. if (checkSecTer) {
  541. result = Collator.LESS; // (strength is SECONDARY)
  542. checkSecTer = false;
  543. }
  544. }
  545. } while ((tOrder = targetCursor.next()) != CollationElementIterator.NULLORDER);
  546. }
  547. // For IDENTICAL comparisons, we use a bitwise character comparison
  548. // as a tiebreaker if all else is equal
  549. if (result == 0 && getStrength() == IDENTICAL) {
  550. result = Normalizer.decompose(source,getDecomposition())
  551. .compareTo(Normalizer.decompose(target,getDecomposition()));
  552. }
  553. return result;
  554. }
  555. /**
  556. * Transforms the string into a series of characters that can be compared
  557. * with CollationKey.compareTo. This overrides java.text.Collator.getCollationKey.
  558. * It can be overriden in a subclass.
  559. */
  560. public CollationKey getCollationKey(String source)
  561. {
  562. //
  563. // The basic algorithm here is to find all of the collation elements for each
  564. // character in the source string, convert them to a char representation,
  565. // and put them into the collation key. But it's trickier than that.
  566. // Each collation element in a string has three components: primary (A vs B),
  567. // secondary (A vs A-acute), and tertiary (A' vs a); and a primary difference
  568. // at the end of a string takes precedence over a secondary or tertiary
  569. // difference earlier in the string.
  570. //
  571. // To account for this, we put all of the primary orders at the beginning of the
  572. // string, followed by the secondary and tertiary orders, separated by nulls.
  573. //
  574. // Here's a hypothetical example, with the collation element represented as
  575. // a three-digit number, one digit for primary, one for secondary, etc.
  576. //
  577. // String: A a B \u00e9 <--(e-acute)
  578. // Collation Elements: 101 100 201 510
  579. //
  580. // Collation Key: 1125<null>0001<null>1010
  581. //
  582. // To make things even trickier, secondary differences (accent marks) are compared
  583. // starting at the *end* of the string in languages with French secondary ordering.
  584. // But when comparing the accent marks on a single base character, they are compared
  585. // from the beginning. To handle this, we reverse all of the accents that belong
  586. // to each base character, then we reverse the entire string of secondary orderings
  587. // at the end. Taking the same example above, a French collator might return
  588. // this instead:
  589. //
  590. // Collation Key: 1125<null>1000<null>1010
  591. //
  592. if (source == null)
  593. return null;
  594. if (primResult == null) {
  595. primResult = new StringBuffer();
  596. secResult = new StringBuffer();
  597. terResult = new StringBuffer();
  598. } else {
  599. primResult.setLength(0);
  600. secResult.setLength(0);
  601. terResult.setLength(0);
  602. }
  603. int order = 0;
  604. boolean compareSec = (getStrength() >= Collator.SECONDARY);
  605. boolean compareTer = (getStrength() >= Collator.TERTIARY);
  606. int secOrder = CollationElementIterator.NULLORDER;
  607. int terOrder = CollationElementIterator.NULLORDER;
  608. int preSecIgnore = 0;
  609. if (sourceCursor == null) {
  610. sourceCursor = getCollationElementIterator(source);
  611. } else {
  612. sourceCursor.setText(source);
  613. }
  614. // walk through each character
  615. while ((order = sourceCursor.next()) !=
  616. CollationElementIterator.NULLORDER)
  617. {
  618. secOrder = CollationElementIterator.secondaryOrder(order);
  619. terOrder = CollationElementIterator.tertiaryOrder(order);
  620. if (!CollationElementIterator.isIgnorable(order))
  621. {
  622. primResult.append((char) (CollationElementIterator.primaryOrder(order)
  623. + COLLATIONKEYOFFSET));
  624. if (compareSec) {
  625. //
  626. // accumulate all of the ignorable/secondary characters attached
  627. // to a given base character
  628. //
  629. if (isFrenchSec && preSecIgnore < secResult.length()) {
  630. //
  631. // We're doing reversed secondary ordering and we've hit a base
  632. // (non-ignorable) character. Reverse any secondary orderings
  633. // that applied to the last base character. (see block comment above.)
  634. //
  635. reverse(secResult, preSecIgnore, secResult.length());
  636. }
  637. // Remember where we are in the secondary orderings - this is how far
  638. // back to go if we need to reverse them later.
  639. secResult.append((char)(secOrder+ COLLATIONKEYOFFSET));
  640. preSecIgnore = secResult.length();
  641. }
  642. if (compareTer) {
  643. terResult.append((char)(terOrder+ COLLATIONKEYOFFSET));
  644. }
  645. }
  646. else
  647. {
  648. if (compareSec && secOrder != 0)
  649. secResult.append((char)
  650. (secOrder+maxSecOrder+ COLLATIONKEYOFFSET));
  651. if (compareTer && terOrder != 0)
  652. terResult.append((char)
  653. (terOrder+maxTerOrder+ COLLATIONKEYOFFSET));
  654. }
  655. }
  656. if (isFrenchSec)
  657. {
  658. if (preSecIgnore < secResult.length()) {
  659. // If we've accumlated any secondary characters after the last base character,
  660. // reverse them.
  661. reverse(secResult, preSecIgnore, secResult.length());
  662. }
  663. // And now reverse the entire secResult to get French secondary ordering.
  664. reverse(secResult, 0, secResult.length());
  665. }
  666. primResult.append((char)0);
  667. secResult.append((char)0);
  668. secResult.append(terResult.toString());
  669. primResult.append(secResult.toString());
  670. if (getStrength() == IDENTICAL) {
  671. primResult.append((char)0);
  672. primResult.append(Normalizer.decompose(source,getDecomposition()));
  673. }
  674. return new CollationKey(source, primResult.toString());
  675. }
  676. /**
  677. * Standard override; no change in semantics.
  678. */
  679. public Object clone() {
  680. RuleBasedCollator other = (RuleBasedCollator) super.clone();
  681. other.primResult = null;
  682. other.secResult = null;
  683. other.terResult = null;
  684. other.sourceCursor = null;
  685. other.targetCursor = null;
  686. other.key = new StringBuffer(MAXKEYSIZE);
  687. return other;
  688. }
  689. /**
  690. * Compares the equality of two collation objects.
  691. * @param obj the table-based collation object to be compared with this.
  692. * @return true if the current table-based collation object is the same
  693. * as the table-based collation object obj; false otherwise.
  694. */
  695. public boolean equals(Object obj) {
  696. if (obj == null) return false;
  697. if (!super.equals(obj)) return false; // super does class check
  698. RuleBasedCollator other = (RuleBasedCollator) obj;
  699. // all other non-transient information is also contained in rules.
  700. return (getRules().equals(other.getRules()));
  701. }
  702. /**
  703. * Generates the hash code for the table-based collation object
  704. */
  705. public int hashCode() {
  706. return getRules().hashCode();
  707. }
  708. // ==============================================================
  709. // private
  710. // ==============================================================
  711. /**
  712. * Create a table-based collation object with the given rules.
  713. * @see java.util.RuleBasedCollator#RuleBasedCollator
  714. * @exception ParseException If the rules format is incorrect.
  715. */
  716. private void build(String pattern) throws ParseException
  717. {
  718. boolean isSource = true;
  719. int i = 0;
  720. String expChars;
  721. String groupChars;
  722. if (pattern.length() == 0)
  723. throw new ParseException("Build rules empty.", 0);
  724. // This array maps Unicode characters to their collation ordering
  725. mapping = new CompactIntArray((int)UNMAPPED);
  726. // Normalize the build rules. Find occurances of all decomposed characters
  727. // and normalize the rules before feeding into the builder. By "normalize",
  728. // we mean that all precomposed Unicode characters must be converted into
  729. // a base character and one or more combining characters (such as accents).
  730. // When there are multiple combining characters attached to a base character,
  731. // the combining characters must be in their canonical order
  732. //
  733. pattern = Normalizer.decompose(pattern, getDecomposition());
  734. // Build the merged collation entries
  735. // Since rules can be specified in any order in the string
  736. // (e.g. "c , C < d , D < e , E .... C < CH")
  737. // this splits all of the rules in the string out into separate
  738. // objects and then sorts them. In the above example, it merges the
  739. // "C < CH" rule in just before the "C < D" rule.
  740. //
  741. mPattern = new MergeCollation(pattern);
  742. int order = 0;
  743. // Now walk though each entry and add it to my own tables
  744. for (i = 0; i < mPattern.getCount(); ++i)
  745. {
  746. PatternEntry entry = mPattern.getItemAt(i);
  747. if (entry != null) {
  748. groupChars = entry.getChars();
  749. if ((groupChars.length() > 1) &&
  750. (groupChars.charAt(groupChars.length()-1) == '@')) {
  751. isFrenchSec = true;
  752. groupChars = groupChars.substring(0, groupChars.length()-1);
  753. }
  754. order = increment(entry.getStrength(), order);
  755. expChars = entry.getExtension();
  756. if (expChars.length() != 0) {
  757. addExpandOrder(groupChars, expChars, order);
  758. } else if (groupChars.length() > 1) {
  759. addContractOrder(groupChars, order);
  760. } else {
  761. char ch = groupChars.charAt(0);
  762. addOrder(ch, order);
  763. }
  764. }
  765. }
  766. addComposedChars();
  767. commit();
  768. mapping.compact();
  769. }
  770. /** Add expanding entries for pre-composed unicode characters so that this
  771. * collator can be used reasonably well with decomposition turned off.
  772. */
  773. private void addComposedChars() throws ParseException {
  774. StringBuffer buf = new StringBuffer(1);
  775. // Iterate through all of the pre-composed characters in Unicode
  776. Normalizer.DecompIterator iter =
  777. Normalizer.getDecompositions(CANONICAL_DECOMPOSITION);
  778. while (iter.hasNext()) {
  779. char c = iter.next();
  780. if (getCharOrder(c) == UNMAPPED) {
  781. //
  782. // We don't already have an ordering for this pre-composed character.
  783. //
  784. // First, see if the decomposed string is already in our
  785. // tables as a single contracting-string ordering.
  786. // If so, just map the precomposed character to that order.
  787. //
  788. // TODO: What we should really be doing here is trying to find the
  789. // longest initial substring of the decomposition that is present
  790. // in the tables as a contracting character sequence, and find its
  791. // ordering. Then do this recursively with the remaining chars
  792. // so that we build a list of orderings, and add that list to
  793. // the expansion table.
  794. // That would be more correct but also significantly slower, so
  795. // I'm not totally sure it's worth doing.
  796. //
  797. String s = iter.decomposition();
  798. int contractOrder = getContractOrder(s);
  799. if (contractOrder != UNMAPPED) {
  800. addOrder(c, contractOrder);
  801. } else {
  802. //
  803. // We don't have a contracting ordering for the entire string
  804. // that results from the decomposition, but if we have orders
  805. // for each individual character, we can add an expanding
  806. // table entry for the pre-composed character
  807. //
  808. boolean allThere = true;
  809. for (int i = 0; i < s.length(); i++) {
  810. if (getCharOrder(s.charAt(i)) == UNMAPPED) {
  811. allThere = false;
  812. break;
  813. }
  814. }
  815. if (allThere) {
  816. buf.setLength(0);
  817. buf.append(c);
  818. addExpandOrder(buf.toString(), s, UNMAPPED);
  819. }
  820. }
  821. }
  822. }
  823. }
  824. /**
  825. * Look up for unmapped values in the expanded character table.
  826. *
  827. * When the expanding character tables are built by addExpandOrder,
  828. * it doesn't know what the final ordering of each character
  829. * in the expansion will be. Instead, it just puts the raw character
  830. * code into the table, adding CHARINDEX as a flag. Now that we've
  831. * finished building the mapping table, we can go back and look up
  832. * that character to see what its real collation order is and
  833. * stick that into the expansion table. That lets us avoid doing
  834. * a two-stage lookup later.
  835. */
  836. private final void commit()
  837. {
  838. if (expandTable != null) {
  839. for (int i = 0; i < expandTable.size(); i++) {
  840. int[] valueList = (int [])expandTable.elementAt(i);
  841. for (int j = 0; j < valueList.length; j++) {
  842. int order = valueList[j];
  843. if (order < EXPANDCHARINDEX && order > CHARINDEX) {
  844. // found a expanding character that isn't filled in yet
  845. char ch = (char)(order - CHARINDEX);
  846. // Get the real values for the non-filled entry
  847. int realValue = getCharOrder(ch);
  848. if (realValue == UNMAPPED) {
  849. // The real value is still unmapped, maybe it's ignorable
  850. valueList[j] = IGNORABLEMASK & ch;
  851. } else {
  852. // just fill in the value
  853. valueList[j] = realValue;
  854. }
  855. }
  856. }
  857. }
  858. }
  859. }
  860. /**
  861. * Increment of the last order based on the comparison level.
  862. */
  863. private final int increment(int aStrength, int lastValue)
  864. {
  865. switch(aStrength)
  866. {
  867. case Collator.PRIMARY:
  868. // increment priamry order and mask off secondary and tertiary difference
  869. lastValue += PRIMARYORDERINCREMENT;
  870. lastValue &= PRIMARYORDERMASK;
  871. isOverIgnore = true;
  872. break;
  873. case Collator.SECONDARY:
  874. // increment secondary order and mask off tertiary difference
  875. lastValue += SECONDARYORDERINCREMENT;
  876. lastValue &= SECONDARYDIFFERENCEONLY;
  877. // record max # of ignorable chars with secondary difference
  878. if (!isOverIgnore)
  879. maxSecOrder++;
  880. break;
  881. case Collator.TERTIARY:
  882. // increment tertiary order
  883. lastValue += TERTIARYORDERINCREMENT;
  884. // record max # of ignorable chars with tertiary difference
  885. if (!isOverIgnore)
  886. maxTerOrder++;
  887. break;
  888. }
  889. return lastValue;
  890. }
  891. /**
  892. * Adds a character and its designated order into the collation table.
  893. */
  894. private final void addOrder(char ch,
  895. int anOrder)
  896. {
  897. // See if the char already has an order in the mapping table
  898. int order = mapping.elementAt(ch);
  899. if (order >= CONTRACTCHARINDEX) {
  900. // There's already an entry for this character that points to a contracting
  901. // character table. Instead of adding the character directly to the mapping
  902. // table, we must add it to the contract table instead.
  903. key.setLength(0);
  904. key.append(ch);
  905. addContractOrder(key.toString(), anOrder);
  906. } else {
  907. // add the entry to the mapping table,
  908. // the same later entry replaces the previous one
  909. mapping.setElementAt(ch, anOrder);
  910. }
  911. }
  912. private final void addContractOrder(String groupChars, int anOrder) {
  913. addContractOrder(groupChars, anOrder, true);
  914. }
  915. /**
  916. * Adds the contracting string into the collation table.
  917. */
  918. private final void addContractOrder(String groupChars, int anOrder,
  919. boolean fwd)
  920. {
  921. if (contractTable == null) {
  922. contractTable = new Vector(INITIALTABLESIZE);
  923. }
  924. // See if the initial character of the string already has a contract table.
  925. int entry = mapping.elementAt(groupChars.charAt(0));
  926. Vector entryTable = getContractValues(entry - CONTRACTCHARINDEX);
  927. if (entryTable == null) {
  928. // We need to create a new table of contract entries for this base char
  929. int tableIndex = CONTRACTCHARINDEX + contractTable.size();
  930. entryTable = new Vector(INITIALTABLESIZE);
  931. contractTable.addElement(entryTable);
  932. // Add the initial character's current ordering first. then
  933. // update its mapping to point to this contract table
  934. entryTable.addElement(new EntryPair(groupChars.substring(0,1), entry));
  935. mapping.setElementAt(groupChars.charAt(0), tableIndex);
  936. }
  937. // Now add (or replace) this string in the table
  938. int index = getEntry(entryTable, groupChars, fwd);
  939. if (index != UNMAPPED) {
  940. EntryPair pair = (EntryPair) entryTable.elementAt(index);
  941. pair.value = anOrder;
  942. } else {
  943. entryTable.addElement(new EntryPair(groupChars, anOrder, fwd));
  944. }
  945. // If this was a forward mapping for a contracting string, also add a
  946. // reverse mapping for it, so that CollationElementIterator.previous
  947. // can work right
  948. if (fwd) {
  949. addContractOrder(new StringBuffer(groupChars).reverse().toString(),
  950. anOrder, false);
  951. }
  952. }
  953. /**
  954. * If the given string has been specified as a contracting string
  955. * in this collation table, return its ordering.
  956. * Otherwise return UNMAPPED.
  957. */
  958. private int getContractOrder(String groupChars)
  959. {
  960. int result = UNMAPPED;
  961. if (contractTable != null) {
  962. Vector entryTable = getContractValues(groupChars.charAt(0));
  963. if (entryTable != null) {
  964. int index = getEntry(entryTable, groupChars, true);
  965. if (index != UNMAPPED) {
  966. EntryPair pair = (EntryPair) entryTable.elementAt(index);
  967. result = pair.value;
  968. }
  969. }
  970. }
  971. return result;
  972. }
  973. final static int getEntry(Vector list, String name, boolean fwd) {
  974. for (int i = 0; i < list.size(); i++) {
  975. EntryPair pair = (EntryPair)list.elementAt(i);
  976. if (pair.fwd == fwd && pair.entryName.equals(name)) {
  977. return i;
  978. }
  979. }
  980. return UNMAPPED;
  981. }
  982. /**
  983. * Get the entry of hash table of the contracting string in the collation
  984. * table.
  985. * @param ch the starting character of the contracting string
  986. */
  987. Vector getContractValues(char ch)
  988. {
  989. int index = mapping.elementAt(ch);
  990. return getContractValues(index - CONTRACTCHARINDEX);
  991. }
  992. Vector getContractValues(int index)
  993. {
  994. if (index >= 0)
  995. {
  996. return (Vector)contractTable.elementAt(index);
  997. }
  998. else // not found
  999. {
  1000. return null;
  1001. }
  1002. }
  1003. private final int getCharOrder(char ch) {
  1004. int order = mapping.elementAt(ch);
  1005. if (order >= CONTRACTCHARINDEX) {
  1006. Vector groupList = getContractValues(order - CONTRACTCHARINDEX);
  1007. EntryPair pair = (EntryPair)groupList.firstElement();
  1008. order = pair.value;
  1009. }
  1010. return order;
  1011. }
  1012. /**
  1013. * Adds the expanding string into the collation table.
  1014. */
  1015. private final void addExpandOrder(String contractChars,
  1016. String expandChars,
  1017. int anOrder) throws ParseException
  1018. {
  1019. // Create an expansion table entry
  1020. int tableIndex = addExpansion(anOrder, expandChars);
  1021. // And add its index into the main mapping table
  1022. if (contractChars.length() > 1) {
  1023. addContractOrder(contractChars, tableIndex);
  1024. } else {
  1025. addOrder(contractChars.charAt(0), tableIndex);
  1026. }
  1027. }
  1028. /**
  1029. * Create a new entry in the expansion table that contains the orderings
  1030. * for the given characers. If anOrder is valid, it is added to the
  1031. * beginning of the expanded list of orders.
  1032. */
  1033. private int addExpansion(int anOrder, String expandChars) {
  1034. if (expandTable == null) {
  1035. expandTable = new Vector(INITIALTABLESIZE);
  1036. }
  1037. // If anOrder is valid, we want to add it at the beginning of the list
  1038. int offset = (anOrder == UNMAPPED) ? 0 : 1;
  1039. int[] valueList = new int[expandChars.length() + offset];
  1040. if (offset == 1) {
  1041. valueList[0] = anOrder;
  1042. }
  1043. for (int i = 0; i < expandChars.length(); i++) {
  1044. char ch = expandChars.charAt(i);
  1045. int mapValue = getCharOrder(ch);
  1046. if (mapValue != UNMAPPED) {
  1047. valueList[i+offset] = mapValue;
  1048. } else {
  1049. // can't find it in the table, will be filled in by commit().
  1050. valueList[i+offset] = CHARINDEX + (int)ch;
  1051. }
  1052. }
  1053. // Add the expanding char list into the expansion table.
  1054. int tableIndex = EXPANDCHARINDEX + expandTable.size();
  1055. expandTable.addElement(valueList);
  1056. return tableIndex;
  1057. }
  1058. /**
  1059. * Return the maximum length of any expansion sequences that end
  1060. * with the specified comparison order.
  1061. *
  1062. * @param order a collation order returned by previous or next.
  1063. * @return the maximum length of any expansion seuences ending
  1064. * with the specified order.
  1065. *
  1066. * @see CollationElementIterator#getMaxExpansion
  1067. */
  1068. int getMaxExpansion(int order)
  1069. {
  1070. int result = 1;
  1071. if (expandTable != null) {
  1072. // Right now this does a linear search through the entire
  1073. // expandsion table. If a collator had a large number of expansions,
  1074. // this could cause a performance problem, but in practise that
  1075. // rarely happens
  1076. for (int i = 0; i < expandTable.size(); i++) {
  1077. int[] valueList = (int [])expandTable.elementAt(i);
  1078. int length = valueList.length;
  1079. if (length > result && valueList[length-1] == order) {
  1080. result = length;
  1081. }
  1082. }
  1083. }
  1084. return result;
  1085. }
  1086. /**
  1087. * Get the entry of hash table of the expanding string in the collation
  1088. * table.
  1089. * @param idx the index of the expanding string value list
  1090. */
  1091. final int[] getExpandValueList(int order) {
  1092. return (int[])expandTable.elementAt(order - EXPANDCHARINDEX);
  1093. }
  1094. /**
  1095. * Get the comarison order of a character from the collation table.
  1096. * @return the comparison order of a character.
  1097. */
  1098. final int getUnicodeOrder(char ch)
  1099. {
  1100. return mapping.elementAt(ch);
  1101. }
  1102. /**
  1103. * Reverse a string.
  1104. */
  1105. private final void reverse (StringBuffer result, int from, int to)
  1106. {
  1107. int i = from;
  1108. char swap;
  1109. int j = to - 1;
  1110. while (i < j) {
  1111. swap = result.charAt(i);
  1112. result.setCharAt(i, result.charAt(j));
  1113. result.setCharAt(j, swap);
  1114. i++;
  1115. j--;
  1116. }
  1117. }
  1118. // Proclaim compatibility with 1.1
  1119. static final long serialVersionUID = 2822366911447564107L;
  1120. final static int CHARINDEX = 0x70000000; // need look up in .commit()
  1121. final static int EXPANDCHARINDEX = 0x7E000000; // Expand index follows
  1122. final static int CONTRACTCHARINDEX = 0x7F000000; // contract indexes follow
  1123. final static int UNMAPPED = 0xFFFFFFFF;
  1124. private final static int PRIMARYORDERINCREMENT = 0x00010000;
  1125. private final static int MAXIGNORABLE = 0x00010000;
  1126. private final static int SECONDARYORDERINCREMENT = 0x00000100;
  1127. private final static int TERTIARYORDERINCREMENT = 0x00000001;
  1128. final static int PRIMARYORDERMASK = 0xffff0000;
  1129. final static int SECONDARYORDERMASK = 0x0000ff00;
  1130. final static int TERTIARYORDERMASK = 0x000000ff;
  1131. final static int PRIMARYDIFFERENCEONLY = 0xffff0000;
  1132. final static int SECONDARYDIFFERENCEONLY = 0xffffff00;
  1133. private final static int SECONDARYRESETMASK = 0x0000ffff;
  1134. private final static int IGNORABLEMASK = 0x0000ffff;
  1135. private final static int INITIALTABLESIZE = 20;
  1136. private final static int MAXKEYSIZE = 5;
  1137. final static int PRIMARYORDERSHIFT = 16;
  1138. final static int SECONDARYORDERSHIFT = 8;
  1139. private final static int MAXTOKENLEN = 256;
  1140. private final static int COLLATIONKEYOFFSET = 1;
  1141. // these data members are reconstructed by readObject()
  1142. private transient boolean isFrenchSec = false;
  1143. private transient String ruleTable = null;
  1144. private transient CompactIntArray mapping = null;
  1145. private transient Vector contractTable = null;
  1146. private transient Vector expandTable = null;
  1147. // transients, only used in build or processing
  1148. private transient MergeCollation mPattern = null;
  1149. private transient boolean isOverIgnore = false;
  1150. private transient short maxSecOrder = 0;
  1151. private transient short maxTerOrder = 0;
  1152. private transient StringBuffer key = new StringBuffer(MAXKEYSIZE);
  1153. private transient int strengthResult = Collator.IDENTICAL;
  1154. // Internal objects that are cached across calls so that they don't have to
  1155. // be created/destroyed on every call to compare() and getCollationKey()
  1156. private transient StringBuffer primResult = null;
  1157. private transient StringBuffer secResult = null;
  1158. private transient StringBuffer terResult = null;
  1159. private transient CollationElementIterator sourceCursor = null;
  1160. private transient CollationElementIterator targetCursor = null;
  1161. }