Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import java.util.Enumeration;
import java.util.List;
import java.util.MissingResourceException;
import java.util.Objects;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;

Expand All @@ -36,10 +37,39 @@ private abstract static class Loader {
abstract ResourceBundleWrapper load();
}

private static CacheBase<String, ResourceBundleWrapper, Loader> BUNDLE_CACHE =
new SoftCache<String, ResourceBundleWrapper, Loader>() {
private static class BundleCacheKey {
public final String baseName;
public final ClassLoader classLoader;

private BundleCacheKey(String baseName, ClassLoader classLoader) {
this.baseName = baseName;
this.classLoader = classLoader;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
BundleCacheKey that = (BundleCacheKey) o;
return Objects.equals(baseName, that.baseName)
&& Objects.equals(classLoader, that.classLoader);
}

@Override
public int hashCode() {
return Objects.hash(baseName, classLoader);
}
}

private static CacheBase<BundleCacheKey, ResourceBundleWrapper, Loader> BUNDLE_CACHE =
new SoftCache<BundleCacheKey, ResourceBundleWrapper, Loader>() {
@Override
protected ResourceBundleWrapper createInstance(String unusedKey, Loader loader) {
protected ResourceBundleWrapper createInstance(
BundleCacheKey unusedKey, Loader loader) {
return loader.load();
}
};
Expand Down Expand Up @@ -153,7 +183,8 @@ private static ResourceBundleWrapper instantiateBundle(
final ClassLoader root,
final boolean disableFallback) {
final String name = localeID.isEmpty() ? baseName : baseName + '_' + localeID;
String cacheKey = disableFallback ? name : name + '#' + defaultID;
BundleCacheKey cacheKey =
new BundleCacheKey(disableFallback ? name : name + '#' + defaultID, root);
return BUNDLE_CACHE.getInstance(
cacheKey,
new Loader() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html

package com.ibm.icu.dev.test.impl;

import static org.junit.Assert.*;

import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.UResourceBundle;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.MissingResourceException;
import org.junit.Test;

public class ResourceBundleWrapperCachingTest {
@Test
public void testCacheOnDifferentClassloaders() {
// Loading first bundle
try (var firstCL =
new URLClassLoader(
new URL[] {getClass().getResource("/com/ibm/icu/dev/test/locale/first/")},
null)) {
// Making sure that resources are available
assertNotNull(firstCL.getResource("localization.properties"));
assertNotNull(firstCL.getResource("localization_de.properties"));

// Getting the bundle. Since RootType here will be JAVA,
// ResourceBundleWrapper is chosen by UResourceBundle#instantiateBundle as
// implementation
// Passed locale here should not matter
var bundle = UResourceBundle.getBundleInstance("localization", ULocale.GERMAN, firstCL);

// Only 'First' should be present
assertEquals("Dies ist eine erste Zeile", bundle.getString("First"));
// 'Second' is not in the first bundle
assertThrows(MissingResourceException.class, () -> bundle.getString("Second"));
} catch (IOException e) {
throw new RuntimeException(e);
}

// Loading second bundle
try (var secondFL =
new URLClassLoader(
new URL[] {getClass().getResource("/com/ibm/icu/dev/test/locale/second/")},
null)) {
// Making sure that resources are available
assertNotNull(secondFL.getResource("localization.properties"));
assertNotNull(secondFL.getResource("localization_de.properties"));

// Making sure that second bundle has `Second` in the localization file (unlike the
// first one)
assertTrue(
new String(
secondFL.getResourceAsStream("localization.properties")
.readAllBytes())
.contains("Second=This is a second line"));

// Getting the bundle, same as the first one
var bundle =
UResourceBundle.getBundleInstance("localization", ULocale.GERMAN, secondFL);

assertEquals("Dies ist eine erste Zeile", bundle.getString("First"));
// Must contain 'Second' and not throw MissingResourceException if cached properly (no
// clash between first and second)
assertEquals("Dies ist eine zweite Zeile", bundle.getString("Second"));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Copyright (C) 2026 and later: Unicode, Inc. and others.
# License & terms of use: http://www.unicode.org/copyright.html#License

First=This is a first line
# No second one yet, see second/localization.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Copyright (C) 2026 and later: Unicode, Inc. and others.
# License & terms of use: http://www.unicode.org/copyright.html#License

First=Dies ist eine erste Zeile
# No second one yet, see second/localization.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Copyright (C) 2026 and later: Unicode, Inc. and others.
# License & terms of use: http://www.unicode.org/copyright.html#License

First=This is a first line
Second=This is a second line
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Copyright (C) 2026 and later: Unicode, Inc. and others.
# License & terms of use: http://www.unicode.org/copyright.html#License

First=Dies ist eine erste Zeile
Second=Dies ist eine zweite Zeile