Skip to content

Commit 86c343e

Browse files
Apply patch from mainline hash: 06ce9e89ab
1 parent 526e91c commit 86c343e

File tree

5 files changed

+354
-85
lines changed

5 files changed

+354
-85
lines changed

src/java.base/share/classes/jdk/internal/jrtfs/ExplodedImage.java

Lines changed: 84 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,15 @@
2727
import java.io.IOException;
2828
import java.io.UncheckedIOException;
2929
import java.nio.file.DirectoryStream;
30-
import java.nio.file.FileSystem;
3130
import java.nio.file.FileSystemException;
32-
import java.nio.file.FileSystems;
3331
import java.nio.file.Files;
3432
import java.nio.file.Path;
3533
import java.nio.file.attribute.BasicFileAttributes;
3634
import java.util.ArrayList;
37-
import java.util.Collections;
3835
import java.util.HashMap;
3936
import java.util.List;
4037
import java.util.Map;
38+
import java.util.Objects;
4139
import java.util.stream.Stream;
4240

4341
import 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
}

src/java.base/share/classes/jdk/internal/jrtfs/SystemImage.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,13 @@ void close() throws IOException {
7878
return new ExplodedImage(explodedModulesDir);
7979
}
8080

81-
static final String RUNTIME_HOME;
81+
private static final String RUNTIME_HOME;
8282
// "modules" jimage file Path
83-
static final Path moduleImageFile;
83+
private static final Path moduleImageFile;
8484
// "modules" jimage exists or not?
85-
static final boolean modulesImageExists;
85+
private static final boolean modulesImageExists;
8686
// <JAVA_HOME>/modules directory Path
87-
static final Path explodedModulesDir;
87+
private static final Path explodedModulesDir;
8888

8989
static {
9090
PrivilegedAction<String> pa = SystemImage::findHome;
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/*
25+
* @test
26+
* @summary Whitebox tests for ExplodedImage to ensure compatibility with ImageReader.
27+
* @modules java.base/jdk.internal.jrtfs java.base/jdk.internal.jimage
28+
* @run junit/othervm java.base/jdk.internal.jrtfs.ExplodedImageTest
29+
*/
30+
public class ExplodedImageTestDriver {}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
modules = \
2+
java.base/jdk.internal.jimage \
3+
java.base/jdk.internal.jrtfs
4+
bootclasspath.dirs=.

0 commit comments

Comments
 (0)