Skip to content

Commit

Permalink
[GR-41209] Modular Resources API
Browse files Browse the repository at this point in the history
PullRequest: graal/13853
  • Loading branch information
ivan-ristovic committed Mar 13, 2023
2 parents 3a73004 + 885b218 commit 382eca0
Show file tree
Hide file tree
Showing 14 changed files with 117 additions and 91 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ public boolean isNativeImageClassLoader(ClassLoader classLoader) {

public interface ResourceCollector {

boolean isIncluded(String moduleName, String resourceName, URI resourceURI);
boolean isIncluded(Module module, String resourceName, URI resourceURI);

void addResource(String moduleName, String resourceName, InputStream resourceStream, boolean fromJar);
void addResource(Module module, String resourceName, InputStream resourceStream, boolean fromJar);

void addDirectoryResource(String moduleName, String dir, String content, boolean fromJar);
void addDirectoryResource(Module module, String dir, String content, boolean fromJar);
}

public abstract void collectResources(ResourceCollector resourceCollector);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -786,9 +786,8 @@ public Enum<?>[] getEnumConstantsShared() {

@Substitute
public InputStream getResourceAsStream(String resourceName) {
String moduleName = module == null ? null : module.getName();
String resolvedName = resolveName(resourceName);
return Resources.createInputStream(moduleName, resolvedName);
return Resources.createInputStream(module, resolvedName);
}

@KeepOriginal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,26 @@ public static Resources singleton() {
Resources() {
}

public EconomicMap<Pair<String, String>, ResourceStorageEntry> resources() {
public EconomicMap<Pair<String, String>, ResourceStorageEntry> getResourceStorage() {
return resources;
}

public Iterable<ResourceStorageEntry> resources() {
return resources.getValues();
}

public int count() {
return resources.size();
}

public long getLastModifiedTime() {
return lastModifiedTime;
}

public static String moduleName(Module module) {
return module == null ? null : module.getName();
}

public static byte[] inputStreamToByteArray(InputStream is) {
try {
return is.readAllBytes();
Expand All @@ -99,7 +111,8 @@ public static byte[] inputStreamToByteArray(InputStream is) {
}
}

private static void addEntry(String moduleName, String resourceName, boolean isDirectory, byte[] data, boolean fromJar) {
private static void addEntry(Module module, String resourceName, boolean isDirectory, byte[] data, boolean fromJar) {
String moduleName = moduleName(module);
var resources = singleton().resources;
synchronized (resources) {
Pair<String, String> key = Pair.create(moduleName, resourceName);
Expand All @@ -126,18 +139,18 @@ public static void registerResource(String resourceName, InputStream is, boolean
}

@Platforms(Platform.HOSTED_ONLY.class)
public static void registerResource(String moduleName, String resourceName, InputStream is) {
registerResource(moduleName, resourceName, is, true);
public static void registerResource(Module module, String resourceName, InputStream is) {
registerResource(module, resourceName, is, true);
}

@Platforms(Platform.HOSTED_ONLY.class)
public static void registerResource(String moduleName, String resourceName, byte[] resourceContent) {
addEntry(moduleName, resourceName, false, resourceContent, true);
public static void registerResource(Module module, String resourceName, byte[] resourceContent) {
addEntry(module, resourceName, false, resourceContent, true);
}

@Platforms(Platform.HOSTED_ONLY.class)
public static void registerResource(String moduleName, String resourceName, InputStream is, boolean fromJar) {
addEntry(moduleName, resourceName, false, inputStreamToByteArray(is), fromJar);
public static void registerResource(Module module, String resourceName, InputStream is, boolean fromJar) {
addEntry(module, resourceName, false, inputStreamToByteArray(is), fromJar);
}

@Platforms(Platform.HOSTED_ONLY.class)
Expand All @@ -151,18 +164,18 @@ public static void registerDirectoryResource(String resourceDirName, String cont
}

@Platforms(Platform.HOSTED_ONLY.class)
public static void registerDirectoryResource(String moduleName, String resourceDirName, String content) {
registerDirectoryResource(moduleName, resourceDirName, content, true);
public static void registerDirectoryResource(Module module, String resourceDirName, String content) {
registerDirectoryResource(module, resourceDirName, content, true);
}

@Platforms(Platform.HOSTED_ONLY.class)
public static void registerDirectoryResource(String moduleName, String resourceDirName, String content, boolean fromJar) {
public static void registerDirectoryResource(Module module, String resourceDirName, String content, boolean fromJar) {
/*
* A directory content represents the names of all files and subdirectories located in the
* 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(moduleName, resourceDirName, true, content.getBytes(), fromJar);
addEntry(module, resourceDirName, true, content.getBytes(), fromJar);
}

/**
Expand Down Expand Up @@ -190,8 +203,9 @@ public static ResourceStorageEntry get(String name) {
return get(null, name);
}

public static ResourceStorageEntry get(String moduleName, String resourceName) {
public static ResourceStorageEntry get(Module module, String resourceName) {
String canonicalResourceName = toCanonicalForm(resourceName);
String moduleName = moduleName(module);
ResourceStorageEntry entry = singleton().resources.get(Pair.create(moduleName, canonicalResourceName));
if (entry == null) {
return null;
Expand All @@ -214,9 +228,10 @@ public static ResourceStorageEntry get(String moduleName, String resourceName) {
}

@SuppressWarnings("deprecation")
private static URL createURL(String moduleName, String resourceName, int index) {
private static URL createURL(Module module, String resourceName, int index) {
try {
String refPart = index != 0 ? '#' + Integer.toString(index) : "";
String moduleName = moduleName(module);
return new URL(JavaNetSubstitutions.RESOURCE_PROTOCOL, moduleName, -1, '/' + resourceName + refPart);
} catch (MalformedURLException ex) {
throw new IllegalStateException(ex);
Expand All @@ -227,12 +242,12 @@ public static URL createURL(String resourceName) {
return createURL(null, resourceName);
}

public static URL createURL(String moduleName, String resourceName) {
public static URL createURL(Module module, String resourceName) {
if (resourceName == null) {
return null;
}

Enumeration<URL> urls = createURLs(moduleName, resourceName);
Enumeration<URL> urls = createURLs(module, resourceName);
return urls.hasMoreElements() ? urls.nextElement() : null;
}

Expand All @@ -241,19 +256,19 @@ public static InputStream createInputStream(String resourceName) {
}

/* Avoid pulling in the URL class when only an InputStream is needed. */
public static InputStream createInputStream(String moduleName, String resourceName) {
public static InputStream createInputStream(Module module, String resourceName) {
if (resourceName == null) {
return null;
}

ResourceStorageEntry entry = Resources.get(moduleName, resourceName);
if (moduleName == null && entry == null) {
ResourceStorageEntry entry = Resources.get(module, resourceName);
if (moduleName(module) == null && entry == null) {
/*
* If no moduleName is specified and entry was not found as classpath-resource we have
* to search for the resource in all modules in the image.
* If module is not specified or is an unnamed module and entry was not found as
* classpath-resource we have to search for the resource in all modules in the image.
*/
for (Module module : BootModuleLayerSupport.instance().getBootLayer().modules()) {
entry = Resources.get(module.getName(), resourceName);
for (Module m : BootModuleLayerSupport.instance().getBootLayer().modules()) {
entry = Resources.get(m, resourceName);
if (entry != null) {
break;
}
Expand All @@ -271,37 +286,37 @@ public static Enumeration<URL> createURLs(String resourceName) {
return createURLs(null, resourceName);
}

public static Enumeration<URL> createURLs(String moduleName, String resourceName) {
public static Enumeration<URL> createURLs(Module module, String resourceName) {
if (resourceName == null) {
return null;
}

List<URL> resourcesURLs = new ArrayList<>();
String canonicalResourceName = toCanonicalForm(resourceName);
boolean shouldAppendTrailingSlash = hasTrailingSlash(resourceName);
/* If moduleName was unspecified we have to consider all modules in the image */
if (moduleName == null) {
for (Module module : BootModuleLayerSupport.instance().getBootLayer().modules()) {
ResourceStorageEntry entry = Resources.get(module.getName(), resourceName);
addURLEntries(resourcesURLs, entry, module.getName(), shouldAppendTrailingSlash ? canonicalResourceName + '/' : canonicalResourceName);
/* If module was unspecified or unnamed, we have to consider all modules in the image */
if (moduleName(module) == null) {
for (Module m : BootModuleLayerSupport.instance().getBootLayer().modules()) {
ResourceStorageEntry entry = Resources.get(m, resourceName);
addURLEntries(resourcesURLs, entry, m, shouldAppendTrailingSlash ? canonicalResourceName + '/' : canonicalResourceName);
}
}
ResourceStorageEntry explicitEntry = Resources.get(moduleName, resourceName);
addURLEntries(resourcesURLs, explicitEntry, moduleName, shouldAppendTrailingSlash ? canonicalResourceName + '/' : canonicalResourceName);
ResourceStorageEntry explicitEntry = Resources.get(module, resourceName);
addURLEntries(resourcesURLs, explicitEntry, module, shouldAppendTrailingSlash ? canonicalResourceName + '/' : canonicalResourceName);

if (resourcesURLs.isEmpty()) {
return Collections.emptyEnumeration();
}
return Collections.enumeration(resourcesURLs);
}

private static void addURLEntries(List<URL> resourcesURLs, ResourceStorageEntry entry, String moduleName, String canonicalResourceName) {
private static void addURLEntries(List<URL> resourcesURLs, ResourceStorageEntry entry, Module module, String canonicalResourceName) {
if (entry == null) {
return;
}
int numberOfResources = entry.getData().size();
for (int index = 0; index < numberOfResources; index++) {
resourcesURLs.add(createURL(moduleName, canonicalResourceName, index));
resourcesURLs.add(createURL(module, canonicalResourceName, index));
}
}
}
Expand All @@ -321,7 +336,7 @@ public void afterCompilation(AfterCompilationAccess access) {
* of lazily initialized fields. Only the byte[] arrays themselves can be safely made
* read-only.
*/
for (ResourceStorageEntry resourceList : Resources.singleton().resources().getValues()) {
for (ResourceStorageEntry resourceList : Resources.singleton().resources()) {
for (byte[] resource : resourceList.getData()) {
access.registerAsImmutable(resource);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ public static URL nameToResourceURL(String resourceName) {
return Resources.createURL(resourceName);
}

public static URL nameToResourceURL(String moduleName, String resourceName) {
return Resources.createURL(moduleName, resourceName);
public static URL nameToResourceURL(Module module, String resourceName) {
return Resources.createURL(module, resourceName);
}

public static InputStream nameToResourceInputStream(String resourceName) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
import java.util.Arrays;
import java.util.Objects;

import com.oracle.svm.core.annotate.Alias;
import com.oracle.svm.core.SubstrateUtil;
import com.oracle.svm.core.annotate.Substitute;
import com.oracle.svm.core.annotate.TargetClass;
import com.oracle.svm.core.annotate.TargetElement;
Expand All @@ -39,17 +39,14 @@
@TargetClass(value = java.lang.Module.class)
final class Target_java_lang_Module {

@Alias //
private String name;

@SuppressWarnings("static-method")
@Substitute
private InputStream getResourceAsStream(String resourceName) {
String resName = resourceName;
if (resName.startsWith("/")) {
resName = resName.substring(1);
}
ResourceStorageEntry res = Resources.get(name, resName);
ResourceStorageEntry res = Resources.get(SubstrateUtil.cast(this, Module.class), resName);
return res == null ? null : new ByteArrayInputStream(res.getData().get(0));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundE

@Substitute
public URL findResource(String mn, String name) {
return ResourcesHelper.nameToResourceURL(mn, name);
Module module = ModuleLayer.boot().findModule(mn).orElse(null);
return ResourcesHelper.nameToResourceURL(module, name);
}

@Substitute
Expand All @@ -81,7 +82,8 @@ private List<URL> findMiscResource(String name) {

@Substitute
private URL findResource(ModuleReference mref, String name) {
return ResourcesHelper.nameToResourceURL(mref.descriptor().name(), name);
Module module = ModuleLayer.boot().findModule(mref.descriptor().name()).orElse(null);
return ResourcesHelper.nameToResourceURL(module, name);
}

@Substitute
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ private Class<?> findClassInModuleOrNull(Target_jdk_internal_loader_Loader_Loade

@Substitute
protected URL findResource(String mn, String name) {
return ResourcesHelper.nameToResourceURL(mn, name);
Module module = ModuleLayer.boot().findModule(mn).orElse(null);
return ResourcesHelper.nameToResourceURL(module, name);
}

@Substitute
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -650,7 +650,7 @@ private void update(Entry e) {
}

private void readAllEntries() {
MapCursor<Pair<String, String>, ResourceStorageEntry> entries = Resources.singleton().resources().getEntries();
MapCursor<Pair<String, String>, ResourceStorageEntry> entries = Resources.singleton().getResourceStorage().getEntries();
while (entries.advance()) {
byte[] name = getBytes(entries.getKey().getRight());
if (!entries.getValue().isDirectory()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ public void connect() {
throw new IllegalArgumentException("Empty URL path not allowed in " + JavaNetSubstitutions.RESOURCE_PROTOCOL + " URL");
}
String resourceName = urlPath.substring(1);
ResourceStorageEntry entry = Resources.get(hostNameOrNull, resourceName);

Module module = hostNameOrNull != null ? ModuleLayer.boot().findModule(hostNameOrNull).orElse(null) : null;
ResourceStorageEntry entry = Resources.get(module, resourceName);
if (entry != null) {
List<byte[]> bytes = entry.getData();
String urlRef = url.getRef();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,25 @@ protected boolean isNativeImageClassLoaderImpl(ClassLoader loader) {
return false;
}

private record ResourceLookupInfo(ResolvedModule resolvedModule, Module module) {
}

private static Stream<ResourceLookupInfo> extractModuleLookupData(ModuleLayer layer) {
List<ResourceLookupInfo> data = new ArrayList<>(layer.configuration().modules().size());
for (ResolvedModule m : layer.configuration().modules()) {
Module module = layer.findModule(m.name()).orElse(null);
ResourceLookupInfo info = new ResourceLookupInfo(m, module);
data.add(info);
}
return data.stream();
}

@Override
public void collectResources(ResourceCollector resourceCollector) {
/* Collect resources from modules */
NativeImageClassLoaderSupport.allLayers(classLoaderSupport.moduleLayerForImageBuild).stream()
.flatMap(moduleLayer -> moduleLayer.configuration().modules().stream())
.forEach(resolvedModule -> collectResourceFromModule(resourceCollector, resolvedModule));
.flatMap(ClassLoaderSupportImpl::extractModuleLookupData)
.forEach(lookup -> collectResourceFromModule(resourceCollector, lookup));

/* Collect remaining resources from classpath */
for (Path classpathFile : classLoaderSupport.classpath()) {
Expand All @@ -121,12 +134,11 @@ public void collectResources(ResourceCollector resourceCollector) {
}
}

private static void collectResourceFromModule(ResourceCollector resourceCollector, ResolvedModule resolvedModule) {
ModuleReference moduleReference = resolvedModule.reference();
private static void collectResourceFromModule(ResourceCollector resourceCollector, ResourceLookupInfo info) {
ModuleReference moduleReference = info.resolvedModule.reference();
try (ModuleReader moduleReader = moduleReference.open()) {
String moduleName = resolvedModule.name();
List<String> foundResources = moduleReader.list()
.filter(resourceName -> resourceCollector.isIncluded(moduleName, resourceName, moduleReference.location().orElse(null)))
.filter(resourceName -> resourceCollector.isIncluded(info.module, resourceName, moduleReference.location().orElse(null)))
.collect(Collectors.toList());

for (String resName : foundResources) {
Expand All @@ -135,7 +147,7 @@ private static void collectResourceFromModule(ResourceCollector resourceCollecto
continue;
}
try (InputStream is = content.get()) {
resourceCollector.addResource(moduleName, resName, is, false);
resourceCollector.addResource(info.module, resName, is, false);
}
}
} catch (IOException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,12 @@ public void afterAnalysis(AfterAnalysisAccess access) {
* is required when filtering the analysis reachable module set.
*/
Set<String> extraModules = ModuleLayerFeatureUtils.parseModuleSetModifierProperty(ModuleSupport.PROPERTY_IMAGE_EXPLICITLY_ADDED_MODULES);
extraModules.addAll(ImageSingletons.lookup(ResourcesFeature.class).includedResourcesModules);
Set<String> includedResourceModules = ImageSingletons.lookup(ResourcesFeature.class).includedResourcesModules
.stream()
.map(Module::getName)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
extraModules.addAll(includedResourceModules);
extraModules.stream().filter(Predicate.not(ModuleSupport.nonExplicitModules::contains)).forEach(moduleName -> {
Optional<?> module = accessImpl.imageClassLoader.findModule(moduleName);
if (module.isEmpty()) {
Expand Down
Loading

0 comments on commit 382eca0

Please sign in to comment.