3535import java .util .Date ;
3636import java .util .EnumSet ;
3737import java .util .Enumeration ;
38+ import java .util .HashMap ;
39+ import java .util .HashSet ;
3840import java .util .List ;
41+ import java .util .Map ;
3942import java .util .Objects ;
4043import java .util .Set ;
4144import java .util .function .BiConsumer ;
4245import java .util .function .Function ;
4346import java .util .stream .Collectors ;
4447import java .util .stream .StreamSupport ;
4548
46- import com .oracle .svm .core .encoder .SymbolEncoder ;
4749import org .graalvm .collections .EconomicMap ;
4850import org .graalvm .collections .MapCursor ;
4951import org .graalvm .nativeimage .ImageInfo ;
5961import com .oracle .svm .core .SubstrateUtil ;
6062import com .oracle .svm .core .configure .ConditionalRuntimeValue ;
6163import com .oracle .svm .core .configure .RuntimeConditionSet ;
64+ import com .oracle .svm .core .encoder .SymbolEncoder ;
6265import com .oracle .svm .core .feature .AutomaticallyRegisteredFeature ;
6366import com .oracle .svm .core .feature .InternalFeature ;
67+ import com .oracle .svm .core .imagelayer .ImageLayerBuildingSupport ;
6468import com .oracle .svm .core .jdk .resources .MissingResourceRegistrationError ;
6569import com .oracle .svm .core .jdk .resources .MissingResourceRegistrationUtils ;
6670import com .oracle .svm .core .jdk .resources .ResourceExceptionEntry ;
6973import com .oracle .svm .core .jdk .resources .ResourceURLConnection ;
7074import com .oracle .svm .core .jdk .resources .CompressedGlobTrie .CompressedGlobTrie ;
7175import com .oracle .svm .core .jdk .resources .CompressedGlobTrie .GlobTrieNode ;
76+ import com .oracle .svm .core .layeredimagesingleton .ImageSingletonLoader ;
77+ import com .oracle .svm .core .layeredimagesingleton .ImageSingletonWriter ;
7278import com .oracle .svm .core .layeredimagesingleton .LayeredImageSingletonBuilderFlags ;
7379import com .oracle .svm .core .layeredimagesingleton .LayeredImageSingletonSupport ;
7480import com .oracle .svm .core .layeredimagesingleton .MultiLayeredImageSingleton ;
75- import com .oracle .svm .core .layeredimagesingleton .UnsavedSingleton ;
7681import com .oracle .svm .core .metadata .MetadataTracer ;
7782import com .oracle .svm .core .util .ImageHeapMap ;
7883import com .oracle .svm .core .util .VMError ;
8792 * Registered resources are then available from DynamicHub#getResource classes and
8893 * {@link Target_java_lang_ClassLoader class loaders}.
8994 */
90- public final class Resources implements MultiLayeredImageSingleton , UnsavedSingleton {
95+ public final class Resources implements MultiLayeredImageSingleton {
9196
9297 private static final int INVALID_TIMESTAMP = -1 ;
9398 public static final char RESOURCES_INTERNAL_PATH_SEPARATOR = '/' ;
94- private final SymbolEncoder encoder = SymbolEncoder .singleton ();
99+ private static final String RESOURCE_KEYS = "resourceKeys" ;
100+ private static final String RESOURCE_REGISTRATION_STATES = "resourceRegistrationStates" ;
101+ private static final String PATTERNS = "patterns" ;
102+
103+ @ Platforms (Platform .HOSTED_ONLY .class ) //
104+ private SymbolEncoder encoder ;
95105
96106 /**
97107 * @return the singleton corresponding to this layer's resources in a layered build, the unique
@@ -121,6 +131,27 @@ public static Resources[] layeredSingletons() {
121131 private final EconomicMap <ModuleResourceKey , ConditionalRuntimeValue <ResourceStorageEntryBase >> resources = ImageHeapMap .createNonLayeredMap ();
122132 private final EconomicMap <RequestedPattern , RuntimeConditionSet > requestedPatterns = ImageHeapMap .createNonLayeredMap ();
123133
134+ /**
135+ * The string representation of {@link ModuleResourceKey} that are already registered in
136+ * previous layers. Since the {@link ModuleResourceKey} contains a reference to a
137+ * {@link Module}, the {@link Module} name is used instead of the object itself in the string
138+ * representation. This works under the assumption that all modules have a different unique name
139+ * in Layered Images. More details can be found in
140+ * {@link Resources#getModuleResourceKeyString(ModuleResourceKey)}.
141+ *
142+ * The boolean associated to each {@link ModuleResourceKey} is true if the registered value is
143+ * complete and false in the case of a negative query.
144+ */
145+ @ Platforms (Platform .HOSTED_ONLY .class ) //
146+ private final Map <String , Boolean > previousLayerResources ;
147+
148+ /**
149+ * The string representation of {@link RequestedPattern} that are already registered in previous
150+ * layers.
151+ */
152+ @ Platforms (Platform .HOSTED_ONLY .class ) //
153+ private final Set <String > previousLayerPatterns ;
154+
124155 public record RequestedPattern (String module , String resource ) {
125156 }
126157
@@ -155,6 +186,17 @@ public record ModuleResourceKey(Module module, String resource) {
155186 private Function <Module , Module > hostedToRuntimeModuleMapper ;
156187
157188 Resources () {
189+ this (Map .of (), Set .of ());
190+ }
191+
192+ Resources (Map <String , Boolean > previousLayerResources , Set <String > previousLayerPatterns ) {
193+ this .previousLayerResources = previousLayerResources ;
194+ this .previousLayerPatterns = previousLayerPatterns ;
195+ }
196+
197+ @ Platforms (Platform .HOSTED_ONLY .class )
198+ public void setEncoder (SymbolEncoder encoder ) {
199+ this .encoder = encoder ;
158200 }
159201
160202 public GlobTrieNode <ConditionWithOrigin > getResourcesTrieRoot () {
@@ -240,6 +282,26 @@ private void updateTimeStamp() {
240282 }
241283 }
242284
285+ private static String getModuleResourceKeyString (ModuleResourceKey m ) {
286+ /*
287+ * A null module in the ModuleResourceKey represents any unnamed module, meaning that only
288+ * one marker is needed for all of them and that if the module is not null, it is named (see
289+ * Resources.createStorageKey). This string representation relies on the assumption that a
290+ * layered image build cannot contain two modules with the same name, so Module#getName() is
291+ * guaranteed to be unique for layered images.
292+ */
293+ String moduleName = m .module == null ? LayeredModuleSingleton .ALL_UNNAMED_MODULE_NAME : m .module .getName ();
294+ return moduleName + m .resource ;
295+ }
296+
297+ private void addResource (ModuleResourceKey key , ConditionalRuntimeValue <ResourceStorageEntryBase > entry ) {
298+ String moduleResourceKeyString = getModuleResourceKeyString (key );
299+ Boolean previousLayerData = previousLayerResources .get (moduleResourceKeyString );
300+ if (previousLayerData == null || (!previousLayerData && entry .getValueUnconditionally () != NEGATIVE_QUERY_MARKER )) {
301+ resources .put (key , entry );
302+ }
303+ }
304+
243305 @ Platforms (Platform .HOSTED_ONLY .class )
244306 private void addEntry (Module module , String resourceName , boolean isDirectory , byte [] data , boolean fromJar , boolean isNegativeQuery ) {
245307 VMError .guarantee (!BuildPhaseProvider .isAnalysisFinished (), "Trying to add a resource entry after analysis." );
@@ -250,15 +312,15 @@ private void addEntry(Module module, String resourceName, boolean isDirectory, b
250312 ConditionalRuntimeValue <ResourceStorageEntryBase > entry = resources .get (key );
251313 if (isNegativeQuery ) {
252314 if (entry == null ) {
253- resources . put (key , new ConditionalRuntimeValue <>(conditionSet , NEGATIVE_QUERY_MARKER ));
315+ addResource (key , new ConditionalRuntimeValue <>(conditionSet , NEGATIVE_QUERY_MARKER ));
254316 }
255317 return ;
256318 }
257319
258320 if (entry == null || entry .getValueUnconditionally () == NEGATIVE_QUERY_MARKER ) {
259321 updateTimeStamp ();
260322 entry = new ConditionalRuntimeValue <>(conditionSet , new ResourceStorageEntry (isDirectory , fromJar ));
261- resources . put (key , entry );
323+ addResource (key , entry );
262324 } else {
263325 if (key .module () != null ) {
264326 // if the entry already exists, and it comes from a module, it is the same entry
@@ -307,7 +369,7 @@ public void registerIOException(Module module, String resourceName, IOException
307369 ModuleResourceKey key = createStorageKey (module , resourceName );
308370 synchronized (resources ) {
309371 updateTimeStamp ();
310- resources . put (key , new ConditionalRuntimeValue <>(RuntimeConditionSet .emptySet (), new ResourceExceptionEntry (e )));
372+ addResource (key , new ConditionalRuntimeValue <>(RuntimeConditionSet .emptySet (), new ResourceExceptionEntry (e )));
311373 }
312374 }
313375
@@ -326,7 +388,13 @@ public void registerIncludePattern(ConfigurationCondition condition, String modu
326388 assert MissingRegistrationUtils .throwMissingRegistrationErrors ();
327389 synchronized (requestedPatterns ) {
328390 updateTimeStamp ();
329- requestedPatterns .put (new RequestedPattern (encoder .encodeModule (module ), handleEscapedCharacters (pattern )), RuntimeConditionSet .createHosted (condition ));
391+ addPattern (new RequestedPattern (encoder .encodeModule (module ), handleEscapedCharacters (pattern )), RuntimeConditionSet .createHosted (condition ));
392+ }
393+ }
394+
395+ private void addPattern (RequestedPattern pattern , RuntimeConditionSet condition ) {
396+ if (!previousLayerPatterns .contains (pattern .toString ())) {
397+ requestedPatterns .put (pattern , condition );
330398 }
331399 }
332400
@@ -601,13 +669,65 @@ private static boolean matchResource(String pattern, String resource) {
601669 public EnumSet <LayeredImageSingletonBuilderFlags > getImageBuilderFlags () {
602670 return LayeredImageSingletonBuilderFlags .ALL_ACCESS ;
603671 }
672+
673+ @ Override
674+ public PersistFlags preparePersist (ImageSingletonWriter writer ) {
675+ List <String > resourceKeys = new ArrayList <>();
676+ List <Boolean > resourceRegistrationStates = new ArrayList <>();
677+ Set <String > patterns = new HashSet <>(previousLayerPatterns );
678+
679+ var cursor = resources .getEntries ();
680+ while (cursor .advance ()) {
681+ resourceKeys .add (getModuleResourceKeyString (cursor .getKey ()));
682+ boolean isNegativeQuery = cursor .getValue ().getValueUnconditionally () == NEGATIVE_QUERY_MARKER ;
683+ resourceRegistrationStates .add (!isNegativeQuery );
684+ }
685+
686+ for (var entry : previousLayerResources .entrySet ()) {
687+ /*
688+ * If a complete entry overwrites a negative query from a previous layer, the
689+ * previousLayerResources map entry needs to be skipped to register the new entry for
690+ * extension layers.
691+ */
692+ if (!resourceKeys .contains (entry .getKey ())) {
693+ resourceKeys .add (entry .getKey ());
694+ resourceRegistrationStates .add (entry .getValue ());
695+ }
696+ }
697+
698+ requestedPatterns .getKeys ().forEach (p -> patterns .add (p .toString ()));
699+
700+ writer .writeStringList (RESOURCE_KEYS , resourceKeys );
701+ writer .writeBoolList (RESOURCE_REGISTRATION_STATES , resourceRegistrationStates );
702+ writer .writeStringList (PATTERNS , patterns .stream ().toList ());
703+
704+ return PersistFlags .CREATE ;
705+ }
706+
707+ @ SuppressWarnings ("unused" )
708+ public static Object createFromLoader (ImageSingletonLoader loader ) {
709+ List <String > previousLayerResourceKeys = loader .readStringList (RESOURCE_KEYS );
710+ List <Boolean > previousLayerRegistrationStates = loader .readBoolList (RESOURCE_REGISTRATION_STATES );
711+ Map <String , Boolean > previousLayerResources = new HashMap <>();
712+
713+ for (int i = 0 ; i < previousLayerResourceKeys .size (); ++i ) {
714+ previousLayerResources .put (previousLayerResourceKeys .get (i ), previousLayerRegistrationStates .get (i ));
715+ }
716+
717+ Set <String > previousLayerPatterns = Set .copyOf (loader .readStringList (PATTERNS ));
718+
719+ return new Resources (Collections .unmodifiableMap (previousLayerResources ), previousLayerPatterns );
720+ }
604721}
605722
606723@ AutomaticallyRegisteredFeature
607724final class ResourcesFeature implements InternalFeature {
608725 @ Override
609726 public void afterRegistration (AfterRegistrationAccess access ) {
610- ImageSingletons .add (Resources .class , new Resources ());
727+ if (ImageLayerBuildingSupport .firstImageBuild ()) {
728+ ImageSingletons .add (Resources .class , new Resources ());
729+ }
730+ Resources .currentLayer ().setEncoder (SymbolEncoder .singleton ());
611731 }
612732
613733 @ Override
0 commit comments