@@ -137,6 +137,45 @@ public Node findNode(String name) throws IOException {
137
137
return reader .findNode (name );
138
138
}
139
139
140
+ /**
141
+ * Returns a resource node in the given module, or null if no resource of
142
+ * that name exists.
143
+ *
144
+ * <p>This is equivalent to:
145
+ * <pre>{@code
146
+ * findNode("/modules/" + moduleName + "/" + resourcePath)
147
+ * }</pre>
148
+ * but more performant, and returns {@code null} for directories.
149
+ *
150
+ * @param moduleName The module name of the requested resource.
151
+ * @param resourcePath Trailing module-relative resource path, not starting
152
+ * with {@code '/'}.
153
+ */
154
+ public Node findResourceNode (String moduleName , String resourcePath )
155
+ throws IOException {
156
+ ensureOpen ();
157
+ return reader .findResourceNode (moduleName , resourcePath );
158
+ }
159
+
160
+ /**
161
+ * Returns whether a resource exists in the given module.
162
+ *
163
+ * <p>This is equivalent to:
164
+ * <pre>{@code
165
+ * findResourceNode(moduleName, resourcePath) != null
166
+ * }</pre>
167
+ * but more performant, and will not create or cache new nodes.
168
+ *
169
+ * @param moduleName The module name of the resource being tested for.
170
+ * @param resourcePath Trailing module-relative resource path, not starting
171
+ * with {@code '/'}.
172
+ */
173
+ public boolean containsResource (String moduleName , String resourcePath )
174
+ throws IOException {
175
+ ensureOpen ();
176
+ return reader .containsResource (moduleName , resourcePath );
177
+ }
178
+
140
179
/**
141
180
* Returns a copy of the content of a resource node. The buffer returned by
142
181
* this method is not cached by the node, and each call returns a new array
@@ -276,10 +315,7 @@ public void close(ImageReader image) throws IOException {
276
315
* Returns a node with the given name, or null if no resource or directory of
277
316
* that name exists.
278
317
*
279
- * <p>This is the only public API by which anything outside this class can access
280
- * {@code Node} instances either directly, or by resolving symbolic links.
281
- *
282
- * <p>Note also that there is no reentrant calling back to this method from within
318
+ * <p>Note that there is no reentrant calling back to this method from within
283
319
* the node handling code.
284
320
*
285
321
* @param name an absolute, {@code /}-separated path string, prefixed with either
@@ -291,6 +327,9 @@ synchronized Node findNode(String name) {
291
327
// We cannot get the root paths ("/modules" or "/packages") here
292
328
// because those nodes are already in the nodes cache.
293
329
if (name .startsWith (MODULES_ROOT + "/" )) {
330
+ // This may perform two lookups, one for a directory (in
331
+ // "/modules/...") and one for a non-prefixed resource
332
+ // (with "/modules" removed).
294
333
node = buildModulesNode (name );
295
334
} else if (name .startsWith (PACKAGES_ROOT + "/" )) {
296
335
node = buildPackagesNode (name );
@@ -307,6 +346,55 @@ synchronized Node findNode(String name) {
307
346
return node ;
308
347
}
309
348
349
+ /**
350
+ * Returns a resource node in the given module, or null if no resource of
351
+ * that name exists.
352
+ *
353
+ * <p>Note that there is no reentrant calling back to this method from within
354
+ * the node handling code.
355
+ */
356
+ Node findResourceNode (String moduleName , String resourcePath ) {
357
+ // Unlike findNode(), this method makes only one lookup in the
358
+ // underlying jimage, but can only reliably return resource nodes.
359
+ if (moduleName .indexOf ('/' ) >= 0 ) {
360
+ throw new IllegalArgumentException ("invalid module name: " + moduleName );
361
+ }
362
+ String nodeName = MODULES_ROOT + "/" + moduleName + "/" + resourcePath ;
363
+ // Synchronize as tightly as possible to reduce locking contention.
364
+ synchronized (this ) {
365
+ Node node = nodes .get (nodeName );
366
+ if (node == null ) {
367
+ ImageLocation loc = findLocation (moduleName , resourcePath );
368
+ if (loc != null && isResource (loc )) {
369
+ node = newResource (nodeName , loc );
370
+ nodes .put (node .getName (), node );
371
+ }
372
+ return node ;
373
+ } else {
374
+ return node .isResource () ? node : null ;
375
+ }
376
+ }
377
+ }
378
+
379
+ /**
380
+ * Returns whether a resource exists in the given module.
381
+ *
382
+ * <p>This method is expected to be called frequently for resources
383
+ * which do not exist in the given module (e.g. as part of classpath
384
+ * search). As such, it skips checking the nodes cache and only checks
385
+ * for an entry in the jimage file, as this is faster if the resource
386
+ * is not present. This also means it doesn't need synchronization.
387
+ */
388
+ boolean containsResource (String moduleName , String resourcePath ) {
389
+ if (moduleName .indexOf ('/' ) >= 0 ) {
390
+ throw new IllegalArgumentException ("invalid module name: " + moduleName );
391
+ }
392
+ // If the given module name is 'modules', then 'isResource()'
393
+ // returns false to prevent false positives.
394
+ ImageLocation loc = findLocation (moduleName , resourcePath );
395
+ return loc != null && isResource (loc );
396
+ }
397
+
310
398
/**
311
399
* Builds a node in the "/modules/..." namespace.
312
400
*
0 commit comments