22 * Copyright (c) Forge Development LLC
33 * SPDX-License-Identifier: LGPL-2.1-only
44 */
5- package net .minecraftforge .bootstrap .dev ;
5+ package net .minecraftforge .bootstrap .api ;
66
77import java .io .ByteArrayInputStream ;
8- import java .io .File ;
98import java .io .IOException ;
109import java .lang .module .ModuleDescriptor ;
1110import java .nio .file .FileSystem ;
1615import java .util .Collection ;
1716import java .util .List ;
1817import java .util .jar .Manifest ;
19- import java .util .zip .ZipEntry ;
20- import java .util .zip .ZipInputStream ;
21-
2218import java .util .jar .Attributes .Name ;
2319import java .util .regex .Pattern ;
24- import java .util .jar .JarFile ;
2520
26- class Util {
21+ /**
22+ * A small utility class that I found useful to have between Bootstrap projects
23+ */
24+ public class Util {
2725 private static final Name AUTOMATIC_MODULE_NAME = new Name ("Automatic-Module-Name" );
2826 private static final Name MULTI_RELEASE = new Name ("Multi-Release" );
2927 private static final Name FORGE_MODULE_LAYER = new Name ("Forge-Module-Layer" );
3028 private static final String META_INF = "META-INF" ;
3129 private static final String MANIFEST = "MANIFEST.MF" ;
3230 private static final String VERSIONS = "versions" ;
33- private static final String VERSION_DIR = META_INF + '/' + VERSIONS + '/' ;
3431 private static final String MODULE_INFO = "module-info.class" ;
3532
36- static ModuleVersion findModuleName (Collection <Path > paths ) {
33+ public record ModuleInfo (String name , String version , String layer ) {}
34+
35+ /**
36+ * Attempt to get the Module Info for a collection of paths.
37+ * The first one found will be returned.
38+ *
39+ * It will throw any errors that happen when reading the paths.
40+ *
41+ * @return Null if no named module is found
42+ */
43+ public static ModuleInfo findModule (Collection <Path > paths ) {
3744 for (var path : paths ) {
3845 var ret = findModuleNameImpl (path , false );
3946 if (ret .name != null )
@@ -42,7 +49,15 @@ static ModuleVersion findModuleName(Collection<Path> paths) {
4249 return null ;
4350 }
4451
45- static ModuleVersion findModuleName (Path ... paths ) {
52+ /**
53+ * Attempt to get the Module Info for a array of paths.
54+ * The first one found will be returned
55+ *
56+ * It will throw any errors that happen when reading the paths.
57+ *
58+ * @return Null if no named module is found
59+ */
60+ public static ModuleInfo findModule (Path ... paths ) {
4661 for (var path : paths ) {
4762 var ret = findModuleNameImpl (path , false );
4863 if (ret .name != null )
@@ -51,14 +66,62 @@ static ModuleVersion findModuleName(Path... paths) {
5166 return null ;
5267 }
5368
54- record ModuleVersion (String name , String version , String layer ) {}
55- static ModuleVersion findModuleNameImpl (Path path , boolean slow ) {
69+ /**
70+ * Attempt to get the Module Info for a collection of paths.
71+ * The first one found will be returned.
72+ *
73+ * If no manifest or module-info is found, and this is a single jar path,
74+ * it will attempt to parse the filename as a module name.
75+ *
76+ * It will throw any errors that happen when reading the paths.
77+ *
78+ * @return Null if no named module is found
79+ */
80+ public static ModuleInfo findAutomaticModule (Collection <Path > paths ) {
81+ var ret = findModule (paths );
82+ if (ret != null )
83+ return ret ;
84+
85+ if (paths .size () == 1 )
86+ return findAutomaticModuleImpl (paths .iterator ().next ());
87+
88+ return null ;
89+ }
90+
91+ /**
92+ * Attempt to get the Module Info for a collection of paths.
93+ * The first one found will be returned.
94+ *
95+ * If no manifest or module-info is found, and this is a single jar path,
96+ * it will attempt to parse the filename as a module name.
97+ *
98+ * It will throw any errors that happen when reading the paths.
99+ *
100+ * @return Null if no named module is found
101+ */
102+ public static ModuleInfo findAutomaticModule (Path ... paths ) {
103+ var ret = findModule (paths );
104+ if (ret != null )
105+ return ret ;
106+
107+ if (paths .length == 1 )
108+ return findAutomaticModuleImpl (paths [0 ]);
109+
110+ return null ;
111+ }
112+
113+
114+
115+ /* ======================================================================
116+ * PRIVATE
117+ * ======================================================================
118+ */
119+
120+ private static ModuleInfo findModuleNameImpl (Path path , boolean slow ) {
56121 try {
57122 Candidates data = null ;
58123 if (Files .isDirectory (path )) {
59124 data = findCandidatesDirectory (path );
60- } else if (slow ) {
61- data = findCandidatesZip (path );
62125 } else {
63126 try (FileSystem jarFS = FileSystems .newFileSystem (path )) {
64127 var root = jarFS .getPath ("/" );
@@ -94,7 +157,7 @@ static ModuleVersion findModuleNameImpl(Path path, boolean slow) {
94157 version = desc .version ().map (Object ::toString ).orElse (null );
95158 }
96159
97- return new ModuleVersion (name , version , layer );
160+ return new ModuleInfo (name , version , layer );
98161 } catch (IOException e ) {
99162 return sneak (e );
100163 }
@@ -144,45 +207,6 @@ private static Candidates findCandidatesDirectory(Path path) {
144207 }
145208 }
146209
147- private static Candidates findCandidatesZip (Path path ) {
148- // I would use JarFile here and let the JVM handle all this.
149- // but it only works on File objects not Paths
150- // Normal java gets around this by extracting all non-file paths
151- // to a temp directory and opening them as normal files.
152- // I do not want to do this. So this is what you get.
153- try (var zip = new ZipInputStream (Files .newInputStream (path ))) {
154- var infos = new ArrayList <InfoData >();
155- Manifest mf = null ;
156- ZipEntry entry ;
157- while ((entry = zip .getNextEntry ()) != null ) {
158- var name = entry .getName ();
159- if (mf == null && JarFile .MANIFEST_NAME .equalsIgnoreCase (name )) {
160- mf = new Manifest (zip );
161- } else if (name .endsWith (MODULE_INFO )) {
162- int version = 0 ;
163- if (name .startsWith (VERSION_DIR )) {
164- if (!isMultiRelease (mf ))
165- continue ;
166-
167- int idx = name .indexOf ('/' , VERSION_DIR .length ());
168- var ver = name .substring (VERSION_DIR .length (), idx );
169- try {
170- version = Integer .parseInt (ver );
171- } catch (NumberFormatException e ) {
172- // If its not a numerical directory we don't care
173- version = Integer .MAX_VALUE ;
174- }
175- }
176- if (version <= Runtime .version ().feature ())
177- infos .add (new InfoData (version , zip .readAllBytes ()));
178- }
179- }
180- return new Candidates (mf , infos );
181- } catch (IOException e ) {
182- return sneak (e );
183- }
184- }
185-
186210 private static Path findInsensitive (Path root , String name ) {
187211 if (root == null )
188212 return null ;
@@ -208,68 +232,16 @@ private static boolean isMultiRelease(Manifest mf) {
208232 }
209233
210234 @ SuppressWarnings ("unchecked" )
211- static <E extends Throwable , R > R sneak (Exception exception ) throws E {
235+ private static <E extends Throwable , R > R sneak (Exception exception ) throws E {
212236 throw (E )exception ;
213237 }
214238
215- // Only here for testing/profiling because I don't want to setup a whole benchmark sub-project
216- // Intentionally left in so others can reproduce and I dont have to write this later.
217- public static void main (String [] args ) {
218- var runs = 100 ;
219- var slow = false ; // Wither or not to force the slow code path
220- var forgeWorkspace = Path .of (args .length == 1 ? args [0 ] : "Z:/Projects/Forge_1214" );
221- if (Files .exists (forgeWorkspace )) {
222- var exploded = forgeWorkspace .resolve ("projects/forge/bin/main" );
223- if (Files .exists (exploded )) {
224- for (int x = 0 ; x < runs ; x ++) {
225- var mod = findModuleNameImpl (exploded , slow );
226- if (mod == null || !"net.minecraftforge.forge" .equals (mod .name ()))
227- throw new IllegalStateException ("Failed to find correct module name from exploded: " + mod );
228- }
229- }
230- var jared = forgeWorkspace .resolve ("projects/mcp/build/mcp/downloadServer/server.jar" );
231- if (Files .exists (jared )) {
232- for (int x = 0 ; x < runs ; x ++) {
233- var mod = findModuleNameImpl (jared , slow );
234- if (mod != null && mod .name () != null )
235- throw new IllegalStateException ("Expected null module from server jar but was " + mod );
236- }
237- }
238- }
239-
240- var paths = findAllClassPathEntries ();
241-
242- for (int x = 0 ; x < runs ; x ++) {
243- for (var path : paths ) {
244- var mod = findModuleNameImpl (path , slow );
245- System .out .println (mod );
246- }
247- }
248- }
249-
250- private static List <Path > findAllClassPathEntries () {
251- try {
252- var parts = System .getProperty ("java.class.path" ).split (File .pathSeparator );
253- var paths = new ArrayList <Path >();
254- for (var part : parts ) {
255- var path = new File (part ).getCanonicalFile ().toPath ();
256- if (!Files .exists (path ))
257- continue ;
258- if (Files .isDirectory (path ) && Files .list (path ).findAny ().isEmpty ())
259- continue ;
260- paths .add (path );
261- }
262- return paths ;
263- } catch (IOException e ) {
264- return sneak (e );
265- }
266- }
267-
268- public static ModuleVersion findAutomaticModuleName (Path [] paths ) {
269- if (paths .length != 1 || paths [0 ].getFileName () == null )
239+ private static ModuleInfo findAutomaticModuleImpl (Path path ) {
240+ var filename = path .getFileName ();
241+ if (filename == null )
270242 return null ;
271243
272- var name = paths [ 0 ] .getFileName ().toString ();
244+ var name = path .getFileName ().toString ();
273245 if (!name .endsWith (".jar" ))
274246 return null ;
275247
@@ -288,7 +260,7 @@ public static ModuleVersion findAutomaticModuleName(Path[] paths) {
288260 name = name .substring (0 , start );
289261 }
290262
291- return new ModuleVersion (cleanModuleName (name ), version , null );
263+ return new ModuleInfo (cleanModuleName (name ), version , null );
292264 }
293265
294266 // Stolen from jdk.internal.module.ModulePath
0 commit comments