diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeResourceSupport.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeResourceSupport.java index 6147690f249c..4c0f8a530285 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeResourceSupport.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeResourceSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -55,4 +55,6 @@ public interface RuntimeResourceSupport { void addResourceBundles(ConfigurationCondition condition, String name); void addResourceBundles(ConfigurationCondition condition, String basename, Collection locales); + + void addLocale(Locale locale); } diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java index 8c3a6f10b98c..1175aacabd02 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java @@ -687,6 +687,13 @@ private static boolean getBundleImpl(JNIEnvironment jni, JNIObjectHandle thread, return true; } + private static boolean loadBundleOf(JNIEnvironment jni, JNIObjectHandle thread, Breakpoint bp, InterceptedState state) { + JNIObjectHandle callerClass = state.getDirectCallerClass(); + JNIObjectHandle locale = getObjectArgument(thread, 1); + traceReflectBreakpoint(jni, nullHandle(), nullHandle(), callerClass, bp.specification.methodName, true, state.getFullStackTraceOrNull(), readLocaleTag(jni, locale)); + return true; + } + private static String readLocaleTag(JNIEnvironment jni, JNIObjectHandle locale) { JNIObjectHandle languageTag = Support.callObjectMethod(jni, locale, agent.handles().javaUtilLocaleToLanguageTag); if (clearException(jni)) { @@ -1559,6 +1566,9 @@ private interface BreakpointHandler { "getBundleImpl", "(Ljava/lang/Module;Ljava/lang/Module;Ljava/lang/String;Ljava/util/Locale;Ljava/util/ResourceBundle$Control;)Ljava/util/ResourceBundle;", BreakpointInterceptor::getBundleImpl), + brk("sun/util/resources/Bundles", "loadBundleOf", + "(Ljava/lang/String;Ljava/util/Locale;Lsun/util/resources/Bundles$Strategy;)Ljava/util/ResourceBundle;", + BreakpointInterceptor::loadBundleOf), // In Java 9+, these are Java methods that call private methods optionalBrk("jdk/internal/misc/Unsafe", "objectFieldOffset", "(Ljava/lang/Class;Ljava/lang/String;)J", BreakpointInterceptor::objectFieldOffsetByName), diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/ResourceConfigurationTest.java b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/ResourceConfigurationTest.java index d4007bbee5e6..fb21ff16ac3e 100644 --- a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/ResourceConfigurationTest.java +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/ResourceConfigurationTest.java @@ -121,6 +121,11 @@ public void addResourceBundles(ConfigurationCondition condition, String basename public void addClassBasedResourceBundle(ConfigurationCondition condition, String basename, String className) { } + + @Override + public void addLocale(Locale locale) { + + } }; ResourceConfigurationParser rcp = new ResourceConfigurationParser(registry, true); diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ResourceConfiguration.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ResourceConfiguration.java index c3aae5d266a8..dda5e6b6919f 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ResourceConfiguration.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ResourceConfiguration.java @@ -89,6 +89,11 @@ public void addResourceBundles(ConfigurationCondition condition, String basename public void addClassBasedResourceBundle(ConfigurationCondition condition, String basename, String className) { configuration.addClassResourceBundle(condition, basename, className); } + + @Override + public void addLocale(Locale locale) { + configuration.addLocale(locale.toLanguageTag()); + } } public static final class BundleConfiguration { @@ -113,6 +118,8 @@ private BundleConfiguration(BundleConfiguration other) { private final ConcurrentMap, Pattern> ignoredResources = new ConcurrentHashMap<>(); private final ConcurrentMap, BundleConfiguration> bundles = new ConcurrentHashMap<>(); + private final Set locales = ConcurrentHashMap.newKeySet(); + public ResourceConfiguration() { } @@ -122,6 +129,7 @@ public ResourceConfiguration(ResourceConfiguration other) { for (Map.Entry, BundleConfiguration> entry : other.bundles.entrySet()) { bundles.put(entry.getKey(), new BundleConfiguration(entry.getValue())); } + locales.addAll(other.locales); } @Override @@ -134,6 +142,7 @@ public void subtract(ResourceConfiguration other) { addedResources.keySet().removeAll(other.addedResources.keySet()); ignoredResources.keySet().removeAll(other.ignoredResources.keySet()); bundles.keySet().removeAll(other.bundles.keySet()); + locales.removeAll(other.locales); } @Override @@ -141,6 +150,7 @@ protected void merge(ResourceConfiguration other) { addedResources.putAll(other.addedResources); ignoredResources.putAll(other.ignoredResources); bundles.putAll(other.bundles); + locales.addAll(other.locales); } @Override @@ -148,6 +158,7 @@ protected void intersect(ResourceConfiguration other) { addedResources.keySet().retainAll(other.addedResources.keySet()); ignoredResources.keySet().retainAll(other.ignoredResources.keySet()); bundles.keySet().retainAll(other.bundles.keySet()); + locales.retainAll(other.locales); } @Override @@ -167,6 +178,7 @@ public void mergeConditional(ConfigurationCondition condition, ResourceConfigura for (Map.Entry, BundleConfiguration> entry : other.bundles.entrySet()) { bundles.put(new ConditionalElement<>(condition, entry.getKey().getElement()), new BundleConfiguration(entry.getValue())); } + locales.addAll(other.locales); } public void addResourcePattern(ConfigurationCondition condition, String pattern) { @@ -197,6 +209,10 @@ public void addBundle(ConfigurationCondition condition, String baseName, String config.locales.add(queriedLocale); } + public void addLocale(String locale) { + locales.add(locale); + } + private BundleConfiguration getOrCreateBundleConfig(ConfigurationCondition condition, String baseName) { ConditionalElement key = new ConditionalElement<>(condition, baseName); return bundles.computeIfAbsent(key, cond -> new BundleConfiguration(condition, baseName)); @@ -238,6 +254,9 @@ public void printJson(JsonWriter writer) throws IOException { writer.append('}').append(',').newline(); writer.quote("bundles").append(':'); JsonPrinter.printCollection(writer, bundles.keySet(), ConditionalElement.comparator(), (p, w) -> printResourceBundle(bundles.get(p), w)); + writer.append(',').newline(); + writer.quote("locales").append(':'); + JsonPrinter.printCollection(writer, locales, Comparator.naturalOrder(), (l, w) -> w.quote(l)); writer.unindent().newline().append('}'); } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java index 58159510767d..c5c15cff3aa3 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java @@ -29,6 +29,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.regex.Pattern; import org.graalvm.collections.EconomicMap; @@ -47,6 +48,7 @@ import jdk.vm.ci.meta.MetaUtil; class ReflectionProcessor extends AbstractProcessor { + private static final List DEFAULT_LOCALE_TAGS = List.of(Locale.ROOT.toLanguageTag(), Locale.ENGLISH.toLanguageTag(), Locale.US.toLanguageTag()); private final AccessAdvisor advisor; ReflectionProcessor(AccessAdvisor advisor) { @@ -266,6 +268,14 @@ public void processEntry(EconomicMap entry, ConfigurationSet configur } break; } + case "loadBundleOf": { + expectSize(args, 1); + String localeTag = (String) args.get(0); + if (!DEFAULT_LOCALE_TAGS.contains(localeTag)) { + resourceConfiguration.addLocale(localeTag); + } + break; + } case "allocateInstance": { configuration.getOrCreateType(condition, clazz).setUnsafeAllocated(); break; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourceConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourceConfigurationParser.java index b3e820e38666..62f19b70a57b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourceConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourceConfigurationParser.java @@ -56,12 +56,15 @@ public void parseAndRegister(Object json, URI origin) { private void parseTopLevelObject(EconomicMap obj) { Object resourcesObject = null; Object bundlesObject = null; + Object localesObject = null; MapCursor cursor = obj.getEntries(); while (cursor.advance()) { if ("resources".equals(cursor.getKey())) { resourcesObject = cursor.getValue(); } else if ("bundles".equals(cursor.getKey())) { bundlesObject = cursor.getValue(); + } else if ("locales".equals(cursor.getKey())) { + localesObject = cursor.getValue(); } } if (resourcesObject != null) { @@ -95,6 +98,12 @@ private void parseTopLevelObject(EconomicMap obj) { parseBundle(bundle); } } + if (localesObject != null) { + List locales = asList(localesObject, "Attribute 'locales' must be a list of locales"); + for (Object locale : locales) { + registry.addLocale(parseLocale(locale)); + } + } } private void parseBundle(Object bundle) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/substitutions/Target_sun_util_resources_Bundles.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/substitutions/Target_sun_util_resources_Bundles.java index cc2023066b88..a1d55f46b3e4 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/substitutions/Target_sun_util_resources_Bundles.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/substitutions/Target_sun_util_resources_Bundles.java @@ -39,7 +39,6 @@ import com.oracle.svm.core.jdk.localization.LocalizationSupport; import com.oracle.svm.core.jdk.localization.substitutions.modes.JvmLocaleMode; import com.oracle.svm.core.jdk.localization.substitutions.modes.OptimizedLocaleMode; -import com.oracle.svm.core.jdk.resources.MissingResourceRegistrationUtils; import sun.util.resources.Bundles.Strategy; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java index d3411c5847d2..02372ed5fd2d 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java @@ -235,6 +235,11 @@ public void addResourceBundles(ConfigurationCondition condition, String basename registerConditionalConfiguration(condition, () -> ImageSingletons.lookup(LocalizationFeature.class).prepareBundle(basename, locales)); } + @Override + public void addLocale(Locale locale) { + ImageSingletons.lookup(LocalizationFeature.class).addLocale(locale); + } + /* * It is possible that one resource can be registered under different conditions * (typeReachable). In some cases, few conditions will be satisfied, and we will try to diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/localization/LocalizationFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/localization/LocalizationFeature.java index 2ea99662a445..cc203eae8c35 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/localization/LocalizationFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/localization/LocalizationFeature.java @@ -539,6 +539,10 @@ protected void addResourceBundles() { } } + public void addLocale(Locale locale) { + allLocales.add(locale); + } + @Platforms(Platform.HOSTED_ONLY.class) private void processRequestedBundle(String input) { int splitIndex = input.indexOf('_');