Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/src/java.base/share/classes/jdk/internal/module/ModulePatcher.java
41159 views
1
/*
2
* Copyright (c) 2015, 2018, 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. Oracle designates this
8
* particular file as subject to the "Classpath" exception as provided
9
* by Oracle in the LICENSE file that accompanied this code.
10
*
11
* This code is distributed in the hope that it will be useful, but WITHOUT
12
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14
* version 2 for more details (a copy is included in the LICENSE file that
15
* accompanied this code).
16
*
17
* You should have received a copy of the GNU General Public License version
18
* 2 along with this work; if not, write to the Free Software Foundation,
19
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20
*
21
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22
* or visit www.oracle.com if you need additional information or have any
23
* questions.
24
*/
25
package jdk.internal.module;
26
27
import java.io.Closeable;
28
import java.io.File;
29
import java.io.IOError;
30
import java.io.IOException;
31
import java.io.InputStream;
32
import java.io.UncheckedIOException;
33
import java.lang.module.ModuleDescriptor;
34
import java.lang.module.ModuleDescriptor.Builder;
35
import java.lang.module.ModuleReader;
36
import java.lang.module.ModuleReference;
37
import java.net.MalformedURLException;
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.HashMap;
46
import java.util.HashSet;
47
import java.util.List;
48
import java.util.Map;
49
import java.util.Optional;
50
import java.util.Set;
51
import java.util.jar.JarEntry;
52
import java.util.jar.JarFile;
53
import java.util.stream.Stream;
54
55
import jdk.internal.loader.Resource;
56
import jdk.internal.access.JavaLangModuleAccess;
57
import jdk.internal.access.SharedSecrets;
58
import sun.net.www.ParseUtil;
59
60
61
/**
62
* Provides support for patching modules, mostly the boot layer.
63
*/
64
65
public final class ModulePatcher {
66
67
private static final JavaLangModuleAccess JLMA
68
= SharedSecrets.getJavaLangModuleAccess();
69
70
// module name -> sequence of patches (directories or JAR files)
71
private final Map<String, List<Path>> map;
72
73
/**
74
* Initialize the module patcher with the given map. The map key is
75
* the module name, the value is a list of path strings.
76
*/
77
public ModulePatcher(Map<String, List<String>> input) {
78
if (input.isEmpty()) {
79
this.map = Map.of();
80
} else {
81
Map<String, List<Path>> map = new HashMap<>();
82
for (Map.Entry<String, List<String>> e : input.entrySet()) {
83
String mn = e.getKey();
84
List<Path> paths = e.getValue().stream()
85
.map(Paths::get)
86
.toList();
87
map.put(mn, paths);
88
}
89
this.map = map;
90
}
91
}
92
93
/**
94
* Returns a module reference that interposes on the given module if
95
* needed. If there are no patches for the given module then the module
96
* reference is simply returned. Otherwise the patches for the module
97
* are scanned (to find any new packages) and a new module reference is
98
* returned.
99
*
100
* @throws UncheckedIOException if an I/O error is detected
101
*/
102
public ModuleReference patchIfNeeded(ModuleReference mref) {
103
// if there are no patches for the module then nothing to do
104
ModuleDescriptor descriptor = mref.descriptor();
105
String mn = descriptor.name();
106
List<Path> paths = map.get(mn);
107
if (paths == null)
108
return mref;
109
110
// Scan the JAR file or directory tree to get the set of packages.
111
// For automatic modules then packages that do not contain class files
112
// must be ignored.
113
Set<String> packages = new HashSet<>();
114
boolean isAutomatic = descriptor.isAutomatic();
115
try {
116
for (Path file : paths) {
117
if (Files.isRegularFile(file)) {
118
119
// JAR file - do not open as a multi-release JAR as this
120
// is not supported by the boot class loader
121
try (JarFile jf = new JarFile(file.toString())) {
122
jf.stream()
123
.filter(e -> !e.isDirectory()
124
&& (!isAutomatic || e.getName().endsWith(".class")))
125
.map(e -> toPackageName(file, e))
126
.filter(Checks::isPackageName)
127
.forEach(packages::add);
128
}
129
130
} else if (Files.isDirectory(file)) {
131
132
// exploded directory without following sym links
133
Path top = file;
134
Files.find(top, Integer.MAX_VALUE,
135
((path, attrs) -> attrs.isRegularFile()))
136
.filter(path -> (!isAutomatic
137
|| path.toString().endsWith(".class"))
138
&& !isHidden(path))
139
.map(path -> toPackageName(top, path))
140
.filter(Checks::isPackageName)
141
.forEach(packages::add);
142
143
}
144
}
145
146
} catch (IOException ioe) {
147
throw new UncheckedIOException(ioe);
148
}
149
150
// if there are new packages then we need a new ModuleDescriptor
151
packages.removeAll(descriptor.packages());
152
if (!packages.isEmpty()) {
153
Builder builder = JLMA.newModuleBuilder(descriptor.name(),
154
/*strict*/ descriptor.isAutomatic(),
155
descriptor.modifiers());
156
if (!descriptor.isAutomatic()) {
157
descriptor.requires().forEach(builder::requires);
158
descriptor.exports().forEach(builder::exports);
159
descriptor.opens().forEach(builder::opens);
160
descriptor.uses().forEach(builder::uses);
161
}
162
descriptor.provides().forEach(builder::provides);
163
164
descriptor.version().ifPresent(builder::version);
165
descriptor.mainClass().ifPresent(builder::mainClass);
166
167
// original + new packages
168
builder.packages(descriptor.packages());
169
builder.packages(packages);
170
171
descriptor = builder.build();
172
}
173
174
// return a module reference to the patched module
175
URI location = mref.location().orElse(null);
176
177
ModuleTarget target = null;
178
ModuleHashes recordedHashes = null;
179
ModuleHashes.HashSupplier hasher = null;
180
ModuleResolution mres = null;
181
if (mref instanceof ModuleReferenceImpl) {
182
ModuleReferenceImpl impl = (ModuleReferenceImpl)mref;
183
target = impl.moduleTarget();
184
recordedHashes = impl.recordedHashes();
185
hasher = impl.hasher();
186
mres = impl.moduleResolution();
187
}
188
189
return new ModuleReferenceImpl(descriptor,
190
location,
191
() -> new PatchedModuleReader(paths, mref),
192
this,
193
target,
194
recordedHashes,
195
hasher,
196
mres);
197
198
}
199
200
/**
201
* Returns true is this module patcher has patches.
202
*/
203
public boolean hasPatches() {
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 in 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
250
this.finders = finders;
251
this.mref = mref;
252
this.delegateCodeSourceURL = codeSourceURL(mref);
253
}
254
255
/**
256
* Closes all resource finders.
257
*/
258
private static void closeAll(List<ResourceFinder> finders) {
259
for (ResourceFinder finder : finders) {
260
try { finder.close(); } catch (IOException ioe) { }
261
}
262
}
263
264
/**
265
* Returns the code source URL for the given module.
266
*/
267
private static URL codeSourceURL(ModuleReference mref) {
268
try {
269
Optional<URI> ouri = mref.location();
270
if (ouri.isPresent())
271
return ouri.get().toURL();
272
} catch (MalformedURLException e) { }
273
return null;
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
* or the name is "module-info.class" as that cannot be overridden.
296
*/
297
private Resource findResourceInPatch(String name) throws IOException {
298
if (!name.equals("module-info.class")) {
299
for (ResourceFinder finder : finders) {
300
Resource r = finder.find(name);
301
if (r != null)
302
return r;
303
}
304
}
305
return null;
306
}
307
308
/**
309
* Finds a resource of the given name in the patched module.
310
*/
311
public Resource findResource(String name) throws IOException {
312
313
// patch locations
314
Resource r = findResourceInPatch(name);
315
if (r != null)
316
return r;
317
318
// original module
319
ByteBuffer bb = delegate().read(name).orElse(null);
320
if (bb == null)
321
return null;
322
323
return new Resource() {
324
private <T> T shouldNotGetHere(Class<T> type) {
325
throw new InternalError("should not get here");
326
}
327
@Override
328
public String getName() {
329
return shouldNotGetHere(String.class);
330
}
331
@Override
332
public URL getURL() {
333
return shouldNotGetHere(URL.class);
334
}
335
@Override
336
public URL getCodeSourceURL() {
337
return delegateCodeSourceURL;
338
}
339
@Override
340
public ByteBuffer getByteBuffer() throws IOException {
341
return bb;
342
}
343
@Override
344
public InputStream getInputStream() throws IOException {
345
return shouldNotGetHere(InputStream.class);
346
}
347
@Override
348
public int getContentLength() throws IOException {
349
return shouldNotGetHere(int.class);
350
}
351
};
352
}
353
354
@Override
355
public Optional<URI> find(String name) throws IOException {
356
Resource r = findResourceInPatch(name);
357
if (r != null) {
358
URI uri = URI.create(r.getURL().toString());
359
return Optional.of(uri);
360
} else {
361
return delegate().find(name);
362
}
363
}
364
365
@Override
366
public Optional<InputStream> open(String name) throws IOException {
367
Resource r = findResourceInPatch(name);
368
if (r != null) {
369
return Optional.of(r.getInputStream());
370
} else {
371
return delegate().open(name);
372
}
373
}
374
375
@Override
376
public Optional<ByteBuffer> read(String name) throws IOException {
377
Resource r = findResourceInPatch(name);
378
if (r != null) {
379
ByteBuffer bb = r.getByteBuffer();
380
assert !bb.isDirect();
381
return Optional.of(bb);
382
} else {
383
return delegate().read(name);
384
}
385
}
386
387
@Override
388
public void release(ByteBuffer bb) {
389
if (bb.isDirect()) {
390
try {
391
delegate().release(bb);
392
} catch (IOException ioe) {
393
throw new InternalError(ioe);
394
}
395
}
396
}
397
398
@Override
399
public Stream<String> list() throws IOException {
400
Stream<String> s = delegate().list();
401
for (ResourceFinder finder : finders) {
402
s = Stream.concat(s, finder.list());
403
}
404
return s.distinct();
405
}
406
407
@Override
408
public void close() throws IOException {
409
closeAll(finders);
410
delegate().close();
411
}
412
}
413
414
415
/**
416
* A resource finder that find resources in a patch location.
417
*/
418
private static interface ResourceFinder extends Closeable {
419
Resource find(String name) throws IOException;
420
Stream<String> list() throws IOException;
421
}
422
423
424
/**
425
* A ResourceFinder that finds resources in a JAR file.
426
*/
427
private static class JarResourceFinder implements ResourceFinder {
428
private final JarFile jf;
429
private final URL csURL;
430
431
JarResourceFinder(Path path) throws IOException {
432
this.jf = new JarFile(path.toString());
433
this.csURL = path.toUri().toURL();
434
}
435
436
@Override
437
public void close() throws IOException {
438
jf.close();
439
}
440
441
@Override
442
public Resource find(String name) throws IOException {
443
JarEntry entry = jf.getJarEntry(name);
444
if (entry == null)
445
return null;
446
447
return new Resource() {
448
@Override
449
public String getName() {
450
return name;
451
}
452
@Override
453
public URL getURL() {
454
String encodedPath = ParseUtil.encodePath(name, false);
455
try {
456
return new URL("jar:" + csURL + "!/" + encodedPath);
457
} catch (MalformedURLException e) {
458
return null;
459
}
460
}
461
@Override
462
public URL getCodeSourceURL() {
463
return csURL;
464
}
465
@Override
466
public ByteBuffer getByteBuffer() throws IOException {
467
byte[] bytes = getInputStream().readAllBytes();
468
return ByteBuffer.wrap(bytes);
469
}
470
@Override
471
public InputStream getInputStream() throws IOException {
472
return jf.getInputStream(entry);
473
}
474
@Override
475
public int getContentLength() throws IOException {
476
long size = entry.getSize();
477
return (size > Integer.MAX_VALUE) ? -1 : (int) size;
478
}
479
};
480
}
481
482
@Override
483
public Stream<String> list() throws IOException {
484
return jf.stream().map(JarEntry::getName);
485
}
486
}
487
488
489
/**
490
* A ResourceFinder that finds resources on the file system.
491
*/
492
private static class ExplodedResourceFinder implements ResourceFinder {
493
private final Path dir;
494
495
ExplodedResourceFinder(Path dir) {
496
this.dir = dir;
497
}
498
499
@Override
500
public void close() { }
501
502
@Override
503
public Resource find(String name) throws IOException {
504
Path file = Resources.toFilePath(dir, name);
505
if (file != null) {
506
return newResource(name, dir, file);
507
} else {
508
return null;
509
}
510
}
511
512
private Resource newResource(String name, Path top, Path file) {
513
return new Resource() {
514
@Override
515
public String getName() {
516
return name;
517
}
518
@Override
519
public URL getURL() {
520
try {
521
return file.toUri().toURL();
522
} catch (IOException | IOError e) {
523
return null;
524
}
525
}
526
@Override
527
public URL getCodeSourceURL() {
528
try {
529
return top.toUri().toURL();
530
} catch (IOException | IOError e) {
531
return null;
532
}
533
}
534
@Override
535
public ByteBuffer getByteBuffer() throws IOException {
536
return ByteBuffer.wrap(Files.readAllBytes(file));
537
}
538
@Override
539
public InputStream getInputStream() throws IOException {
540
return Files.newInputStream(file);
541
}
542
@Override
543
public int getContentLength() throws IOException {
544
long size = Files.size(file);
545
return (size > Integer.MAX_VALUE) ? -1 : (int)size;
546
}
547
};
548
}
549
550
@Override
551
public Stream<String> list() throws IOException {
552
return Files.walk(dir, Integer.MAX_VALUE)
553
.map(f -> Resources.toResourceName(dir, f))
554
.filter(s -> !s.isEmpty());
555
}
556
}
557
558
559
/**
560
* Derives a package name from the file path of an entry in an exploded patch
561
*/
562
private static String toPackageName(Path top, Path file) {
563
Path entry = top.relativize(file);
564
Path parent = entry.getParent();
565
if (parent == null) {
566
return warnIfModuleInfo(top, entry.toString());
567
} else {
568
return parent.toString().replace(File.separatorChar, '.');
569
}
570
}
571
572
/**
573
* Returns true if the given file exists and is a hidden file
574
*/
575
private boolean isHidden(Path file) {
576
try {
577
return Files.isHidden(file);
578
} catch (IOException ioe) {
579
return false;
580
}
581
}
582
583
/**
584
* Derives a package name from the name of an entry in a JAR file.
585
*/
586
private static String toPackageName(Path file, JarEntry entry) {
587
String name = entry.getName();
588
int index = name.lastIndexOf("/");
589
if (index == -1) {
590
return warnIfModuleInfo(file, name);
591
} else {
592
return name.substring(0, index).replace('/', '.');
593
}
594
}
595
596
private static String warnIfModuleInfo(Path file, String e) {
597
if (e.equals("module-info.class"))
598
System.err.println("WARNING: " + e + " ignored in patch: " + file);
599
return "";
600
}
601
}
602
603