27
27
import java .io .IOException ;
28
28
import java .io .UncheckedIOException ;
29
29
import java .nio .file .DirectoryStream ;
30
- import java .nio .file .FileSystem ;
31
30
import java .nio .file .FileSystemException ;
32
- import java .nio .file .FileSystems ;
33
31
import java .nio .file .Files ;
34
32
import java .nio .file .Path ;
35
33
import java .nio .file .attribute .BasicFileAttributes ;
36
34
import java .util .ArrayList ;
37
- import java .util .Collections ;
38
35
import java .util .HashMap ;
39
36
import java .util .List ;
40
37
import java .util .Map ;
38
+ import java .util .Objects ;
41
39
import java .util .stream .Stream ;
42
40
43
41
import jdk .internal .jimage .ImageReader .Node ;
@@ -56,16 +54,15 @@ class ExplodedImage extends SystemImage {
56
54
57
55
private static final String MODULES = "/modules/" ;
58
56
private static final String PACKAGES = "/packages/" ;
59
- private static final int PACKAGES_LEN = PACKAGES .length ();
60
57
61
- private final FileSystem defaultFS ;
58
+ private final Path modulesDir ;
62
59
private final String separator ;
63
- private final Map <String , PathNode > nodes = Collections . synchronizedMap ( new HashMap <>() );
60
+ private final Map <String , PathNode > nodes = new HashMap <>();
64
61
private final BasicFileAttributes modulesDirAttrs ;
65
62
66
63
ExplodedImage (Path modulesDir ) throws IOException {
67
- defaultFS = FileSystems . getDefault () ;
68
- String str = defaultFS .getSeparator ();
64
+ this . modulesDir = modulesDir ;
65
+ String str = modulesDir . getFileSystem () .getSeparator ();
69
66
separator = str .equals ("/" ) ? null : str ;
70
67
modulesDirAttrs = Files .readAttributes (modulesDir , BasicFileAttributes .class );
71
68
initNodes ();
@@ -79,21 +76,26 @@ private final class PathNode extends Node {
79
76
private PathNode link ;
80
77
private List <Node > children ;
81
78
82
- PathNode (String name , Path path , BasicFileAttributes attrs ) { // path
79
+ private PathNode (String name , Path path , BasicFileAttributes attrs ) { // path
83
80
super (name , attrs );
84
81
this .path = path ;
85
82
}
86
83
87
- PathNode (String name , Node link ) { // link
84
+ private PathNode (String name , Node link ) { // link
88
85
super (name , link .getFileAttributes ());
89
86
this .link = (PathNode )link ;
90
87
}
91
88
92
- PathNode (String name , List <Node > children ) { // dir
89
+ private PathNode (String name , List <Node > children ) { // dir
93
90
super (name , modulesDirAttrs );
94
91
this .children = children ;
95
92
}
96
93
94
+ @ Override
95
+ public boolean isResource () {
96
+ return link == null && !getFileAttributes ().isDirectory ();
97
+ }
98
+
97
99
@ Override
98
100
public boolean isDirectory () {
99
101
return children != null ||
@@ -112,7 +114,7 @@ public PathNode resolveLink(boolean recursive) {
112
114
return recursive && link .isLink () ? link .resolveLink (true ) : link ;
113
115
}
114
116
115
- byte [] getContent () throws IOException {
117
+ private byte [] getContent () throws IOException {
116
118
if (!getFileAttributes ().isRegularFile ())
117
119
throw new FileSystemException (getName () + " is not file" );
118
120
return Files .readAllBytes (path );
@@ -126,7 +128,7 @@ public Stream<String> getChildNames() {
126
128
List <Node > list = new ArrayList <>();
127
129
try (DirectoryStream <Path > stream = Files .newDirectoryStream (path )) {
128
130
for (Path p : stream ) {
129
- p = explodedModulesDir .relativize (p );
131
+ p = modulesDir .relativize (p );
130
132
String pName = MODULES + nativeSlashToFrontSlash (p .toString ());
131
133
Node node = findNode (pName );
132
134
if (node != null ) { // findNode may choose to hide certain files!
@@ -152,7 +154,7 @@ public long size() {
152
154
}
153
155
154
156
@ Override
155
- public void close () throws IOException {
157
+ public synchronized void close () throws IOException {
156
158
nodes .clear ();
157
159
}
158
160
@@ -161,74 +163,78 @@ public byte[] getResource(Node node) throws IOException {
161
163
return ((PathNode )node ).getContent ();
162
164
}
163
165
164
- // find Node for the given Path
165
166
@ 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 );
168
169
if (node != null ) {
169
170
return node ;
170
171
}
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 ;
193
178
}
194
- return null ;
179
+ // This can still return null for hidden files.
180
+ return createModulesNode (name , path );
195
181
}
196
182
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 ;
213
206
}
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 ;
219
209
}
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 );
220
216
}
221
- return null ;
222
217
}
223
218
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 ;
228
228
}
229
229
return null ;
230
230
}
231
231
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
+
232
238
// convert "/" to platform path separator
233
239
private String frontSlashToNativeSlash (String str ) {
234
240
return separator == null ? str : str .replace ("/" , separator );
@@ -249,24 +255,21 @@ private void initNodes() throws IOException {
249
255
// same package prefix may exist in multiple modules. This Map
250
256
// is filled by walking "jdk modules" directory recursively!
251
257
Map <String , List <String >> packageToModules = new HashMap <>();
252
- try (DirectoryStream <Path > stream = Files .newDirectoryStream (explodedModulesDir )) {
258
+ try (DirectoryStream <Path > stream = Files .newDirectoryStream (modulesDir )) {
253
259
for (Path module : stream ) {
254
260
if (Files .isDirectory (module )) {
255
261
String moduleName = module .getFileName ().toString ();
256
262
// make sure "/modules/<moduleName>" is created
257
- findModulesNode ( MODULES + moduleName );
263
+ Objects . requireNonNull ( createModulesNode ( MODULES + moduleName , module ) );
258
264
try (Stream <Path > contentsStream = Files .walk (module )) {
259
265
contentsStream .filter (Files ::isDirectory ).forEach ((p ) -> {
260
266
p = module .relativize (p );
261
267
String pkgName = slashesToDots (p .toString ());
262
268
// skip META-INF and empty strings
263
269
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 );
270
273
}
271
274
});
272
275
}
@@ -275,8 +278,8 @@ private void initNodes() throws IOException {
275
278
}
276
279
// create "/modules" directory
277
280
// "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 );
280
283
281
284
// create children under "/packages"
282
285
List <Node > packagesChildren = new ArrayList <>(packageToModules .size ());
@@ -285,7 +288,7 @@ private void initNodes() throws IOException {
285
288
List <String > moduleNameList = entry .getValue ();
286
289
List <Node > moduleLinkNodes = new ArrayList <>(moduleNameList .size ());
287
290
for (String moduleName : moduleNameList ) {
288
- Node moduleNode = findModulesNode ( MODULES + moduleName );
291
+ Node moduleNode = Objects . requireNonNull ( nodes . get ( MODULES + moduleName ) );
289
292
PathNode linkNode = new PathNode (PACKAGES + pkgName + "/" + moduleName , moduleNode );
290
293
nodes .put (linkNode .getName (), linkNode );
291
294
moduleLinkNodes .add (linkNode );
@@ -295,13 +298,13 @@ private void initNodes() throws IOException {
295
298
packagesChildren .add (pkgDir );
296
299
}
297
300
// "/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 );
300
303
301
304
// finally "/" dir!
302
305
List <Node > rootChildren = new ArrayList <>();
303
- rootChildren .add (packagesDir );
304
- rootChildren .add (modulesDir );
306
+ rootChildren .add (packagesRootNode );
307
+ rootChildren .add (modulesRootNode );
305
308
PathNode root = new PathNode ("/" , rootChildren );
306
309
nodes .put (root .getName (), root );
307
310
}
0 commit comments