1 /*******************************************************************************
2  * Copyright (c) 2015, 2020 IBM Corporation.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *******************************************************************************/
14 package org.eclipse.jdt.internal.compiler.util;
15 
16 import java.io.ByteArrayInputStream;
17 import java.io.File;
18 import java.io.IOException;
19 import java.io.InputStream;
20 import java.net.URI;
21 import java.net.URL;
22 import java.net.URLClassLoader;
23 import java.nio.channels.ClosedByInterruptException;
24 import java.nio.file.DirectoryStream;
25 import java.nio.file.FileSystem;
26 import java.nio.file.FileSystems;
27 import java.nio.file.FileVisitResult;
28 import java.nio.file.FileVisitor;
29 import java.nio.file.Files;
30 import java.nio.file.NoSuchFileException;
31 import java.nio.file.Path;
32 import java.nio.file.Paths;
33 import java.nio.file.attribute.BasicFileAttributes;
34 import java.util.ArrayList;
35 import java.util.Collections;
36 import java.util.HashMap;
37 import java.util.List;
38 import java.util.Map;
39 import java.util.Optional;
40 import java.util.concurrent.ConcurrentHashMap;
41 import java.util.function.Predicate;
42 
43 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
44 import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
45 import org.eclipse.jdt.internal.compiler.env.IModule;
46 
47 public class JRTUtil {
48 
49 	public static final boolean DISABLE_CACHE = Boolean.getBoolean("org.eclipse.jdt.disable_JRT_cache"); //$NON-NLS-1$
50 
51 	public static final String JAVA_BASE = "java.base"; //$NON-NLS-1$
52 	public static final char[] JAVA_BASE_CHAR = JAVA_BASE.toCharArray();
53 	static final String MODULES_SUBDIR = "/modules"; //$NON-NLS-1$
54 	static final String[] DEFAULT_MODULE = new String[]{JAVA_BASE};
55 	static final String[] NO_MODULE = new String[0];
56 	static final String MULTIPLE = "MU"; //$NON-NLS-1$
57 	static final String DEFAULT_PACKAGE = ""; //$NON-NLS-1$
58 	static String MODULE_TO_LOAD;
59 	public static final String JRT_FS_JAR = "jrt-fs.jar"; //$NON-NLS-1$
60 	static URI JRT_URI = URI.create("jrt:/"); //$NON-NLS-1$
61 	public static final int NOTIFY_FILES = 0x0001;
62 	public static final int NOTIFY_PACKAGES = 0x0002;
63 	public static final int NOTIFY_MODULES = 0x0004;
64 	public static final int NOTIFY_ALL = NOTIFY_FILES | NOTIFY_PACKAGES | NOTIFY_MODULES;
65 
66 	// TODO: Java 9 Think about clearing the cache too.
67 	private static Map<String, Optional<JrtFileSystem>> images = new ConcurrentHashMap<>();
68 
69 	/**
70 	 * Map from JDK home path to ct.sym file (located in /lib in the JDK)
71 	 */
72 	private static final Map<Path, CtSym> ctSymFiles = new ConcurrentHashMap<>();
73 
74 	public interface JrtFileVisitor<T> {
75 
visitPackage(T dir, T mod, BasicFileAttributes attrs)76 		public FileVisitResult visitPackage(T dir, T mod, BasicFileAttributes attrs) throws IOException;
77 
visitFile(T file, T mod, BasicFileAttributes attrs)78 		public FileVisitResult visitFile(T file, T mod, BasicFileAttributes attrs) throws IOException;
79 		/**
80 		 * Invoked when a root directory of a module being visited. The element returned
81 		 * contains only the module name segment - e.g. "java.base". Clients can use this to control
82 		 * how the JRT needs to be processed, for e.g., clients can skip a particular module
83 		 * by returning FileVisitResult.SKIP_SUBTREE
84 		 */
visitModule(T path, String name)85 		public FileVisitResult visitModule(T path, String name) throws IOException;
86 	}
87 
88 	static abstract class AbstractFileVisitor<T> implements FileVisitor<T> {
89 		@Override
preVisitDirectory(T dir, BasicFileAttributes attrs)90 		public FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs) throws IOException {
91 			return FileVisitResult.CONTINUE;
92 		}
93 
94 		@Override
visitFile(T file, BasicFileAttributes attrs)95 		public FileVisitResult visitFile(T file, BasicFileAttributes attrs) throws IOException {
96 			return FileVisitResult.CONTINUE;
97 		}
98 
99 		@Override
visitFileFailed(T file, IOException exc)100 		public FileVisitResult visitFileFailed(T file, IOException exc) throws IOException {
101 			return FileVisitResult.CONTINUE;
102 		}
103 
104 		@Override
postVisitDirectory(T dir, IOException exc)105 		public FileVisitResult postVisitDirectory(T dir, IOException exc) throws IOException {
106 			return FileVisitResult.CONTINUE;
107 		}
108 	}
109 
getJrtSystem(File image)110 	public static JrtFileSystem getJrtSystem(File image) {
111 		return getJrtSystem(image, null);
112 	}
113 
getJrtSystem(File image, String release)114 	public static JrtFileSystem getJrtSystem(File image, String release) {
115 		String key = image.toString();
116 		if (release != null) key = key + "|" + release; //$NON-NLS-1$
117 		Optional<JrtFileSystem> system = images.computeIfAbsent(key, x -> {
118 			try {
119 				return Optional.ofNullable(JrtFileSystem.getNewJrtFileSystem(image, release));
120 			} catch (IOException e) {
121 				// Needs better error handling downstream? But for now, make sure
122 				// a dummy JrtFileSystem is not created.
123 				e.printStackTrace();
124 				return Optional.empty();
125 			}
126 		});
127 		return system.orElse(null);
128 	}
129 
getCtSym(Path jdkHome)130 	public static CtSym getCtSym(Path jdkHome) throws IOException {
131 		CtSym ctSym;
132 		try {
133 			ctSym = ctSymFiles.compute(jdkHome, (Path x, CtSym current) -> {
134 				if (current == null || !current.getFs().isOpen()) {
135 					try {
136 						return new CtSym(x);
137 					} catch (IOException e) {
138 						throw new RuntimeIOException(e);
139 					}
140 				}
141 				return current;
142 			});
143 		} catch (RuntimeIOException rio) {
144 			throw rio.getCause();
145 		}
146 		return ctSym;
147 	}
148 
149 	/** TEST ONLY (use when changing the "modules.to.load" property). */
reset()150 	public static void reset() {
151 		images.clear();
152 		MODULE_TO_LOAD = System.getProperty("modules.to.load"); //$NON-NLS-1$
153 	}
154 
155 	/**
156 	 * Given the path of a modular image file, this method walks the archive content and
157 	 * notifies the supplied visitor about packages and files visited.
158 	 *
159 	 * The file system contains the following top level directories:
160 	 *  /modules/$MODULE/$PATH
161 	 *  /packages/$PACKAGE/$MODULE
162 	 *  The latter provides quick look up of the module that contains a particular package. However,
163 	 *  this method only notifies its clients of the entries within the modules (latter) sub-directory.
164 	 *  Clients can decide which notifications they want to receive. See {@link JRTUtil#NOTIFY_ALL},
165 	 *  {@link JRTUtil#NOTIFY_FILES}, {@link JRTUtil#NOTIFY_PACKAGES} and {@link JRTUtil#NOTIFY_MODULES}.
166 	 *
167 	 * @param image a java.io.File handle to the JRT image.
168 	 * @param visitor an instance of JrtFileVisitor to be notified of the entries in the JRT image.
169 	 * @param notify flag indicating the notifications the client is interested in.
170 	 * @throws IOException
171 	 */
walkModuleImage(File image, final JRTUtil.JrtFileVisitor<java.nio.file.Path> visitor, int notify)172 	public static void walkModuleImage(File image, final JRTUtil.JrtFileVisitor<java.nio.file.Path> visitor, int notify) throws IOException {
173 		getJrtSystem(image, null).walkModuleImage(visitor, notify);
174 	}
175 
walkModuleImage(File image, String release, final JRTUtil.JrtFileVisitor<java.nio.file.Path> visitor, int notify)176 	public static void walkModuleImage(File image, String release, final JRTUtil.JrtFileVisitor<java.nio.file.Path> visitor, int notify) throws IOException {
177 		getJrtSystem(image, release).walkModuleImage(visitor, notify);
178 	}
179 
getContentFromJrt(File jrt, String fileName, String module)180 	public static InputStream getContentFromJrt(File jrt, String fileName, String module) throws IOException {
181 		return getJrtSystem(jrt).getContentFromJrt(fileName, module);
182 	}
183 
getClassfileContent(File jrt, String fileName, String module)184 	public static byte[] getClassfileContent(File jrt, String fileName, String module) throws IOException {
185 		return getJrtSystem(jrt).getClassfileContent(fileName, module);
186 	}
187 
getClassfile(File jrt, String fileName, IModule module)188 	public static ClassFileReader getClassfile(File jrt, String fileName, IModule module) throws IOException, ClassFormatException {
189 		return getJrtSystem(jrt).getClassfile(fileName, module);
190 	}
191 
getClassfile(File jrt, String fileName, String module, Predicate<String> moduleNameFilter)192 	public static ClassFileReader getClassfile(File jrt, String fileName, String module, Predicate<String> moduleNameFilter) throws IOException, ClassFormatException {
193 		return getJrtSystem(jrt).getClassfile(fileName, module, moduleNameFilter);
194 	}
195 
getModulesDeclaringPackage(File jrt, String qName, String moduleName)196 	public static List<String> getModulesDeclaringPackage(File jrt, String qName, String moduleName) {
197 		return getJrtSystem(jrt).getModulesDeclaringPackage(qName, moduleName);
198 	}
199 
hasCompilationUnit(File jrt, String qualifiedPackageName, String moduleName)200 	public static boolean hasCompilationUnit(File jrt, String qualifiedPackageName, String moduleName) {
201 		return getJrtSystem(jrt).hasClassFile(qualifiedPackageName, moduleName);
202 	}
203 
204 	/*
205 	 * Returns only the file name after removing trailing '/' if any for folders
206 	 */
sanitizedFileName(Path path)207 	public static String sanitizedFileName(Path path) {
208 		String p = path.getFileName().toString();
209 		if (p.length() > 1 && p.charAt(p.length() - 1) == '/') {
210 			return p.substring(0, p.length() - 1);
211 		}
212 		return p;
213 	}
214 
215 	/**
216 	 * Tries to read all bytes of the file denoted by path,
217 	 * returns null if the file could not be found or if the read was interrupted.
218 	 * @param path
219 	 * @return bytes or null
220 	 * @throws IOException any IO exception other than NoSuchFileException
221 	 */
safeReadBytes(Path path)222 	public static byte[] safeReadBytes(Path path) throws IOException {
223 		try {
224 			return Files.readAllBytes(path);
225 		} catch (ClosedByInterruptException | NoSuchFileException e) {
226 			return null;
227 		}
228 	}
229 }
230 
231 class JrtFileSystemWithOlderRelease extends JrtFileSystem {
232 
233 	final String release;
234 	String releaseInHex;
235 	private List<Path> releaseRoots = Collections.emptyList();
236 	protected Path modulePath;
237 	private CtSym ctSym;
238 
239 	/**
240 	 * The jrt file system is based on the location of the JRE home whose libraries
241 	 * need to be loaded.
242 	 *
243 	 * @param jrt the path to the root of the JRE whose libraries we are interested in.
244 	 * @param release the older release where classes and modules should be searched for.
245 	 * @throws IOException
246 	 */
JrtFileSystemWithOlderRelease(File jrt, String release)247 	JrtFileSystemWithOlderRelease(File jrt, String release) throws IOException {
248 		super(jrt);
249 		this.release = release;
250 		initialize(jrt, release);
251 	}
252 
253 	@Override
initialize(File jdk)254 	void initialize(File jdk) throws IOException {
255 		// Just to make sure we don't do anything in super.initialize()
256 		// before setting this.release
257 	}
258 
initialize(File jdk, String rel)259 	private void initialize(File jdk, String rel) throws IOException {
260 		super.initialize(jdk);
261 		this.fs = null;// reset and proceed, TODO: this is crude and need to be removed.
262 		this.releaseInHex = Integer.toHexString(Integer.parseInt(this.release)).toUpperCase();
263 		this.ctSym = JRTUtil.getCtSym(Paths.get(this.jdkHome));
264 		this.fs = this.ctSym.getFs();
265 		if (!Files.exists(this.fs.getPath(this.releaseInHex))
266 				|| Files.exists(this.fs.getPath(this.releaseInHex, "system-modules"))) { //$NON-NLS-1$
267 			this.fs = null;
268 		}
269 		this.releaseRoots = this.ctSym.releaseRoots(this.releaseInHex);
270 	}
271 
272 	@Override
walkModuleImage(final JRTUtil.JrtFileVisitor<java.nio.file.Path> visitor, final int notify)273 	void walkModuleImage(final JRTUtil.JrtFileVisitor<java.nio.file.Path> visitor, final int notify) throws IOException {
274 		for (Path p : this.releaseRoots) {
275 			Files.walkFileTree(p, new JRTUtil.AbstractFileVisitor<java.nio.file.Path>() {
276 				@Override
277 				public FileVisitResult preVisitDirectory(java.nio.file.Path dir, BasicFileAttributes attrs)
278 						throws IOException {
279 					int count = dir.getNameCount();
280 					if (count == 1) {
281 						return FileVisitResult.CONTINUE;
282 					}
283 					if (count == 2) {
284 						// e.g. /9A/java.base
285 						java.nio.file.Path mod = dir.getName(1);
286 						if ((JRTUtil.MODULE_TO_LOAD != null && JRTUtil.MODULE_TO_LOAD.length() > 0
287 								&& JRTUtil.MODULE_TO_LOAD.indexOf(mod.toString()) == -1)) {
288 							return FileVisitResult.SKIP_SUBTREE;
289 						}
290 						return ((notify & JRTUtil.NOTIFY_MODULES) == 0) ? FileVisitResult.CONTINUE
291 								: visitor.visitModule(dir, JRTUtil.sanitizedFileName(mod));
292 					}
293 					if ((notify & JRTUtil.NOTIFY_PACKAGES) == 0) {
294 						// client is not interested in packages
295 						return FileVisitResult.CONTINUE;
296 					}
297 					return visitor.visitPackage(dir.subpath(2, count), dir.getName(1), attrs);
298 				}
299 
300 				@Override
301 				public FileVisitResult visitFile(java.nio.file.Path file, BasicFileAttributes attrs)
302 						throws IOException {
303 					if ((notify & JRTUtil.NOTIFY_FILES) == 0) {
304 						return FileVisitResult.CONTINUE;
305 					}
306 					// This happens when a file in a default package is present. E.g. /modules/some.module/file.name
307 					if (file.getNameCount() == 3) {
308 						cachePackage(JRTUtil.DEFAULT_PACKAGE, file.getName(1).toString());
309 					}
310 					return visitor.visitFile(file.subpath(2, file.getNameCount()), file.getName(1), attrs);
311 				}
312 			});
313 		}
314 	}
315 
316 }
317 
318 final class RuntimeIOException extends RuntimeException {
319 	private static final long serialVersionUID = 1L;
320 
RuntimeIOException(IOException cause)321 	public RuntimeIOException(IOException cause) {
322 		super(cause);
323 	}
324 
325 	@Override
getCause()326 	public synchronized IOException getCause() {
327 		return (IOException) super.getCause();
328 	}
329 }
330 
331 class JrtFileSystem {
332 
333 	private final Map<String, String> packageToModule = new HashMap<String, String>();
334 
335 	private final Map<String, List<String>> packageToModules = new HashMap<String, List<String>>();
336 
337 
338 	private final Map<Path, Optional<byte[]>> classCache = new ConcurrentHashMap<>(10007);
339 
340 	FileSystem fs;
341 	Path modRoot;
342 	String jdkHome;
343 
getNewJrtFileSystem(File jrt, String release)344 	public static JrtFileSystem getNewJrtFileSystem(File jrt, String release) throws IOException {
345 		return (release == null) ? new JrtFileSystem(jrt) :
346 				new JrtFileSystemWithOlderRelease(jrt, release);
347 
348 	}
349 
350 	/**
351 	 * The jrt file system is based on the location of the JRE home whose libraries
352 	 * need to be loaded.
353 	 *
354 	 * @param jrt the path to the root of the JRE whose libraries we are interested in.
355 	 * @throws IOException
356 	 */
JrtFileSystem(File jrt)357 	JrtFileSystem(File jrt) throws IOException {
358 		initialize(jrt);
359 	}
360 
initialize(File jrt)361 	void initialize(File jrt) throws IOException {
362 		URL jrtPath = null;
363 		this.jdkHome = null;
364 		if (jrt.toString().endsWith(JRTUtil.JRT_FS_JAR)) {
365 			jrtPath = jrt.toPath().toUri().toURL();
366 			this.jdkHome = jrt.getParentFile().getParent();
367 		} else {
368 			this.jdkHome = jrt.toPath().toString();
369 			jrtPath = Paths.get(this.jdkHome, "lib", JRTUtil.JRT_FS_JAR).toUri().toURL(); //$NON-NLS-1$
370 
371 		}
372 		JRTUtil.MODULE_TO_LOAD = System.getProperty("modules.to.load"); //$NON-NLS-1$
373 		String javaVersion = System.getProperty("java.version"); //$NON-NLS-1$
374 		if (javaVersion != null && javaVersion.startsWith("1.8")) { //$NON-NLS-1$
375 			URLClassLoader loader = new URLClassLoader(new URL[] { jrtPath });
376 			HashMap<String, ?> env = new HashMap<>();
377 			this.fs = FileSystems.newFileSystem(JRTUtil.JRT_URI, env, loader);
378 		} else {
379 			HashMap<String, String> env = new HashMap<>();
380 			env.put("java.home", this.jdkHome); //$NON-NLS-1$
381 			this.fs = FileSystems.newFileSystem(JRTUtil.JRT_URI, env);
382 		}
383 		this.modRoot = this.fs.getPath(JRTUtil.MODULES_SUBDIR);
384 		// Set up the root directory wherere modules are located
385 		walkJrtForModules();
386 	}
387 
getModulesDeclaringPackage(String qualifiedPackageName, String moduleName)388 	public List<String> getModulesDeclaringPackage(String qualifiedPackageName, String moduleName) {
389 		qualifiedPackageName = qualifiedPackageName.replace('.', '/');
390 		String module = this.packageToModule.get(qualifiedPackageName);
391 		if (moduleName == null) {
392 			// wildcard search:
393 			if (module == null)
394 				return null;
395 			if (module == JRTUtil.MULTIPLE)
396 				return this.packageToModules.get(qualifiedPackageName);
397 			return Collections.singletonList(module);
398 		}
399 		if (module != null) {
400 			// specific search:
401 			if (module == JRTUtil.MULTIPLE) {
402 				List<String> list = this.packageToModules.get(qualifiedPackageName);
403 				if (list.contains(moduleName))
404 					return Collections.singletonList(moduleName);
405 			} else {
406 				if (module.equals(moduleName))
407 					return Collections.singletonList(moduleName);
408 			}
409 		}
410 		return null;
411 	}
412 
getModules(String fileName)413 	public String[] getModules(String fileName) {
414 		int idx = fileName.lastIndexOf('/');
415 		String pack = null;
416 		if (idx != -1) {
417 			pack = fileName.substring(0, idx);
418 		} else {
419 			pack = JRTUtil.DEFAULT_PACKAGE;
420 		}
421 		String module = this.packageToModule.get(pack);
422 		if (module != null) {
423 			if (module == JRTUtil.MULTIPLE) {
424 				List<String> list = this.packageToModules.get(pack);
425 				return list.toArray(new String[0]);
426 			} else {
427 				return new String[]{module};
428 			}
429 		}
430 		return JRTUtil.DEFAULT_MODULE;
431 	}
432 
hasClassFile(String qualifiedPackageName, String module)433 	public boolean hasClassFile(String qualifiedPackageName, String module) {
434 		if (module == null)
435 			return false;
436 		// easy checks first:
437 		String knownModule = this.packageToModule.get(qualifiedPackageName);
438 		if (knownModule == null || (knownModule != JRTUtil.MULTIPLE && !knownModule.equals(module)))
439 			return false;
440 		Path packagePath = this.fs.getPath(JRTUtil.MODULES_SUBDIR, module, qualifiedPackageName);
441 		if (!Files.exists(packagePath))
442 			return false;
443 		// iterate files:
444 		try {
445 			return Files.list(packagePath)
446 				.anyMatch(filePath -> filePath.toString().endsWith(SuffixConstants.SUFFIX_STRING_class)
447 										|| filePath.toString().endsWith(SuffixConstants.SUFFIX_STRING_CLASS));
448 		} catch (IOException e) {
449 			return false;
450 		}
451 	}
452 
getContentFromJrt(String fileName, String module)453 	public InputStream getContentFromJrt(String fileName, String module) throws IOException {
454 		if (module != null) {
455 			byte[] fileBytes = getFileBytes(fileName, module);
456 			if(fileBytes == null) {
457 				return null;
458 			}
459 			return new ByteArrayInputStream(fileBytes);
460 		}
461 		String[] modules = getModules(fileName);
462 		for (String mod : modules) {
463 			byte[] fileBytes = getFileBytes(fileName, mod);
464 			if(fileBytes != null) {
465 				return new ByteArrayInputStream(fileBytes);
466 			}
467 		}
468 		return null;
469 	}
470 
getClassfile(String fileName, Predicate<String> moduleNameFilter)471 	private ClassFileReader getClassfile(String fileName, Predicate<String> moduleNameFilter) throws IOException, ClassFormatException {
472 		String[] modules = getModules(fileName);
473 		byte[] content = null;
474 		String module = null;
475 		for (String mod : modules) {
476 			if (moduleNameFilter != null && !moduleNameFilter.test(mod)) {
477 				continue;
478 			}
479 			content = getFileBytes(fileName, mod);
480 			if (content != null) {
481 				module = mod;
482 				break;
483 			}
484 		}
485 		if (content != null) {
486 			ClassFileReader reader = new ClassFileReader(content, fileName.toCharArray());
487 			reader.moduleName = module.toCharArray();
488 			return reader;
489 		}
490 		return null;
491 	}
492 
getClassfileContent(String fileName, String module)493 	byte[] getClassfileContent(String fileName, String module) throws IOException {
494 		byte[] content = null;
495 		if (module != null) {
496 			content = getFileBytes(fileName, module);
497 		} else {
498 			String[] modules = getModules(fileName);
499 			for (String mod : modules) {
500 				content = getFileBytes(fileName, mod);
501 				if (content != null) {
502 					break;
503 				}
504 			}
505 		}
506 		return content;
507 	}
508 
getFileBytes(String fileName, String module)509 	private byte[] getFileBytes(String fileName, String module) throws IOException {
510 		Path path = this.fs.getPath(JRTUtil.MODULES_SUBDIR, module, fileName);
511 		if(JRTUtil.DISABLE_CACHE) {
512 			return JRTUtil.safeReadBytes(path);
513 		} else {
514 			try {
515 				Optional<byte[]> bytes = this.classCache.computeIfAbsent(path, key -> {
516 					try {
517 						return Optional.ofNullable(JRTUtil.safeReadBytes(key));
518 					} catch (IOException e) {
519 						throw new RuntimeIOException(e);
520 					}
521 				});
522 				return bytes.orElse(null);
523 			} catch (RuntimeIOException rio) {
524 				throw rio.getCause();
525 			}
526 		}
527 	}
528 
getClassfile(String fileName, String module, Predicate<String> moduleNameFilter)529 	public ClassFileReader getClassfile(String fileName, String module, Predicate<String> moduleNameFilter) throws IOException, ClassFormatException {
530 		ClassFileReader reader = null;
531 		if (module == null) {
532 			reader = getClassfile(fileName, moduleNameFilter);
533 		} else {
534 			byte[] content = getFileBytes(fileName, module);
535 			if (content != null) {
536 				reader = new ClassFileReader(content, fileName.toCharArray());
537 				reader.moduleName = module.toCharArray();
538 			}
539 		}
540 		return reader;
541 	}
542 
getClassfile(String fileName, IModule module)543 	public ClassFileReader getClassfile(String fileName, IModule module) throws IOException, ClassFormatException {
544 		ClassFileReader reader = null;
545 		if (module == null) {
546 			reader = getClassfile(fileName, (Predicate<String>)null);
547 		} else {
548 			byte[] content = getFileBytes(fileName, new String(module.name()));
549 			if (content != null) {
550 				reader = new ClassFileReader(content, fileName.toCharArray());
551 			}
552 		}
553 		return reader;
554 	}
555 
walkJrtForModules()556 	void walkJrtForModules() throws IOException {
557 		Iterable<java.nio.file.Path> roots = this.fs.getRootDirectories();
558 		for (java.nio.file.Path path : roots) {
559 			try (DirectoryStream<java.nio.file.Path> stream = Files.newDirectoryStream(path)) {
560 				for (final java.nio.file.Path subdir: stream) {
561 					if (!subdir.toString().equals(JRTUtil.MODULES_SUBDIR)) {
562 						Files.walkFileTree(subdir, new JRTUtil.AbstractFileVisitor<java.nio.file.Path>() {
563 							@Override
564 							public FileVisitResult visitFile(java.nio.file.Path file, BasicFileAttributes attrs) throws IOException {
565 								// e.g. /modules/java.base
566 								java.nio.file.Path relative = subdir.relativize(file);
567 								cachePackage(relative.getParent().toString(), relative.getFileName().toString());
568 								return FileVisitResult.CONTINUE;
569 							}
570 						});
571 					}
572 			    }
573 			} catch (Exception e) {
574 				throw new IOException(e.getMessage(), e);
575 			}
576 		}
577 	}
578 
walkModuleImage(final JRTUtil.JrtFileVisitor<java.nio.file.Path> visitor, final int notify)579 	void walkModuleImage(final JRTUtil.JrtFileVisitor<java.nio.file.Path> visitor, final int notify) throws IOException {
580 		Files.walkFileTree(this.modRoot, new JRTUtil.AbstractFileVisitor<java.nio.file.Path>() {
581 			@Override
582 			public FileVisitResult preVisitDirectory(java.nio.file.Path dir, BasicFileAttributes attrs) throws IOException {
583 				int count = dir.getNameCount();
584 				if (count == 1) return FileVisitResult.CONTINUE;
585 				if (count == 2) {
586 					// e.g. /modules/java.base
587 					java.nio.file.Path mod = dir.getName(1);
588 					if ((JRTUtil.MODULE_TO_LOAD != null && JRTUtil.MODULE_TO_LOAD.length() > 0 &&
589 							JRTUtil.MODULE_TO_LOAD.indexOf(mod.toString()) == -1)) {
590 						return FileVisitResult.SKIP_SUBTREE;
591 					}
592 					return ((notify & JRTUtil.NOTIFY_MODULES) == 0) ?
593 							FileVisitResult.CONTINUE : visitor.visitModule(dir, JRTUtil.sanitizedFileName(mod));
594 				}
595 				if ((notify & JRTUtil.NOTIFY_PACKAGES) == 0) {
596 					// We are dealing with a module or not client is not interested in packages
597 					return FileVisitResult.CONTINUE;
598 				}
599 				return visitor.visitPackage(dir.subpath(2, count), dir.getName(1), attrs);
600 			}
601 
602 			@Override
603 			public FileVisitResult visitFile(java.nio.file.Path file, BasicFileAttributes attrs) throws IOException {
604 				if ((notify & JRTUtil.NOTIFY_FILES) == 0)
605 					return FileVisitResult.CONTINUE;
606 				int count = file.getNameCount();
607 				// This happens when a file in a default package is present. E.g. /modules/some.module/file.name
608 				if (count == 3) {
609 					cachePackage(JRTUtil.DEFAULT_PACKAGE, file.getName(1).toString());
610 				}
611 				return visitor.visitFile(file.subpath(2, count), file.getName(1), attrs);
612 			}
613 		});
614 	}
615 
cachePackage(String packageName, String module)616 	void cachePackage(String packageName, String module) {
617 		packageName = packageName.intern();
618 		module = module.intern();
619 		packageName = packageName.replace('.', '/');
620 		Object current = this.packageToModule.get(packageName);
621 		if (current == null) {
622 			this.packageToModule.put(packageName, module);
623 		} else if(current == module || current.equals(module)) {
624 			return;
625 		} else if (current == JRTUtil.MULTIPLE) {
626 			List<String> list = this.packageToModules.get(packageName);
627 			if (!list.contains(module)) {
628 				if (JRTUtil.JAVA_BASE == module || JRTUtil.JAVA_BASE.equals(module)) {
629 					list.add(0, JRTUtil.JAVA_BASE);
630 				} else {
631 					list.add(module);
632 				}
633 			}
634 		} else {
635 			String first = (String) current;
636 			this.packageToModule.put(packageName, JRTUtil.MULTIPLE);
637 			List<String> list = new ArrayList<String>();
638 			// Just do this as comparator might be overkill
639 			if (JRTUtil.JAVA_BASE == current || JRTUtil.JAVA_BASE.equals(current)) {
640 				list.add(first);
641 				list.add(module);
642 			} else {
643 				list.add(module);
644 				list.add(first);
645 			}
646 			this.packageToModules.put(packageName, list);
647 		}
648 	}
649 }