Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/src/java.base/share/classes/sun/util/resources/Bundles.java
41159 views
1
/*
2
* Copyright (c) 2015, 2021, 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 - 1999 - 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 sun.util.resources;
42
43
import java.lang.ref.ReferenceQueue;
44
import java.lang.ref.SoftReference;
45
import java.security.AccessController;
46
import java.security.PrivilegedAction;
47
import java.util.Enumeration;
48
import java.util.Iterator;
49
import java.util.List;
50
import java.util.Locale;
51
import java.util.MissingResourceException;
52
import java.util.Objects;
53
import java.util.ResourceBundle;
54
import java.util.ServiceConfigurationError;
55
import java.util.ServiceLoader;
56
import java.util.concurrent.ConcurrentHashMap;
57
import java.util.concurrent.ConcurrentMap;
58
import java.util.spi.ResourceBundleProvider;
59
import jdk.internal.access.JavaUtilResourceBundleAccess;
60
import jdk.internal.access.SharedSecrets;
61
62
/**
63
*/
64
public abstract class Bundles {
65
66
/** initial size of the bundle cache */
67
private static final int INITIAL_CACHE_SIZE = 32;
68
69
/** constant indicating that no resource bundle exists */
70
private static final ResourceBundle NONEXISTENT_BUNDLE = new ResourceBundle() {
71
@Override
72
public Enumeration<String> getKeys() { return null; }
73
@Override
74
protected Object handleGetObject(String key) { return null; }
75
@Override
76
public String toString() { return "NONEXISTENT_BUNDLE"; }
77
};
78
79
private static final JavaUtilResourceBundleAccess bundleAccess
80
= SharedSecrets.getJavaUtilResourceBundleAccess();
81
82
/**
83
* The cache is a map from cache keys (with bundle base name, locale, and
84
* class loader) to either a resource bundle or NONEXISTENT_BUNDLE wrapped by a
85
* BundleReference.
86
*
87
* The cache is a ConcurrentMap, allowing the cache to be searched
88
* concurrently by multiple threads. This will also allow the cache keys
89
* to be reclaimed along with the ClassLoaders they reference.
90
*
91
* This variable would be better named "cache", but we keep the old
92
* name for compatibility with some workarounds for bug 4212439.
93
*/
94
private static final ConcurrentMap<CacheKey, BundleReference> cacheList
95
= new ConcurrentHashMap<>(INITIAL_CACHE_SIZE);
96
97
/**
98
* Queue for reference objects referring to class loaders or bundles.
99
*/
100
private static final ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
101
102
private Bundles() {
103
}
104
105
public static ResourceBundle of(String baseName, Locale locale, Strategy strategy) {
106
return loadBundleOf(baseName, locale, strategy);
107
}
108
109
private static ResourceBundle loadBundleOf(String baseName,
110
Locale targetLocale,
111
Strategy strategy) {
112
Objects.requireNonNull(baseName);
113
Objects.requireNonNull(targetLocale);
114
Objects.requireNonNull(strategy);
115
116
CacheKey cacheKey = new CacheKey(baseName, targetLocale);
117
118
ResourceBundle bundle = null;
119
120
// Quick lookup of the cache.
121
BundleReference bundleRef = cacheList.get(cacheKey);
122
if (bundleRef != null) {
123
bundle = bundleRef.get();
124
}
125
126
// If this bundle and all of its parents are valid,
127
// then return this bundle.
128
if (isValidBundle(bundle)) {
129
return bundle;
130
}
131
132
// Get the providers for loading the "leaf" bundle (i.e., bundle for
133
// targetLocale). If no providers are required for the bundle,
134
// none of its parents will require providers.
135
Class<? extends ResourceBundleProvider> type
136
= strategy.getResourceBundleProviderType(baseName, targetLocale);
137
if (type != null) {
138
@SuppressWarnings("unchecked")
139
ServiceLoader<ResourceBundleProvider> providers
140
= (ServiceLoader<ResourceBundleProvider>) ServiceLoader.loadInstalled(type);
141
cacheKey.setProviders(providers);
142
}
143
144
List<Locale> candidateLocales = strategy.getCandidateLocales(baseName, targetLocale);
145
bundle = findBundleOf(cacheKey, strategy, baseName, candidateLocales, 0);
146
if (bundle == null) {
147
throwMissingResourceException(baseName, targetLocale, cacheKey.getCause());
148
}
149
return bundle;
150
}
151
152
private static ResourceBundle findBundleOf(CacheKey cacheKey,
153
Strategy strategy,
154
String baseName,
155
List<Locale> candidateLocales,
156
int index) {
157
ResourceBundle parent = null;
158
Locale targetLocale = candidateLocales.get(index);
159
if (index != candidateLocales.size() - 1) {
160
parent = findBundleOf(cacheKey, strategy, baseName, candidateLocales, index + 1);
161
}
162
163
// Before we do the real loading work, see whether we need to
164
// do some housekeeping: If resource bundles have been nulled out,
165
// remove all related information from the cache.
166
cleanupCache();
167
168
// find an individual ResourceBundle in the cache
169
cacheKey.setLocale(targetLocale);
170
ResourceBundle bundle = findBundleInCache(cacheKey);
171
if (bundle != null) {
172
if (bundle == NONEXISTENT_BUNDLE) {
173
return parent;
174
}
175
if (bundleAccess.getParent(bundle) == parent) {
176
return bundle;
177
}
178
// Remove bundle from the cache.
179
BundleReference bundleRef = cacheList.get(cacheKey);
180
if (bundleRef != null && bundleRef.get() == bundle) {
181
cacheList.remove(cacheKey, bundleRef);
182
}
183
}
184
185
// Determine if providers should be used for loading the bundle.
186
// An assumption here is that if the leaf bundle of a look-up path is
187
// in java.base, all bundles of the path are in java.base.
188
// (e.g., en_US of path en_US -> en -> root is in java.base and the rest
189
// are in java.base as well)
190
// This assumption isn't valid for general bundle loading.
191
ServiceLoader<ResourceBundleProvider> providers = cacheKey.getProviders();
192
if (providers != null) {
193
if (strategy.getResourceBundleProviderType(baseName, targetLocale) == null) {
194
providers = null;
195
}
196
}
197
198
CacheKey constKey = (CacheKey) cacheKey.clone();
199
try {
200
if (providers != null) {
201
bundle = loadBundleFromProviders(baseName, targetLocale, providers, cacheKey);
202
} else {
203
try {
204
String bundleName = strategy.toBundleName(baseName, targetLocale);
205
Class<?> c = Class.forName(Bundles.class.getModule(), bundleName);
206
if (c != null && ResourceBundle.class.isAssignableFrom(c)) {
207
@SuppressWarnings("unchecked")
208
Class<ResourceBundle> bundleClass = (Class<ResourceBundle>) c;
209
bundle = bundleAccess.newResourceBundle(bundleClass);
210
}
211
if (bundle == null) {
212
var otherBundleName = toOtherBundleName(baseName, bundleName, targetLocale);
213
if (!bundleName.equals(otherBundleName)) {
214
c = Class.forName(Bundles.class.getModule(), otherBundleName);
215
if (c != null && ResourceBundle.class.isAssignableFrom(c)) {
216
@SuppressWarnings("unchecked")
217
Class<ResourceBundle> bundleClass = (Class<ResourceBundle>) c;
218
bundle = bundleAccess.newResourceBundle(bundleClass);
219
}
220
}
221
}
222
} catch (Exception e) {
223
cacheKey.setCause(e);
224
}
225
}
226
} finally {
227
if (constKey.getCause() instanceof InterruptedException) {
228
Thread.currentThread().interrupt();
229
}
230
}
231
232
if (bundle == null) {
233
// Put NONEXISTENT_BUNDLE in the cache as a mark that there's no bundle
234
// instance for the locale.
235
putBundleInCache(cacheKey, NONEXISTENT_BUNDLE);
236
return parent;
237
}
238
239
if (parent != null && bundleAccess.getParent(bundle) == null) {
240
bundleAccess.setParent(bundle, parent);
241
}
242
bundleAccess.setLocale(bundle, targetLocale);
243
bundleAccess.setName(bundle, baseName);
244
bundle = putBundleInCache(cacheKey, bundle);
245
return bundle;
246
}
247
248
private static void cleanupCache() {
249
Object ref;
250
while ((ref = referenceQueue.poll()) != null) {
251
cacheList.remove(((CacheKeyReference)ref).getCacheKey());
252
}
253
}
254
255
/**
256
* Loads ResourceBundle from service providers.
257
*/
258
@SuppressWarnings("removal")
259
private static ResourceBundle loadBundleFromProviders(String baseName,
260
Locale locale,
261
ServiceLoader<ResourceBundleProvider> providers,
262
CacheKey cacheKey)
263
{
264
return AccessController.doPrivileged(
265
new PrivilegedAction<>() {
266
public ResourceBundle run() {
267
for (Iterator<ResourceBundleProvider> itr = providers.iterator(); itr.hasNext(); ) {
268
try {
269
ResourceBundleProvider provider = itr.next();
270
ResourceBundle bundle = provider.getBundle(baseName, locale);
271
if (bundle != null) {
272
return bundle;
273
}
274
} catch (ServiceConfigurationError | SecurityException e) {
275
if (cacheKey != null) {
276
cacheKey.setCause(e);
277
}
278
}
279
}
280
return null;
281
}
282
});
283
284
}
285
286
private static boolean isValidBundle(ResourceBundle bundle) {
287
return bundle != null && bundle != NONEXISTENT_BUNDLE;
288
}
289
290
/**
291
* Throw a MissingResourceException with proper message
292
*/
293
private static void throwMissingResourceException(String baseName,
294
Locale locale,
295
Throwable cause) {
296
// If the cause is a MissingResourceException, avoid creating
297
// a long chain. (6355009)
298
if (cause instanceof MissingResourceException) {
299
cause = null;
300
}
301
MissingResourceException e;
302
e = new MissingResourceException("Can't find bundle for base name "
303
+ baseName + ", locale " + locale,
304
baseName + "_" + locale, // className
305
"");
306
e.initCause(cause);
307
throw e;
308
}
309
310
/**
311
* Finds a bundle in the cache.
312
*
313
* @param cacheKey the key to look up the cache
314
* @return the ResourceBundle found in the cache or null
315
*/
316
private static ResourceBundle findBundleInCache(CacheKey cacheKey) {
317
BundleReference bundleRef = cacheList.get(cacheKey);
318
if (bundleRef == null) {
319
return null;
320
}
321
return bundleRef.get();
322
}
323
324
/**
325
* Put a new bundle in the cache.
326
*
327
* @param cacheKey the key for the resource bundle
328
* @param bundle the resource bundle to be put in the cache
329
* @return the ResourceBundle for the cacheKey; if someone has put
330
* the bundle before this call, the one found in the cache is
331
* returned.
332
*/
333
private static ResourceBundle putBundleInCache(CacheKey cacheKey,
334
ResourceBundle bundle) {
335
CacheKey key = (CacheKey) cacheKey.clone();
336
BundleReference bundleRef = new BundleReference(bundle, referenceQueue, key);
337
338
// Put the bundle in the cache if it's not been in the cache.
339
BundleReference result = cacheList.putIfAbsent(key, bundleRef);
340
341
// If someone else has put the same bundle in the cache before
342
// us, we should use the one in the cache.
343
if (result != null) {
344
ResourceBundle rb = result.get();
345
if (rb != null) {
346
// Clear the back link to the cache key
347
bundle = rb;
348
// Clear the reference in the BundleReference so that
349
// it won't be enqueued.
350
bundleRef.clear();
351
} else {
352
// Replace the invalid (garbage collected)
353
// instance with the valid one.
354
cacheList.put(key, bundleRef);
355
}
356
}
357
return bundle;
358
}
359
360
/**
361
* Generates the other bundle name for languages that have changed,
362
* i.e. "he", "id", and "yi"
363
*
364
* @param baseName ResourceBundle base name
365
* @param bundleName ResourceBundle bundle name
366
* @param locale locale
367
* @return the other bundle name, or the same name for non-legacy ISO languages
368
*/
369
public static String toOtherBundleName(String baseName, String bundleName, Locale locale) {
370
var simpleName= baseName.substring(baseName.lastIndexOf('.') + 1);
371
var suffix = bundleName.substring(bundleName.lastIndexOf(simpleName) + simpleName.length());
372
var otherSuffix = switch(locale.getLanguage()) {
373
case "he" -> suffix.replaceFirst("^_he(_.*)?$", "_iw$1");
374
case "id" -> suffix.replaceFirst("^_id(_.*)?$", "_in$1");
375
case "yi" -> suffix.replaceFirst("^_yi(_.*)?$", "_ji$1");
376
case "iw" -> suffix.replaceFirst("^_iw(_.*)?$", "_he$1");
377
case "in" -> suffix.replaceFirst("^_in(_.*)?$", "_id$1");
378
case "ji" -> suffix.replaceFirst("^_ji(_.*)?$", "_yi$1");
379
default -> suffix;
380
};
381
382
if (suffix.equals(otherSuffix)) {
383
return bundleName;
384
} else {
385
return bundleName.substring(0, bundleName.lastIndexOf(suffix)) + otherSuffix;
386
}
387
}
388
389
/**
390
* The Strategy interface defines methods that are called by Bundles.of during
391
* the resource bundle loading process.
392
*/
393
public interface Strategy {
394
/**
395
* Returns a list of locales to be looked up for bundle loading.
396
*/
397
List<Locale> getCandidateLocales(String baseName, Locale locale);
398
399
/**
400
* Returns the bundle name for the given baseName and locale.
401
*/
402
String toBundleName(String baseName, Locale locale);
403
404
/**
405
* Returns the service provider type for the given baseName
406
* and locale, or null if no service providers should be used.
407
*/
408
Class<? extends ResourceBundleProvider> getResourceBundleProviderType(String baseName,
409
Locale locale);
410
}
411
412
/**
413
* The common interface to get a CacheKey in LoaderReference and
414
* BundleReference.
415
*/
416
private static interface CacheKeyReference {
417
CacheKey getCacheKey();
418
}
419
420
/**
421
* References to bundles are soft references so that they can be garbage
422
* collected when they have no hard references.
423
*/
424
private static class BundleReference extends SoftReference<ResourceBundle>
425
implements CacheKeyReference {
426
private final CacheKey cacheKey;
427
428
BundleReference(ResourceBundle referent, ReferenceQueue<Object> q, CacheKey key) {
429
super(referent, q);
430
cacheKey = key;
431
}
432
433
@Override
434
public CacheKey getCacheKey() {
435
return cacheKey;
436
}
437
}
438
439
/**
440
* Key used for cached resource bundles. The key checks the base
441
* name, the locale, and the class loader to determine if the
442
* resource is a match to the requested one. The loader may be
443
* null, but the base name and the locale must have a non-null
444
* value.
445
*/
446
private static class CacheKey implements Cloneable {
447
// These two are the actual keys for lookup in Map.
448
private String name;
449
private Locale locale;
450
451
// Placeholder for an error report by a Throwable
452
private Throwable cause;
453
454
// Hash code value cache to avoid recalculating the hash code
455
// of this instance.
456
private int hashCodeCache;
457
458
// The service loader to load bundles or null if no service loader
459
// is required.
460
private ServiceLoader<ResourceBundleProvider> providers;
461
462
CacheKey(String baseName, Locale locale) {
463
this.name = baseName;
464
this.locale = locale;
465
calculateHashCode();
466
}
467
468
String getName() {
469
return name;
470
}
471
472
CacheKey setName(String baseName) {
473
if (!this.name.equals(baseName)) {
474
this.name = baseName;
475
calculateHashCode();
476
}
477
return this;
478
}
479
480
Locale getLocale() {
481
return locale;
482
}
483
484
CacheKey setLocale(Locale locale) {
485
if (!this.locale.equals(locale)) {
486
this.locale = locale;
487
calculateHashCode();
488
}
489
return this;
490
}
491
492
ServiceLoader<ResourceBundleProvider> getProviders() {
493
return providers;
494
}
495
496
void setProviders(ServiceLoader<ResourceBundleProvider> providers) {
497
this.providers = providers;
498
}
499
500
@Override
501
public boolean equals(Object other) {
502
if (this == other) {
503
return true;
504
}
505
try {
506
final CacheKey otherEntry = (CacheKey)other;
507
//quick check to see if they are not equal
508
if (hashCodeCache != otherEntry.hashCodeCache) {
509
return false;
510
}
511
return locale.equals(otherEntry.locale)
512
&& name.equals(otherEntry.name);
513
} catch (NullPointerException | ClassCastException e) {
514
}
515
return false;
516
}
517
518
@Override
519
public int hashCode() {
520
return hashCodeCache;
521
}
522
523
private void calculateHashCode() {
524
hashCodeCache = name.hashCode() << 3;
525
hashCodeCache ^= locale.hashCode();
526
}
527
528
@Override
529
public Object clone() {
530
try {
531
CacheKey clone = (CacheKey) super.clone();
532
// Clear the reference to a Throwable
533
clone.cause = null;
534
// Clear the reference to a ServiceLoader
535
clone.providers = null;
536
return clone;
537
} catch (CloneNotSupportedException e) {
538
//this should never happen
539
throw new InternalError(e);
540
}
541
}
542
543
private void setCause(Throwable cause) {
544
if (this.cause == null) {
545
this.cause = cause;
546
} else {
547
// Override the cause if the previous one is
548
// ClassNotFoundException.
549
if (this.cause instanceof ClassNotFoundException) {
550
this.cause = cause;
551
}
552
}
553
}
554
555
private Throwable getCause() {
556
return cause;
557
}
558
559
@Override
560
public String toString() {
561
String l = locale.toString();
562
if (l.isEmpty()) {
563
if (!locale.getVariant().isEmpty()) {
564
l = "__" + locale.getVariant();
565
} else {
566
l = "\"\"";
567
}
568
}
569
return "CacheKey[" + name + ", lc=" + l + ")]";
570
}
571
}
572
}
573
574