Skip to content

Commit

Permalink
[GR-52096] Set default locale at run-time.
Browse files Browse the repository at this point in the history
PullRequest: graal/16807
  • Loading branch information
loicottet authored and jovanstevanovic committed Sep 17, 2024
2 parents 0df39c8 + 9f1ae94 commit c4cdb11
Show file tree
Hide file tree
Showing 15 changed files with 1,075 additions and 91 deletions.
1 change: 1 addition & 0 deletions substratevm/mx.substratevm/suite.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
Expand Down Expand Up @@ -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<String, Supplier<String>> lazyRuntimeValues;

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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);
}
}
}
Expand Down Expand Up @@ -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";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ public class BundleContentSubstitutedLocalizationSupport extends LocalizationSup

private final Set<String> existingBundles = ConcurrentHashMap.newKeySet();

public BundleContentSubstitutedLocalizationSupport(Locale defaultLocale, Set<Locale> locales, Charset defaultCharset, List<String> requestedPatterns, ForkJoinPool pool) {
super(defaultLocale, locales, defaultCharset);
public BundleContentSubstitutedLocalizationSupport(Set<Locale> locales, Charset defaultCharset, List<String> requestedPatterns, ForkJoinPool pool) {
super(locales, defaultCharset);
this.pool = pool;
this.compressBundlesPatterns = parseCompressBundlePatterns(requestedPatterns);
}
Expand Down Expand Up @@ -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<String, Object> content = BundleSerializationUtils.extractContent(bundle);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,6 @@ public class LocalizationSupport {

public final Map<String, Charset> charsets = new HashMap<>();

public final Locale defaultLocale;

public final Locale[] allLocales;

public final Set<String> supportedLanguageTags;
Expand All @@ -91,8 +89,7 @@ public class LocalizationSupport {

private final EconomicMap<String, RuntimeConditionSet> registeredBundles = ImageHeapMap.create();

public LocalizationSupport(Locale defaultLocale, Set<Locale> locales, Charset defaultCharset) {
this.defaultLocale = defaultLocale;
public LocalizationSupport(Set<Locale> locales, Charset defaultCharset) {
this.allLocales = locales.toArray(new Locale[0]);
this.defaultCharset = defaultCharset;
this.supportedLanguageTags = locales.stream().map(Locale::toString).collect(Collectors.toSet());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ private record BundleCacheKey(String bundleName, Locale locale) {

final Map<BundleCacheKey, ResourceBundle> resourceBundles = new HashMap<>();

public OptimizedLocalizationSupport(Locale defaultLocale, Set<Locale> locales, Charset defaultCharset) {
super(defaultLocale, locales, defaultCharset);
public OptimizedLocalizationSupport(Set<Locale> locales, Charset defaultCharset) {
super(locales, defaultCharset);
}

@Override
Expand Down
Loading

0 comments on commit c4cdb11

Please sign in to comment.