Skip to content

Commit 399e5ec

Browse files
committed
Use NIO FileSystem for zips, which is much faster then ZipInputStream.
1 parent ca7c10a commit 399e5ec

File tree

3 files changed

+157
-74
lines changed

3 files changed

+157
-74
lines changed

bs-api/src/main/java/net/minecraftforge/bootstrap/api/BootstrapClasspathModifier.java

-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
import java.nio.file.Path;
88
import java.util.List;
9-
import java.util.Map;
109

1110
/*
1211
* This is mean to allow modifications to the classpath before booting.

bs-dev/src/main/java/net/minecraftforge/bootstrap/dev/BootstrapDevClasspathFixer.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ private boolean processAuto(List<Path[]> classpath) {
198198
lst.add(path);
199199
} //else if (DEBUG) log("Unknown directory format: " + path);
200200
} else {
201-
var module = Util.findModuleName(path, null);
201+
var module = Util.findModuleNameImpl(path, false);
202202
if (module.name() == null) {
203203
//var meta = JarMetadata.fromFileName(path, Set.of(), List.of());
204204
//module = new ModuleVersion(meta.name(), meta.version(), module.layer());

bs-dev/src/main/java/net/minecraftforge/bootstrap/dev/Util.java

+156-72
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,21 @@
55
package net.minecraftforge.bootstrap.dev;
66

77
import java.io.ByteArrayInputStream;
8+
import java.io.File;
89
import java.io.IOException;
910
import java.lang.module.ModuleDescriptor;
11+
import java.nio.file.FileSystem;
12+
import java.nio.file.FileSystems;
1013
import java.nio.file.Files;
1114
import java.nio.file.Path;
1215
import java.util.ArrayList;
1316
import java.util.Collection;
14-
import java.util.jar.JarFile;
17+
import java.util.List;
1518
import java.util.jar.Manifest;
16-
import java.util.jar.Attributes.Name;
1719
import java.util.zip.ZipEntry;
1820
import java.util.zip.ZipInputStream;
21+
import java.util.jar.Attributes.Name;
22+
import java.util.jar.JarFile;
1923

2024
class Util {
2125
private static final Name AUTOMATIC_MODULE_NAME = new Name("Automatic-Module-Name");
@@ -29,7 +33,7 @@ class Util {
2933

3034
static ModuleVersion findModuleName(Collection<Path> paths) {
3135
for (var path : paths) {
32-
var ret = findModuleName(path, null);
36+
var ret = findModuleNameImpl(path, false);
3337
if (ret.name != null)
3438
return ret;
3539
}
@@ -38,87 +42,31 @@ static ModuleVersion findModuleName(Collection<Path> paths) {
3842

3943
static ModuleVersion findModuleName(Path... paths) {
4044
for (var path : paths) {
41-
var ret = findModuleName(path, null);
45+
var ret = findModuleNameImpl(path, false);
4246
if (ret.name != null)
4347
return ret;
4448
}
4549
return null;
4650
}
4751

4852
record ModuleVersion(String name, String version, String layer) {}
49-
static ModuleVersion findModuleName(Path path, String _default) {
53+
static ModuleVersion findModuleNameImpl(Path path, boolean slow) {
5054
try {
51-
Manifest mf = null;
52-
record InfoData(int version, byte[] data) {}
53-
var infos = new ArrayList<InfoData>();
54-
55+
Candidates data = null;
5556
if (Files.isDirectory(path)) {
56-
var meta_inf = findInsensitive(path, META_INF);
57-
var manifest = findInsensitive(meta_inf, MANIFEST);
58-
59-
if (manifest != null && Files.exists(manifest)) {
60-
try (var is = Files.newInputStream(manifest)) {
61-
mf = new Manifest(is);
62-
}
63-
}
64-
65-
var module_info = path.resolve(MODULE_INFO);
66-
if (Files.exists(module_info)) {
67-
infos.add(new InfoData(0, Files.readAllBytes(path)));
68-
}
69-
70-
if (isMultiRelease(mf)) {
71-
var versions = findInsensitive(meta_inf, VERSIONS);
72-
if (versions != null) {
73-
Files.list(versions)
74-
.forEach(v -> {
75-
try {
76-
int version = Integer.parseInt(v.getFileName().toString());
77-
var mod_info = v.resolve(MODULE_INFO);
78-
if (version <= Runtime.version().feature() && Files.exists(mod_info))
79-
infos.add(new InfoData(version, Files.readAllBytes(mod_info)));
80-
} catch (NumberFormatException e) {
81-
// If its not a numerical directory we don't care
82-
} catch (IOException e) {
83-
sneak(e);
84-
}
85-
});
86-
}
87-
}
57+
data = findCandidatesDirectory(path);
58+
} else if (slow) {
59+
data = findCandidatesZip(path);
8860
} else {
89-
// I would use JarFile here and let the JVM handle all this.
90-
// but it only works on File objects not Paths
91-
// Normal java gets around this by extracting all non-file paths
92-
// to a temp directory and opening them as normal files.
93-
// I do not want to do this. So this is what you get.
94-
try (var zip = new ZipInputStream(Files.newInputStream(path))) {
95-
ZipEntry entry;
96-
while ((entry = zip.getNextEntry()) != null) {
97-
var name = entry.getName();
98-
if (mf == null && JarFile.MANIFEST_NAME.equalsIgnoreCase(name)) {
99-
mf = new Manifest(zip);
100-
} else if (name.endsWith(MODULE_INFO)) {
101-
int version = 0;
102-
if (name.startsWith(VERSION_DIR)) {
103-
if (!isMultiRelease(mf))
104-
continue;
105-
106-
int idx = name.indexOf('/', VERSION_DIR.length());
107-
var ver = name.substring(VERSION_DIR.length(), idx);
108-
try {
109-
version = Integer.parseInt(ver);
110-
} catch (NumberFormatException e) {
111-
// If its not a numerical directory we don't care
112-
version = Integer.MAX_VALUE;
113-
}
114-
}
115-
if (version <= Runtime.version().feature())
116-
infos.add(new InfoData(version, zip.readAllBytes()));
117-
}
118-
}
61+
try (FileSystem jarFS = FileSystems.newFileSystem(path)) {
62+
var root = jarFS.getPath("/");
63+
data = findCandidatesDirectory(root);
11964
}
12065
}
12166

67+
var mf = data.mf;
68+
var infos = data.modules;
69+
12270
InfoData info = null;
12371
if (infos.size() == 1) {
12472
info = infos.get(0);
@@ -129,7 +77,7 @@ record InfoData(int version, byte[] data) {}
12977
.orElse(null);
13078
}
13179

132-
String name = _default;
80+
String name = null;
13381
String version = null;
13482
String layer = null;
13583

@@ -150,6 +98,89 @@ record InfoData(int version, byte[] data) {}
15098
}
15199
}
152100

101+
record InfoData(int version, byte[] data) {}
102+
record Candidates(Manifest mf, List<InfoData> modules) {}
103+
private static Candidates findCandidatesDirectory(Path path) {
104+
try {
105+
var infos = new ArrayList<InfoData>();
106+
var meta_inf = findInsensitive(path, META_INF);
107+
var manifest = findInsensitive(meta_inf, MANIFEST);
108+
Manifest mf = null;
109+
110+
if (manifest != null && Files.exists(manifest)) {
111+
try (var is = Files.newInputStream(manifest)) {
112+
mf = new Manifest(is);
113+
}
114+
}
115+
116+
var module_info = path.resolve(MODULE_INFO);
117+
if (Files.exists(module_info)) {
118+
infos.add(new InfoData(0, Files.readAllBytes(module_info)));
119+
}
120+
121+
if (isMultiRelease(mf)) {
122+
var versions = findInsensitive(meta_inf, VERSIONS);
123+
if (versions != null) {
124+
Files.list(versions)
125+
.forEach(v -> {
126+
try {
127+
int version = Integer.parseInt(v.getFileName().toString());
128+
var mod_info = v.resolve(MODULE_INFO);
129+
if (version <= Runtime.version().feature() && Files.exists(mod_info))
130+
infos.add(new InfoData(version, Files.readAllBytes(mod_info)));
131+
} catch (NumberFormatException e) {
132+
// If its not a numerical directory we don't care
133+
} catch (IOException e) {
134+
sneak(e);
135+
}
136+
});
137+
}
138+
}
139+
return new Candidates(mf, infos);
140+
} catch (IOException e) {
141+
return sneak(e);
142+
}
143+
}
144+
145+
private static Candidates findCandidatesZip(Path path) {
146+
// I would use JarFile here and let the JVM handle all this.
147+
// but it only works on File objects not Paths
148+
// Normal java gets around this by extracting all non-file paths
149+
// to a temp directory and opening them as normal files.
150+
// I do not want to do this. So this is what you get.
151+
try (var zip = new ZipInputStream(Files.newInputStream(path))) {
152+
var infos = new ArrayList<InfoData>();
153+
Manifest mf = null;
154+
ZipEntry entry;
155+
while ((entry = zip.getNextEntry()) != null) {
156+
var name = entry.getName();
157+
if (mf == null && JarFile.MANIFEST_NAME.equalsIgnoreCase(name)) {
158+
mf = new Manifest(zip);
159+
} else if (name.endsWith(MODULE_INFO)) {
160+
int version = 0;
161+
if (name.startsWith(VERSION_DIR)) {
162+
if (!isMultiRelease(mf))
163+
continue;
164+
165+
int idx = name.indexOf('/', VERSION_DIR.length());
166+
var ver = name.substring(VERSION_DIR.length(), idx);
167+
try {
168+
version = Integer.parseInt(ver);
169+
} catch (NumberFormatException e) {
170+
// If its not a numerical directory we don't care
171+
version = Integer.MAX_VALUE;
172+
}
173+
}
174+
if (version <= Runtime.version().feature())
175+
infos.add(new InfoData(version, zip.readAllBytes()));
176+
}
177+
}
178+
return new Candidates(mf, infos);
179+
} catch (IOException e) {
180+
return sneak(e);
181+
}
182+
}
183+
153184
private static Path findInsensitive(Path root, String name) {
154185
if (root == null)
155186
return null;
@@ -178,4 +209,57 @@ private static boolean isMultiRelease(Manifest mf) {
178209
static <E extends Throwable, R> R sneak(Exception exception) throws E {
179210
throw (E)exception;
180211
}
212+
213+
// Only here for testing/profiling because I don't want to setup a whole benchmark sub-project
214+
// Intentionally left in so others can reproduce and I dont have to write this later.
215+
public static void main(String[] args) {
216+
var runs = 100;
217+
var slow = false; // Wither or not to force the slow code path
218+
var forgeWorkspace = Path.of(args.length == 1 ? args[0] : "Z:/Projects/Forge_1214");
219+
if (Files.exists(forgeWorkspace)) {
220+
var exploded = forgeWorkspace.resolve("projects/forge/bin/main");
221+
if (Files.exists(exploded)) {
222+
for (int x = 0; x < runs; x++) {
223+
var mod = findModuleNameImpl(exploded, slow);
224+
if (mod == null || !"net.minecraftforge.forge".equals(mod.name()))
225+
throw new IllegalStateException("Failed to find correct module name from exploded: " + mod);
226+
}
227+
}
228+
var jared = forgeWorkspace.resolve("projects/mcp/build/mcp/downloadServer/server.jar");
229+
if (Files.exists(jared)) {
230+
for (int x = 0; x < runs; x++) {
231+
var mod = findModuleNameImpl(jared, slow);
232+
if (mod != null && mod.name() != null)
233+
throw new IllegalStateException("Expected null module from server jar but was " + mod);
234+
}
235+
}
236+
}
237+
238+
var paths = findAllClassPathEntries();
239+
240+
for (int x = 0; x < runs; x++) {
241+
for (var path : paths) {
242+
var mod = findModuleNameImpl(path, slow);
243+
System.out.println(mod);
244+
}
245+
}
246+
}
247+
248+
private static List<Path> findAllClassPathEntries() {
249+
try {
250+
var parts = System.getProperty("java.class.path").split(File.pathSeparator);
251+
var paths = new ArrayList<Path>();
252+
for (var part : parts) {
253+
var path = new File(part).getCanonicalFile().toPath();
254+
if (!Files.exists(path))
255+
continue;
256+
if (Files.isDirectory(path) && Files.list(path).findAny().isEmpty())
257+
continue;
258+
paths.add(path);
259+
}
260+
return paths;
261+
} catch (IOException e) {
262+
return sneak(e);
263+
}
264+
}
181265
}

0 commit comments

Comments
 (0)