1 /* 2 * Copyright (c) 1996, 2006, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 /* 27 * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved 28 * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved 29 * 30 * The original version of this source code and documentation 31 * is copyrighted and owned by Taligent, Inc., a wholly-owned 32 * subsidiary of IBM. These materials are provided under terms 33 * of a License Agreement between Taligent and Sun. This technology 34 * is protected by multiple US and International patents. 35 * 36 * This notice and attribution to Taligent may not be removed. 37 * Taligent is a registered trademark of Taligent, Inc. 38 * 39 */ 40 41 package java.text; 42 43 import java.lang.ref.SoftReference; 44 import java.net.URL; 45 import java.io.InputStream; 46 import java.io.IOException; 47 import java.security.AccessController; 48 import java.security.PrivilegedAction; 49 import java.text.CharacterIterator; 50 import java.text.StringCharacterIterator; 51 import java.text.spi.BreakIteratorProvider; 52 import java.util.Locale; 53 import java.util.MissingResourceException; 54 import java.util.ResourceBundle; 55 import java.util.spi.LocaleServiceProvider; 56 import sun.util.LocaleServiceProviderPool; 57 import sun.util.resources.LocaleData; 58 59 60 /** 61 * The <code>BreakIterator</code> class implements methods for finding 62 * the location of boundaries in text. Instances of <code>BreakIterator</code> 63 * maintain a current position and scan over text 64 * returning the index of characters where boundaries occur. 65 * Internally, <code>BreakIterator</code> scans text using a 66 * <code>CharacterIterator</code>, and is thus able to scan text held 67 * by any object implementing that protocol. A <code>StringCharacterIterator</code> 68 * is used to scan <code>String</code> objects passed to <code>setText</code>. 69 * 70 * <p> 71 * You use the factory methods provided by this class to create 72 * instances of various types of break iterators. In particular, 73 * use <code>getWordInstance</code>, <code>getLineInstance</code>, 74 * <code>getSentenceInstance</code>, and <code>getCharacterInstance</code> 75 * to create <code>BreakIterator</code>s that perform 76 * word, line, sentence, and character boundary analysis respectively. 77 * A single <code>BreakIterator</code> can work only on one unit 78 * (word, line, sentence, and so on). You must use a different iterator 79 * for each unit boundary analysis you wish to perform. 80 * 81 * <p><a name="line"></a> 82 * Line boundary analysis determines where a text string can be 83 * broken when line-wrapping. The mechanism correctly handles 84 * punctuation and hyphenated words. Actual line breaking needs 85 * to also consider the available line width and is handled by 86 * higher-level software. 87 * 88 * <p><a name="sentence"></a> 89 * Sentence boundary analysis allows selection with correct interpretation 90 * of periods within numbers and abbreviations, and trailing punctuation 91 * marks such as quotation marks and parentheses. 92 * 93 * <p><a name="word"></a> 94 * Word boundary analysis is used by search and replace functions, as 95 * well as within text editing applications that allow the user to 96 * select words with a double click. Word selection provides correct 97 * interpretation of punctuation marks within and following 98 * words. Characters that are not part of a word, such as symbols 99 * or punctuation marks, have word-breaks on both sides. 100 * 101 * <p><a name="character"></a> 102 * Character boundary analysis allows users to interact with characters 103 * as they expect to, for example, when moving the cursor through a text 104 * string. Character boundary analysis provides correct navigation 105 * through character strings, regardless of how the character is stored. 106 * The boundaries returned may be those of supplementary characters, 107 * combining character sequences, or ligature clusters. 108 * For example, an accented character might be stored as a base character 109 * and a diacritical mark. What users consider to be a character can 110 * differ between languages. 111 * 112 * <p> 113 * The <code>BreakIterator</code> instances returned by the factory methods 114 * of this class are intended for use with natural languages only, not for 115 * programming language text. It is however possible to define subclasses 116 * that tokenize a programming language. 117 * 118 * <P> 119 * <strong>Examples</strong>:<P> 120 * Creating and using text boundaries: 121 * <blockquote> 122 * <pre> 123 * public static void main(String args[]) { 124 * if (args.length == 1) { 125 * String stringToExamine = args[0]; 126 * //print each word in order 127 * BreakIterator boundary = BreakIterator.getWordInstance(); 128 * boundary.setText(stringToExamine); 129 * printEachForward(boundary, stringToExamine); 130 * //print each sentence in reverse order 131 * boundary = BreakIterator.getSentenceInstance(Locale.US); 132 * boundary.setText(stringToExamine); 133 * printEachBackward(boundary, stringToExamine); 134 * printFirst(boundary, stringToExamine); 135 * printLast(boundary, stringToExamine); 136 * } 137 * } 138 * </pre> 139 * </blockquote> 140 * 141 * Print each element in order: 142 * <blockquote> 143 * <pre> 144 * public static void printEachForward(BreakIterator boundary, String source) { 145 * int start = boundary.first(); 146 * for (int end = boundary.next(); 147 * end != BreakIterator.DONE; 148 * start = end, end = boundary.next()) { 149 * System.out.println(source.substring(start,end)); 150 * } 151 * } 152 * </pre> 153 * </blockquote> 154 * 155 * Print each element in reverse order: 156 * <blockquote> 157 * <pre> 158 * public static void printEachBackward(BreakIterator boundary, String source) { 159 * int end = boundary.last(); 160 * for (int start = boundary.previous(); 161 * start != BreakIterator.DONE; 162 * end = start, start = boundary.previous()) { 163 * System.out.println(source.substring(start,end)); 164 * } 165 * } 166 * </pre> 167 * </blockquote> 168 * 169 * Print first element: 170 * <blockquote> 171 * <pre> 172 * public static void printFirst(BreakIterator boundary, String source) { 173 * int start = boundary.first(); 174 * int end = boundary.next(); 175 * System.out.println(source.substring(start,end)); 176 * } 177 * </pre> 178 * </blockquote> 179 * 180 * Print last element: 181 * <blockquote> 182 * <pre> 183 * public static void printLast(BreakIterator boundary, String source) { 184 * int end = boundary.last(); 185 * int start = boundary.previous(); 186 * System.out.println(source.substring(start,end)); 187 * } 188 * </pre> 189 * </blockquote> 190 * 191 * Print the element at a specified position: 192 * <blockquote> 193 * <pre> 194 * public static void printAt(BreakIterator boundary, int pos, String source) { 195 * int end = boundary.following(pos); 196 * int start = boundary.previous(); 197 * System.out.println(source.substring(start,end)); 198 * } 199 * </pre> 200 * </blockquote> 201 * 202 * Find the next word: 203 * <blockquote> 204 * <pre> 205 * public static int nextWordStartAfter(int pos, String text) { 206 * BreakIterator wb = BreakIterator.getWordInstance(); 207 * wb.setText(text); 208 * int last = wb.following(pos); 209 * int current = wb.next(); 210 * while (current != BreakIterator.DONE) { 211 * for (int p = last; p < current; p++) { 212 * if (Character.isLetter(text.codePointAt(p))) 213 * return last; 214 * } 215 * last = current; 216 * current = wb.next(); 217 * } 218 * return BreakIterator.DONE; 219 * } 220 * </pre> 221 * (The iterator returned by BreakIterator.getWordInstance() is unique in that 222 * the break positions it returns don't represent both the start and end of the 223 * thing being iterated over. That is, a sentence-break iterator returns breaks 224 * that each represent the end of one sentence and the beginning of the next. 225 * With the word-break iterator, the characters between two boundaries might be a 226 * word, or they might be the punctuation or whitespace between two words. The 227 * above code uses a simple heuristic to determine which boundary is the beginning 228 * of a word: If the characters between this boundary and the next boundary 229 * include at least one letter (this can be an alphabetical letter, a CJK ideograph, 230 * a Hangul syllable, a Kana character, etc.), then the text between this boundary 231 * and the next is a word; otherwise, it's the material between words.) 232 * </blockquote> 233 * 234 * @see CharacterIterator 235 * 236 */ 237 238 public abstract class BreakIterator implements Cloneable 239 { 240 /** 241 * Constructor. BreakIterator is stateless and has no default behavior. 242 */ 243 protected BreakIterator() 244 { 245 } 246 247 /** 248 * Create a copy of this iterator 249 * @return A copy of this 250 */ 251 public Object clone() 252 { 253 try { 254 return super.clone(); 255 } 256 catch (CloneNotSupportedException e) { 257 throw new InternalError(e); 258 } 259 } 260 261 /** 262 * DONE is returned by previous(), next(), next(int), preceding(int) 263 * and following(int) when either the first or last text boundary has been 264 * reached. 265 */ 266 public static final int DONE = -1; 267 268 /** 269 * Returns the first boundary. The iterator's current position is set 270 * to the first text boundary. 271 * @return The character index of the first text boundary. 272 */ 273 public abstract int first(); 274 275 /** 276 * Returns the last boundary. The iterator's current position is set 277 * to the last text boundary. 278 * @return The character index of the last text boundary. 279 */ 280 public abstract int last(); 281 282 /** 283 * Returns the nth boundary from the current boundary. If either 284 * the first or last text boundary has been reached, it returns 285 * <code>BreakIterator.DONE</code> and the current position is set to either 286 * the first or last text boundary depending on which one is reached. Otherwise, 287 * the iterator's current position is set to the new boundary. 288 * For example, if the iterator's current position is the mth text boundary 289 * and three more boundaries exist from the current boundary to the last text 290 * boundary, the next(2) call will return m + 2. The new text position is set 291 * to the (m + 2)th text boundary. A next(4) call would return 292 * <code>BreakIterator.DONE</code> and the last text boundary would become the 293 * new text position. 294 * @param n which boundary to return. A value of 0 295 * does nothing. Negative values move to previous boundaries 296 * and positive values move to later boundaries. 297 * @return The character index of the nth boundary from the current position 298 * or <code>BreakIterator.DONE</code> if either first or last text boundary 299 * has been reached. 300 */ 301 public abstract int next(int n); 302 303 /** 304 * Returns the boundary following the current boundary. If the current boundary 305 * is the last text boundary, it returns <code>BreakIterator.DONE</code> and 306 * the iterator's current position is unchanged. Otherwise, the iterator's 307 * current position is set to the boundary following the current boundary. 308 * @return The character index of the next text boundary or 309 * <code>BreakIterator.DONE</code> if the current boundary is the last text 310 * boundary. 311 * Equivalent to next(1). 312 * @see #next(int) 313 */ 314 public abstract int next(); 315 316 /** 317 * Returns the boundary preceding the current boundary. If the current boundary 318 * is the first text boundary, it returns <code>BreakIterator.DONE</code> and 319 * the iterator's current position is unchanged. Otherwise, the iterator's 320 * current position is set to the boundary preceding the current boundary. 321 * @return The character index of the previous text boundary or 322 * <code>BreakIterator.DONE</code> if the current boundary is the first text 323 * boundary. 324 */ 325 public abstract int previous(); 326 327 /** 328 * Returns the first boundary following the specified character offset. If the 329 * specified offset equals to the last text boundary, it returns 330 * <code>BreakIterator.DONE</code> and the iterator's current position is unchanged. 331 * Otherwise, the iterator's current position is set to the returned boundary. 332 * The value returned is always greater than the offset or the value 333 * <code>BreakIterator.DONE</code>. 334 * @param offset the character offset to begin scanning. 335 * @return The first boundary after the specified offset or 336 * <code>BreakIterator.DONE</code> if the last text boundary is passed in 337 * as the offset. 338 * @exception IllegalArgumentException if the specified offset is less than 339 * the first text boundary or greater than the last text boundary. 340 */ 341 public abstract int following(int offset); 342 343 /** 344 * Returns the last boundary preceding the specified character offset. If the 345 * specified offset equals to the first text boundary, it returns 346 * <code>BreakIterator.DONE</code> and the iterator's current position is unchanged. 347 * Otherwise, the iterator's current position is set to the returned boundary. 348 * The value returned is always less than the offset or the value 349 * <code>BreakIterator.DONE</code>. 350 * @param offset the characater offset to begin scanning. 351 * @return The last boundary before the specified offset or 352 * <code>BreakIterator.DONE</code> if the first text boundary is passed in 353 * as the offset. 354 * @exception IllegalArgumentException if the specified offset is less than 355 * the first text boundary or greater than the last text boundary. 356 * @since 1.2 357 */ 358 public int preceding(int offset) { 359 // NOTE: This implementation is here solely because we can't add new 360 // abstract methods to an existing class. There is almost ALWAYS a 361 // better, faster way to do this. 362 int pos = following(offset); 363 while (pos >= offset && pos != DONE) 364 pos = previous(); 365 return pos; 366 } 367 368 /** 369 * Returns true if the specified character offset is a text boundary. 370 * @param offset the character offset to check. 371 * @return <code>true</code> if "offset" is a boundary position, 372 * <code>false</code> otherwise. 373 * @exception IllegalArgumentException if the specified offset is less than 374 * the first text boundary or greater than the last text boundary. 375 * @since 1.2 376 */ 377 public boolean isBoundary(int offset) { 378 // NOTE: This implementation probably is wrong for most situations 379 // because it fails to take into account the possibility that a 380 // CharacterIterator passed to setText() may not have a begin offset 381 // of 0. But since the abstract BreakIterator doesn't have that 382 // knowledge, it assumes the begin offset is 0. If you subclass 383 // BreakIterator, copy the SimpleTextBoundary implementation of this 384 // function into your subclass. [This should have been abstract at 385 // this level, but it's too late to fix that now.] 386 if (offset == 0) { 387 return true; 388 } 389 int boundary = following(offset - 1); 390 if (boundary == DONE) { 391 throw new IllegalArgumentException(); 392 } 393 return boundary == offset; 394 } 395 396 /** 397 * Returns character index of the text boundary that was most 398 * recently returned by next(), next(int), previous(), first(), last(), 399 * following(int) or preceding(int). If any of these methods returns 400 * <code>BreakIterator.DONE</code> because either first or last text boundary 401 * has been reached, it returns the first or last text boundary depending on 402 * which one is reached. 403 * @return The text boundary returned from the above methods, first or last 404 * text boundary. 405 * @see #next() 406 * @see #next(int) 407 * @see #previous() 408 * @see #first() 409 * @see #last() 410 * @see #following(int) 411 * @see #preceding(int) 412 */ 413 public abstract int current(); 414 415 /** 416 * Get the text being scanned 417 * @return the text being scanned 418 */ 419 public abstract CharacterIterator getText(); 420 421 /** 422 * Set a new text string to be scanned. The current scan 423 * position is reset to first(). 424 * @param newText new text to scan. 425 */ 426 public void setText(String newText) 427 { 428 setText(new StringCharacterIterator(newText)); 429 } 430 431 /** 432 * Set a new text for scanning. The current scan 433 * position is reset to first(). 434 * @param newText new text to scan. 435 */ 436 public abstract void setText(CharacterIterator newText); 437 438 private static final int CHARACTER_INDEX = 0; 439 private static final int WORD_INDEX = 1; 440 private static final int LINE_INDEX = 2; 441 private static final int SENTENCE_INDEX = 3; 442 443 @SuppressWarnings("unchecked") 444 private static final SoftReference<BreakIteratorCache>[] iterCache = (SoftReference<BreakIteratorCache>[]) new SoftReference<?>[4]; 445 446 /** 447 * Returns a new <code>BreakIterator</code> instance 448 * for <a href="BreakIterator.html#word">word breaks</a> 449 * for the {@linkplain Locale#getDefault() default locale}. 450 * @return A break iterator for word breaks 451 */ 452 public static BreakIterator getWordInstance() 453 { 454 return getWordInstance(Locale.getDefault()); 455 } 456 457 /** 458 * Returns a new <code>BreakIterator</code> instance 459 * for <a href="BreakIterator.html#word">word breaks</a> 460 * for the given locale. 461 * @param locale the desired locale 462 * @return A break iterator for word breaks 463 * @exception NullPointerException if <code>locale</code> is null 464 */ 465 public static BreakIterator getWordInstance(Locale locale) 466 { 467 return getBreakInstance(locale, 468 WORD_INDEX, 469 "WordData", 470 "WordDictionary"); 471 } 472 473 /** 474 * Returns a new <code>BreakIterator</code> instance 475 * for <a href="BreakIterator.html#line">line breaks</a> 476 * for the {@linkplain Locale#getDefault() default locale}. 477 * @return A break iterator for line breaks 478 */ 479 public static BreakIterator getLineInstance() 480 { 481 return getLineInstance(Locale.getDefault()); 482 } 483 484 /** 485 * Returns a new <code>BreakIterator</code> instance 486 * for <a href="BreakIterator.html#line">line breaks</a> 487 * for the given locale. 488 * @param locale the desired locale 489 * @return A break iterator for line breaks 490 * @exception NullPointerException if <code>locale</code> is null 491 */ 492 public static BreakIterator getLineInstance(Locale locale) 493 { 494 return getBreakInstance(locale, 495 LINE_INDEX, 496 "LineData", 497 "LineDictionary"); 498 } 499 500 /** 501 * Returns a new <code>BreakIterator</code> instance 502 * for <a href="BreakIterator.html#character">character breaks</a> 503 * for the {@linkplain Locale#getDefault() default locale}. 504 * @return A break iterator for character breaks 505 */ 506 public static BreakIterator getCharacterInstance() 507 { 508 return getCharacterInstance(Locale.getDefault()); 509 } 510 511 /** 512 * Returns a new <code>BreakIterator</code> instance 513 * for <a href="BreakIterator.html#character">character breaks</a> 514 * for the given locale. 515 * @param locale the desired locale 516 * @return A break iterator for character breaks 517 * @exception NullPointerException if <code>locale</code> is null 518 */ 519 public static BreakIterator getCharacterInstance(Locale locale) 520 { 521 return getBreakInstance(locale, 522 CHARACTER_INDEX, 523 "CharacterData", 524 "CharacterDictionary"); 525 } 526 527 /** 528 * Returns a new <code>BreakIterator</code> instance 529 * for <a href="BreakIterator.html#sentence">sentence breaks</a> 530 * for the {@linkplain Locale#getDefault() default locale}. 531 * @return A break iterator for sentence breaks 532 */ 533 public static BreakIterator getSentenceInstance() 534 { 535 return getSentenceInstance(Locale.getDefault()); 536 } 537 538 /** 539 * Returns a new <code>BreakIterator</code> instance 540 * for <a href="BreakIterator.html#sentence">sentence breaks</a> 541 * for the given locale. 542 * @param locale the desired locale 543 * @return A break iterator for sentence breaks 544 * @exception NullPointerException if <code>locale</code> is null 545 */ 546 public static BreakIterator getSentenceInstance(Locale locale) 547 { 548 return getBreakInstance(locale, 549 SENTENCE_INDEX, 550 "SentenceData", 551 "SentenceDictionary"); 552 } 553 554 private static BreakIterator getBreakInstance(Locale locale, 555 int type, 556 String dataName, 557 String dictionaryName) { 558 if (iterCache[type] != null) { 559 BreakIteratorCache cache = iterCache[type].get(); 560 if (cache != null) { 561 if (cache.getLocale().equals(locale)) { 562 return cache.createBreakInstance(); 563 } 564 } 565 } 566 567 BreakIterator result = createBreakInstance(locale, 568 type, 569 dataName, 570 dictionaryName); 571 BreakIteratorCache cache = new BreakIteratorCache(locale, result); 572 iterCache[type] = new SoftReference<>(cache); 573 return result; 574 } 575 576 private static ResourceBundle getBundle(final String baseName, final Locale locale) { 577 return AccessController.doPrivileged(new PrivilegedAction<ResourceBundle>() { 578 public ResourceBundle run() { 579 return ResourceBundle.getBundle(baseName, locale); 580 } 581 }); 582 } 583 584 private static BreakIterator createBreakInstance(Locale locale, 585 int type, 586 String dataName, 587 String dictionaryName) { 588 589 // Check whether a provider can provide an implementation that's closer 590 // to the requested locale than what the Java runtime itself can provide. 591 LocaleServiceProviderPool pool = 592 LocaleServiceProviderPool.getPool(BreakIteratorProvider.class); 593 if (pool.hasProviders()) { 594 BreakIterator providersInstance = pool.getLocalizedObject( 595 BreakIteratorGetter.INSTANCE, 596 locale, type); 597 if (providersInstance != null) { 598 return providersInstance; 599 } 600 } 601 602 ResourceBundle bundle = getBundle( 603 "sun.text.resources.BreakIteratorInfo", locale); 604 String[] classNames = bundle.getStringArray("BreakIteratorClasses"); 605 606 String dataFile = bundle.getString(dataName); 607 608 try { 609 if (classNames[type].equals("RuleBasedBreakIterator")) { 610 return new RuleBasedBreakIterator(dataFile); 611 } 612 else if (classNames[type].equals("DictionaryBasedBreakIterator")) { 613 String dictionaryFile = bundle.getString(dictionaryName); 614 return new DictionaryBasedBreakIterator(dataFile, dictionaryFile); 615 } 616 else { 617 throw new IllegalArgumentException("Invalid break iterator class \"" + 618 classNames[type] + "\""); 619 } 620 } 621 catch (Exception e) { 622 throw new InternalError(e.toString(), e); 623 } 624 } 625 626 /** 627 * Returns an array of all locales for which the 628 * <code>get*Instance</code> methods of this class can return 629 * localized instances. 630 * The returned array represents the union of locales supported by the Java 631 * runtime and by installed 632 * {@link java.text.spi.BreakIteratorProvider BreakIteratorProvider} implementations. 633 * It must contain at least a <code>Locale</code> 634 * instance equal to {@link java.util.Locale#US Locale.US}. 635 * 636 * @return An array of locales for which localized 637 * <code>BreakIterator</code> instances are available. 638 */ 639 public static synchronized Locale[] getAvailableLocales() 640 { 641 LocaleServiceProviderPool pool = 642 LocaleServiceProviderPool.getPool(BreakIteratorProvider.class); 643 return pool.getAvailableLocales(); 644 } 645 646 private static final class BreakIteratorCache { 647 648 private BreakIterator iter; 649 private Locale locale; 650 651 BreakIteratorCache(Locale locale, BreakIterator iter) { 652 this.locale = locale; 653 this.iter = (BreakIterator) iter.clone(); 654 } 655 656 Locale getLocale() { 657 return locale; 658 } 659 660 BreakIterator createBreakInstance() { 661 return (BreakIterator) iter.clone(); 662 } 663 } 664 665 static long getLong(byte[] buf, int offset) { 666 long num = buf[offset]&0xFF; 667 for (int i = 1; i < 8; i++) { 668 num = num<<8 | (buf[offset+i]&0xFF); 669 } 670 return num; 671 } 672 673 static int getInt(byte[] buf, int offset) { 674 int num = buf[offset]&0xFF; 675 for (int i = 1; i < 4; i++) { 676 num = num<<8 | (buf[offset+i]&0xFF); 677 } 678 return num; 679 } 680 681 static short getShort(byte[] buf, int offset) { 682 short num = (short)(buf[offset]&0xFF); 683 num = (short)(num<<8 | (buf[offset+1]&0xFF)); 684 return num; 685 } 686 687 /** 688 * Obtains a BreakIterator instance from a BreakIteratorProvider 689 * implementation. 690 */ 691 private static class BreakIteratorGetter 692 implements LocaleServiceProviderPool.LocalizedObjectGetter<BreakIteratorProvider, BreakIterator> { 693 private static final BreakIteratorGetter INSTANCE = 694 new BreakIteratorGetter(); 695 696 public BreakIterator getObject(BreakIteratorProvider breakIteratorProvider, 697 Locale locale, 698 String key, 699 Object... params) { 700 assert params.length == 1; 701 702 switch ((Integer)params[0]) { 703 case CHARACTER_INDEX: 704 return breakIteratorProvider.getCharacterInstance(locale); 705 case WORD_INDEX: 706 return breakIteratorProvider.getWordInstance(locale); 707 case LINE_INDEX: 708 return breakIteratorProvider.getLineInstance(locale); 709 case SENTENCE_INDEX: 710 return breakIteratorProvider.getSentenceInstance(locale); 711 default: 712 assert false : "should not happen"; 713 } 714 return null; 715 } 716 } 717 }