38 import java.net.URI;
39 import java.net.URL;
40 import java.nio.ByteBuffer;
41 import java.nio.file.Files;
42 import java.nio.file.Path;
43 import java.nio.file.Paths;
44 import java.util.ArrayList;
45 import java.util.Collections;
46 import java.util.HashMap;
47 import java.util.HashSet;
48 import java.util.List;
49 import java.util.Map;
50 import java.util.Optional;
51 import java.util.Set;
52 import java.util.jar.JarEntry;
53 import java.util.jar.JarFile;
54 import java.util.stream.Collectors;
55 import java.util.stream.Stream;
56
57 import jdk.internal.loader.Resource;
58 import jdk.internal.loader.ResourceHelper;
59 import jdk.internal.misc.JavaLangModuleAccess;
60 import jdk.internal.misc.SharedSecrets;
61 import sun.net.www.ParseUtil;
62
63
64 /**
65 * Provides support for patching modules, mostly the boot layer.
66 */
67
68 public final class ModulePatcher {
69
70 private static final JavaLangModuleAccess JLMA
71 = SharedSecrets.getJavaLangModuleAccess();
72
73 // module name -> sequence of patches (directories or JAR files)
74 private final Map<String, List<Path>> map;
75
76 /**
77 * Initialize the module patcher with the given map. The map key is
78 * the module name, the value is a list of path strings.
148 } catch (IOException ioe) {
149 throw new UncheckedIOException(ioe);
150 }
151
152 // if there are new packages then we need a new ModuleDescriptor
153 packages.removeAll(descriptor.packages());
154 if (!packages.isEmpty()) {
155 Builder builder = JLMA.newModuleBuilder(descriptor.name(),
156 /*strict*/ false,
157 descriptor.modifiers());
158 if (!descriptor.isAutomatic()) {
159 descriptor.requires().forEach(builder::requires);
160 descriptor.exports().forEach(builder::exports);
161 descriptor.opens().forEach(builder::opens);
162 descriptor.uses().forEach(builder::uses);
163 }
164 descriptor.provides().forEach(builder::provides);
165
166 descriptor.version().ifPresent(builder::version);
167 descriptor.mainClass().ifPresent(builder::mainClass);
168 descriptor.osName().ifPresent(builder::osName);
169 descriptor.osArch().ifPresent(builder::osArch);
170 descriptor.osVersion().ifPresent(builder::osVersion);
171
172 // original + new packages
173 builder.packages(descriptor.packages());
174 builder.packages(packages);
175
176 descriptor = builder.build();
177 }
178
179 // return a module reference to the patched module
180 URI location = mref.location().orElse(null);
181
182 ModuleHashes recordedHashes = null;
183 ModuleResolution mres = null;
184 if (mref instanceof ModuleReferenceImpl) {
185 ModuleReferenceImpl impl = (ModuleReferenceImpl)mref;
186 recordedHashes = impl.recordedHashes();
187 mres = impl.moduleResolution();
188 }
189
190 return new ModuleReferenceImpl(descriptor,
191 location,
192 () -> new PatchedModuleReader(paths, mref),
193 this,
194 recordedHashes,
195 null,
196 mres);
197
198 }
199
200 /**
201 * Returns true is this module patcher has no patches.
202 */
203 public boolean isEmpty() {
204 return map.isEmpty();
205 }
206
207 /*
208 * Returns the names of the patched modules.
209 */
210 Set<String> patchedModules() {
211 return map.keySet();
212 }
213
214 /**
215 * A ModuleReader that reads resources from a patched module.
216 *
217 * This class is public so as to expose the findResource method to the
218 * built-in class loaders and avoid locating the resource twice during
219 * class loading (once to locate the resource, the second to gets the
220 * URL for the CodeSource).
221 */
222 public static class PatchedModuleReader implements ModuleReader {
223 private final List<ResourceFinder> finders;
224 private final ModuleReference mref;
225 private final URL delegateCodeSourceURL;
226 private volatile ModuleReader delegate;
227
228 /**
229 * Creates the ModuleReader to reads resources a patched module.
230 */
231 PatchedModuleReader(List<Path> patches, ModuleReference mref) {
232 List<ResourceFinder> finders = new ArrayList<>();
233 boolean initialized = false;
234 try {
235 for (Path file : patches) {
236 if (Files.isRegularFile(file)) {
237 finders.add(new JarResourceFinder(file));
238 } else {
239 finders.add(new ExplodedResourceFinder(file));
240 }
241 }
242 initialized = true;
243 } catch (IOException ioe) {
244 throw new UncheckedIOException(ioe);
245 } finally {
246 // close all ResourceFinder in the event of an error
247 if (!initialized) closeAll(finders);
248 }
249
274 }
275
276 /**
277 * Returns the ModuleReader to delegate to when the resource is not
278 * found in a patch location.
279 */
280 private ModuleReader delegate() throws IOException {
281 ModuleReader r = delegate;
282 if (r == null) {
283 synchronized (this) {
284 r = delegate;
285 if (r == null) {
286 delegate = r = mref.open();
287 }
288 }
289 }
290 return r;
291 }
292
293 /**
294 * Finds a resources in the patch locations. Returns null if not found.
295 */
296 private Resource findResourceInPatch(String name) throws IOException {
297 for (ResourceFinder finder : finders) {
298 Resource r = finder.find(name);
299 if (r != null)
300 return r;
301 }
302 return null;
303 }
304
305 /**
306 * Finds a resource of the given name in the patched module.
307 */
308 public Resource findResource(String name) throws IOException {
309
310 // patch locations
311 Resource r = findResourceInPatch(name);
312 if (r != null)
313 return r;
314
315 // original module
316 ByteBuffer bb = delegate().read(name).orElse(null);
317 if (bb == null)
318 return null;
319
320 return new Resource() {
321 private <T> T shouldNotGetHere(Class<T> type) {
461 }
462 @Override
463 public ByteBuffer getByteBuffer() throws IOException {
464 byte[] bytes = getInputStream().readAllBytes();
465 return ByteBuffer.wrap(bytes);
466 }
467 @Override
468 public InputStream getInputStream() throws IOException {
469 return jf.getInputStream(entry);
470 }
471 @Override
472 public int getContentLength() throws IOException {
473 long size = entry.getSize();
474 return (size > Integer.MAX_VALUE) ? -1 : (int) size;
475 }
476 };
477 }
478
479 @Override
480 public Stream<String> list() throws IOException {
481 return jf.stream()
482 .filter(e -> !e.isDirectory())
483 .map(JarEntry::getName);
484 }
485 }
486
487
488 /**
489 * A ResourceFinder that finds resources on the file system.
490 */
491 private static class ExplodedResourceFinder implements ResourceFinder {
492 private final Path dir;
493
494 ExplodedResourceFinder(Path dir) {
495 this.dir = dir;
496 }
497
498 @Override
499 public void close() { }
500
501 @Override
502 public Resource find(String name) throws IOException {
503 Path path = ResourceHelper.toFilePath(name);
504 if (path != null) {
505 Path file = dir.resolve(path);
506 if (Files.isRegularFile(file)) {
507 return newResource(name, dir, file);
508 }
509 }
510 return null;
511 }
512
513 private Resource newResource(String name, Path top, Path file) {
514 return new Resource() {
515 @Override
516 public String getName() {
517 return name;
518 }
519 @Override
520 public URL getURL() {
521 try {
522 return file.toUri().toURL();
523 } catch (IOException | IOError e) {
524 return null;
525 }
526 }
527 @Override
528 public URL getCodeSourceURL() {
529 try {
530 return top.toUri().toURL();
531 } catch (IOException | IOError e) {
533 }
534 }
535 @Override
536 public ByteBuffer getByteBuffer() throws IOException {
537 return ByteBuffer.wrap(Files.readAllBytes(file));
538 }
539 @Override
540 public InputStream getInputStream() throws IOException {
541 return Files.newInputStream(file);
542 }
543 @Override
544 public int getContentLength() throws IOException {
545 long size = Files.size(file);
546 return (size > Integer.MAX_VALUE) ? -1 : (int)size;
547 }
548 };
549 }
550
551 @Override
552 public Stream<String> list() throws IOException {
553 return Files.find(dir, Integer.MAX_VALUE,
554 (path, attrs) -> attrs.isRegularFile())
555 .map(f -> dir.relativize(f)
556 .toString()
557 .replace(File.separatorChar, '/'));
558 }
559 }
560
561
562 /**
563 * Derives a package name from a file path to a .class file.
564 */
565 private static String toPackageName(Path top, Path file) {
566 Path entry = top.relativize(file);
567 Path parent = entry.getParent();
568 if (parent == null) {
569 return warnIfModuleInfo(top, entry.toString());
570 } else {
571 return parent.toString().replace(File.separatorChar, '.');
572 }
573 }
574
575 /**
576 * Derives a package name from the name of an entry in a JAR file.
577 */
|
38 import java.net.URI;
39 import java.net.URL;
40 import java.nio.ByteBuffer;
41 import java.nio.file.Files;
42 import java.nio.file.Path;
43 import java.nio.file.Paths;
44 import java.util.ArrayList;
45 import java.util.Collections;
46 import java.util.HashMap;
47 import java.util.HashSet;
48 import java.util.List;
49 import java.util.Map;
50 import java.util.Optional;
51 import java.util.Set;
52 import java.util.jar.JarEntry;
53 import java.util.jar.JarFile;
54 import java.util.stream.Collectors;
55 import java.util.stream.Stream;
56
57 import jdk.internal.loader.Resource;
58 import jdk.internal.misc.JavaLangModuleAccess;
59 import jdk.internal.misc.SharedSecrets;
60 import sun.net.www.ParseUtil;
61
62
63 /**
64 * Provides support for patching modules, mostly the boot layer.
65 */
66
67 public final class ModulePatcher {
68
69 private static final JavaLangModuleAccess JLMA
70 = SharedSecrets.getJavaLangModuleAccess();
71
72 // module name -> sequence of patches (directories or JAR files)
73 private final Map<String, List<Path>> map;
74
75 /**
76 * Initialize the module patcher with the given map. The map key is
77 * the module name, the value is a list of path strings.
147 } catch (IOException ioe) {
148 throw new UncheckedIOException(ioe);
149 }
150
151 // if there are new packages then we need a new ModuleDescriptor
152 packages.removeAll(descriptor.packages());
153 if (!packages.isEmpty()) {
154 Builder builder = JLMA.newModuleBuilder(descriptor.name(),
155 /*strict*/ false,
156 descriptor.modifiers());
157 if (!descriptor.isAutomatic()) {
158 descriptor.requires().forEach(builder::requires);
159 descriptor.exports().forEach(builder::exports);
160 descriptor.opens().forEach(builder::opens);
161 descriptor.uses().forEach(builder::uses);
162 }
163 descriptor.provides().forEach(builder::provides);
164
165 descriptor.version().ifPresent(builder::version);
166 descriptor.mainClass().ifPresent(builder::mainClass);
167
168 // original + new packages
169 builder.packages(descriptor.packages());
170 builder.packages(packages);
171
172 descriptor = builder.build();
173 }
174
175 // return a module reference to the patched module
176 URI location = mref.location().orElse(null);
177
178 ModuleTarget target = null;
179 ModuleHashes recordedHashes = null;
180 ModuleResolution mres = null;
181 if (mref instanceof ModuleReferenceImpl) {
182 ModuleReferenceImpl impl = (ModuleReferenceImpl)mref;
183 target = impl.moduleTarget();
184 recordedHashes = impl.recordedHashes();
185 mres = impl.moduleResolution();
186 }
187
188 return new ModuleReferenceImpl(descriptor,
189 location,
190 () -> new PatchedModuleReader(paths, mref),
191 this,
192 target,
193 recordedHashes,
194 null,
195 mres);
196
197 }
198
199 /**
200 * Returns true is this module patcher has no patches.
201 */
202 public boolean isEmpty() {
203 return map.isEmpty();
204 }
205
206 /*
207 * Returns the names of the patched modules.
208 */
209 Set<String> patchedModules() {
210 return map.keySet();
211 }
212
213 /**
214 * A ModuleReader that reads resources from a patched module.
215 *
216 * This class is public so as to expose the findResource method to the
217 * built-in class loaders and avoid locating the resource twice during
218 * class loading (once to locate the resource, the second to gets the
219 * URL for the CodeSource).
220 */
221 public static class PatchedModuleReader implements ModuleReader {
222 private final List<ResourceFinder> finders;
223 private final ModuleReference mref;
224 private final URL delegateCodeSourceURL;
225 private volatile ModuleReader delegate;
226
227 /**
228 * Creates the ModuleReader to reads resources in a patched module.
229 */
230 PatchedModuleReader(List<Path> patches, ModuleReference mref) {
231 List<ResourceFinder> finders = new ArrayList<>();
232 boolean initialized = false;
233 try {
234 for (Path file : patches) {
235 if (Files.isRegularFile(file)) {
236 finders.add(new JarResourceFinder(file));
237 } else {
238 finders.add(new ExplodedResourceFinder(file));
239 }
240 }
241 initialized = true;
242 } catch (IOException ioe) {
243 throw new UncheckedIOException(ioe);
244 } finally {
245 // close all ResourceFinder in the event of an error
246 if (!initialized) closeAll(finders);
247 }
248
273 }
274
275 /**
276 * Returns the ModuleReader to delegate to when the resource is not
277 * found in a patch location.
278 */
279 private ModuleReader delegate() throws IOException {
280 ModuleReader r = delegate;
281 if (r == null) {
282 synchronized (this) {
283 r = delegate;
284 if (r == null) {
285 delegate = r = mref.open();
286 }
287 }
288 }
289 return r;
290 }
291
292 /**
293 * Finds a resources in the patch locations. Returns null if not found
294 * or the name is "module-info.class" as that cannot be overridden.
295 */
296 private Resource findResourceInPatch(String name) throws IOException {
297 if (!name.equals("module-info.class")) {
298 for (ResourceFinder finder : finders) {
299 Resource r = finder.find(name);
300 if (r != null)
301 return r;
302 }
303 }
304 return null;
305 }
306
307 /**
308 * Finds a resource of the given name in the patched module.
309 */
310 public Resource findResource(String name) throws IOException {
311
312 // patch locations
313 Resource r = findResourceInPatch(name);
314 if (r != null)
315 return r;
316
317 // original module
318 ByteBuffer bb = delegate().read(name).orElse(null);
319 if (bb == null)
320 return null;
321
322 return new Resource() {
323 private <T> T shouldNotGetHere(Class<T> type) {
463 }
464 @Override
465 public ByteBuffer getByteBuffer() throws IOException {
466 byte[] bytes = getInputStream().readAllBytes();
467 return ByteBuffer.wrap(bytes);
468 }
469 @Override
470 public InputStream getInputStream() throws IOException {
471 return jf.getInputStream(entry);
472 }
473 @Override
474 public int getContentLength() throws IOException {
475 long size = entry.getSize();
476 return (size > Integer.MAX_VALUE) ? -1 : (int) size;
477 }
478 };
479 }
480
481 @Override
482 public Stream<String> list() throws IOException {
483 return jf.stream().map(JarEntry::getName);
484 }
485 }
486
487
488 /**
489 * A ResourceFinder that finds resources on the file system.
490 */
491 private static class ExplodedResourceFinder implements ResourceFinder {
492 private final Path dir;
493
494 ExplodedResourceFinder(Path dir) {
495 this.dir = dir;
496 }
497
498 @Override
499 public void close() { }
500
501 @Override
502 public Resource find(String name) throws IOException {
503 Path file = Resources.toFilePath(dir, name);
504 if (file != null) {
505 return newResource(name, dir, file);
506 } else {
507 return null;
508 }
509 }
510
511 private Resource newResource(String name, Path top, Path file) {
512 return new Resource() {
513 @Override
514 public String getName() {
515 return name;
516 }
517 @Override
518 public URL getURL() {
519 try {
520 return file.toUri().toURL();
521 } catch (IOException | IOError e) {
522 return null;
523 }
524 }
525 @Override
526 public URL getCodeSourceURL() {
527 try {
528 return top.toUri().toURL();
529 } catch (IOException | IOError e) {
531 }
532 }
533 @Override
534 public ByteBuffer getByteBuffer() throws IOException {
535 return ByteBuffer.wrap(Files.readAllBytes(file));
536 }
537 @Override
538 public InputStream getInputStream() throws IOException {
539 return Files.newInputStream(file);
540 }
541 @Override
542 public int getContentLength() throws IOException {
543 long size = Files.size(file);
544 return (size > Integer.MAX_VALUE) ? -1 : (int)size;
545 }
546 };
547 }
548
549 @Override
550 public Stream<String> list() throws IOException {
551 return Files.walk(dir, Integer.MAX_VALUE)
552 .map(f -> Resources.toResourceName(dir, f))
553 .filter(s -> s.length() > 0);
554 }
555 }
556
557
558 /**
559 * Derives a package name from a file path to a .class file.
560 */
561 private static String toPackageName(Path top, Path file) {
562 Path entry = top.relativize(file);
563 Path parent = entry.getParent();
564 if (parent == null) {
565 return warnIfModuleInfo(top, entry.toString());
566 } else {
567 return parent.toString().replace(File.separatorChar, '.');
568 }
569 }
570
571 /**
572 * Derives a package name from the name of an entry in a JAR file.
573 */
|