Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/src/java.base/share/classes/java/time/format/DateTimeTextProvider.java
41159 views
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 static java.time.temporal.ChronoField.AMPM_OF_DAY;
65
import static java.time.temporal.ChronoField.DAY_OF_WEEK;
66
import static java.time.temporal.ChronoField.ERA;
67
import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
68
69
import java.time.chrono.Chronology;
70
import java.time.chrono.IsoChronology;
71
import java.time.chrono.JapaneseChronology;
72
import java.time.temporal.ChronoField;
73
import java.time.temporal.IsoFields;
74
import java.time.temporal.TemporalField;
75
import java.util.AbstractMap.SimpleImmutableEntry;
76
import java.util.ArrayList;
77
import java.util.Calendar;
78
import java.util.Collections;
79
import java.util.Comparator;
80
import java.util.HashMap;
81
import java.util.Iterator;
82
import java.util.List;
83
import java.util.Locale;
84
import java.util.Map;
85
import java.util.Map.Entry;
86
import java.util.ResourceBundle;
87
import java.util.concurrent.ConcurrentHashMap;
88
import java.util.concurrent.ConcurrentMap;
89
90
import sun.util.locale.provider.CalendarDataUtility;
91
import sun.util.locale.provider.LocaleProviderAdapter;
92
import sun.util.locale.provider.LocaleResources;
93
94
/**
95
* A provider to obtain the textual form of a date-time field.
96
*
97
* @implSpec
98
* Implementations must be thread-safe.
99
* Implementations should cache the textual information.
100
*
101
* @since 1.8
102
*/
103
class DateTimeTextProvider {
104
105
/** Cache. */
106
private static final ConcurrentMap<Entry<TemporalField, Locale>, Object> CACHE = new ConcurrentHashMap<>(16, 0.75f, 2);
107
/** Comparator. */
108
private static final Comparator<Entry<String, Long>> COMPARATOR = new Comparator<Entry<String, Long>>() {
109
@Override
110
public int compare(Entry<String, Long> obj1, Entry<String, Long> obj2) {
111
return obj2.getKey().length() - obj1.getKey().length(); // longest to shortest
112
}
113
};
114
115
// Singleton instance
116
private static final DateTimeTextProvider INSTANCE = new DateTimeTextProvider();
117
118
DateTimeTextProvider() {}
119
120
/**
121
* Gets the provider of text.
122
*
123
* @return the provider, not null
124
*/
125
static DateTimeTextProvider getInstance() {
126
return INSTANCE;
127
}
128
129
/**
130
* Gets the text for the specified field, locale and style
131
* for the purpose of formatting.
132
* <p>
133
* The text associated with the value is returned.
134
* The null return value should be used if there is no applicable text, or
135
* if the text would be a numeric representation of the value.
136
*
137
* @param field the field to get text for, not null
138
* @param value the field value to get text for, not null
139
* @param style the style to get text for, not null
140
* @param locale the locale to get text for, not null
141
* @return the text for the field value, null if no text found
142
*/
143
public String getText(TemporalField field, long value, TextStyle style, Locale locale) {
144
Object store = findStore(field, locale);
145
if (store instanceof LocaleStore) {
146
return ((LocaleStore) store).getText(value, style);
147
}
148
return null;
149
}
150
151
/**
152
* Gets the text for the specified chrono, field, locale and style
153
* for the purpose of formatting.
154
* <p>
155
* The text associated with the value is returned.
156
* The null return value should be used if there is no applicable text, or
157
* if the text would be a numeric representation of the value.
158
*
159
* @param chrono the Chronology to get text for, not null
160
* @param field the field to get text for, not null
161
* @param value the field value to get text for, not null
162
* @param style the style to get text for, not null
163
* @param locale the locale to get text for, not null
164
* @return the text for the field value, null if no text found
165
*/
166
public String getText(Chronology chrono, TemporalField field, long value,
167
TextStyle style, Locale locale) {
168
if (chrono == IsoChronology.INSTANCE
169
|| !(field instanceof ChronoField)) {
170
return getText(field, value, style, locale);
171
}
172
173
int fieldIndex;
174
int fieldValue;
175
if (field == ERA) {
176
fieldIndex = Calendar.ERA;
177
if (chrono == JapaneseChronology.INSTANCE) {
178
if (value == -999) {
179
fieldValue = 0;
180
} else {
181
fieldValue = (int) value + 2;
182
}
183
} else {
184
fieldValue = (int) value;
185
}
186
} else if (field == MONTH_OF_YEAR) {
187
fieldIndex = Calendar.MONTH;
188
fieldValue = (int) value - 1;
189
} else if (field == DAY_OF_WEEK) {
190
fieldIndex = Calendar.DAY_OF_WEEK;
191
fieldValue = (int) value + 1;
192
if (fieldValue > 7) {
193
fieldValue = Calendar.SUNDAY;
194
}
195
} else if (field == AMPM_OF_DAY) {
196
fieldIndex = Calendar.AM_PM;
197
fieldValue = (int) value;
198
} else {
199
return null;
200
}
201
return CalendarDataUtility.retrieveJavaTimeFieldValueName(
202
chrono.getCalendarType(), fieldIndex, fieldValue, style.toCalendarStyle(), locale);
203
}
204
205
/**
206
* Gets an iterator of text to field for the specified field, locale and style
207
* for the purpose of parsing.
208
* <p>
209
* The iterator must be returned in order from the longest text to the shortest.
210
* <p>
211
* The null return value should be used if there is no applicable parsable text, or
212
* if the text would be a numeric representation of the value.
213
* Text can only be parsed if all the values for that field-style-locale combination are unique.
214
*
215
* @param field the field to get text for, not null
216
* @param style the style to get text for, null for all parsable text
217
* @param locale the locale to get text for, not null
218
* @return the iterator of text to field pairs, in order from longest text to shortest text,
219
* null if the field or style is not parsable
220
*/
221
public Iterator<Entry<String, Long>> getTextIterator(TemporalField field, TextStyle style, Locale locale) {
222
Object store = findStore(field, locale);
223
if (store instanceof LocaleStore) {
224
return ((LocaleStore) store).getTextIterator(style);
225
}
226
return null;
227
}
228
229
/**
230
* Gets an iterator of text to field for the specified chrono, field, locale and style
231
* for the purpose of parsing.
232
* <p>
233
* The iterator must be returned in order from the longest text to the shortest.
234
* <p>
235
* The null return value should be used if there is no applicable parsable text, or
236
* if the text would be a numeric representation of the value.
237
* Text can only be parsed if all the values for that field-style-locale combination are unique.
238
*
239
* @param chrono the Chronology to get text for, not null
240
* @param field the field to get text for, not null
241
* @param style the style to get text for, null for all parsable text
242
* @param locale the locale to get text for, not null
243
* @return the iterator of text to field pairs, in order from longest text to shortest text,
244
* null if the field or style is not parsable
245
*/
246
public Iterator<Entry<String, Long>> getTextIterator(Chronology chrono, TemporalField field,
247
TextStyle style, Locale locale) {
248
if (chrono == IsoChronology.INSTANCE
249
|| !(field instanceof ChronoField)) {
250
return getTextIterator(field, style, locale);
251
}
252
253
int fieldIndex;
254
switch ((ChronoField)field) {
255
case ERA:
256
fieldIndex = Calendar.ERA;
257
break;
258
case MONTH_OF_YEAR:
259
fieldIndex = Calendar.MONTH;
260
break;
261
case DAY_OF_WEEK:
262
fieldIndex = Calendar.DAY_OF_WEEK;
263
break;
264
case AMPM_OF_DAY:
265
fieldIndex = Calendar.AM_PM;
266
break;
267
default:
268
return null;
269
}
270
271
int calendarStyle = (style == null) ? Calendar.ALL_STYLES : style.toCalendarStyle();
272
Map<String, Integer> map = CalendarDataUtility.retrieveJavaTimeFieldValueNames(
273
chrono.getCalendarType(), fieldIndex, calendarStyle, locale);
274
if (map == null) {
275
return null;
276
}
277
List<Entry<String, Long>> list = new ArrayList<>(map.size());
278
switch (fieldIndex) {
279
case Calendar.ERA:
280
for (Map.Entry<String, Integer> entry : map.entrySet()) {
281
int era = entry.getValue();
282
if (chrono == JapaneseChronology.INSTANCE) {
283
if (era == 0) {
284
era = -999;
285
} else {
286
era -= 2;
287
}
288
}
289
list.add(createEntry(entry.getKey(), (long)era));
290
}
291
break;
292
case Calendar.MONTH:
293
for (Map.Entry<String, Integer> entry : map.entrySet()) {
294
list.add(createEntry(entry.getKey(), (long)(entry.getValue() + 1)));
295
}
296
break;
297
case Calendar.DAY_OF_WEEK:
298
for (Map.Entry<String, Integer> entry : map.entrySet()) {
299
list.add(createEntry(entry.getKey(), (long)toWeekDay(entry.getValue())));
300
}
301
break;
302
default:
303
for (Map.Entry<String, Integer> entry : map.entrySet()) {
304
list.add(createEntry(entry.getKey(), (long)entry.getValue()));
305
}
306
break;
307
}
308
return list.iterator();
309
}
310
311
private Object findStore(TemporalField field, Locale locale) {
312
Entry<TemporalField, Locale> key = createEntry(field, locale);
313
Object store = CACHE.get(key);
314
if (store == null) {
315
store = createStore(field, locale);
316
CACHE.putIfAbsent(key, store);
317
store = CACHE.get(key);
318
}
319
return store;
320
}
321
322
private static int toWeekDay(int calWeekDay) {
323
if (calWeekDay == Calendar.SUNDAY) {
324
return 7;
325
} else {
326
return calWeekDay - 1;
327
}
328
}
329
330
private Object createStore(TemporalField field, Locale locale) {
331
Map<TextStyle, Map<Long, String>> styleMap = new HashMap<>();
332
if (field == ERA) {
333
for (TextStyle textStyle : TextStyle.values()) {
334
if (textStyle.isStandalone()) {
335
// Stand-alone isn't applicable to era names.
336
continue;
337
}
338
Map<String, Integer> displayNames = CalendarDataUtility.retrieveJavaTimeFieldValueNames(
339
"gregory", Calendar.ERA, textStyle.toCalendarStyle(), locale);
340
if (displayNames != null) {
341
Map<Long, String> map = new HashMap<>();
342
for (Entry<String, Integer> entry : displayNames.entrySet()) {
343
map.put((long) entry.getValue(), entry.getKey());
344
}
345
if (!map.isEmpty()) {
346
styleMap.put(textStyle, map);
347
}
348
}
349
}
350
return new LocaleStore(styleMap);
351
}
352
353
if (field == MONTH_OF_YEAR) {
354
for (TextStyle textStyle : TextStyle.values()) {
355
Map<Long, String> map = new HashMap<>();
356
// Narrow names may have duplicated names, such as "J" for January, June, July.
357
// Get names one by one in that case.
358
if ((textStyle.equals(TextStyle.NARROW) ||
359
textStyle.equals(TextStyle.NARROW_STANDALONE))) {
360
for (int month = Calendar.JANUARY; month <= Calendar.DECEMBER; month++) {
361
String name;
362
name = CalendarDataUtility.retrieveJavaTimeFieldValueName(
363
"gregory", Calendar.MONTH,
364
month, textStyle.toCalendarStyle(), locale);
365
if (name == null) {
366
break;
367
}
368
map.put((month + 1L), name);
369
}
370
} else {
371
Map<String, Integer> displayNames = CalendarDataUtility.retrieveJavaTimeFieldValueNames(
372
"gregory", Calendar.MONTH, textStyle.toCalendarStyle(), locale);
373
if (displayNames != null) {
374
for (Entry<String, Integer> entry : displayNames.entrySet()) {
375
map.put((long)(entry.getValue() + 1), entry.getKey());
376
}
377
} else {
378
// Although probability is very less, but if other styles have duplicate names.
379
// Get names one by one in that case.
380
for (int month = Calendar.JANUARY; month <= Calendar.DECEMBER; month++) {
381
String name;
382
name = CalendarDataUtility.retrieveJavaTimeFieldValueName(
383
"gregory", Calendar.MONTH, month, textStyle.toCalendarStyle(), locale);
384
if (name == null) {
385
break;
386
}
387
map.put((month + 1L), name);
388
}
389
}
390
}
391
if (!map.isEmpty()) {
392
styleMap.put(textStyle, map);
393
}
394
}
395
return new LocaleStore(styleMap);
396
}
397
398
if (field == DAY_OF_WEEK) {
399
for (TextStyle textStyle : TextStyle.values()) {
400
Map<Long, String> map = new HashMap<>();
401
// Narrow names may have duplicated names, such as "S" for Sunday and Saturday.
402
// Get names one by one in that case.
403
if ((textStyle.equals(TextStyle.NARROW) ||
404
textStyle.equals(TextStyle.NARROW_STANDALONE))) {
405
for (int wday = Calendar.SUNDAY; wday <= Calendar.SATURDAY; wday++) {
406
String name;
407
name = CalendarDataUtility.retrieveJavaTimeFieldValueName(
408
"gregory", Calendar.DAY_OF_WEEK,
409
wday, textStyle.toCalendarStyle(), locale);
410
if (name == null) {
411
break;
412
}
413
map.put((long)toWeekDay(wday), name);
414
}
415
} else {
416
Map<String, Integer> displayNames = CalendarDataUtility.retrieveJavaTimeFieldValueNames(
417
"gregory", Calendar.DAY_OF_WEEK, textStyle.toCalendarStyle(), locale);
418
if (displayNames != null) {
419
for (Entry<String, Integer> entry : displayNames.entrySet()) {
420
map.put((long)toWeekDay(entry.getValue()), entry.getKey());
421
}
422
} else {
423
// Although probability is very less, but if other styles have duplicate names.
424
// Get names one by one in that case.
425
for (int wday = Calendar.SUNDAY; wday <= Calendar.SATURDAY; wday++) {
426
String name;
427
name = CalendarDataUtility.retrieveJavaTimeFieldValueName(
428
"gregory", Calendar.DAY_OF_WEEK, wday, textStyle.toCalendarStyle(), locale);
429
if (name == null) {
430
break;
431
}
432
map.put((long)toWeekDay(wday), name);
433
}
434
}
435
}
436
if (!map.isEmpty()) {
437
styleMap.put(textStyle, map);
438
}
439
}
440
return new LocaleStore(styleMap);
441
}
442
443
if (field == AMPM_OF_DAY) {
444
for (TextStyle textStyle : TextStyle.values()) {
445
if (textStyle.isStandalone()) {
446
// Stand-alone isn't applicable to AM/PM.
447
continue;
448
}
449
Map<String, Integer> displayNames = CalendarDataUtility.retrieveJavaTimeFieldValueNames(
450
"gregory", Calendar.AM_PM, textStyle.toCalendarStyle(), locale);
451
if (displayNames != null) {
452
Map<Long, String> map = new HashMap<>();
453
for (Entry<String, Integer> entry : displayNames.entrySet()) {
454
map.put((long) entry.getValue(), entry.getKey());
455
}
456
if (!map.isEmpty()) {
457
styleMap.put(textStyle, map);
458
}
459
}
460
}
461
return new LocaleStore(styleMap);
462
}
463
464
if (field == IsoFields.QUARTER_OF_YEAR) {
465
// The order of keys must correspond to the TextStyle.values() order.
466
final String[] keys = {
467
"QuarterNames",
468
"standalone.QuarterNames",
469
"QuarterAbbreviations",
470
"standalone.QuarterAbbreviations",
471
"QuarterNarrows",
472
"standalone.QuarterNarrows",
473
};
474
for (int i = 0; i < keys.length; i++) {
475
String[] names = getLocalizedResource(keys[i], locale);
476
if (names != null) {
477
Map<Long, String> map = new HashMap<>();
478
for (int q = 0; q < names.length; q++) {
479
map.put((long) (q + 1), names[q]);
480
}
481
styleMap.put(TextStyle.values()[i], map);
482
}
483
}
484
return new LocaleStore(styleMap);
485
}
486
487
return ""; // null marker for map
488
}
489
490
/**
491
* Helper method to create an immutable entry.
492
*
493
* @param text the text, not null
494
* @param field the field, not null
495
* @return the entry, not null
496
*/
497
private static <A, B> Entry<A, B> createEntry(A text, B field) {
498
return new SimpleImmutableEntry<>(text, field);
499
}
500
501
/**
502
* Returns the localized resource of the given key and locale, or null
503
* if no localized resource is available.
504
*
505
* @param key the key of the localized resource, not null
506
* @param locale the locale, not null
507
* @return the localized resource, or null if not available
508
* @throws NullPointerException if key or locale is null
509
*/
510
@SuppressWarnings("unchecked")
511
static <T> T getLocalizedResource(String key, Locale locale) {
512
LocaleResources lr = LocaleProviderAdapter.getResourceBundleBased()
513
.getLocaleResources(
514
CalendarDataUtility.findRegionOverride(locale));
515
ResourceBundle rb = lr.getJavaTimeFormatData();
516
return rb.containsKey(key) ? (T) rb.getObject(key) : null;
517
}
518
519
/**
520
* Stores the text for a single locale.
521
* <p>
522
* Some fields have a textual representation, such as day-of-week or month-of-year.
523
* These textual representations can be captured in this class for printing
524
* and parsing.
525
* <p>
526
* This class is immutable and thread-safe.
527
*/
528
static final class LocaleStore {
529
/**
530
* Map of value to text.
531
*/
532
private final Map<TextStyle, Map<Long, String>> valueTextMap;
533
/**
534
* Parsable data.
535
*/
536
private final Map<TextStyle, List<Entry<String, Long>>> parsable;
537
538
/**
539
* Constructor.
540
*
541
* @param valueTextMap the map of values to text to store, assigned and not altered, not null
542
*/
543
LocaleStore(Map<TextStyle, Map<Long, String>> valueTextMap) {
544
this.valueTextMap = valueTextMap;
545
Map<TextStyle, List<Entry<String, Long>>> map = new HashMap<>();
546
List<Entry<String, Long>> allList = new ArrayList<>();
547
for (Map.Entry<TextStyle, Map<Long, String>> vtmEntry : valueTextMap.entrySet()) {
548
Map<String, Entry<String, Long>> reverse = new HashMap<>();
549
for (Map.Entry<Long, String> entry : vtmEntry.getValue().entrySet()) {
550
if (reverse.put(entry.getValue(), createEntry(entry.getValue(), entry.getKey())) != null) {
551
// TODO: BUG: this has no effect
552
continue; // not parsable, try next style
553
}
554
}
555
List<Entry<String, Long>> list = new ArrayList<>(reverse.values());
556
Collections.sort(list, COMPARATOR);
557
map.put(vtmEntry.getKey(), list);
558
allList.addAll(list);
559
map.put(null, allList);
560
}
561
Collections.sort(allList, COMPARATOR);
562
this.parsable = map;
563
}
564
565
/**
566
* Gets the text for the specified field value, locale and style
567
* for the purpose of printing.
568
*
569
* @param value the value to get text for, not null
570
* @param style the style to get text for, not null
571
* @return the text for the field value, null if no text found
572
*/
573
String getText(long value, TextStyle style) {
574
Map<Long, String> map = valueTextMap.get(style);
575
return map != null ? map.get(value) : null;
576
}
577
578
/**
579
* Gets an iterator of text to field for the specified style for the purpose of parsing.
580
* <p>
581
* The iterator must be returned in order from the longest text to the shortest.
582
*
583
* @param style the style to get text for, null for all parsable text
584
* @return the iterator of text to field pairs, in order from longest text to shortest text,
585
* null if the style is not parsable
586
*/
587
Iterator<Entry<String, Long>> getTextIterator(TextStyle style) {
588
List<Entry<String, Long>> list = parsable.get(style);
589
return list != null ? list.iterator() : null;
590
}
591
}
592
}
593
594