diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py index 13d43d010fd7..4d533cb67d28 100644 --- a/substratevm/mx.substratevm/suite.py +++ b/substratevm/mx.substratevm/suite.py @@ -291,6 +291,7 @@ "sun.security.util", "sun.text.spi", "sun.util", + "sun.util.locale", "sun.util.calendar", "sun.util.locale.provider", "sun.util.resources", diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/LibCHelper.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/LibCHelper.java index c91ae4d9ca5f..1bf7562194b1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/LibCHelper.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/LibCHelper.java @@ -30,6 +30,8 @@ import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.nativeimage.c.type.CCharPointerPointer; +import com.oracle.svm.core.util.BasedOnJDKFile; + @CLibrary(value = "libchelper", requireStatic = true, dependsOn = "java") public class LibCHelper { @CFunction(transition = Transition.NO_TRANSITION) @@ -39,4 +41,18 @@ public class LibCHelper { // Checkstyle: stop public static native CCharPointer SVM_FindJavaTZmd(CCharPointer tzMappings, int length); // Checkstyle: start + + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+13/src/java.base/unix/native/libjava/locale_str.h") + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+13/src/java.base/windows/native/libjava/locale_str.h") + public static class Locale { + @CFunction(transition = Transition.TO_NATIVE) + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+13/src/java.base/unix/native/libjava/java_props_md.c#L93-L540") + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+13/src/java.base/windows/native/libjava/java_props_md.c#L257-L713") + public static native CCharPointerPointer parseDisplayLocale(); + + @CFunction(transition = Transition.TO_NATIVE) + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+13/src/java.base/unix/native/libjava/java_props_md.c#L93-L540") + @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+13/src/java.base/windows/native/libjava/java_props_md.c#L257-L713") + public static native CCharPointerPointer parseFormatLocale(); + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SystemPropertiesSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SystemPropertiesSupport.java index aa761c6576e7..1bff154f0bf3 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SystemPropertiesSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SystemPropertiesSupport.java @@ -24,6 +24,9 @@ */ package com.oracle.svm.core.jdk; +import static java.util.Locale.Category.DISPLAY; +import static java.util.Locale.Category.FORMAT; + import java.util.Collections; import java.util.HashMap; import java.util.Locale; @@ -35,13 +38,18 @@ import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.c.type.CCharPointerPointer; +import org.graalvm.nativeimage.c.type.CTypeConversion; import org.graalvm.nativeimage.impl.RuntimeSystemPropertiesSupport; +import com.oracle.svm.core.LibCHelper; import com.oracle.svm.core.VM; import com.oracle.svm.core.config.ConfigurationValues; +import com.oracle.svm.core.headers.LibCSupport; import com.oracle.svm.core.util.VMError; import jdk.graal.compiler.api.replacements.Fold; +import jdk.graal.compiler.debug.GraalError; /** * This class maintains the system properties at run time. @@ -76,6 +84,13 @@ public abstract class SystemPropertiesSupport implements RuntimeSystemProperties "java.vm.specification.version" }; + /* The list of field positions in locale_props_t (see locale_str.h). */ + private static final int LANGUAGE_POSITION = 0; + private static final int SCRIPT_POSITION = LANGUAGE_POSITION + 1; + private static final int COUNTRY_POSITION = SCRIPT_POSITION + 1; + private static final int VARIANT_POSITION = COUNTRY_POSITION + 1; + private static final int EXTENSION_POSITION = VARIANT_POSITION + 1; + /** System properties that are lazily computed at run time on first access. */ private final Map> lazyRuntimeValues; @@ -139,6 +154,21 @@ protected SystemPropertiesSupport() { lazyRuntimeValues.put("java.io.tmpdir", this::javaIoTmpDir); lazyRuntimeValues.put("java.library.path", this::javaLibraryPath); lazyRuntimeValues.put("os.version", this::osVersionValue); + lazyRuntimeValues.put(UserSystemProperty.USER_LANGUAGE, () -> postProcessLocale(UserSystemProperty.USER_LANGUAGE, parseLocale(DISPLAY).language(), null)); + lazyRuntimeValues.put(UserSystemProperty.USER_LANGUAGE_DISPLAY, () -> postProcessLocale(UserSystemProperty.USER_LANGUAGE, parseLocale(DISPLAY).language(), DISPLAY)); + lazyRuntimeValues.put(UserSystemProperty.USER_LANGUAGE_FORMAT, () -> postProcessLocale(UserSystemProperty.USER_LANGUAGE, parseLocale(FORMAT).language(), FORMAT)); + lazyRuntimeValues.put(UserSystemProperty.USER_SCRIPT, () -> postProcessLocale(UserSystemProperty.USER_SCRIPT, parseLocale(DISPLAY).script(), null)); + lazyRuntimeValues.put(UserSystemProperty.USER_SCRIPT_DISPLAY, () -> postProcessLocale(UserSystemProperty.USER_SCRIPT, parseLocale(DISPLAY).script(), DISPLAY)); + lazyRuntimeValues.put(UserSystemProperty.USER_SCRIPT_FORMAT, () -> postProcessLocale(UserSystemProperty.USER_SCRIPT, parseLocale(FORMAT).script(), FORMAT)); + lazyRuntimeValues.put(UserSystemProperty.USER_COUNTRY, () -> postProcessLocale(UserSystemProperty.USER_COUNTRY, parseLocale(DISPLAY).country(), null)); + lazyRuntimeValues.put(UserSystemProperty.USER_COUNTRY_DISPLAY, () -> postProcessLocale(UserSystemProperty.USER_COUNTRY, parseLocale(DISPLAY).country(), DISPLAY)); + lazyRuntimeValues.put(UserSystemProperty.USER_COUNTRY_FORMAT, () -> postProcessLocale(UserSystemProperty.USER_COUNTRY, parseLocale(FORMAT).country(), FORMAT)); + lazyRuntimeValues.put(UserSystemProperty.USER_VARIANT, () -> postProcessLocale(UserSystemProperty.USER_VARIANT, parseLocale(DISPLAY).variant(), null)); + lazyRuntimeValues.put(UserSystemProperty.USER_VARIANT_DISPLAY, () -> postProcessLocale(UserSystemProperty.USER_VARIANT, parseLocale(DISPLAY).variant(), DISPLAY)); + lazyRuntimeValues.put(UserSystemProperty.USER_VARIANT_FORMAT, () -> postProcessLocale(UserSystemProperty.USER_VARIANT, parseLocale(FORMAT).variant(), FORMAT)); + lazyRuntimeValues.put(UserSystemProperty.USER_EXTENSIONS, () -> postProcessLocale(UserSystemProperty.USER_EXTENSIONS, parseLocale(DISPLAY).extensions(), null)); + lazyRuntimeValues.put(UserSystemProperty.USER_EXTENSIONS_DISPLAY, () -> postProcessLocale(UserSystemProperty.USER_EXTENSIONS, parseLocale(DISPLAY).extensions(), DISPLAY)); + lazyRuntimeValues.put(UserSystemProperty.USER_EXTENSIONS_FORMAT, () -> postProcessLocale(UserSystemProperty.USER_EXTENSIONS, parseLocale(FORMAT).extensions(), FORMAT)); String targetName = System.getProperty("svm.targetName"); if (targetName != null) { @@ -183,6 +213,12 @@ protected String getProperty(String key) { return properties.getProperty(key); } + protected String getSavedProperty(String key, String defaultValue) { + initializeLazyValue(key); + String value = savedProperties.get(key); + return value != null ? value : defaultValue; + } + public void setProperties(Properties props) { // Flush lazy values into savedProperties ensureFullyInitialized(); @@ -235,10 +271,14 @@ private void initializeLazyValue(String key) { * manual updates of the same property key. */ String value = lazyRuntimeValues.get(key).get(); - if (properties.putIfAbsent(key, value) == null) { - synchronized (savedProperties) { - savedProperties.put(key, value); - } + setRawProperty(key, value); + } + } + + private void setRawProperty(String key, String value) { + if (value != null && properties.putIfAbsent(key, value) == null) { + synchronized (savedProperties) { + savedProperties.put(key, value); } } } @@ -318,4 +358,90 @@ protected String osNameValue() { } protected abstract String osVersionValue(); + + public record LocaleEncoding(String language, String script, String country, String variant, String extensions) { + private LocaleEncoding(CCharPointerPointer properties) { + this(fromCStringArray(properties, LANGUAGE_POSITION), + fromCStringArray(properties, SCRIPT_POSITION), + fromCStringArray(properties, COUNTRY_POSITION), + fromCStringArray(properties, VARIANT_POSITION), + fromCStringArray(properties, EXTENSION_POSITION)); + } + + private static String fromCStringArray(CCharPointerPointer cString, int index) { + if (cString.isNull()) { + return null; + } + return CTypeConversion.toJavaString(cString.read(index)); + } + } + + private LocaleEncoding displayLocale; + + private LocaleEncoding formatLocale; + + protected LocaleEncoding parseLocale(Locale.Category category) { + if (!ImageSingletons.contains(LibCSupport.class)) { + /* If native calls are not supported, just return fixed values. */ + return new LocaleEncoding("en", "", "US", "", ""); + } + switch (category) { + case DISPLAY -> { + if (displayLocale == null) { + displayLocale = new LocaleEncoding(LibCHelper.Locale.parseDisplayLocale()); + } + return displayLocale; + } + case FORMAT -> { + if (formatLocale == null) { + formatLocale = new LocaleEncoding(LibCHelper.Locale.parseFormatLocale()); + } + return formatLocale; + } + default -> throw new GraalError("Unknown locale category: " + category + "."); + } + } + + private String postProcessLocale(String base, String value, Locale.Category category) { + if (category == null) { + /* user.xxx property */ + String baseValue = null; + if (value != null) { + setRawProperty(base, value); + baseValue = value; + } + return baseValue; + } + switch (category) { + case DISPLAY, FORMAT -> { + /* user.xxx.(display|format) property */ + String baseValue = getProperty(base); + if (baseValue == null && value != null) { + setRawProperty(base + '.' + category.name().toLowerCase(Locale.ROOT), value); + return value; + } + return null; + } + default -> throw new GraalError("Unknown locale category: " + category + "."); + } + } + + public static class UserSystemProperty { + public static final String USER_LANGUAGE = "user.language"; + public static final String USER_LANGUAGE_DISPLAY = USER_LANGUAGE + ".display"; + public static final String USER_LANGUAGE_FORMAT = USER_LANGUAGE + ".format"; + public static final String USER_SCRIPT = "user.script"; + public static final String USER_SCRIPT_DISPLAY = USER_SCRIPT + ".display"; + public static final String USER_SCRIPT_FORMAT = USER_SCRIPT + ".format"; + public static final String USER_COUNTRY = "user.country"; + public static final String USER_COUNTRY_DISPLAY = USER_COUNTRY + ".display"; + public static final String USER_COUNTRY_FORMAT = USER_COUNTRY + ".format"; + public static final String USER_VARIANT = "user.variant"; + public static final String USER_VARIANT_DISPLAY = USER_VARIANT + ".display"; + public static final String USER_VARIANT_FORMAT = USER_VARIANT + ".format"; + public static final String USER_EXTENSIONS = "user.extensions"; + public static final String USER_EXTENSIONS_DISPLAY = USER_EXTENSIONS + ".display"; + public static final String USER_EXTENSIONS_FORMAT = USER_EXTENSIONS + ".format"; + public static final String USER_REGION = "user.region"; + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_util_StaticProperty.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_util_StaticProperty.java index 1765ad86098b..16747c29576d 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_util_StaticProperty.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_util_StaticProperty.java @@ -26,6 +26,9 @@ import java.util.function.BooleanSupplier; +import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; import com.oracle.svm.core.annotate.TargetElement; @@ -44,6 +47,93 @@ @SuppressWarnings("unused") final class Target_jdk_internal_util_StaticProperty { + // Checkstyle: stop + @Alias// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)// + public static String USER_LANGUAGE; + + @Alias// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)// + public static String USER_LANGUAGE_DISPLAY; + + @Alias// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)// + public static String USER_LANGUAGE_FORMAT; + + @Alias// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)// + public static String USER_SCRIPT; + + @Alias// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)// + public static String USER_SCRIPT_DISPLAY; + + @Alias// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)// + public static String USER_SCRIPT_FORMAT; + + @Alias// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)// + public static String USER_COUNTRY; + + @Alias// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)// + public static String USER_COUNTRY_DISPLAY; + + @Alias// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)// + public static String USER_COUNTRY_FORMAT; + + @Alias// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)// + public static String USER_VARIANT; + + @Alias// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)// + public static String USER_VARIANT_DISPLAY; + + @Alias// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)// + public static String USER_VARIANT_FORMAT; + + @Alias// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)// + public static String USER_EXTENSIONS; + + @Alias// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)// + public static String USER_EXTENSIONS_DISPLAY; + + @Alias// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)// + public static String USER_EXTENSIONS_FORMAT; + + @Alias// + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)// + public static String USER_REGION; + // Checkstyle: resume + + static { + if (!SubstrateUtil.HOSTED) { + USER_LANGUAGE = SystemPropertiesSupport.singleton().getSavedProperty(SystemPropertiesSupport.UserSystemProperty.USER_LANGUAGE, "en"); + USER_LANGUAGE_DISPLAY = SystemPropertiesSupport.singleton().getSavedProperty(SystemPropertiesSupport.UserSystemProperty.USER_LANGUAGE_DISPLAY, USER_LANGUAGE); + USER_LANGUAGE_FORMAT = SystemPropertiesSupport.singleton().getSavedProperty(SystemPropertiesSupport.UserSystemProperty.USER_SCRIPT_FORMAT, USER_LANGUAGE); + USER_SCRIPT = SystemPropertiesSupport.singleton().getSavedProperty(SystemPropertiesSupport.UserSystemProperty.USER_SCRIPT, ""); + USER_SCRIPT_DISPLAY = SystemPropertiesSupport.singleton().getSavedProperty(SystemPropertiesSupport.UserSystemProperty.USER_SCRIPT_DISPLAY, USER_SCRIPT); + USER_SCRIPT_FORMAT = SystemPropertiesSupport.singleton().getSavedProperty(SystemPropertiesSupport.UserSystemProperty.USER_SCRIPT_FORMAT, USER_SCRIPT); + USER_COUNTRY = SystemPropertiesSupport.singleton().getSavedProperty(SystemPropertiesSupport.UserSystemProperty.USER_COUNTRY, ""); + USER_COUNTRY_DISPLAY = SystemPropertiesSupport.singleton().getSavedProperty(SystemPropertiesSupport.UserSystemProperty.USER_COUNTRY_DISPLAY, USER_COUNTRY); + USER_COUNTRY_FORMAT = SystemPropertiesSupport.singleton().getSavedProperty(SystemPropertiesSupport.UserSystemProperty.USER_COUNTRY_FORMAT, USER_COUNTRY); + USER_VARIANT = SystemPropertiesSupport.singleton().getSavedProperty(SystemPropertiesSupport.UserSystemProperty.USER_VARIANT, ""); + USER_VARIANT_DISPLAY = SystemPropertiesSupport.singleton().getSavedProperty(SystemPropertiesSupport.UserSystemProperty.USER_VARIANT_DISPLAY, USER_VARIANT); + USER_VARIANT_FORMAT = SystemPropertiesSupport.singleton().getSavedProperty(SystemPropertiesSupport.UserSystemProperty.USER_VARIANT_FORMAT, USER_VARIANT); + USER_EXTENSIONS = SystemPropertiesSupport.singleton().getSavedProperty(SystemPropertiesSupport.UserSystemProperty.USER_EXTENSIONS, ""); + USER_EXTENSIONS_DISPLAY = SystemPropertiesSupport.singleton().getSavedProperty(SystemPropertiesSupport.UserSystemProperty.USER_EXTENSIONS_DISPLAY, USER_EXTENSIONS); + USER_EXTENSIONS_FORMAT = SystemPropertiesSupport.singleton().getSavedProperty(SystemPropertiesSupport.UserSystemProperty.USER_EXTENSIONS_FORMAT, USER_EXTENSIONS); + USER_REGION = SystemPropertiesSupport.singleton().getSavedProperty(SystemPropertiesSupport.UserSystemProperty.USER_REGION, ""); + } + } + @Substitute private static String javaHome() { /* Native images do not have a Java home directory. */ diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/BundleContentSubstitutedLocalizationSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/BundleContentSubstitutedLocalizationSupport.java index 3b2a82f90db3..9dacbb053103 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/BundleContentSubstitutedLocalizationSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/BundleContentSubstitutedLocalizationSupport.java @@ -69,8 +69,8 @@ public class BundleContentSubstitutedLocalizationSupport extends LocalizationSup private final Set existingBundles = ConcurrentHashMap.newKeySet(); - public BundleContentSubstitutedLocalizationSupport(Locale defaultLocale, Set locales, Charset defaultCharset, List requestedPatterns, ForkJoinPool pool) { - super(defaultLocale, locales, defaultCharset); + public BundleContentSubstitutedLocalizationSupport(Set locales, Charset defaultCharset, List requestedPatterns, ForkJoinPool pool) { + super(locales, defaultCharset); this.pool = pool; this.compressBundlesPatterns = parseCompressBundlePatterns(requestedPatterns); } @@ -112,8 +112,7 @@ private void storeBundleContentOf(ResourceBundle bundle) { @Platforms(Platform.HOSTED_ONLY.class) private StoredBundle processBundle(ResourceBundle bundle) { - boolean isInDefaultLocale = bundle.getLocale().equals(defaultLocale); - if (!isInDefaultLocale && shouldCompressBundle(bundle) && GzipBundleCompression.canCompress(bundle)) { + if (shouldCompressBundle(bundle) && GzipBundleCompression.canCompress(bundle)) { return GzipBundleCompression.compress(bundle); } Map content = BundleSerializationUtils.extractContent(bundle); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/LocalizationSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/LocalizationSupport.java index cd648985fc24..5f400ef73cd4 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/LocalizationSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/LocalizationSupport.java @@ -77,8 +77,6 @@ public class LocalizationSupport { public final Map charsets = new HashMap<>(); - public final Locale defaultLocale; - public final Locale[] allLocales; public final Set supportedLanguageTags; @@ -91,8 +89,7 @@ public class LocalizationSupport { private final EconomicMap registeredBundles = ImageHeapMap.create(); - public LocalizationSupport(Locale defaultLocale, Set locales, Charset defaultCharset) { - this.defaultLocale = defaultLocale; + public LocalizationSupport(Set locales, Charset defaultCharset) { this.allLocales = locales.toArray(new Locale[0]); this.defaultCharset = defaultCharset; this.supportedLanguageTags = locales.stream().map(Locale::toString).collect(Collectors.toSet()); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/OptimizedLocalizationSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/OptimizedLocalizationSupport.java index fb902b47e885..9d78333a9b85 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/OptimizedLocalizationSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/OptimizedLocalizationSupport.java @@ -58,8 +58,8 @@ private record BundleCacheKey(String bundleName, Locale locale) { final Map resourceBundles = new HashMap<>(); - public OptimizedLocalizationSupport(Locale defaultLocale, Set locales, Charset defaultCharset) { - super(defaultLocale, locales, defaultCharset); + public OptimizedLocalizationSupport(Set locales, Charset defaultCharset) { + super(locales, defaultCharset); } @Override diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/substitutions/DefaultLocaleComputer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/substitutions/DefaultLocaleComputer.java deleted file mode 100644 index db412334bacb..000000000000 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/substitutions/DefaultLocaleComputer.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package com.oracle.svm.core.jdk.localization.substitutions; - -import org.graalvm.nativeimage.ImageSingletons; -import org.graalvm.nativeimage.hosted.FieldValueTransformer; - -import com.oracle.svm.core.jdk.localization.LocalizationSupport; - -final class DefaultLocaleComputer implements FieldValueTransformer { - @Override - public Object transform(Object receiver, Object originalValue) { - return ImageSingletons.lookup(LocalizationSupport.class).defaultLocale; - } -} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/substitutions/Target_java_util_Locale.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/substitutions/Target_java_util_Locale.java index cb1517ad9ac9..9b4473c24702 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/substitutions/Target_java_util_Locale.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/substitutions/Target_java_util_Locale.java @@ -24,31 +24,41 @@ */ package com.oracle.svm.core.jdk.localization.substitutions; +import java.util.Locale; + import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.InjectAccessors; import com.oracle.svm.core.annotate.RecomputeFieldValue; -import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; -import com.oracle.svm.core.util.VMError; - -import java.util.Locale; @TargetClass(java.util.Locale.class) final class Target_java_util_Locale { - - @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = DefaultLocaleComputer.class) // + @Alias @InjectAccessors(DefaultLocaleAccessors.class) // private static Locale defaultLocale; - @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = DefaultLocaleComputer.class) // + + @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) // private static Locale defaultDisplayLocale; - @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = DefaultLocaleComputer.class) // + @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) // private static Locale defaultFormatLocale; - @Substitute - private static Locale initDefault() { - throw VMError.shouldNotReachHere("The default Locale must be initialized during image generation"); - } + @Alias + private static native Locale initDefault(); + + @SuppressWarnings("unused") + private static class DefaultLocaleAccessors { + static Locale get() { + if (Util_java_util_Locale.injectedDefaultLocale == null) { + Util_java_util_Locale.injectedDefaultLocale = Target_java_util_Locale.initDefault(); + } + return Util_java_util_Locale.injectedDefaultLocale; + } - @Substitute - private static Locale initDefault(Locale.Category category) { - throw VMError.shouldNotReachHere("The default Locale must be initialized during image generation: " + category); + static void set(Locale defaultLocale) { + Util_java_util_Locale.injectedDefaultLocale = defaultLocale; + } } } + +final class Util_java_util_Locale { + static Locale injectedDefaultLocale; +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/substitutions/Target_sun_util_locale_InternalLocaleBuilder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/substitutions/Target_sun_util_locale_InternalLocaleBuilder.java new file mode 100644 index 000000000000..f3bf41911e89 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/localization/substitutions/Target_sun_util_locale_InternalLocaleBuilder.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.jdk.localization.substitutions; + +import java.util.ArrayList; +import java.util.List; + +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +import sun.util.locale.BaseLocale; +import sun.util.locale.InternalLocaleBuilder; +import sun.util.locale.LanguageTag; +import sun.util.locale.LocaleSyntaxException; +import sun.util.locale.LocaleUtils; +import sun.util.locale.StringTokenIterator; + +@TargetClass(InternalLocaleBuilder.class) +public final class Target_sun_util_locale_InternalLocaleBuilder { + + @Alias + public native Target_sun_util_locale_InternalLocaleBuilder clearExtensions(); + + @Alias + private native Target_sun_util_locale_InternalLocaleBuilder setExtensions(List bcpExtensions, String privateuse); + + /** + * This method replicates the JDK implementation of + * {@link InternalLocaleBuilder#setExtensions(String)}, with a minor adjustment. Instead of + * using {@link String#replaceAll(String, String)}, which would pull a lot of JDK code, we use a + * custom implementation. + */ + @Substitute + public Target_sun_util_locale_InternalLocaleBuilder setExtensions(String subtagsParam) throws LocaleSyntaxException { + if (LocaleUtils.isEmpty(subtagsParam)) { + clearExtensions(); + return this; + } + String subtags = InternalLocaleBuilderUtil.replaceAll(subtagsParam, BaseLocale.SEP.charAt(0), LanguageTag.SEP.charAt(0)); + StringTokenIterator itr = new StringTokenIterator(subtags, LanguageTag.SEP); + + List extensions = null; + String privateuse = null; + + int parsed = 0; + int start; + + // Make a list of extension subtags + while (!itr.isDone()) { + String s = itr.current(); + if (LanguageTag.isExtensionSingleton(s)) { + start = itr.currentStart(); + String singleton = s; + StringBuilder sb = new StringBuilder(singleton); + + itr.next(); + while (!itr.isDone()) { + s = itr.current(); + if (LanguageTag.isExtensionSubtag(s)) { + sb.append(LanguageTag.SEP).append(s); + parsed = itr.currentEnd(); + } else { + break; + } + itr.next(); + } + + if (parsed < start) { + throw new LocaleSyntaxException("Incomplete extension '" + singleton + "'", + start); + } + + if (extensions == null) { + extensions = new ArrayList<>(4); + } + extensions.add(sb.toString()); + } else { + break; + } + } + if (!itr.isDone()) { + String s = itr.current(); + if (LanguageTag.isPrivateusePrefix(s)) { + start = itr.currentStart(); + StringBuilder sb = new StringBuilder(s); + + itr.next(); + while (!itr.isDone()) { + s = itr.current(); + if (!LanguageTag.isPrivateuseSubtag(s)) { + break; + } + sb.append(LanguageTag.SEP).append(s); + parsed = itr.currentEnd(); + + itr.next(); + } + if (parsed <= start) { + throw new LocaleSyntaxException("Incomplete privateuse:" + subtags.substring(start), + start); + } else { + privateuse = sb.toString(); + } + } + } + + if (!itr.isDone()) { + throw new LocaleSyntaxException("Ill-formed extension subtags:" + subtags.substring(itr.currentStart()), + itr.currentStart()); + } + + return setExtensions(extensions, privateuse); + } +} + +class InternalLocaleBuilderUtil { + + public static String replaceAll(String input, char toReplace, char replaceWith) { + if (input == null) { + return null; + } + + StringBuilder result = new StringBuilder(); + for (int i = 0; i < input.length(); i++) { + char currentChar = input.charAt(i); + if (currentChar == toReplace) { + result.append(replaceWith); + } else { + result.append(currentChar); + } + } + + return result.toString(); + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JDKInitializationFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JDKInitializationFeature.java index e591d5e576bf..36d623b93fa4 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JDKInitializationFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/JDKInitializationFeature.java @@ -187,6 +187,7 @@ public void afterRegistration(AfterRegistrationAccess access) { rci.initializeAtRunTime("java.lang.StrictMath$RandomNumberGeneratorHolder", "Contains random seeds"); rci.initializeAtRunTime("jdk.internal.misc.InnocuousThread", "Contains a thread group INNOCUOUSTHREADGROUP."); + rci.initializeAtRunTime("jdk.internal.util.StaticProperty", "Contains run time specific values."); rci.initializeAtRunTime("sun.nio.ch.Poller", "Contains an InnocuousThread."); rci.initializeAtRunTime("jdk.internal.jimage", "Pulls in direct byte buffers"); 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 8b6292f244bf..7197aa323e1a 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 @@ -84,6 +84,7 @@ import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl; import com.oracle.svm.hosted.ImageClassLoader; +import com.oracle.svm.util.LogUtils; import jdk.graal.compiler.nodes.ValueNode; import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderContext; @@ -150,11 +151,6 @@ public class LocalizationFeature implements InternalFeature { private final ForkJoinPool compressionPool = Options.LocalizationCompressInParallel.getValue() ? ForkJoinPool.commonPool() : null; - /** - * The Locale that the native image is built for. - */ - protected Locale defaultLocale = Locale.getDefault(); - private Charset defaultCharset; protected Set allLocales; @@ -264,8 +260,9 @@ public void afterRegistration(AfterRegistrationAccess access) { findClassByName = access::findClassByName; allLocales = processLocalesOption(); if (Options.DefaultLocale.hasBeenSet()) { - defaultLocale = LocalizationSupport.parseLocaleFromTag(Options.DefaultLocale.getValue()); - UserError.guarantee(defaultLocale != null, "Invalid default locale %s", Options.DefaultLocale.getValue()); + LogUtils.warning("Option %s is deprecated and has no effect. The program's default locale is determined at run-time. " + + "Use %s and %s to manage the locales included in the image.\n", + Options.DefaultLocale.getName(), Options.IncludeLocales.getName(), Options.IncludeAllLocales.getName()); } String defaultCharsetOptionValue = Options.DefaultCharset.getValue(); try { @@ -274,7 +271,6 @@ public void afterRegistration(AfterRegistrationAccess access) { } catch (IllegalCharsetNameException | UnsupportedCharsetException ex) { throw UserError.abort(ex, "Invalid default charset %s", defaultCharsetOptionValue); } - allLocales.add(defaultLocale); support = selectLocalizationSupport(); ImageSingletons.add(LocalizationSupport.class, support); @@ -341,12 +337,12 @@ private void eagerlyInitializeBundles(DuringAnalysisAccess access, ResourceBundl @Platforms(Platform.HOSTED_ONLY.class) private LocalizationSupport selectLocalizationSupport() { if (optimizedMode) { - return new OptimizedLocalizationSupport(defaultLocale, allLocales, defaultCharset); + return new OptimizedLocalizationSupport(allLocales, defaultCharset); } else if (substituteLoadLookup) { List requestedPatterns = Options.LocalizationCompressBundles.getValue().values(); - return new BundleContentSubstitutedLocalizationSupport(defaultLocale, allLocales, defaultCharset, requestedPatterns, compressionPool); + return new BundleContentSubstitutedLocalizationSupport(allLocales, defaultCharset, requestedPatterns, compressionPool); } - return new LocalizationSupport(defaultLocale, allLocales, defaultCharset); + return new LocalizationSupport(allLocales, defaultCharset); } @Override @@ -383,7 +379,7 @@ private static Set processLocalesOption() { Set locales = new HashSet<>(); if (Options.IncludeAllLocales.getValue()) { Collections.addAll(locales, Locale.getAvailableLocales()); - /*- Fallthrough to also allow adding custom locales */ + /* Fallthrough to also allow adding custom locales */ } else { Collections.addAll(locales, MINIMAL_LOCALES); } @@ -462,7 +458,7 @@ public static void addCharset(Charset charset) { private void addProviders() { OptimizedLocalizationSupport optimizedLocalizationSupport = support.asOptimizedSupport(); for (Class providerClass : spiClasses) { - LocaleProviderAdapter adapter = Objects.requireNonNull(LocaleProviderAdapter.getAdapter(providerClass, defaultLocale)); + LocaleProviderAdapter adapter = Objects.requireNonNull(LocaleProviderAdapter.getAdapter(providerClass, Locale.ROOT)); LocaleServiceProvider provider = Objects.requireNonNull(adapter.getLocaleServiceProvider(providerClass)); optimizedLocalizationSupport.providerPools.put(providerClass, new Target_sun_util_locale_provider_LocaleServiceProviderPool_OptimizedLocaleMode(provider)); } @@ -551,18 +547,18 @@ protected void addResourceBundles() { private void processRequestedBundle(String input) { int splitIndex = input.indexOf('_'); boolean specificLocaleRequested = splitIndex != -1; - if (!specificLocaleRequested) { - prepareBundle(ConfigurationCondition.alwaysTrue(), input, allLocales); - return; - } - Locale locale = splitIndex + 1 < input.length() ? LocalizationSupport.parseLocaleFromTag(input.substring(splitIndex + 1)) : Locale.ROOT; - if (locale == null) { - trace("Cannot parse wanted locale " + input.substring(splitIndex + 1) + ", default will be used instead."); - locale = defaultLocale; + if (specificLocaleRequested) { + Locale locale = splitIndex + 1 < input.length() ? LocalizationSupport.parseLocaleFromTag(input.substring(splitIndex + 1)) : Locale.ROOT; + if (locale != null) { + /* Get rid of locale specific suffix. */ + String baseName = input.substring(0, splitIndex); + prepareBundle(ConfigurationCondition.alwaysTrue(), baseName, Collections.singletonList(locale)); + return; + } else { + trace("Cannot parse wanted locale " + input.substring(splitIndex + 1) + ", default will be used instead."); + } } - /*- Get rid of locale specific suffix. */ - String baseName = input.substring(0, splitIndex); - prepareBundle(ConfigurationCondition.alwaysTrue(), baseName, Collections.singletonList(locale)); + prepareBundle(ConfigurationCondition.alwaysTrue(), input, allLocales); } @Platforms(Platform.HOSTED_ONLY.class) diff --git a/substratevm/src/com.oracle.svm.native.libchelper/include/locale_str.h b/substratevm/src/com.oracle.svm.native.libchelper/include/locale_str.h new file mode 100644 index 000000000000..d1373f30cb9f --- /dev/null +++ b/substratevm/src/com.oracle.svm.native.libchelper/include/locale_str.h @@ -0,0 +1,242 @@ +/* + * Copyright (c) 1996, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +typedef struct { + char* language; + char* script; + char* country; + char* variant; + char* extensions; +} locale_props_t; + +/* + * Mappings from partial locale names to full locale names + */ +static char *locale_aliases[] = { + "ar", "ar_EG", + "be", "be_BY", + "bg", "bg_BG", + "br", "br_FR", + "ca", "ca_ES", + "cs", "cs_CZ", + "cz", "cs_CZ", + "da", "da_DK", + "de", "de_DE", + "el", "el_GR", + "en", "en_US", + "eo", "eo", /* no country for Esperanto */ + "es", "es_ES", + "et", "et_EE", + "eu", "eu_ES", + "fi", "fi_FI", + "fr", "fr_FR", + "ga", "ga_IE", + "gl", "gl_ES", + "he", "iw_IL", + "hr", "hr_HR", +#ifdef __linux__ + "hs", "en_US", // used on Linux, not clear what it stands for +#endif + "hu", "hu_HU", + "id", "in_ID", + "in", "in_ID", + "is", "is_IS", + "it", "it_IT", + "iw", "iw_IL", + "ja", "ja_JP", + "kl", "kl_GL", + "ko", "ko_KR", + "lt", "lt_LT", + "lv", "lv_LV", + "mk", "mk_MK", + "nl", "nl_NL", + "no", "no_NO", + "pl", "pl_PL", + "pt", "pt_PT", + "ro", "ro_RO", + "ru", "ru_RU", + "se", "se_NO", + "sk", "sk_SK", + "sl", "sl_SI", + "sq", "sq_AL", + "sr", "sr_CS", + "su", "fi_FI", + "sv", "sv_SE", + "th", "th_TH", + "tr", "tr_TR", +#ifdef __linux__ + "ua", "en_US", // used on Linux, not clear what it stands for +#endif + "uk", "uk_UA", + "vi", "vi_VN", + "wa", "wa_BE", + "zh", "zh_CN", +#ifdef __linux__ + "bokmal", "nb_NO", + "bokm\xE5l", "nb_NO", + "catalan", "ca_ES", + "croatian", "hr_HR", + "czech", "cs_CZ", + "danish", "da_DK", + "dansk", "da_DK", + "deutsch", "de_DE", + "dutch", "nl_NL", + "eesti", "et_EE", + "estonian", "et_EE", + "finnish", "fi_FI", + "fran\xE7\x61is", "fr_FR", + "french", "fr_FR", + "galego", "gl_ES", + "galician", "gl_ES", + "german", "de_DE", + "greek", "el_GR", + "hebrew", "iw_IL", + "hrvatski", "hr_HR", + "hungarian", "hu_HU", + "icelandic", "is_IS", + "italian", "it_IT", + "japanese", "ja_JP", + "korean", "ko_KR", + "lithuanian", "lt_LT", + "norwegian", "no_NO", + "nynorsk", "nn_NO", + "polish", "pl_PL", + "portuguese", "pt_PT", + "romanian", "ro_RO", + "russian", "ru_RU", + "slovak", "sk_SK", + "slovene", "sl_SI", + "slovenian", "sl_SI", + "spanish", "es_ES", + "swedish", "sv_SE", + "thai", "th_TH", + "turkish", "tr_TR", +#else + "big5", "zh_TW.Big5", + "chinese", "zh_CN", + "iso_8859_1", "en_US.ISO8859-1", + "iso_8859_15", "en_US.ISO8859-15", + "japanese", "ja_JP", + "no_NY", "no_NO@nynorsk", + "sr_SP", "sr_YU", + "tchinese", "zh_TW", +#endif + "", "", +}; + +/* + * Linux/Solaris language string to ISO639 string mapping table. + */ +static char *language_names[] = { + "C", "en", + "POSIX", "en", + "cz", "cs", + "he", "iw", +#ifdef __linux__ + "hs", "en", // used on Linux, not clear what it stands for +#endif + "id", "in", + "sh", "sr", // sh is deprecated + "su", "fi", +#ifdef __linux__ + "ua", "en", // used on Linux, not clear what it stands for + "catalan", "ca", + "croatian", "hr", + "czech", "cs", + "danish", "da", + "dansk", "da", + "deutsch", "de", + "dutch", "nl", + "finnish", "fi", + "fran\xE7\x61is", "fr", + "french", "fr", + "german", "de", + "greek", "el", + "hebrew", "he", + "hrvatski", "hr", + "hungarian", "hu", + "icelandic", "is", + "italian", "it", + "japanese", "ja", + "norwegian", "no", + "polish", "pl", + "portuguese", "pt", + "romanian", "ro", + "russian", "ru", + "slovak", "sk", + "slovene", "sl", + "slovenian", "sl", + "spanish", "es", + "swedish", "sv", + "turkish", "tr", +#else + "chinese", "zh", + "japanese", "ja", + "korean", "ko", +#endif + "", "", +}; + +/* + * Linux/Solaris script string to Java script name mapping table. + */ +static char *script_names[] = { +#ifdef __linux__ + "cyrillic", "Cyrl", + "devanagari", "Deva", + "iqtelif", "Latn", + "latin", "Latn", +#endif + "Arab", "Arab", + "Cyrl", "Cyrl", + "Deva", "Deva", + "Ethi", "Ethi", + "Hans", "Hans", + "Hant", "Hant", + "Latn", "Latn", + "Sund", "Sund", + "Syrc", "Syrc", + "Tfng", "Tfng", + "", "", +}; + +/* + * Linux/Solaris country string to ISO3166 string mapping table. + */ +static char *country_names[] = { +#ifdef __linux__ + "RN", "US", // used on Linux, not clear what it stands for +#endif + "YU", "CS", // YU has been removed from ISO 3166 + "", "", +}; + +/* + * Linux/Solaris variant string to Java variant name mapping table. + */ +static char *variant_names[] = { + "nynorsk", "NY", + "", "", +}; \ No newline at end of file diff --git a/substratevm/src/com.oracle.svm.native.libchelper/src/locale.c b/substratevm/src/com.oracle.svm.native.libchelper/src/locale.c new file mode 100644 index 000000000000..21b33919b87c --- /dev/null +++ b/substratevm/src/com.oracle.svm.native.libchelper/src/locale.c @@ -0,0 +1,384 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include +#include +#include +#include +#include "locale_str.h" + +#ifdef _WIN64 +#include +#include + +#define PROPSIZE 9 // eight-letter + null terminator +#define SNAMESIZE 86 // max number of chars for LOCALE_SNAME is 85 +#endif + +/* Take an array of string pairs (map of key->value) and a string (key). + * Examine each pair in the map to see if the first string (key) matches the + * string. If so, store the second string of the pair (value) in the value and + * return 1. Otherwise do nothing and return 0. The end of the map is + * indicated by an empty string at the start of a pair (key of ""). + */ +static int mapLookup(char* map[], const char* key, char** value) { + int i; + for (i = 0; strcmp(map[i], ""); i += 2){ + if (!strcmp(key, map[i])){ + *value = map[i + 1]; + return 1; + } + } + return 0; +} + +static locale_props_t *allocateJavaProps() { + locale_props_t* sprops = malloc(sizeof(locale_props_t)); + sprops->language = NULL; + sprops->country = NULL; + sprops->variant = NULL; + sprops->script = NULL; + sprops->extensions = NULL; + return sprops; +} + +/* + * Copied and adapted from java.base/unix/native/libjava/java_props_md.c. We are not calling + * these functions via static linking of libjava, because we only need a small subset of the + * functionalities that exist in the original method. + */ +static int ParseLocale(locale_props_t * sprops, int cat) { + setlocale(LC_ALL, ""); + char **std_language = &(sprops->language); + char **std_country = &(sprops->country); + char **std_variant = &(sprops->variant); + char **std_script = &(sprops->script); + char **std_extensions = &(sprops->extensions); + + char *temp = NULL; + char *language = NULL, *country = NULL, *variant = NULL, + *encoding = NULL; + char *p, *encoding_variant, *old_temp, *old_ev; + char *lc; + + /* Query the locale set for the category */ + +#ifdef MACOSX + lc = setupMacOSXLocale(cat); // malloc'd memory, need to free +#else + lc = setlocale(cat, NULL); +#endif + +#ifndef __linux__ + if (lc == NULL) { + return 0; + } + + temp = malloc(strlen(lc) + 1); + if (temp == NULL) { +#ifdef MACOSX + free(lc); // malloced memory +#endif + // JNU_ThrowOutOfMemoryError(env, NULL); + return 0; + } + + if (cat == LC_CTYPE) { + /* + * Workaround for Solaris bug 4201684: Xlib doesn't like @euro + * locales. Since we don't depend on the libc @euro behavior, + * we just remove the qualifier. + * On Linux, the bug doesn't occur; on the other hand, @euro + * is needed there because it's a shortcut that also determines + * the encoding - without it, we wouldn't get ISO-8859-15. + * Therefore, this code section is Solaris-specific. + */ + strcpy(temp, lc); + p = strstr(temp, "@euro"); + if (p != NULL) { + *p = '\0'; + setlocale(LC_ALL, temp); + } + } +#else + if (lc == NULL || !strcmp(lc, "C") || !strcmp(lc, "POSIX")) { + lc = "en_US"; + } + + temp = malloc(strlen(lc) + 1); + if (temp == NULL) { + // JNU_ThrowOutOfMemoryError(env, NULL); + return 0; + } + +#endif + + /* + * locale string format in Solaris is + * _.@ + * , , and are optional. + */ + + strcpy(temp, lc); +#ifdef MACOSX + free(lc); // malloced memory +#endif + /* Parse the language, country, encoding, and variant from the + * locale. Any of the elements may be missing, but they must occur + * in the order language_country.encoding@variant, and must be + * preceded by their delimiter (except for language). + * + * If the locale name (without .encoding@variant, if any) matches + * any of the names in the locale_aliases list, map it to the + * corresponding full locale name. Most of the entries in the + * locale_aliases list are locales that include a language name but + * no country name, and this facility is used to map each language + * to a default country if that's possible. It's also used to map + * the Solaris locale aliases to their proper Java locale IDs. + */ + + encoding_variant = malloc(strlen(temp)+1); + if (encoding_variant == NULL) { + free(temp); + // JNU_ThrowOutOfMemoryError(env, NULL); + return 0; + } + + if ((p = strchr(temp, '.')) != NULL) { + strcpy(encoding_variant, p); /* Copy the leading '.' */ + *p = '\0'; + } else if ((p = strchr(temp, '@')) != NULL) { + strcpy(encoding_variant, p); /* Copy the leading '@' */ + *p = '\0'; + } else { + *encoding_variant = '\0'; + } + + if (mapLookup(locale_aliases, temp, &p)) { + old_temp = temp; + temp = realloc(temp, strlen(p)+1); + if (temp == NULL) { + free(old_temp); + free(encoding_variant); + // JNU_ThrowOutOfMemoryError(env, NULL); + return 0; + } + strcpy(temp, p); + old_ev = encoding_variant; + encoding_variant = realloc(encoding_variant, strlen(temp)+1); + if (encoding_variant == NULL) { + free(old_ev); + free(temp); + // JNU_ThrowOutOfMemoryError(env, NULL); + return 0; + } + // check the "encoding_variant" again, if any. + if ((p = strchr(temp, '.')) != NULL) { + strcpy(encoding_variant, p); /* Copy the leading '.' */ + *p = '\0'; + } else if ((p = strchr(temp, '@')) != NULL) { + strcpy(encoding_variant, p); /* Copy the leading '@' */ + *p = '\0'; + } + } + + language = temp; + if ((country = strchr(temp, '_')) != NULL) { + *country++ = '\0'; + } + + p = encoding_variant; + if ((encoding = strchr(p, '.')) != NULL) { + p[encoding++ - p] = '\0'; + p = encoding; + } + if ((variant = strchr(p, '@')) != NULL) { + p[variant++ - p] = '\0'; + } + + /* Normalize the language name */ + if (std_language != NULL) { + *std_language = "en"; + if (language != NULL && mapLookup(language_names, language, std_language) == 0) { + *std_language = malloc(strlen(language)+1); + if (*std_language == NULL) { + free(encoding_variant); + // JNU_ThrowOutOfMemoryError(env, NULL); + return 0; + } + strcpy(*std_language, language); + } + } + + /* Normalize the country name */ + if (std_country != NULL && country != NULL) { + if (mapLookup(country_names, country, std_country) == 0) { + *std_country = malloc(strlen(country)+1); + if (*std_country == NULL) { + free(encoding_variant); + // JNU_ThrowOutOfMemoryError(env, NULL); + return 0; + } + strcpy(*std_country, country); + } + } + + /* Normalize the script and variant name. Note that we only use + * variants listed in the mapping array; others are ignored. + */ + if (variant != NULL) { + if (std_script != NULL) { + mapLookup(script_names, variant, std_script); + } + + if (std_variant != NULL) { + mapLookup(variant_names, variant, std_variant); + } + } + + free(temp); + free(encoding_variant); + + return 1; +} + +#ifdef _WIN64 +/* + * Copied and adapted from java.base/windows/native/libjava/java_props_md.c. We are not calling + * these functions via static linking of libjava, because we only need a small subset of the + * functionalities that exist in the original method. + */ +static int SetupI18nProps(LCID lcid, char** language, char** script, char** country, char** variant) { + /* script */ + char tmp[SNAMESIZE]; + *script = malloc(PROPSIZE); + if (*script == NULL) { + return 0; + } + if (GetLocaleInfo(lcid, + LOCALE_SNAME, tmp, SNAMESIZE) == 0 || + sscanf(tmp, "%*[a-z\\-]%1[A-Z]%[a-z]", *script, &((*script)[1])) == 0 || + strlen(*script) != 4) { + (*script)[0] = '\0'; + } + + /* country */ + *country = malloc(PROPSIZE); + if (*country == NULL) { + return 0; + } + if (GetLocaleInfo(lcid, + LOCALE_SISO3166CTRYNAME, *country, PROPSIZE) == 0 && + GetLocaleInfo(lcid, + LOCALE_SISO3166CTRYNAME2, *country, PROPSIZE) == 0) { + (*country)[0] = '\0'; + } + + /* language */ + *language = malloc(PROPSIZE); + if (*language == NULL) { + return 0; + } + if (GetLocaleInfo(lcid, + LOCALE_SISO639LANGNAME, *language, PROPSIZE) == 0 && + GetLocaleInfo(lcid, + LOCALE_SISO639LANGNAME2, *language, PROPSIZE) == 0) { + /* defaults to en_US */ + strcpy(*language, "en"); + strcpy(*country, "US"); + } + + /* variant */ + *variant = malloc(PROPSIZE); + if (*variant == NULL) { + return 0; + } + (*variant)[0] = '\0'; + + /* handling for Norwegian */ + if (strcmp(*language, "nb") == 0) { + strcpy(*language, "no"); + strcpy(*country , "NO"); + } else if (strcmp(*language, "nn") == 0) { + strcpy(*language, "no"); + strcpy(*country , "NO"); + strcpy(*variant, "NY"); + } + + return 1; +} + +void PopulateJavaProperties(locale_props_t *sprops, int isFormatLocale) { + /* + * query the system for the current system default locale + * (which is a Windows LCID value), + */ + LCID userDefaultLCID = GetUserDefaultLCID(); + LANGID userDefaultUILang = GetUserDefaultUILanguage(); + LCID userDefaultUILCID = MAKELCID(userDefaultUILang, SORTIDFROMLCID(userDefaultLCID)); + + // Windows UI Language selection list only cares "language" + // information of the UI Language. For example, the list + // just lists "English" but it actually means "en_US", and + // the user cannot select "en_GB" (if exists) in the list. + // So, this hack is to use the user LCID region information + // for the UI Language, if the "language" portion of those + // two locales are the same. + if (PRIMARYLANGID(LANGIDFROMLCID(userDefaultLCID)) == + PRIMARYLANGID(userDefaultUILang)) { + userDefaultUILCID = userDefaultLCID; + } + + SetupI18nProps(isFormatLocale ? userDefaultLCID : userDefaultUILCID, + &(sprops->language), + &(sprops->script), + &(sprops->country), + &(sprops->variant)); +} +#endif + +locale_props_t* parseDisplayLocale() { + locale_props_t *sprops = allocateJavaProps(); +#ifndef _WIN64 + int result = ParseLocale(sprops, LC_MESSAGES); + if (!result) { + sprops->language = "en"; + } +#else + PopulateJavaProperties(sprops, 0); +#endif + return sprops; +} + +locale_props_t* parseFormatLocale() { + locale_props_t *sprops = allocateJavaProps(); +#ifndef _WIN64 + int result = ParseLocale(sprops, LC_CTYPE); +#else + PopulateJavaProperties(sprops, 1); +#endif + + return sprops; +} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/ProcessPropertiesTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/ProcessPropertiesTest.java index 8b47dedc40af..8b4fe7b52ab2 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/ProcessPropertiesTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/ProcessPropertiesTest.java @@ -51,7 +51,7 @@ public void testGetPID() { public void testSetLocale() { /* Get the default locale. */ String before = ProcessProperties.setLocale("LC_ALL", null); - Assert.assertTrue("Default locale is wrong.", before.equals("C") || before.equals("POSIX")); + Assert.assertFalse("Default locale is empty.", before.isEmpty()); /* Set locale to a new value. */ ProcessProperties.setLocale("LC_ALL", "en_US.UTF-8");