1 /* 2 * Copyright (c) 2012, 2017, 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 * This file is available under and governed by the GNU General Public 28 * License version 2 only, as published by the Free Software Foundation. 29 * However, the following notice accompanied the original version of this 30 * file: 31 * 32 * Copyright (c) 2011-2012, Stephen Colebourne & Michael Nascimento Santos 33 * 34 * All rights reserved. 35 * 36 * Redistribution and use in source and binary forms, with or without 37 * modification, are permitted provided that the following conditions are met: 38 * 39 * * Redistributions of source code must retain the above copyright notice, 40 * this list of conditions and the following disclaimer. 41 * 42 * * Redistributions in binary form must reproduce the above copyright notice, 43 * this list of conditions and the following disclaimer in the documentation 44 * and/or other materials provided with the distribution. 45 * 46 * * Neither the name of JSR-310 nor the names of its contributors 47 * may be used to endorse or promote products derived from this software 48 * without specific prior written permission. 49 * 50 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 51 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 52 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 53 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 54 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 55 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 56 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 57 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 58 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 59 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 60 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 61 */ 62 package java.time.format; 63 64 import android.icu.text.DateFormatSymbols; 65 import android.icu.util.ULocale; 66 67 import static java.time.temporal.ChronoField.AMPM_OF_DAY; 68 import static java.time.temporal.ChronoField.DAY_OF_WEEK; 69 import static java.time.temporal.ChronoField.ERA; 70 import static java.time.temporal.ChronoField.MONTH_OF_YEAR; 71 72 import com.android.icu.text.ExtendedDateFormatSymbols; 73 74 import java.time.chrono.Chronology; 75 import java.time.chrono.IsoChronology; 76 import java.time.chrono.JapaneseChronology; 77 import java.time.temporal.ChronoField; 78 import java.time.temporal.IsoFields; 79 import java.time.temporal.TemporalField; 80 import java.util.AbstractMap.SimpleImmutableEntry; 81 import java.util.ArrayList; 82 import java.util.Calendar; 83 import java.util.Collections; 84 import java.util.Comparator; 85 import java.util.HashMap; 86 import java.util.Iterator; 87 import java.util.List; 88 import java.util.Locale; 89 import java.util.Map; 90 import java.util.Map.Entry; 91 import java.util.concurrent.ConcurrentHashMap; 92 import java.util.concurrent.ConcurrentMap; 93 94 import sun.util.locale.provider.CalendarDataUtility; 95 96 /** 97 * A provider to obtain the textual form of a date-time field. 98 * 99 * @implSpec 100 * Implementations must be thread-safe. 101 * Implementations should cache the textual information. 102 * 103 * @since 1.8 104 */ 105 class DateTimeTextProvider { 106 107 /** Cache. */ 108 private static final ConcurrentMap<Entry<TemporalField, Locale>, Object> CACHE = new ConcurrentHashMap<>(16, 0.75f, 2); 109 /** Comparator. */ 110 private static final Comparator<Entry<String, Long>> COMPARATOR = new Comparator<Entry<String, Long>>() { 111 @Override 112 public int compare(Entry<String, Long> obj1, Entry<String, Long> obj2) { 113 return obj2.getKey().length() - obj1.getKey().length(); // longest to shortest 114 } 115 }; 116 117 // Singleton instance 118 private static final DateTimeTextProvider INSTANCE = new DateTimeTextProvider(); 119 DateTimeTextProvider()120 DateTimeTextProvider() {} 121 122 /** 123 * Gets the provider of text. 124 * 125 * @return the provider, not null 126 */ getInstance()127 static DateTimeTextProvider getInstance() { 128 return INSTANCE; 129 } 130 131 /** 132 * Gets the text for the specified field, locale and style 133 * for the purpose of formatting. 134 * <p> 135 * The text associated with the value is returned. 136 * The null return value should be used if there is no applicable text, or 137 * if the text would be a numeric representation of the value. 138 * 139 * @param field the field to get text for, not null 140 * @param value the field value to get text for, not null 141 * @param style the style to get text for, not null 142 * @param locale the locale to get text for, not null 143 * @return the text for the field value, null if no text found 144 */ getText(TemporalField field, long value, TextStyle style, Locale locale)145 public String getText(TemporalField field, long value, TextStyle style, Locale locale) { 146 Object store = findStore(field, locale); 147 if (store instanceof LocaleStore) { 148 return ((LocaleStore) store).getText(value, style); 149 } 150 return null; 151 } 152 153 /** 154 * Gets the text for the specified chrono, field, locale and style 155 * for the purpose of formatting. 156 * <p> 157 * The text associated with the value is returned. 158 * The null return value should be used if there is no applicable text, or 159 * if the text would be a numeric representation of the value. 160 * 161 * @param chrono the Chronology to get text for, not null 162 * @param field the field to get text for, not null 163 * @param value the field value to get text for, not null 164 * @param style the style to get text for, not null 165 * @param locale the locale to get text for, not null 166 * @return the text for the field value, null if no text found 167 */ getText(Chronology chrono, TemporalField field, long value, TextStyle style, Locale locale)168 public String getText(Chronology chrono, TemporalField field, long value, 169 TextStyle style, Locale locale) { 170 if (chrono == IsoChronology.INSTANCE 171 || !(field instanceof ChronoField)) { 172 return getText(field, value, style, locale); 173 } 174 175 int fieldIndex; 176 int fieldValue; 177 if (field == ERA) { 178 fieldIndex = Calendar.ERA; 179 if (chrono == JapaneseChronology.INSTANCE) { 180 if (value == -999) { 181 fieldValue = 0; 182 } else { 183 fieldValue = (int) value + 2; 184 } 185 } else { 186 fieldValue = (int) value; 187 } 188 } else if (field == MONTH_OF_YEAR) { 189 fieldIndex = Calendar.MONTH; 190 fieldValue = (int) value - 1; 191 } else if (field == DAY_OF_WEEK) { 192 fieldIndex = Calendar.DAY_OF_WEEK; 193 fieldValue = (int) value + 1; 194 if (fieldValue > 7) { 195 fieldValue = Calendar.SUNDAY; 196 } 197 } else if (field == AMPM_OF_DAY) { 198 fieldIndex = Calendar.AM_PM; 199 fieldValue = (int) value; 200 } else { 201 return null; 202 } 203 return CalendarDataUtility.retrieveJavaTimeFieldValueName( 204 chrono.getCalendarType(), fieldIndex, fieldValue, style.toCalendarStyle(), locale); 205 } 206 207 /** 208 * Gets an iterator of text to field for the specified field, locale and style 209 * for the purpose of parsing. 210 * <p> 211 * The iterator must be returned in order from the longest text to the shortest. 212 * <p> 213 * The null return value should be used if there is no applicable parsable text, or 214 * if the text would be a numeric representation of the value. 215 * Text can only be parsed if all the values for that field-style-locale combination are unique. 216 * 217 * @param field the field to get text for, not null 218 * @param style the style to get text for, null for all parsable text 219 * @param locale the locale to get text for, not null 220 * @return the iterator of text to field pairs, in order from longest text to shortest text, 221 * null if the field or style is not parsable 222 */ getTextIterator(TemporalField field, TextStyle style, Locale locale)223 public Iterator<Entry<String, Long>> getTextIterator(TemporalField field, TextStyle style, Locale locale) { 224 Object store = findStore(field, locale); 225 if (store instanceof LocaleStore) { 226 return ((LocaleStore) store).getTextIterator(style); 227 } 228 return null; 229 } 230 231 /** 232 * Gets an iterator of text to field for the specified chrono, field, locale and style 233 * for the purpose of parsing. 234 * <p> 235 * The iterator must be returned in order from the longest text to the shortest. 236 * <p> 237 * The null return value should be used if there is no applicable parsable text, or 238 * if the text would be a numeric representation of the value. 239 * Text can only be parsed if all the values for that field-style-locale combination are unique. 240 * 241 * @param chrono the Chronology to get text for, not null 242 * @param field the field to get text for, not null 243 * @param style the style to get text for, null for all parsable text 244 * @param locale the locale to get text for, not null 245 * @return the iterator of text to field pairs, in order from longest text to shortest text, 246 * null if the field or style is not parsable 247 */ getTextIterator(Chronology chrono, TemporalField field, TextStyle style, Locale locale)248 public Iterator<Entry<String, Long>> getTextIterator(Chronology chrono, TemporalField field, 249 TextStyle style, Locale locale) { 250 if (chrono == IsoChronology.INSTANCE 251 || !(field instanceof ChronoField)) { 252 return getTextIterator(field, style, locale); 253 } 254 255 int fieldIndex; 256 switch ((ChronoField)field) { 257 case ERA: 258 fieldIndex = Calendar.ERA; 259 break; 260 case MONTH_OF_YEAR: 261 fieldIndex = Calendar.MONTH; 262 break; 263 case DAY_OF_WEEK: 264 fieldIndex = Calendar.DAY_OF_WEEK; 265 break; 266 case AMPM_OF_DAY: 267 fieldIndex = Calendar.AM_PM; 268 break; 269 default: 270 return null; 271 } 272 273 int calendarStyle = (style == null) ? Calendar.ALL_STYLES : style.toCalendarStyle(); 274 Map<String, Integer> map = CalendarDataUtility.retrieveJavaTimeFieldValueNames( 275 chrono.getCalendarType(), fieldIndex, calendarStyle, locale); 276 if (map == null) { 277 return null; 278 } 279 List<Entry<String, Long>> list = new ArrayList<>(map.size()); 280 switch (fieldIndex) { 281 case Calendar.ERA: 282 for (Map.Entry<String, Integer> entry : map.entrySet()) { 283 int era = entry.getValue(); 284 if (chrono == JapaneseChronology.INSTANCE) { 285 if (era == 0) { 286 era = -999; 287 } else { 288 era -= 2; 289 } 290 } 291 list.add(createEntry(entry.getKey(), (long)era)); 292 } 293 break; 294 case Calendar.MONTH: 295 for (Map.Entry<String, Integer> entry : map.entrySet()) { 296 list.add(createEntry(entry.getKey(), (long)(entry.getValue() + 1))); 297 } 298 break; 299 case Calendar.DAY_OF_WEEK: 300 for (Map.Entry<String, Integer> entry : map.entrySet()) { 301 list.add(createEntry(entry.getKey(), (long)toWeekDay(entry.getValue()))); 302 } 303 break; 304 default: 305 for (Map.Entry<String, Integer> entry : map.entrySet()) { 306 list.add(createEntry(entry.getKey(), (long)entry.getValue())); 307 } 308 break; 309 } 310 return list.iterator(); 311 } 312 findStore(TemporalField field, Locale locale)313 private Object findStore(TemporalField field, Locale locale) { 314 Entry<TemporalField, Locale> key = createEntry(field, locale); 315 Object store = CACHE.get(key); 316 if (store == null) { 317 store = createStore(field, locale); 318 CACHE.putIfAbsent(key, store); 319 store = CACHE.get(key); 320 } 321 return store; 322 } 323 toWeekDay(int calWeekDay)324 private static int toWeekDay(int calWeekDay) { 325 if (calWeekDay == Calendar.SUNDAY) { 326 return 7; 327 } else { 328 return calWeekDay - 1; 329 } 330 } 331 createStore(TemporalField field, Locale locale)332 private Object createStore(TemporalField field, Locale locale) { 333 Map<TextStyle, Map<Long, String>> styleMap = new HashMap<>(); 334 if (field == ERA) { 335 for (TextStyle textStyle : TextStyle.values()) { 336 if (textStyle.isStandalone()) { 337 // Stand-alone isn't applicable to era names. 338 continue; 339 } 340 Map<String, Integer> displayNames = CalendarDataUtility.retrieveJavaTimeFieldValueNames( 341 "gregory", Calendar.ERA, textStyle.toCalendarStyle(), locale); 342 if (displayNames != null) { 343 Map<Long, String> map = new HashMap<>(); 344 for (Entry<String, Integer> entry : displayNames.entrySet()) { 345 map.put((long) entry.getValue(), entry.getKey()); 346 } 347 if (!map.isEmpty()) { 348 styleMap.put(textStyle, map); 349 } 350 } 351 } 352 return new LocaleStore(styleMap); 353 } 354 355 if (field == MONTH_OF_YEAR) { 356 for (TextStyle textStyle : TextStyle.values()) { 357 Map<Long, String> map = new HashMap<>(); 358 // Narrow names may have duplicated names, such as "J" for January, June, July. 359 // Get names one by one in that case. 360 if ((textStyle.equals(TextStyle.NARROW) || 361 textStyle.equals(TextStyle.NARROW_STANDALONE))) { 362 for (int month = Calendar.JANUARY; month <= Calendar.DECEMBER; month++) { 363 String name; 364 name = CalendarDataUtility.retrieveJavaTimeFieldValueName( 365 "gregory", Calendar.MONTH, 366 month, textStyle.toCalendarStyle(), locale); 367 if (name == null) { 368 break; 369 } 370 map.put((month + 1L), name); 371 } 372 } else { 373 Map<String, Integer> displayNames = CalendarDataUtility.retrieveJavaTimeFieldValueNames( 374 "gregory", Calendar.MONTH, textStyle.toCalendarStyle(), locale); 375 if (displayNames != null) { 376 for (Entry<String, Integer> entry : displayNames.entrySet()) { 377 map.put((long)(entry.getValue() + 1), entry.getKey()); 378 } 379 } else { 380 // Although probability is very less, but if other styles have duplicate names. 381 // Get names one by one in that case. 382 for (int month = Calendar.JANUARY; month <= Calendar.DECEMBER; month++) { 383 String name; 384 name = CalendarDataUtility.retrieveJavaTimeFieldValueName( 385 "gregory", Calendar.MONTH, month, textStyle.toCalendarStyle(), locale); 386 if (name == null) { 387 break; 388 } 389 map.put((month + 1L), name); 390 } 391 } 392 } 393 if (!map.isEmpty()) { 394 styleMap.put(textStyle, map); 395 } 396 } 397 return new LocaleStore(styleMap); 398 } 399 400 if (field == DAY_OF_WEEK) { 401 for (TextStyle textStyle : TextStyle.values()) { 402 Map<Long, String> map = new HashMap<>(); 403 // Narrow names may have duplicated names, such as "S" for Sunday and Saturday. 404 // Get names one by one in that case. 405 if ((textStyle.equals(TextStyle.NARROW) || 406 textStyle.equals(TextStyle.NARROW_STANDALONE))) { 407 for (int wday = Calendar.SUNDAY; wday <= Calendar.SATURDAY; wday++) { 408 String name; 409 name = CalendarDataUtility.retrieveJavaTimeFieldValueName( 410 "gregory", Calendar.DAY_OF_WEEK, 411 wday, textStyle.toCalendarStyle(), locale); 412 if (name == null) { 413 break; 414 } 415 map.put((long)toWeekDay(wday), name); 416 } 417 } else { 418 Map<String, Integer> displayNames = CalendarDataUtility.retrieveJavaTimeFieldValueNames( 419 "gregory", Calendar.DAY_OF_WEEK, textStyle.toCalendarStyle(), locale); 420 if (displayNames != null) { 421 for (Entry<String, Integer> entry : displayNames.entrySet()) { 422 map.put((long)toWeekDay(entry.getValue()), entry.getKey()); 423 } 424 } else { 425 // Although probability is very less, but if other styles have duplicate names. 426 // Get names one by one in that case. 427 for (int wday = Calendar.SUNDAY; wday <= Calendar.SATURDAY; wday++) { 428 String name; 429 name = CalendarDataUtility.retrieveJavaTimeFieldValueName( 430 "gregory", Calendar.DAY_OF_WEEK, wday, textStyle.toCalendarStyle(), locale); 431 if (name == null) { 432 break; 433 } 434 map.put((long)toWeekDay(wday), name); 435 } 436 } 437 } 438 if (!map.isEmpty()) { 439 styleMap.put(textStyle, map); 440 } 441 } 442 return new LocaleStore(styleMap); 443 } 444 445 if (field == AMPM_OF_DAY) { 446 for (TextStyle textStyle : TextStyle.values()) { 447 if (textStyle.isStandalone()) { 448 // Stand-alone isn't applicable to AM/PM. 449 continue; 450 } 451 Map<String, Integer> displayNames = CalendarDataUtility.retrieveJavaTimeFieldValueNames( 452 "gregory", Calendar.AM_PM, textStyle.toCalendarStyle(), locale); 453 if (displayNames != null) { 454 Map<Long, String> map = new HashMap<>(); 455 for (Entry<String, Integer> entry : displayNames.entrySet()) { 456 map.put((long) entry.getValue(), entry.getKey()); 457 } 458 if (!map.isEmpty()) { 459 styleMap.put(textStyle, map); 460 } 461 } 462 } 463 return new LocaleStore(styleMap); 464 } 465 466 if (field == IsoFields.QUARTER_OF_YEAR) { 467 // BEGIN Android-changed: Use ICU resources. 468 /* 469 // The order of keys must correspond to the TextStyle.values() order. 470 final String[] keys = { 471 "QuarterNames", 472 "standalone.QuarterNames", 473 "QuarterAbbreviations", 474 "standalone.QuarterAbbreviations", 475 "QuarterNarrows", 476 "standalone.QuarterNarrows", 477 }; 478 for (int i = 0; i < keys.length; i++) { 479 String[] names = getLocalizedResource(keys[i], locale); 480 if (names != null) { 481 Map<Long, String> map = new HashMap<>(); 482 for (int q = 0; q < names.length; q++) { 483 map.put((long) (q + 1), names[q]); 484 } 485 styleMap.put(TextStyle.values()[i], map); 486 } 487 } 488 */ 489 ULocale uLocale = ULocale.forLocale(locale); 490 // TODO: Figure why we forced Gregorian calendar in the first patch in 491 // https://r.android.com/311224 492 uLocale.setKeywordValue("calendar", "gregorian"); 493 ExtendedDateFormatSymbols extendedDfs = ExtendedDateFormatSymbols.getInstance(uLocale); 494 DateFormatSymbols dfs = extendedDfs.getDateFormatSymbols(); 495 styleMap.put(TextStyle.FULL, extractQuarters( 496 dfs.getQuarters(DateFormatSymbols.FORMAT, DateFormatSymbols.WIDE))); 497 styleMap.put(TextStyle.FULL_STANDALONE, extractQuarters( 498 dfs.getQuarters(DateFormatSymbols.STANDALONE, DateFormatSymbols.WIDE))); 499 styleMap.put(TextStyle.SHORT, extractQuarters( 500 dfs.getQuarters(DateFormatSymbols.FORMAT, DateFormatSymbols.ABBREVIATED))); 501 styleMap.put(TextStyle.SHORT_STANDALONE, extractQuarters( 502 dfs.getQuarters(DateFormatSymbols.STANDALONE, DateFormatSymbols.ABBREVIATED))); 503 styleMap.put(TextStyle.NARROW, extractQuarters( 504 extendedDfs.getNarrowQuarters(DateFormatSymbols.FORMAT))); 505 styleMap.put(TextStyle.NARROW_STANDALONE, extractQuarters( 506 extendedDfs.getNarrowQuarters(DateFormatSymbols.STANDALONE))); 507 508 // END Android-changed: Use ICU resources. 509 return new LocaleStore(styleMap); 510 } 511 512 return ""; // null marker for map 513 } 514 515 // BEGIN Android-added: Extracts a Map of quarter names. extractQuarters(String[] quarters)516 private static Map<Long, String> extractQuarters(String[] quarters) { 517 Map<Long, String> map = new HashMap<>(); 518 for (int q = 0; q < quarters.length; q++) { 519 map.put((long) (q + 1), quarters[q]); 520 } 521 return map; 522 } 523 // END Android-added: Extracts a Map of quarter names. 524 525 /** 526 * Helper method to create an immutable entry. 527 * 528 * @param text the text, not null 529 * @param field the field, not null 530 * @return the entry, not null 531 */ createEntry(A text, B field)532 private static <A, B> Entry<A, B> createEntry(A text, B field) { 533 return new SimpleImmutableEntry<>(text, field); 534 } 535 536 // BEGIN Android-removed: Android uses ICU resources and has no LocaleProviderAdapter. 537 /** 538 * Returns the localized resource of the given key and locale, or null 539 * if no localized resource is available. 540 * 541 * @param key the key of the localized resource, not null 542 * @param locale the locale, not null 543 * @return the localized resource, or null if not available 544 * @throws NullPointerException if key or locale is null 545 */ 546 // @SuppressWarnings("unchecked") 547 // static <T> T getLocalizedResource(String key, Locale locale) { 548 // LocaleResources lr = LocaleProviderAdapter.getResourceBundleBased() 549 // .getLocaleResources( 550 // CalendarDataUtility.findRegionOverride(locale)); 551 // ResourceBundle rb = lr.getJavaTimeFormatData(); 552 // return rb.containsKey(key) ? (T) rb.getObject(key) : null; 553 // } 554 // END Android-removed: Android uses ICU resources and has no LocaleProviderAdapter. 555 556 /** 557 * Stores the text for a single locale. 558 * <p> 559 * Some fields have a textual representation, such as day-of-week or month-of-year. 560 * These textual representations can be captured in this class for printing 561 * and parsing. 562 * <p> 563 * This class is immutable and thread-safe. 564 */ 565 static final class LocaleStore { 566 /** 567 * Map of value to text. 568 */ 569 private final Map<TextStyle, Map<Long, String>> valueTextMap; 570 /** 571 * Parsable data. 572 */ 573 private final Map<TextStyle, List<Entry<String, Long>>> parsable; 574 575 /** 576 * Constructor. 577 * 578 * @param valueTextMap the map of values to text to store, assigned and not altered, not null 579 */ LocaleStore(Map<TextStyle, Map<Long, String>> valueTextMap)580 LocaleStore(Map<TextStyle, Map<Long, String>> valueTextMap) { 581 this.valueTextMap = valueTextMap; 582 Map<TextStyle, List<Entry<String, Long>>> map = new HashMap<>(); 583 List<Entry<String, Long>> allList = new ArrayList<>(); 584 for (Map.Entry<TextStyle, Map<Long, String>> vtmEntry : valueTextMap.entrySet()) { 585 Map<String, Entry<String, Long>> reverse = new HashMap<>(); 586 for (Map.Entry<Long, String> entry : vtmEntry.getValue().entrySet()) { 587 if (reverse.put(entry.getValue(), createEntry(entry.getValue(), entry.getKey())) != null) { 588 // TODO: BUG: this has no effect 589 continue; // not parsable, try next style 590 } 591 } 592 List<Entry<String, Long>> list = new ArrayList<>(reverse.values()); 593 Collections.sort(list, COMPARATOR); 594 map.put(vtmEntry.getKey(), list); 595 allList.addAll(list); 596 map.put(null, allList); 597 } 598 Collections.sort(allList, COMPARATOR); 599 this.parsable = map; 600 } 601 602 /** 603 * Gets the text for the specified field value, locale and style 604 * for the purpose of printing. 605 * 606 * @param value the value to get text for, not null 607 * @param style the style to get text for, not null 608 * @return the text for the field value, null if no text found 609 */ getText(long value, TextStyle style)610 String getText(long value, TextStyle style) { 611 Map<Long, String> map = valueTextMap.get(style); 612 return map != null ? map.get(value) : null; 613 } 614 615 /** 616 * Gets an iterator of text to field for the specified style for the purpose of parsing. 617 * <p> 618 * The iterator must be returned in order from the longest text to the shortest. 619 * 620 * @param style the style to get text for, null for all parsable text 621 * @return the iterator of text to field pairs, in order from longest text to shortest text, 622 * null if the style is not parsable 623 */ getTextIterator(TextStyle style)624 Iterator<Entry<String, Long>> getTextIterator(TextStyle style) { 625 List<Entry<String, Long>> list = parsable.get(style); 626 return list != null ? list.iterator() : null; 627 } 628 } 629 } 630