11/*
2- * Copyright (c) 2015, 2022 , Oracle and/or its affiliates. All rights reserved.
2+ * Copyright (c) 2015, 2025 , Oracle and/or its affiliates. All rights reserved.
33 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44 *
55 * This code is free software; you can redistribute it and/or modify it
2727import java .io .IOException ;
2828import java .io .UncheckedIOException ;
2929import java .nio .file .DirectoryStream ;
30- import java .nio .file .FileSystem ;
3130import java .nio .file .FileSystemException ;
32- import java .nio .file .FileSystems ;
3331import java .nio .file .Files ;
3432import java .nio .file .Path ;
3533import java .nio .file .attribute .BasicFileAttributes ;
3634import java .util .ArrayList ;
37- import java .util .Collections ;
3835import java .util .HashMap ;
3936import java .util .List ;
4037import java .util .Map ;
38+ import java .util .Objects ;
4139import java .util .stream .Stream ;
4240
4341import jdk .internal .jimage .ImageReader .Node ;
@@ -56,16 +54,15 @@ class ExplodedImage extends SystemImage {
5654
5755 private static final String MODULES = "/modules/" ;
5856 private static final String PACKAGES = "/packages/" ;
59- private static final int PACKAGES_LEN = PACKAGES .length ();
6057
61- private final FileSystem defaultFS ;
58+ private final Path modulesDir ;
6259 private final String separator ;
63- private final Map <String , PathNode > nodes = Collections . synchronizedMap ( new HashMap <>() );
60+ private final Map <String , PathNode > nodes = new HashMap <>();
6461 private final BasicFileAttributes modulesDirAttrs ;
6562
6663 ExplodedImage (Path modulesDir ) throws IOException {
67- defaultFS = FileSystems . getDefault () ;
68- String str = defaultFS .getSeparator ();
64+ this . modulesDir = modulesDir ;
65+ String str = modulesDir . getFileSystem () .getSeparator ();
6966 separator = str .equals ("/" ) ? null : str ;
7067 modulesDirAttrs = Files .readAttributes (modulesDir , BasicFileAttributes .class );
7168 initNodes ();
@@ -79,21 +76,26 @@ private final class PathNode extends Node {
7976 private PathNode link ;
8077 private List <Node > children ;
8178
82- PathNode (String name , Path path , BasicFileAttributes attrs ) { // path
79+ private PathNode (String name , Path path , BasicFileAttributes attrs ) { // path
8380 super (name , attrs );
8481 this .path = path ;
8582 }
8683
87- PathNode (String name , Node link ) { // link
84+ private PathNode (String name , Node link ) { // link
8885 super (name , link .getFileAttributes ());
8986 this .link = (PathNode )link ;
9087 }
9188
92- PathNode (String name , List <Node > children ) { // dir
89+ private PathNode (String name , List <Node > children ) { // dir
9390 super (name , modulesDirAttrs );
9491 this .children = children ;
9592 }
9693
94+ @ Override
95+ public boolean isResource () {
96+ return link == null && !getFileAttributes ().isDirectory ();
97+ }
98+
9799 @ Override
98100 public boolean isDirectory () {
99101 return children != null ||
@@ -112,7 +114,7 @@ public PathNode resolveLink(boolean recursive) {
112114 return recursive && link .isLink () ? link .resolveLink (true ) : link ;
113115 }
114116
115- byte [] getContent () throws IOException {
117+ private byte [] getContent () throws IOException {
116118 if (!getFileAttributes ().isRegularFile ())
117119 throw new FileSystemException (getName () + " is not file" );
118120 return Files .readAllBytes (path );
@@ -126,7 +128,7 @@ public Stream<String> getChildNames() {
126128 List <Node > list = new ArrayList <>();
127129 try (DirectoryStream <Path > stream = Files .newDirectoryStream (path )) {
128130 for (Path p : stream ) {
129- p = explodedModulesDir .relativize (p );
131+ p = modulesDir .relativize (p );
130132 String pName = MODULES + nativeSlashToFrontSlash (p .toString ());
131133 Node node = findNode (pName );
132134 if (node != null ) { // findNode may choose to hide certain files!
@@ -152,7 +154,7 @@ public long size() {
152154 }
153155
154156 @ Override
155- public void close () throws IOException {
157+ public synchronized void close () throws IOException {
156158 nodes .clear ();
157159 }
158160
@@ -161,74 +163,78 @@ public byte[] getResource(Node node) throws IOException {
161163 return ((PathNode )node ).getContent ();
162164 }
163165
164- // find Node for the given Path
165166 @ Override
166- public synchronized Node findNode (String str ) {
167- Node node = findModulesNode ( str );
167+ public synchronized Node findNode (String name ) {
168+ PathNode node = nodes . get ( name );
168169 if (node != null ) {
169170 return node ;
170171 }
171- // lazily created for paths like /packages/<package>/<module>/xyz
172- // For example /packages/java.lang/java.base/java/lang/
173- if (str .startsWith (PACKAGES )) {
174- // pkgEndIdx marks end of <package> part
175- int pkgEndIdx = str .indexOf ('/' , PACKAGES_LEN );
176- if (pkgEndIdx != -1 ) {
177- // modEndIdx marks end of <module> part
178- int modEndIdx = str .indexOf ('/' , pkgEndIdx + 1 );
179- if (modEndIdx != -1 ) {
180- // make sure we have such module link!
181- // ie., /packages/<package>/<module> is valid
182- Node linkNode = nodes .get (str .substring (0 , modEndIdx ));
183- if (linkNode == null || !linkNode .isLink ()) {
184- return null ;
185- }
186- // map to "/modules/zyz" path and return that node
187- // For example, "/modules/java.base/java/lang" for
188- // "/packages/java.lang/java.base/java/lang".
189- String mod = MODULES + str .substring (pkgEndIdx + 1 );
190- return findModulesNode (mod );
191- }
192- }
172+ // If null, this was not the name of "/modules/..." node, and since all
173+ // "/packages/..." nodes were created and cached in advance, the name
174+ // cannot reference a valid node.
175+ Path path = underlyingModulesPath (name );
176+ if (path == null ) {
177+ return null ;
193178 }
194- return null ;
179+ // This can still return null for hidden files.
180+ return createModulesNode (name , path );
195181 }
196182
197- // find a Node for a path that starts like "/modules/..."
198- Node findModulesNode (String str ) {
199- PathNode node = nodes .get (str );
200- if (node != null ) {
201- return node ;
202- }
203- // lazily created "/modules/xyz/abc/" Node
204- // This is mapped to default file system path "<JDK_MODULES_DIR>/xyz/abc"
205- Path p = underlyingPath (str );
206- if (p != null ) {
207- try {
208- BasicFileAttributes attrs = Files .readAttributes (p , BasicFileAttributes .class );
209- if (attrs .isRegularFile ()) {
210- Path f = p .getFileName ();
211- if (f .toString ().startsWith ("_the." ))
212- return null ;
183+ /**
184+ * Lazily creates and caches a {@code Node} for the given "/modules/..." name
185+ * and corresponding path to a file or directory.
186+ *
187+ * @param name a resource or directory node name, of the form "/modules/...".
188+ * @param path the path of a file for a resource or directory.
189+ * @return the newly created and cached node, or {@code null} if the given
190+ * path references a file which must be hidden in the node hierarchy.
191+ */
192+ private Node createModulesNode (String name , Path path ) {
193+ assert !nodes .containsKey (name ) : "Node must not already exist: " + name ;
194+ assert isNonEmptyModulesPath (name ) : "Invalid modules name: " + name ;
195+
196+ try {
197+ // We only know if we're creating a resource of directory when we
198+ // look up file attributes, and we only do that once. Thus, we can
199+ // only reject "marker files" here, rather than by inspecting the
200+ // given name string, since it doesn't apply to directories.
201+ BasicFileAttributes attrs = Files .readAttributes (path , BasicFileAttributes .class );
202+ if (attrs .isRegularFile ()) {
203+ Path f = path .getFileName ();
204+ if (f .toString ().startsWith ("_the." )) {
205+ return null ;
213206 }
214- node = new PathNode (str , p , attrs );
215- nodes .put (str , node );
216- return node ;
217- } catch (IOException x ) {
218- // does not exists or unable to determine
207+ } else if (!attrs .isDirectory ()) {
208+ return null ;
219209 }
210+ PathNode node = new PathNode (name , path , attrs );
211+ nodes .put (name , node );
212+ return node ;
213+ } catch (IOException x ) {
214+ // Since the path reference a file, any errors should not be ignored.
215+ throw new UncheckedIOException (x );
220216 }
221- return null ;
222217 }
223218
224- Path underlyingPath (String str ) {
225- if (str .startsWith (MODULES )) {
226- str = frontSlashToNativeSlash (str .substring ("/modules" .length ()));
227- return defaultFS .getPath (explodedModulesDir .toString (), str );
219+ /**
220+ * Returns the expected file path for name in the "/modules/..." namespace,
221+ * or {@code null} if the name is not in the "/modules/..." namespace or the
222+ * path does not reference a file.
223+ */
224+ private Path underlyingModulesPath (String name ) {
225+ if (isNonEmptyModulesPath (name )) {
226+ Path path = modulesDir .resolve (frontSlashToNativeSlash (name .substring (MODULES .length ())));
227+ return Files .exists (path ) ? path : null ;
228228 }
229229 return null ;
230230 }
231231
232+ private static boolean isNonEmptyModulesPath (String name ) {
233+ // Don't just check the prefix, there must be something after it too
234+ // (otherwise you end up with an empty string after trimming).
235+ return name .startsWith (MODULES ) && name .length () > MODULES .length ();
236+ }
237+
232238 // convert "/" to platform path separator
233239 private String frontSlashToNativeSlash (String str ) {
234240 return separator == null ? str : str .replace ("/" , separator );
@@ -249,24 +255,21 @@ private void initNodes() throws IOException {
249255 // same package prefix may exist in multiple modules. This Map
250256 // is filled by walking "jdk modules" directory recursively!
251257 Map <String , List <String >> packageToModules = new HashMap <>();
252- try (DirectoryStream <Path > stream = Files .newDirectoryStream (explodedModulesDir )) {
258+ try (DirectoryStream <Path > stream = Files .newDirectoryStream (modulesDir )) {
253259 for (Path module : stream ) {
254260 if (Files .isDirectory (module )) {
255261 String moduleName = module .getFileName ().toString ();
256262 // make sure "/modules/<moduleName>" is created
257- findModulesNode ( MODULES + moduleName );
263+ Objects . requireNonNull ( createModulesNode ( MODULES + moduleName , module ) );
258264 try (Stream <Path > contentsStream = Files .walk (module )) {
259265 contentsStream .filter (Files ::isDirectory ).forEach ((p ) -> {
260266 p = module .relativize (p );
261267 String pkgName = slashesToDots (p .toString ());
262268 // skip META-INF and empty strings
263269 if (!pkgName .isEmpty () && !pkgName .startsWith ("META-INF" )) {
264- List <String > moduleNames = packageToModules .get (pkgName );
265- if (moduleNames == null ) {
266- moduleNames = new ArrayList <>();
267- packageToModules .put (pkgName , moduleNames );
268- }
269- moduleNames .add (moduleName );
270+ packageToModules
271+ .computeIfAbsent (pkgName , k -> new ArrayList <>())
272+ .add (moduleName );
270273 }
271274 });
272275 }
@@ -275,8 +278,8 @@ private void initNodes() throws IOException {
275278 }
276279 // create "/modules" directory
277280 // "nodes" map contains only /modules/<foo> nodes only so far and so add all as children of /modules
278- PathNode modulesDir = new PathNode ("/modules" , new ArrayList <>(nodes .values ()));
279- nodes .put (modulesDir .getName (), modulesDir );
281+ PathNode modulesRootNode = new PathNode ("/modules" , new ArrayList <>(nodes .values ()));
282+ nodes .put (modulesRootNode .getName (), modulesRootNode );
280283
281284 // create children under "/packages"
282285 List <Node > packagesChildren = new ArrayList <>(packageToModules .size ());
@@ -285,7 +288,7 @@ private void initNodes() throws IOException {
285288 List <String > moduleNameList = entry .getValue ();
286289 List <Node > moduleLinkNodes = new ArrayList <>(moduleNameList .size ());
287290 for (String moduleName : moduleNameList ) {
288- Node moduleNode = findModulesNode ( MODULES + moduleName );
291+ Node moduleNode = Objects . requireNonNull ( nodes . get ( MODULES + moduleName ) );
289292 PathNode linkNode = new PathNode (PACKAGES + pkgName + "/" + moduleName , moduleNode );
290293 nodes .put (linkNode .getName (), linkNode );
291294 moduleLinkNodes .add (linkNode );
@@ -295,13 +298,13 @@ private void initNodes() throws IOException {
295298 packagesChildren .add (pkgDir );
296299 }
297300 // "/packages" dir
298- PathNode packagesDir = new PathNode ("/packages" , packagesChildren );
299- nodes .put (packagesDir .getName (), packagesDir );
301+ PathNode packagesRootNode = new PathNode ("/packages" , packagesChildren );
302+ nodes .put (packagesRootNode .getName (), packagesRootNode );
300303
301304 // finally "/" dir!
302305 List <Node > rootChildren = new ArrayList <>();
303- rootChildren .add (packagesDir );
304- rootChildren .add (modulesDir );
306+ rootChildren .add (packagesRootNode );
307+ rootChildren .add (modulesRootNode );
305308 PathNode root = new PathNode ("/" , rootChildren );
306309 nodes .put (root .getName (), root );
307310 }
0 commit comments