diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeResourceAccess.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeResourceAccess.java index 355a4f06e4f7..1e26a651c290 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeResourceAccess.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeResourceAccess.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2023, 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 @@ -40,17 +40,16 @@ */ package org.graalvm.nativeimage.hosted; -import java.util.Arrays; -import java.util.Locale; -import java.util.Objects; -import java.util.regex.Pattern; - import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.nativeimage.impl.RuntimeResourceSupport; +import java.util.Arrays; +import java.util.Locale; +import java.util.Objects; + /** * This class can be used to register Java resources and ResourceBundles that should be accessible * at run time. @@ -69,8 +68,7 @@ public final class RuntimeResourceAccess { public static void addResource(Module module, String resourcePath) { Objects.requireNonNull(module); Objects.requireNonNull(resourcePath); - ImageSingletons.lookup(RuntimeResourceSupport.class).addResources(ConfigurationCondition.alwaysTrue(), - withModuleName(module, Pattern.quote(resourcePath))); + ImageSingletons.lookup(RuntimeResourceSupport.class).addResource(module, resourcePath); } /** diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ConfigurationCondition.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ConfigurationCondition.java index 72a76b2d2718..e089491291de 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ConfigurationCondition.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ConfigurationCondition.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2023, 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 @@ -50,6 +50,10 @@ public static ConfigurationCondition alwaysTrue() { return OBJECT_REACHABLE; } + public static boolean isAlwaysTrue(ConfigurationCondition condition) { + return ConfigurationCondition.alwaysTrue().equals(condition); + } + public static ConfigurationCondition create(String typeReachability) { Objects.requireNonNull(typeReachability); if (OBJECT_REACHABLE.typeName.equals(typeReachability)) { 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 68242b311d69..6147690f249c 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, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2023, 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 @@ -46,6 +46,8 @@ public interface RuntimeResourceSupport { void addResources(ConfigurationCondition condition, String pattern); + void addResource(Module module, String resourcePath); + void injectResource(Module module, String resourcePath, byte[] resourceContent); void ignoreResources(ConfigurationCondition condition, String pattern); diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index a2013afb94e5..3c873193198e 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -28,6 +28,7 @@ import tempfile from glob import glob from contextlib import contextmanager +from distutils.dir_util import mkpath # pylint: disable=no-name-in-module from os.path import join, exists, dirname import pipes from argparse import ArgumentParser @@ -475,9 +476,26 @@ def native_unittests_task(extra_build_args=None): # GR-24075 mx_unittest.add_global_ignore_glob('com.oracle.svm.test.ProcessPropertiesTest') + # add resources that are not in jar but in the separate directory + cp_entry_name = join(svmbuild_dir(), 'cpEntryDir') + resources_from_dir = join(cp_entry_name, 'resourcesFromDir') + simple_dir = join(cp_entry_name, 'simpleDir') + + mkpath(cp_entry_name) + mkpath(resources_from_dir) + mkpath(simple_dir) + + for i in range(4): + with open(join(cp_entry_name, "resourcesFromDir", f'cond-resource{i}.txt'), 'w') as out: + out.write(f"Conditional file{i}" + '\n') + + with open(join(cp_entry_name, "simpleDir", f'simple-resource{i}.txt'), 'w') as out: + out.write(f"Simple file{i}" + '\n') + additional_build_args = svm_experimental_options([ '-H:AdditionalSecurityProviders=com.oracle.svm.test.SecurityServiceTest$NoOpProvider', '-H:AdditionalSecurityServiceTypes=com.oracle.svm.test.SecurityServiceTest$JCACompliantNoOpService', + '-cp', cp_entry_name ]) if extra_build_args is not None: additional_build_args += extra_build_args 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 600b21440bb4..d4007bbee5e6 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 @@ -38,9 +38,10 @@ import org.junit.Test; import com.oracle.svm.configure.config.ResourceConfiguration; -import com.oracle.svm.core.util.json.JsonWriter; import com.oracle.svm.core.configure.ResourceConfigurationParser; import com.oracle.svm.core.configure.ResourcesRegistry; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.core.util.json.JsonWriter; public class ResourceConfigurationTest { @@ -93,6 +94,11 @@ public void addResources(ConfigurationCondition condition, String pattern) { addedResources.add(pattern); } + @Override + public void addResource(Module module, String resourcePath) { + throw VMError.shouldNotReachHere("Unused function."); + } + @Override public void injectResource(Module module, String resourcePath, byte[] resourceContent) { } 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 803fbb86fa34..6a561ee133e7 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 @@ -38,13 +38,13 @@ import org.graalvm.nativeimage.impl.ConfigurationCondition; import com.oracle.svm.configure.ConfigurationBase; -import com.oracle.svm.core.util.json.JsonPrinter; -import com.oracle.svm.core.util.json.JsonWriter; import com.oracle.svm.core.configure.ConditionalElement; import com.oracle.svm.core.configure.ConfigurationParser; import com.oracle.svm.core.configure.ResourceConfigurationParser; import com.oracle.svm.core.configure.ResourcesRegistry; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.core.util.json.JsonPrinter; +import com.oracle.svm.core.util.json.JsonWriter; public final class ResourceConfiguration extends ConfigurationBase { @@ -63,6 +63,11 @@ public void addResources(ConfigurationCondition condition, String pattern) { configuration.addResourcePattern(condition, pattern); } + @Override + public void addResource(Module module, String resourcePath) { + throw VMError.shouldNotReachHere("Unused function."); + } + @Override public void injectResource(Module module, String resourcePath, byte[] resourceContent) { VMError.shouldNotReachHere("Resource injection is only supported via Feature implementation"); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/ClassLoaderSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/ClassLoaderSupport.java index 4c7e0aaca4ad..aa53efe3871a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/ClassLoaderSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/ClassLoaderSupport.java @@ -25,14 +25,16 @@ package com.oracle.svm.core; import java.io.IOException; -import java.io.InputStream; import java.net.URI; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.ResourceBundle; +import java.util.Set; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; +import org.graalvm.nativeimage.impl.ConfigurationCondition; @Platforms(Platform.HOSTED_ONLY.class) public abstract class ClassLoaderSupport { @@ -51,12 +53,11 @@ public boolean isNativeImageClassLoader(ClassLoader classLoader) { protected abstract boolean isNativeImageClassLoaderImpl(ClassLoader classLoader); public interface ResourceCollector { + List isIncluded(Module module, String resourceName, URI resourceURI); - boolean isIncluded(Module module, String resourceName, URI resourceURI); + void addResource(Module module, String resourceName); - void addResource(Module module, String resourceName, InputStream resourceStream, boolean fromJar); - - void addDirectoryResource(Module module, String dir, String content, boolean fromJar); + void addResourceConditionally(Module module, String resourceName, ConfigurationCondition condition); void registerNegativeQuery(Module module, String resourceName); @@ -66,4 +67,6 @@ public interface ResourceCollector { public abstract void collectResources(ResourceCollector resourceCollector); public abstract List getResourceBundle(String bundleName, Locale locale); + + public abstract Map> getPackageToModules(); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Resources.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Resources.java index 00faeab2e2cb..069e34e89dfe 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Resources.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Resources.java @@ -166,7 +166,7 @@ private void updateTimeStamp() { } @Platforms(Platform.HOSTED_ONLY.class) - private ResourceStorageEntryBase addEntry(Module module, String resourceName, ResourceStorageEntryBase newEntry, boolean isDirectory, boolean fromJar) { + private void addEntry(Module module, String resourceName, boolean isDirectory, byte[] data, boolean fromJar, boolean isNegativeQuery) { VMError.guarantee(!BuildPhaseProvider.isAnalysisFinished(), "Trying to add a resource entry after analysis."); Module m = module != null && module.isNamed() ? module : null; if (m != null) { @@ -175,18 +175,27 @@ private ResourceStorageEntryBase addEntry(Module module, String resourceName, Re synchronized (resources) { Pair key = createStorageKey(m, resourceName); ResourceStorageEntryBase entry = resources.get(key); + if (isNegativeQuery) { + if (entry == null) { + resources.put(key, NEGATIVE_QUERY_MARKER); + } + return; + } + if (entry == null || entry == NEGATIVE_QUERY_MARKER) { - entry = newEntry == null ? new ResourceStorageEntry(isDirectory, fromJar) : newEntry; updateTimeStamp(); + entry = new ResourceStorageEntry(isDirectory, fromJar); resources.put(key, entry); + } else { + if (key.getLeft() != null) { + // if the entry already exists, and it comes from a module, it is the same entry + // that we registered at some point before + return; + } } - return entry; - } - } - private void addEntry(Module module, String resourceName, boolean isDirectory, byte[] data, boolean fromJar) { - ResourceStorageEntryBase entry = addEntry(module, resourceName, null, isDirectory, fromJar); - entry.getData().add(data); + entry.getData().add(data); + } } @Platforms(Platform.HOSTED_ONLY.class) @@ -194,39 +203,14 @@ public static void registerResource(String resourceName, InputStream is) { singleton().registerResource(null, resourceName, is, true); } - @Platforms(Platform.HOSTED_ONLY.class) - public void registerResource(String resourceName, InputStream is, boolean fromJar) { - registerResource(null, resourceName, is, fromJar); - } - - @Platforms(Platform.HOSTED_ONLY.class) - public void registerResource(Module module, String resourceName, InputStream is) { - registerResource(module, resourceName, is, true); - } - @Platforms(Platform.HOSTED_ONLY.class) public void registerResource(Module module, String resourceName, byte[] resourceContent) { - addEntry(module, resourceName, false, resourceContent, true); + addEntry(module, resourceName, false, resourceContent, true, false); } @Platforms(Platform.HOSTED_ONLY.class) public void registerResource(Module module, String resourceName, InputStream is, boolean fromJar) { - addEntry(module, resourceName, false, inputStreamToByteArray(is), fromJar); - } - - @Platforms(Platform.HOSTED_ONLY.class) - public void registerDirectoryResource(String resourceDirName, String content) { - registerDirectoryResource(null, resourceDirName, content, true); - } - - @Platforms(Platform.HOSTED_ONLY.class) - public void registerDirectoryResource(String resourceDirName, String content, boolean fromJar) { - registerDirectoryResource(null, resourceDirName, content, fromJar); - } - - @Platforms(Platform.HOSTED_ONLY.class) - public void registerDirectoryResource(Module module, String resourceDirName, String content) { - registerDirectoryResource(module, resourceDirName, content, true); + addEntry(module, resourceName, false, inputStreamToByteArray(is), fromJar, false); } @Platforms(Platform.HOSTED_ONLY.class) @@ -236,21 +220,16 @@ public void registerDirectoryResource(Module module, String resourceDirName, Str * specified directory, separated with new line delimiter and joined into one string which * is later converted into a byte array and placed into the resources map. */ - addEntry(module, resourceDirName, true, content.getBytes(), fromJar); - } - - @Platforms(Platform.HOSTED_ONLY.class) - public void registerIOException(String resourceName, IOException e, boolean linkAtBuildTime) { - registerIOException(null, resourceName, e, linkAtBuildTime); + addEntry(module, resourceDirName, true, content.getBytes(), fromJar, false); } @Platforms(Platform.HOSTED_ONLY.class) public void registerIOException(Module module, String resourceName, IOException e, boolean linkAtBuildTime) { if (linkAtBuildTime) { if (SubstrateOptions.ThrowLinkAtBuildTimeIOExceptions.getValue()) { - throw new RuntimeException("Resource " + resourceName + " from module " + module.getName() + " produced an IOException.", e); + throw new RuntimeException("Resource " + resourceName + " from module " + moduleName(module) + " produced an IOException.", e); } else { - LogUtils.warning("Resource " + resourceName + " from module " + module.getName() + " produced the following IOException: " + e.getClass().getTypeName() + ": " + e.getMessage()); + LogUtils.warning("Resource " + resourceName + " from module " + moduleName(module) + " produced the following IOException: " + e.getClass().getTypeName() + ": " + e.getMessage()); } } Pair key = createStorageKey(module, resourceName); @@ -267,7 +246,7 @@ public void registerNegativeQuery(String resourceName) { @Platforms(Platform.HOSTED_ONLY.class) public void registerNegativeQuery(Module module, String resourceName) { - addEntry(module, resourceName, NEGATIVE_QUERY_MARKER, false, false); + addEntry(module, resourceName, false, null, false, true); } @Platforms(Platform.HOSTED_ONLY.class) 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 9f918232531e..a178d797270b 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 @@ -30,14 +30,15 @@ import java.util.ListResourceBundle; import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.ResourceBundle; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ForkJoinPool; +import java.util.function.Function; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; -import jdk.graal.compiler.debug.GraalError; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -49,6 +50,7 @@ import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; +import jdk.graal.compiler.debug.GraalError; import sun.util.resources.OpenListResourceBundle; import sun.util.resources.ParallelListResourceBundle; @@ -172,8 +174,8 @@ public boolean isNotIncluded(String bundleName) { } @Override - public void prepareBundle(String bundleName, ResourceBundle bundle, Locale locale) { - super.prepareBundle(bundleName, bundle, locale); + public void prepareBundle(String bundleName, ResourceBundle bundle, Function> findModule, Locale locale) { + super.prepareBundle(bundleName, bundle, findModule, locale); /* Initialize ResourceBundle.keySet eagerly */ bundle.keySet(); this.existingBundles.add(control.toBundleName(bundleName, locale)); 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 d6ab244df836..a036d9c5520a 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 @@ -25,18 +25,23 @@ package com.oracle.svm.core.jdk.localization; +import static com.oracle.svm.util.StringUtil.toDotSeparated; +import static com.oracle.svm.util.StringUtil.toSlashSeparated; + import java.nio.charset.Charset; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.IllformedLocaleException; import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.PropertyResourceBundle; import java.util.ResourceBundle; import java.util.Set; +import java.util.function.Function; import java.util.stream.Collectors; -import jdk.graal.compiler.debug.GraalError; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -45,10 +50,13 @@ import org.graalvm.nativeimage.impl.RuntimeReflectionSupport; import org.graalvm.nativeimage.impl.RuntimeResourceSupport; +import com.oracle.svm.core.ClassLoaderSupport; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.jdk.Resources; import com.oracle.svm.core.util.VMError; +import jdk.graal.compiler.debug.GraalError; + /** * Holder for localization information that is computed during image generation and used at run * time. @@ -98,17 +106,31 @@ public Map getBundleContentOf(Object bundle) { } @Platforms(Platform.HOSTED_ONLY.class) - public void prepareBundle(String bundleName, ResourceBundle bundle, Locale locale) { + public void prepareBundle(String bundleName, ResourceBundle bundle, Function> findModule, Locale locale) { if (bundle instanceof PropertyResourceBundle) { String[] bundleNameWithModule = SubstrateUtil.split(bundleName, ":", 2); - String resultingPattern; + String resourceName; if (bundleNameWithModule.length < 2) { - resultingPattern = control.toBundleName(bundleName, locale).replace('.', '/'); + resourceName = toSlashSeparated(control.toBundleName(bundleName, locale)).concat(".properties"); + + Map> packageToModules = ImageSingletons.lookup(ClassLoaderSupport.class).getPackageToModules(); + Set modules = packageToModules.getOrDefault(packageName(bundleName), Collections.emptySet()); + + for (Module m : modules) { + ImageSingletons.lookup(RuntimeResourceSupport.class).addResource(m, resourceName); + } + + if (modules.isEmpty()) { + ImageSingletons.lookup(RuntimeResourceSupport.class).addResource(null, resourceName); + } } else { - String patternWithLocale = control.toBundleName(bundleNameWithModule[1], locale).replace('.', '/'); - resultingPattern = bundleNameWithModule[0] + ':' + patternWithLocale; + if (findModule != null) { + resourceName = toSlashSeparated(control.toBundleName(bundleNameWithModule[1], locale)).concat(".properties"); + Optional module = findModule.apply(bundleNameWithModule[0]); + String finalResourceName = resourceName; + module.ifPresent(m -> ImageSingletons.lookup(RuntimeResourceSupport.class).addResource(m, finalResourceName)); + } } - ImageSingletons.lookup(RuntimeResourceSupport.class).addResources(ConfigurationCondition.alwaysTrue(), resultingPattern + "\\.properties"); } else { registerRequiredReflectionAndResourcesForBundle(bundleName, Set.of(locale)); RuntimeReflection.register(bundle.getClass()); @@ -117,6 +139,15 @@ public void prepareBundle(String bundleName, ResourceBundle bundle, Locale local } } + private static String packageName(String bundleName) { + String uniformBundleName = toDotSeparated(bundleName); + int classSep = uniformBundleName.lastIndexOf('.'); + if (classSep == -1) { + return ""; /* unnamed package */ + } + return uniformBundleName.substring(0, classSep); + } + public String getResultingPattern(String bundleName, Locale locale) { String fixedBundleName = bundleName.replace("$", "\\$"); return getBundleName(fixedBundleName, locale); @@ -125,9 +156,9 @@ public String getResultingPattern(String bundleName, Locale locale) { private String getBundleName(String fixedBundleName, Locale locale) { String[] bundleNameWithModule = SubstrateUtil.split(fixedBundleName, ":", 2); if (bundleNameWithModule.length < 2) { - return control.toBundleName(fixedBundleName, locale).replace('.', '/'); + return toSlashSeparated(control.toBundleName(fixedBundleName, locale)); } else { - String patternWithLocale = control.toBundleName(bundleNameWithModule[1], locale).replace('.', '/'); + String patternWithLocale = toSlashSeparated(control.toBundleName(bundleNameWithModule[1], locale)); return bundleNameWithModule[0] + ':' + patternWithLocale; } } 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 91187cdc9bd8..5a8a29f4a1b4 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 @@ -30,8 +30,10 @@ import java.util.Locale; import java.util.Map; import java.util.MissingResourceException; +import java.util.Optional; import java.util.ResourceBundle; import java.util.Set; +import java.util.function.Function; import java.util.spi.LocaleServiceProvider; import org.graalvm.collections.Pair; @@ -89,12 +91,21 @@ public void prepareClassResourceBundle(String basename, Class bundleClass) { /*- Set the basename and locale to be consistent with JVM lookup process */ bundleNameField.set(bundle, basename); bundleLocaleField.set(bundle, locale); - prepareBundle(basename, bundle, locale); + + // override in this class does not use findModule + prepareBundle(basename, bundle, null, locale); } catch (ReflectionUtil.ReflectionUtilError | ReflectiveOperationException e) { throw UserError.abort(e, "Failed to instantiated bundle from class %s, reason %s", bundleClass, e.getCause().getMessage()); } } + @Platforms(Platform.HOSTED_ONLY.class) + @Override + public void prepareBundle(String bundleName, ResourceBundle bundle, Function> findModule, Locale locale) { + bundle.keySet(); + this.resourceBundles.put(Pair.create(bundleName, locale), bundle); + } + private static Locale extractLocale(Class bundleClass) { String name = bundleClass.getName(); int split = name.lastIndexOf('_'); @@ -104,13 +115,6 @@ private static Locale extractLocale(Class bundleClass) { return parseLocaleFromTag(name.substring(split + 1)); } - @Platforms(Platform.HOSTED_ONLY.class) - @Override - public void prepareBundle(String bundleName, ResourceBundle bundle, Locale locale) { - bundle.keySet(); - this.resourceBundles.put(Pair.create(bundleName, locale), bundle); - } - @Override public boolean shouldSubstituteLoadLookup(String className) { /*- All bundles are stored in the image heap as objects, no need to keep the content around */ diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassLoaderSupportImpl.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassLoaderSupportImpl.java index 37dfcb4134b8..433b0680c4b1 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassLoaderSupportImpl.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ClassLoaderSupportImpl.java @@ -39,7 +39,6 @@ import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; @@ -55,6 +54,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import org.graalvm.nativeimage.impl.ConfigurationCondition; + import com.oracle.svm.core.ClassLoaderSupport; import com.oracle.svm.core.util.ClasspathUtils; import com.oracle.svm.core.util.UserError; @@ -71,6 +72,9 @@ public class ClassLoaderSupportImpl extends ClassLoaderSupport { private final Map> packageToModules; + private record ConditionalResource(ConfigurationCondition condition, String resourceName) { + } + public ClassLoaderSupportImpl(NativeImageClassLoaderSupport classLoaderSupport) { this.classLoaderSupport = classLoaderSupport; imageClassLoader = classLoaderSupport.getClassLoader(); @@ -107,57 +111,60 @@ public void collectResources(ResourceCollector resourceCollector) { /* Collect resources from modules */ NativeImageClassLoaderSupport.allLayers(classLoaderSupport.moduleLayerForImageBuild).stream() .flatMap(ClassLoaderSupportImpl::extractModuleLookupData) + .parallel() .forEach(lookup -> collectResourceFromModule(resourceCollector, lookup)); /* Collect remaining resources from classpath */ - for (Path classpathFile : classLoaderSupport.classpath()) { - boolean includeAll = classLoaderSupport.getJavaPathsToInclude().contains(classpathFile); + classLoaderSupport.classpath().stream().parallel().forEach(classpathFile -> { + boolean includeCurrent = classLoaderSupport.getJavaPathsToInclude().contains(classpathFile); try { if (Files.isDirectory(classpathFile)) { - scanDirectory(classpathFile, resourceCollector, includeAll); + scanDirectory(classpathFile, resourceCollector, includeCurrent); } else if (ClasspathUtils.isJar(classpathFile)) { - scanJar(classpathFile, resourceCollector, includeAll); + scanJar(classpathFile, resourceCollector, includeCurrent); } } catch (IOException ex) { throw UserError.abort("Unable to handle classpath element '%s'. Make sure that all classpath entries are either directories or valid jar files.", classpathFile); } - } + }); } private void collectResourceFromModule(ResourceCollector resourceCollector, ResourceLookupInfo info) { ModuleReference moduleReference = info.resolvedModule.reference(); try (ModuleReader moduleReader = moduleReference.open()) { - var includeAll = classLoaderSupport.getJavaModuleNamesToInclude().contains(info.resolvedModule().name()); - List foundResources = moduleReader.list() - .filter(resourceName -> shouldIncludeEntry(info.module(), resourceCollector, resourceName, moduleReference.location().orElse(null), includeAll)) - .toList(); + boolean includeCurrent = classLoaderSupport.getJavaModuleNamesToInclude().contains(info.resolvedModule().name()); + List resourcesFound = new ArrayList<>(); + moduleReader.list().forEach(resourceName -> { + List conditions = shouldIncludeEntry(info.module, resourceCollector, resourceName, moduleReference.location().orElse(null), includeCurrent); + for (ConfigurationCondition condition : conditions) { + resourcesFound.add(new ConditionalResource(condition, resourceName)); + } + }); - for (String resName : foundResources) { + for (ConditionalResource entry : resourcesFound) { + ConfigurationCondition condition = entry.condition(); + String resName = entry.resourceName(); if (resName.endsWith("/")) { - resourceCollector.addDirectoryResource(info.module, resName, "", false); + includeResource(resourceCollector, info.module, resName, condition); continue; } + Optional content = moduleReader.open(resName); if (content.isEmpty()) { /* This is to be resilient, but the resources returned by list() should exist */ resourceCollector.registerNegativeQuery(info.module, resName); continue; } - try (InputStream is = content.get()) { - resourceCollector.addResource(info.module, resName, is, false); - } catch (IOException resourceException) { - resourceCollector.registerIOException(info.module, resName, resourceException, LinkAtBuildTimeSupport.singleton().moduleLinkAtBuildTime(info.module.getName())); - } + + includeResource(resourceCollector, info.module, resName, condition); } + } catch (IOException e) { throw VMError.shouldNotReachHere(e); } } - private static void scanDirectory(Path root, ResourceCollector collector, boolean includeAll) { - Map> matchedDirectoryResources = new HashMap<>(); - Set allEntries = new HashSet<>(); - + private static void scanDirectory(Path root, ResourceCollector collector, boolean includeCurrent) { ArrayDeque queue = new ArrayDeque<>(); queue.push(root); while (!queue.isEmpty()) { @@ -167,15 +174,20 @@ private static void scanDirectory(Path root, ResourceCollector collector, boolea String relativeFilePath; if (entry != root) { relativeFilePath = root.relativize(entry).toString().replace(File.separatorChar, RESOURCES_INTERNAL_PATH_SEPARATOR); - allEntries.add(relativeFilePath); } else { - relativeFilePath = ""; + relativeFilePath = String.valueOf(RESOURCES_INTERNAL_PATH_SEPARATOR); + } + + List conditions = shouldIncludeEntry(null, collector, relativeFilePath, Path.of(relativeFilePath).toUri(), includeCurrent); + for (ConfigurationCondition condition : conditions) { + includeResource(collector, null, relativeFilePath, condition); } if (Files.isDirectory(entry)) { - if (shouldIncludeEntry(null, collector, relativeFilePath, Path.of(relativeFilePath).toUri(), includeAll)) { - matchedDirectoryResources.put(relativeFilePath, new ArrayList<>()); + if (conditions.isEmpty()) { + collector.registerNegativeQuery(null, relativeFilePath); } + try (Stream pathStream = Files.list(entry)) { Stream filtered = pathStream; if (ClassUtil.CLASS_MODULE_PATH_EXCLUDE_DIRECTORIES_ROOT.equals(entry)) { @@ -185,62 +197,42 @@ private static void scanDirectory(Path root, ResourceCollector collector, boolea } catch (IOException resourceException) { collector.registerIOException(null, relativeFilePath, resourceException, LinkAtBuildTimeSupport.singleton().packageOrClassAtBuildTime(relativeFilePath)); } - } else { - if (shouldIncludeEntry(null, collector, relativeFilePath, Path.of(relativeFilePath).toUri(), includeAll)) { - try (InputStream is = Files.newInputStream(entry)) { - collector.addResource(null, relativeFilePath, is, false); - } catch (IOException resourceException) { - collector.registerIOException(null, relativeFilePath, resourceException, LinkAtBuildTimeSupport.singleton().packageOrClassAtBuildTime(relativeFilePath)); - } - } } } - - for (String entry : allEntries) { - int last = entry.lastIndexOf(RESOURCES_INTERNAL_PATH_SEPARATOR); - String key = last == -1 ? "" : entry.substring(0, last); - List dirContent = matchedDirectoryResources.get(key); - if (dirContent != null && !dirContent.contains(entry)) { - dirContent.add(entry.substring(last + 1)); - } else if (dirContent == null) { - collector.registerNegativeQuery(null, key); - } - } - - matchedDirectoryResources.forEach((dir, content) -> { - content.sort(Comparator.naturalOrder()); - collector.addDirectoryResource(null, dir, String.join(System.lineSeparator(), content), false); - }); } - private static void scanJar(Path jarPath, ResourceCollector collector, boolean includeAll) throws IOException { + private static void scanJar(Path jarPath, ResourceCollector collector, boolean includeCurrent) throws IOException { try (JarFile jf = new JarFile(jarPath.toFile())) { Enumeration entries = jf.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); - URI uri = jarPath.toUri(); + String entryName = entry.getName(); if (entry.isDirectory()) { - String dirName = entry.getName().substring(0, entry.getName().length() - 1); - if (shouldIncludeEntry(null, collector, dirName, uri, includeAll)) { - // Register the directory with empty content to preserve Java behavior - collector.addDirectoryResource(null, dirName, "", true); - } - } else { - if (shouldIncludeEntry(null, collector, entry.getName(), uri, includeAll)) { - try (InputStream is = jf.getInputStream(entry)) { - collector.addResource(null, entry.getName(), is, true); - } catch (IOException resourceException) { - collector.registerIOException(null, entry.getName(), resourceException, LinkAtBuildTimeSupport.singleton().packageOrClassAtBuildTime(entry.getName())); - } - } + entryName = entryName.substring(0, entry.getName().length() - 1); + } + + List conditions = shouldIncludeEntry(null, collector, entryName, jarPath.toUri(), includeCurrent); + for (ConfigurationCondition condition : conditions) { + includeResource(collector, null, entryName, condition); } } } } - private static boolean shouldIncludeEntry(Module module, ResourceCollector collector, String fileName, URI uri, boolean includeAll) { - var isIncluded = collector.isIncluded(module, fileName, uri); - return isIncluded || (includeAll && !(fileName.endsWith(".class") || fileName.endsWith(".jar"))); + private static void includeResource(ResourceCollector collector, Module module, String name, ConfigurationCondition condition) { + if (ConfigurationCondition.isAlwaysTrue(condition)) { + collector.addResource(module, name); + } else { + collector.addResourceConditionally(module, name, condition); + } + } + + private static List shouldIncludeEntry(Module module, ResourceCollector collector, String fileName, URI uri, boolean includeCurrent) { + if (includeCurrent && !(fileName.endsWith(".class") || fileName.endsWith(".jar"))) { + return Collections.singletonList(ConfigurationCondition.alwaysTrue()); + } + + return collector.isIncluded(module, fileName, uri); } @Override @@ -282,6 +274,11 @@ public List getResourceBundle(String bundleSpec, Locale locale) return resourceBundles; } + @Override + public Map> getPackageToModules() { + return packageToModules; + } + private static String packageName(String bundleName) { int classSep = bundleName.lastIndexOf('.'); if (classSep == -1) { 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 99f6b23593fe..f46a8f9c3d77 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 @@ -32,32 +32,36 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.net.JarURLConnection; import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Set; +import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.LongAdder; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; -import jdk.graal.compiler.api.replacements.SnippetReflectionProvider; -import jdk.graal.compiler.debug.DebugContext; -import jdk.graal.compiler.nodes.ValueNode; -import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration; -import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderContext; -import jdk.graal.compiler.nodes.graphbuilderconf.InvocationPlugin; -import jdk.graal.compiler.nodes.graphbuilderconf.InvocationPlugins; -import jdk.graal.compiler.options.Option; -import jdk.graal.compiler.options.OptionType; -import jdk.graal.compiler.phases.util.Providers; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.hosted.RuntimeResourceAccess; import org.graalvm.nativeimage.impl.ConfigurationCondition; @@ -83,12 +87,21 @@ import com.oracle.svm.core.option.LocatableMultiOptionValue; import com.oracle.svm.core.util.UserError; import com.oracle.svm.core.util.VMError; -import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; import com.oracle.svm.hosted.config.ConfigurationParserUtils; import com.oracle.svm.hosted.jdk.localization.LocalizationFeature; import com.oracle.svm.util.LogUtils; +import com.oracle.svm.util.ModuleSupport; import com.oracle.svm.util.ReflectionUtil; +import jdk.graal.compiler.api.replacements.SnippetReflectionProvider; +import jdk.graal.compiler.nodes.ValueNode; +import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration; +import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderContext; +import jdk.graal.compiler.nodes.graphbuilderconf.InvocationPlugin; +import jdk.graal.compiler.nodes.graphbuilderconf.InvocationPlugins; +import jdk.graal.compiler.options.Option; +import jdk.graal.compiler.options.OptionType; +import jdk.graal.compiler.phases.util.Providers; import jdk.vm.ci.meta.ResolvedJavaMethod; /** @@ -133,13 +146,21 @@ public static class Options { } private boolean sealed = false; - private final Set resourcePatternWorkSet = Collections.newSetFromMap(new ConcurrentHashMap<>()); + + private record ConditionalPattern(ConfigurationCondition condition, String pattern) { + } + + private record CompiledConditionalPattern(ConfigurationCondition condition, ResourcePattern compiledPattern) { + } + + private Set resourcePatternWorkSet = Collections.newSetFromMap(new ConcurrentHashMap<>()); private final Set excludedResourcePatterns = Collections.newSetFromMap(new ConcurrentHashMap<>()); private int loadedConfigurations; private ImageClassLoader imageClassLoader; private class ResourcesRegistryImpl extends ConditionalConfigurationRegistry implements ResourcesRegistry { private final ConfigurationTypeResolver configurationTypeResolver; + private final Set alreadyAddedResources = new HashSet<>(); ResourcesRegistryImpl(ConfigurationTypeResolver configurationTypeResolver) { this.configurationTypeResolver = configurationTypeResolver; @@ -150,10 +171,26 @@ public void addResources(ConfigurationCondition condition, String pattern) { if (configurationTypeResolver.resolveConditionType(condition.getTypeName()) == null) { return; } - registerConditionalConfiguration(condition, () -> { - UserError.guarantee(!sealed, "Resources added too late: %s", pattern); - resourcePatternWorkSet.add(pattern); - }); + + try { + resourcePatternWorkSet.add(new ConditionalPattern(condition, pattern)); + } catch (UnsupportedOperationException e) { + throw UserError.abort("Resource registration should be performed before beforeAnalysis phase."); + } + } + + /* Adds single resource defined with its module and name */ + @Override + public void addResource(Module module, String resourcePath) { + if (!shouldRegisterResource(module, resourcePath)) { + return; + } + + if (module != null && module.isNamed()) { + processResourceFromModule(module, resourcePath); + } else { + processResourceFromClasspath(resourcePath); + } } @Override @@ -196,6 +233,181 @@ public void addResourceBundles(ConfigurationCondition condition, String basename } registerConditionalConfiguration(condition, () -> ImageSingletons.lookup(LocalizationFeature.class).prepareBundle(basename, locales)); } + + /* + * 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 + * register same resource for each satisfied condition. This function will check if the + * resource is already registered and prevent multiple registrations of same resource under + * different conditions + */ + public boolean shouldRegisterResource(Module module, String resourceName) { + // we only do this if we are on the classPath + if ((module == null || !module.isNamed())) { + /* + * This check is not thread safe! If the resource is not added yet, maybe some other + * thread is attempting to do it, so we have to perform same check again in + * synchronized block (in that case). Anyway this check will cut the case when we + * are sure that resource is added (so we don't need to enter synchronized block) + */ + if (alreadyAddedResources.contains(resourceName)) { + return false; + } + + // addResources can be called from multiple threads + synchronized (alreadyAddedResources) { + if (!alreadyAddedResources.contains(resourceName)) { + alreadyAddedResources.add(resourceName); + return true; + } else { + return false; + } + } + } else { + // always try to register module entries (we will check duplicates in addEntries) + return true; + } + } + + private void processResourceFromModule(Module module, String resourcePath) { + try { + String resourcePackage = jdk.internal.module.Resources.toPackageName(resourcePath); + if (!resourcePackage.isEmpty()) { + // if processing resource package, make sure that module exports that package + if (module.getPackages().contains(resourcePackage)) { + // Use Access.OPEN to find ALL resources within resource package + ModuleSupport.accessModuleByClass(ModuleSupport.Access.OPEN, ResourcesFeature.class, module, resourcePackage); + } + } + + InputStream is = module.getResourceAsStream(resourcePath); + boolean isDirectory = Files.isDirectory(Path.of(resourcePath)); + if (isDirectory) { + String content = getDirectoryContent(resourcePath, false); + Resources.singleton().registerDirectoryResource(module, resourcePath, content, false); + } else { + registerResource(module, resourcePath, false, is); + } + } catch (IOException e) { + Resources.singleton().registerIOException(module, resourcePath, e, LinkAtBuildTimeSupport.singleton().packageOrClassAtBuildTime(resourcePath)); + } + } + + private void processResourceFromClasspath(String resourcePath) { + Enumeration urls; + try { + /* + * There is an edge case where same resource name can be present in multiple jars + * (different resources), so we are collecting all resources with given name in all + * jars on classpath + */ + urls = imageClassLoader.getClassLoader().getResources(resourcePath); + } catch (IOException e) { + throw VMError.shouldNotReachHere("getResources for resourcePath " + resourcePath + " failed", e); + } + + while (urls.hasMoreElements()) { + URL url = urls.nextElement(); + try { + InputStream is = url.openStream(); + boolean fromJar = url.getProtocol().equalsIgnoreCase("jar"); + boolean isDirectory = resourceIsDirectory(url, fromJar, resourcePath); + if (isDirectory) { + String content = getDirectoryContent(fromJar ? url.toString() : Paths.get(url.toURI()).toString(), fromJar); + Resources.singleton().registerDirectoryResource(null, resourcePath, content, fromJar); + } else { + registerResource(null, resourcePath, fromJar, is); + } + } catch (IOException e) { + Resources.singleton().registerIOException(null, resourcePath, e, LinkAtBuildTimeSupport.singleton().packageOrClassAtBuildTime(resourcePath)); + return; + } catch (URISyntaxException e) { + throw VMError.shouldNotReachHere("resourceIsDirectory for resourcePath " + resourcePath + " failed", e); + } + } + } + + private void registerResource(Module module, String resourcePath, boolean fromJar, InputStream is) { + if (is == null) { + return; + } + + Resources.singleton().registerResource(module, resourcePath, is, fromJar); + + try { + is.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /* Util functions for resource attributes calculations */ + private String urlToJarPath(URL url) { + try { + return ((JarURLConnection) url.openConnection()).getJarFileURL().getFile(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private boolean resourceIsDirectory(URL url, boolean fromJar, String resourcePath) throws IOException, URISyntaxException { + if (fromJar) { + try (JarFile jf = new JarFile(urlToJarPath(url))) { + return jf.getEntry(resourcePath).isDirectory(); + } + } else { + return Files.isDirectory(Path.of(url.toURI())); + } + } + + private String getDirectoryContent(String path, boolean fromJar) throws IOException { + Set content = new TreeSet<>(); + if (fromJar) { + try (JarFile jf = new JarFile(urlToJarPath(URI.create(path).toURL()))) { + String pathSeparator = FileSystems.getDefault().getSeparator(); + String directoryPath = path.split("!")[1]; + + // we are removing leading slash because jar entry names don't start with slash + if (directoryPath.startsWith(pathSeparator)) { + directoryPath = directoryPath.substring(1); + } + + Enumeration entries = jf.entries(); + while (entries.hasMoreElements()) { + String entry = entries.nextElement().getName(); + if (entry.startsWith(directoryPath)) { + String contentEntry = entry.substring(directoryPath.length()); + + // remove the leading slash + if (contentEntry.startsWith(pathSeparator)) { + contentEntry = contentEntry.substring(1); + } + + // prevent adding empty strings as a content + if (!contentEntry.isEmpty()) { + // get top level content only + int firstSlash = contentEntry.indexOf(pathSeparator); + if (firstSlash != -1) { + content.add(contentEntry.substring(0, firstSlash)); + } else { + content.add(contentEntry); + } + } + } + } + + } + } else { + try (Stream contentStream = Files.list(Path.of(path))) { + content = new TreeSet<>(contentStream + .map(Path::getFileName) + .map(Path::toString) + .toList()); + } + } + + return String.join(System.lineSeparator(), content); + } } @Override @@ -217,30 +429,60 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurations(parser, imageClassLoader, "resource", ConfigurationFiles.Options.ResourceConfigurationFiles, ConfigurationFiles.Options.ResourceConfigurationResources, ConfigurationFile.RESOURCES.getFileName()); - - resourcePatternWorkSet.addAll(Options.IncludeResources.getValue().values()); + resourcePatternWorkSet.addAll(Options.IncludeResources.getValue() + .values() + .stream() + .map(e -> new ConditionalPattern(ConfigurationCondition.alwaysTrue(), e)) + .toList()); excludedResourcePatterns.addAll(Options.ExcludeResources.getValue().values()); + + if (!resourcePatternWorkSet.isEmpty()) { + FeatureImpl.BeforeAnalysisAccessImpl beforeAnalysisAccess = (FeatureImpl.BeforeAnalysisAccessImpl) access; + Set includePatterns = resourcePatternWorkSet + .stream() + .map(e -> new CompiledConditionalPattern(e.condition(), makeResourcePattern(e.pattern()))) + .collect(Collectors.toSet()); + if (MissingRegistrationUtils.throwMissingRegistrationErrors()) { + includePatterns.stream() + .map(pattern -> pattern.compiledPattern) + .forEach(resourcePattern -> { + Resources.singleton().registerIncludePattern(resourcePattern.moduleName, resourcePattern.pattern.pattern()); + }); + } + ResourcePattern[] excludePatterns = compilePatterns(excludedResourcePatterns); + ResourceCollectorImpl collector = new ResourceCollectorImpl(includePatterns, excludePatterns, beforeAnalysisAccess); + try { + collector.prepareProgressReporter(); + ImageSingletons.lookup(ClassLoaderSupport.class).collectResources(collector); + } finally { + collector.shutDownProgressReporter(); + } + + // We set resourcePatternWorkSet to empty unmodifiable set, so we can be sure that it + // won't be populated in some later phase + resourcePatternWorkSet = Set.of(); + } + resourceRegistryImpl().flushConditionalConfiguration(access); } private static final class ResourceCollectorImpl implements ResourceCollector { - private final DebugContext debugContext; - private final ResourcePattern[] includePatterns; + private final Set includePatterns; private final ResourcePattern[] excludePatterns; - private static final int WATCHDOG_RESET_AFTER_EVERY_N_RESOURCES = 1000; private static final int WATCHDOG_INITIAL_WARNING_AFTER_N_SECONDS = 60; private static final int WATCHDOG_WARNING_AFTER_EVERY_N_SECONDS = 20; + private final FeatureImpl.BeforeAnalysisAccessImpl access; private final LongAdder reachedResourceEntries; private boolean initialReport; private volatile String currentlyProcessedEntry; ScheduledExecutorService scheduledExecutor; - private ResourceCollectorImpl(DebugContext debugContext, ResourcePattern[] includePatterns, ResourcePattern[] excludePatterns) { - this.debugContext = debugContext; + private ResourceCollectorImpl(Set includePatterns, ResourcePattern[] excludePatterns, FeatureImpl.BeforeAnalysisAccessImpl access) { this.includePatterns = includePatterns; this.excludePatterns = excludePatterns; + this.access = access; this.reachedResourceEntries = new LongAdder(); this.initialReport = true; this.currentlyProcessedEntry = null; @@ -267,7 +509,7 @@ private void shutDownProgressReporter() { } @Override - public boolean isIncluded(Module module, String resourceName, URI resource) { + public List isIncluded(Module module, String resourceName, URI resource) { this.currentlyProcessedEntry = resource.getScheme().equals("jrt") ? (resource + "/" + resourceName) : resource.toString(); this.reachedResourceEntries.increment(); @@ -283,30 +525,32 @@ public boolean isIncluded(Module module, String resourceName, URI resource) { continue; } if (rp.pattern.matcher(resourceName).matches() || rp.pattern.matcher(relativePathWithTrailingSlash).matches()) { - return false; + return List.of(); // nothing should match excluded resource } } - for (ResourcePattern rp : includePatterns) { - if (!rp.moduleNameMatches(moduleName)) { + // Possibly we can have multiple conditions for one resource + List conditions = new ArrayList<>(); + for (CompiledConditionalPattern rp : includePatterns) { + if (!rp.compiledPattern().moduleNameMatches(moduleName)) { continue; } - if (rp.pattern.matcher(resourceName).matches() || rp.pattern.matcher(relativePathWithTrailingSlash).matches()) { - return true; + if (rp.compiledPattern().pattern.matcher(resourceName).matches() || rp.compiledPattern().pattern.matcher(relativePathWithTrailingSlash).matches()) { + conditions.add(rp.condition()); } } - return false; + return conditions; } @Override - public void addResource(Module module, String resourceName, InputStream resourceStream, boolean fromJar) { - registerResource(debugContext, module, resourceName, resourceStream, fromJar); + public void addResource(Module module, String resourceName) { + ImageSingletons.lookup(RuntimeResourceSupport.class).addResource(module, resourceName); } @Override - public void addDirectoryResource(Module module, String dir, String content, boolean fromJar) { - registerDirectoryResource(debugContext, module, dir, content, fromJar); + public void addResourceConditionally(Module module, String resourceName, ConfigurationCondition condition) { + access.registerReachabilityHandler(e -> addResource(module, resourceName), access.findClassByName(condition.getTypeName())); } @Override @@ -320,34 +564,6 @@ public void registerNegativeQuery(Module module, String resourceName) { } } - @Override - public void duringAnalysis(DuringAnalysisAccess access) { - resourceRegistryImpl().flushConditionalConfiguration(access); - if (resourcePatternWorkSet.isEmpty()) { - return; - } - - access.requireAnalysisIteration(); - - DuringAnalysisAccessImpl duringAnalysisAccess = ((DuringAnalysisAccessImpl) access); - ResourcePattern[] includePatterns = compilePatterns(resourcePatternWorkSet); - if (MissingRegistrationUtils.throwMissingRegistrationErrors()) { - for (ResourcePattern resourcePattern : includePatterns) { - Resources.singleton().registerIncludePattern(resourcePattern.moduleName, resourcePattern.pattern.pattern()); - } - } - ResourcePattern[] excludePatterns = compilePatterns(excludedResourcePatterns); - DebugContext debugContext = duringAnalysisAccess.getDebugContext(); - ResourceCollectorImpl collector = new ResourceCollectorImpl(debugContext, includePatterns, excludePatterns); - try { - collector.prepareProgressReporter(); - ImageSingletons.lookup(ClassLoaderSupport.class).collectResources(collector); - } finally { - collector.shutDownProgressReporter(); - } - resourcePatternWorkSet.clear(); - } - private ResourcePattern[] compilePatterns(Set patterns) { return patterns.stream() .filter(s -> s.length() > 0) @@ -366,14 +582,7 @@ private ResourcePattern makeResourcePattern(String rawPattern) { } } - private static final class ResourcePattern { - final String moduleName; - final Pattern pattern; - - private ResourcePattern(String moduleName, Pattern pattern) { - this.moduleName = moduleName; - this.pattern = pattern; - } + private record ResourcePattern(String moduleName, Pattern pattern) { boolean moduleNameMatches(String resourceContainerModuleName) { if (moduleName == null) { @@ -404,24 +613,6 @@ public void beforeCompilation(BeforeCompilationAccess access) { } } - @SuppressWarnings("try") - private static void registerResource(DebugContext debugContext, Module module, String resourceName, InputStream resourceStream, boolean fromJar) { - try (DebugContext.Scope s = debugContext.scope("registerResource")) { - String moduleNamePrefix = module == null ? "" : module.getName() + ":"; - debugContext.log(DebugContext.VERBOSE_LEVEL, "ResourcesFeature: registerResource: %s%s", moduleNamePrefix, resourceName); - Resources.singleton().registerResource(module, resourceName, resourceStream, fromJar); - } - } - - @SuppressWarnings("try") - private static void registerDirectoryResource(DebugContext debugContext, Module module, String dir, String content, boolean fromJar) { - try (DebugContext.Scope s = debugContext.scope("registerResource")) { - String moduleNamePrefix = module == null ? "" : module.getName() + ":"; - debugContext.log(DebugContext.VERBOSE_LEVEL, "ResourcesFeature: registerResource: %s%s", moduleNamePrefix, dir); - Resources.singleton().registerDirectoryResource(module, dir, content, fromJar); - } - } - @Override public void registerInvocationPlugins(Providers providers, SnippetReflectionProvider snippetReflection, GraphBuilderConfiguration.Plugins plugins, ParsingReason reason) { if (!reason.duringAnalysis() || reason == ParsingReason.JITCompilation) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/localization/CharsetSubstitutionsFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/localization/CharsetSubstitutionsFeature.java index 0d54073ac9ba..8da31a190df0 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/localization/CharsetSubstitutionsFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/localization/CharsetSubstitutionsFeature.java @@ -24,17 +24,16 @@ */ package com.oracle.svm.hosted.jdk.localization; -import org.graalvm.nativeimage.ImageSingletons; -import org.graalvm.nativeimage.impl.ConfigurationCondition; +import org.graalvm.nativeimage.hosted.RuntimeResourceAccess; -import com.oracle.svm.core.configure.ResourcesRegistry; -import com.oracle.svm.core.feature.InternalFeature; import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.InternalFeature; @AutomaticallyRegisteredFeature class CharsetSubstitutionsFeature implements InternalFeature { @Override public void beforeAnalysis(BeforeAnalysisAccess access) { - ImageSingletons.lookup(ResourcesRegistry.class).addResources(ConfigurationCondition.create("java.lang.CharacterName"), "java/lang/uniName.dat"); + access.registerReachabilityHandler((e) -> RuntimeResourceAccess.addResource(Class.class.getModule(), + "java/lang/uniName.dat"), access.findClassByName("java.lang.CharacterName")); } } 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 573ef4ddb6d9..89dc8f45fdcf 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 @@ -61,12 +61,6 @@ import java.util.stream.Collectors; import org.graalvm.collections.Pair; -import jdk.graal.compiler.nodes.ValueNode; -import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderContext; -import jdk.graal.compiler.nodes.graphbuilderconf.NodePlugin; -import jdk.graal.compiler.options.Option; -import jdk.graal.compiler.options.OptionStability; -import jdk.graal.compiler.options.OptionType; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -89,7 +83,14 @@ import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.FeatureImpl.DuringAnalysisAccessImpl; import com.oracle.svm.hosted.FeatureImpl.DuringSetupAccessImpl; +import com.oracle.svm.hosted.ImageClassLoader; +import jdk.graal.compiler.nodes.ValueNode; +import jdk.graal.compiler.nodes.graphbuilderconf.GraphBuilderContext; +import jdk.graal.compiler.nodes.graphbuilderconf.NodePlugin; +import jdk.graal.compiler.options.Option; +import jdk.graal.compiler.options.OptionStability; +import jdk.graal.compiler.options.OptionType; import jdk.internal.access.SharedSecrets; import jdk.vm.ci.meta.ResolvedJavaField; import jdk.vm.ci.meta.ResolvedJavaMethod; @@ -161,6 +162,7 @@ public class LocalizationFeature implements InternalFeature { private Field localeObjectCacheMapField; private Field langAliasesCacheField; private Field parentLocalesMapField; + @Platforms(Platform.HOSTED_ONLY.class) private ImageClassLoader imageClassLoader; public static class Options { @Option(help = "Comma separated list of bundles to be included into the image.", type = OptionType.User)// @@ -292,6 +294,8 @@ public void duringSetup(DuringSetupAccess a) { String reason = "All ResourceBundleControlProvider that are registered as services end up as objects in the image heap, and are therefore registered to be initialized at image build time"; ServiceLoader.load(ResourceBundleControlProvider.class).stream() .forEach(provider -> ImageSingletons.lookup(RuntimeClassInitializationSupport.class).initializeAtBuildTime(provider.type(), reason)); + + this.imageClassLoader = access.getImageClassLoader(); } /** @@ -639,14 +643,14 @@ private void prepareBundle(String bundleName, ResourceBundle bundle, Locale loca */ for (ResourceBundle cur = bundle; cur != null; cur = SharedSecrets.getJavaUtilResourceBundleAccess().getParent(cur)) { /* Register all bundles with their corresponding locales */ - support.prepareBundle(bundleName, cur, cur.getLocale()); + support.prepareBundle(bundleName, cur, this.imageClassLoader::findModule, cur.getLocale()); } /* * Finally, register the requested bundle with requested locale (Requested might be more * specific than the actual bundle locale */ - support.prepareBundle(bundleName, bundle, locale); + support.prepareBundle(bundleName, bundle, this.imageClassLoader::findModule, locale); } @Platforms(Platform.HOSTED_ONLY.class) diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/xml/XMLParsersRegistration.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/xml/XMLParsersRegistration.java index ed1cb565df8f..13940b2be407 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/xml/XMLParsersRegistration.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/xml/XMLParsersRegistration.java @@ -135,7 +135,20 @@ void registerResources() { ImageSingletons.lookup(RuntimeResourceSupport.class).addResourceBundles(ConfigurationCondition.alwaysTrue(), "com.sun.org.apache.xml.internal.serializer.utils.SerializerMessages"); ImageSingletons.lookup(RuntimeResourceSupport.class).addResourceBundles(ConfigurationCondition.alwaysTrue(), "com.sun.org.apache.xalan.internal.xsltc.compiler.util.ErrorMessages"); - ImageSingletons.lookup(RuntimeResourceSupport.class).addResources(ConfigurationCondition.alwaysTrue(), "com.sun.*.properties"); + ImageSingletons.lookup(RuntimeResourceSupport.class).addResourceBundles(ConfigurationCondition.alwaysTrue(), "com.sun.org.apache.xml.internal.serializer.Encodings"); + ImageSingletons.lookup(RuntimeResourceSupport.class).addResourceBundles(ConfigurationCondition.alwaysTrue(), "com.sun.org.apache.xml.internal.serializer.HTMLEntities"); + ImageSingletons.lookup(RuntimeResourceSupport.class).addResourceBundles(ConfigurationCondition.alwaysTrue(), "com.sun.org.apache.xml.internal.serializer.XMLEntities"); + ImageSingletons.lookup(RuntimeResourceSupport.class).addResourceBundles(ConfigurationCondition.alwaysTrue(), "com.sun.org.apache.xerces.internal.impl.xpath.regex.message"); + ImageSingletons.lookup(RuntimeResourceSupport.class).addResourceBundles(ConfigurationCondition.alwaysTrue(), "com.sun.org.apache.xerces.internal.impl.msg.DOMMessages"); + ImageSingletons.lookup(RuntimeResourceSupport.class).addResourceBundles(ConfigurationCondition.alwaysTrue(), "com.sun.org.apache.xerces.internal.impl.msg.DatatypeMessages"); + ImageSingletons.lookup(RuntimeResourceSupport.class).addResourceBundles(ConfigurationCondition.alwaysTrue(), "com.sun.org.apache.xerces.internal.impl.msg.JAXPValidationMessages"); + ImageSingletons.lookup(RuntimeResourceSupport.class).addResourceBundles(ConfigurationCondition.alwaysTrue(), "com.sun.org.apache.xerces.internal.impl.msg.SAXMessages"); + ImageSingletons.lookup(RuntimeResourceSupport.class).addResourceBundles(ConfigurationCondition.alwaysTrue(), "com.sun.org.apache.xerces.internal.impl.msg.XIncludeMessages"); + ImageSingletons.lookup(RuntimeResourceSupport.class).addResourceBundles(ConfigurationCondition.alwaysTrue(), "com.sun.org.apache.xerces.internal.impl.msg.XMLMessages"); + ImageSingletons.lookup(RuntimeResourceSupport.class).addResourceBundles(ConfigurationCondition.alwaysTrue(), "com.sun.org.apache.xerces.internal.impl.msg.XMLSchemaMessages"); + ImageSingletons.lookup(RuntimeResourceSupport.class).addResourceBundles(ConfigurationCondition.alwaysTrue(), "com.sun.org.apache.xerces.internal.impl.msg.XMLSerializerMessages"); + ImageSingletons.lookup(RuntimeResourceSupport.class).addResourceBundles(ConfigurationCondition.alwaysTrue(), "com.sun.org.apache.xerces.internal.impl.msg.XPointerMessages"); + ImageSingletons.lookup(RuntimeResourceSupport.class).addResourceBundles(ConfigurationCondition.alwaysTrue(), "com.sun.org.apache.xalan.internal.res.XSLTInfo"); classInitializationSupport.setConfigurationSealed(true); } diff --git a/substratevm/src/com.oracle.svm.test/src/META-INF/native-image/com.oracle.svm.test/resource-config.json b/substratevm/src/com.oracle.svm.test/src/META-INF/native-image/com.oracle.svm.test/resource-config.json new file mode 100644 index 000000000000..16146de3e580 --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/META-INF/native-image/com.oracle.svm.test/resource-config.json @@ -0,0 +1,13 @@ +{ + "bundles": [], + "resources": { + "includes": [ + { + "condition": { + "typeReachable": "java.lang.String" + }, + "pattern": "resourcesFromDir" + } + ] + } +} \ No newline at end of file diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/NativeImageResourceTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/NativeImageResourceTest.java index 2afbe70d49cb..a4fe9af3cfbf 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/NativeImageResourceTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/NativeImageResourceTest.java @@ -30,21 +30,26 @@ import static com.oracle.svm.test.NativeImageResourceUtils.RESOURCE_FILE_2; import static com.oracle.svm.test.NativeImageResourceUtils.RESOURCE_FILE_3; import static com.oracle.svm.test.NativeImageResourceUtils.RESOURCE_FILE_4; +import static com.oracle.svm.test.NativeImageResourceUtils.SIMPLE_RESOURCE_DIR; import static com.oracle.svm.test.NativeImageResourceUtils.compareTwoURLs; import static com.oracle.svm.test.NativeImageResourceUtils.resourceNameToURL; +import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.net.URLConnection; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.stream.Collectors; +import java.util.stream.IntStream; import java.util.stream.StreamSupport; import org.junit.Assert; @@ -126,6 +131,36 @@ public void classGetDirectoryResource() { resourceNameToURL(nonCanonicalResourceDirectoryName, false); } + @Test + public void registeredResourceDirectoryHasContent() throws IOException { + URL directory = NativeImageResourceUtils.class.getResource(SIMPLE_RESOURCE_DIR); + Assert.assertNotNull("Resource " + SIMPLE_RESOURCE_DIR + " is not found!", directory); + + BufferedReader reader = new BufferedReader(new InputStreamReader(directory.openStream())); + Assert.assertNotNull("Resource" + SIMPLE_RESOURCE_DIR + " should have content", reader.readLine()); + } + + @Test + public void getConditionalDirectoryResource() throws IOException { + // check if resource is added conditionally + String directoryName = "/resourcesFromDir"; + URL directory = NativeImageResourceUtils.class.getResource(directoryName); + Assert.assertNotNull("Resource " + directoryName + " is not found!", directory); + + // check content of resource + List expected = IntStream.range(0, 4).mapToObj(i -> "cond-resource" + i + ".txt").toList(); + BufferedReader reader = new BufferedReader(new InputStreamReader(directory.openStream())); + List actual = new ArrayList<>(); + String line; + while ((line = reader.readLine()) != null) { + actual.add(line); + } + + for (String resource : expected) { + Assert.assertTrue(actual.contains(resource)); + } + } + /** *

* Access a resource using {@link URLClassLoader}. diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/NativeImageResourceUtils.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/NativeImageResourceUtils.java index 8d07f56f7f91..7cd52b889c7a 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/NativeImageResourceUtils.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/NativeImageResourceUtils.java @@ -42,6 +42,7 @@ public class NativeImageResourceUtils { public static final String ROOT_DIRECTORY = "/"; public static final String RESOURCE_DIR = "/resources"; + public static final String SIMPLE_RESOURCE_DIR = "/simpleDir"; public static final String RESOURCE_EMPTY_DIR = RESOURCE_DIR + "/empty"; public static final String RESOURCE_FILE_1 = RESOURCE_DIR + "/resource-test1.txt"; public static final String RESOURCE_FILE_2 = RESOURCE_DIR + "/resource-test2.txt"; @@ -55,6 +56,7 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { // Remove leading / for the resource patterns Module resourceModule = TestFeature.class.getModule(); RuntimeResourceAccess.addResource(resourceModule, RESOURCE_DIR.substring(1)); + RuntimeResourceAccess.addResource(resourceModule, SIMPLE_RESOURCE_DIR.substring(1)); RuntimeResourceAccess.addResource(resourceModule, RESOURCE_EMPTY_DIR.substring(1)); RuntimeResourceAccess.addResource(resourceModule, RESOURCE_FILE_1.substring(1)); RuntimeResourceAccess.addResource(resourceModule, RESOURCE_FILE_2.substring(1)); diff --git a/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleBaseFeature.java b/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleBaseFeature.java index 75f89555ad7b..5bc549275818 100644 --- a/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleBaseFeature.java +++ b/substratevm/src/com.oracle.svm.truffle/src/com/oracle/svm/truffle/TruffleBaseFeature.java @@ -229,8 +229,6 @@ public boolean getAsBoolean() { private final Set> registeredClasses = new HashSet<>(); private final Map, PossibleReplaceCandidatesSubtypeHandler> subtypeChecks = new HashMap<>(); private boolean profilingEnabled; - private boolean needsAllEncodings; - private Field uncachedDispatchField; private Field layoutInfoMapField; private Field layoutMapField; @@ -241,6 +239,8 @@ public boolean getAsBoolean() { private Consumer markAsUnsafeAccessed; private final ConcurrentMap processedInlinedFields = new ConcurrentHashMap<>(); + private boolean needsAllEncodings; + private static void initializeTruffleReflectively(ClassLoader imageClassLoader) { invokeStaticMethod("com.oracle.truffle.api.impl.Accessor", "getTVMCI", Collections.emptyList()); invokeStaticMethod("com.oracle.truffle.polyglot.InternalResourceCache", "initializeNativeImageState", @@ -449,6 +449,10 @@ public void duringSetup(DuringSetupAccess access) { if (Options.TruffleCheckPreinitializedFiles.getValue()) { access.registerObjectReplacer(new TruffleFileCheck(((FeatureImpl.DuringSetupAccessImpl) access).getHostVM().getClassInitializationSupport())); } + + if (needsAllEncodings) { + ImageSingletons.lookup(RuntimeResourceSupport.class).addResources(ConfigurationCondition.alwaysTrue(), "org/graalvm/shadowed/org/jcodings/tables/.*bin$"); + } } void setGraalGraphObjectReplacer(GraalGraphObjectReplacer graalGraphObjectReplacer) { @@ -482,9 +486,6 @@ public void beforeAnalysis(BeforeAnalysisAccess access) { config.registerSubtypeReachabilityHandler(TruffleBaseFeature::registerTruffleLibrariesAsInHeap, LibraryExport.class); - if (needsAllEncodings) { - ImageSingletons.lookup(RuntimeResourceSupport.class).addResources(ConfigurationCondition.alwaysTrue(), "org/graalvm/shadowed/org/jcodings/tables/.*bin$"); - } Class frameClass = config.findClassByName("com.oracle.truffle.api.impl.FrameWithoutBoxing"); config.registerFieldValueTransformer(config.findField(frameClass, "ASSERTIONS_ENABLED"), new AssertionStatusFieldTransformer(frameClass)); } diff --git a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ModuleSupport.java b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ModuleSupport.java index 03ec51bdfa18..4b5738a6116d 100644 --- a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ModuleSupport.java +++ b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/ModuleSupport.java @@ -131,7 +131,7 @@ public static void accessPackagesToClass(Access access, Class accessingClass, } @Platforms(Platform.HOSTED_ONLY.class) - private static void accessModuleByClass(Access access, Class accessingClass, Module declaringModule, String packageName) { + public static void accessModuleByClass(Access access, Class accessingClass, Module declaringModule, String packageName) { Module namedAccessingModule = null; if (accessingClass != null) { Module accessingModule = accessingClass.getModule(); diff --git a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/StringUtil.java b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/StringUtil.java index f6061945d9d2..d8c339153c63 100644 --- a/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/StringUtil.java +++ b/substratevm/src/com.oracle.svm.util/src/com/oracle/svm/util/StringUtil.java @@ -97,4 +97,12 @@ public static final String joinSingleQuoted(Object[] values) { public static final String joinSingleQuoted(List strings) { return joinSingleQuoted(strings.toArray(new String[strings.size()])); } + + public static String toSlashSeparated(String string) { + return string.replace('.', '/'); + } + + public static String toDotSeparated(String string) { + return string.replace('/', '.'); + } }