1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.bcel.util;
18
19 import java.io.Closeable;
20 import java.io.DataInputStream;
21 import java.io.File;
22 import java.io.FileInputStream;
23 import java.io.FilenameFilter;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.net.MalformedURLException;
27 import java.net.URL;
28 import java.nio.file.Files;
29 import java.nio.file.Path;
30 import java.nio.file.Paths;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.Enumeration;
34 import java.util.List;
35 import java.util.Locale;
36 import java.util.Objects;
37 import java.util.StringTokenizer;
38 import java.util.Vector;
39 import java.util.stream.Collectors;
40 import java.util.zip.ZipEntry;
41 import java.util.zip.ZipFile;
42
43 import org.apache.bcel.classfile.JavaClass;
44 import org.apache.bcel.classfile.Utility;
45
46
47
48
49 public class ClassPath implements Closeable {
50
51 private abstract static class AbstractPathEntry implements Closeable {
52
53 abstract ClassFile getClassFile(String name, String suffix);
54
55 abstract URL getResource(String name);
56
57 abstract InputStream getResourceAsStream(String name);
58 }
59
60 private abstract static class AbstractZip extends AbstractPathEntry {
61
62 private final ZipFile zipFile;
63
64 AbstractZip(final ZipFile zipFile) {
65 this.zipFile = Objects.requireNonNull(zipFile, "zipFile");
66 }
67
68 @Override
69 public void close() throws IOException {
70 if (zipFile != null) {
71 zipFile.close();
72 }
73
74 }
75
76 @Override
77 ClassFile getClassFile(final String name, final String suffix) {
78 final ZipEntry entry = zipFile.getEntry(toEntryName(name, suffix));
79
80 if (entry == null) {
81 return null;
82 }
83
84 return new ClassFile() {
85
86 @Override
87 public String getBase() {
88 return zipFile.getName();
89 }
90
91 @Override
92 public InputStream getInputStream() throws IOException {
93 return zipFile.getInputStream(entry);
94 }
95
96 @Override
97 public String getPath() {
98 return entry.toString();
99 }
100
101 @Override
102 public long getSize() {
103 return entry.getSize();
104 }
105
106 @Override
107 public long getTime() {
108 return entry.getTime();
109 }
110 };
111 }
112
113 @Override
114 URL getResource(final String name) {
115 final ZipEntry entry = zipFile.getEntry(name);
116 try {
117 return entry != null ? new URL("jar:file:" + zipFile.getName() + "!/" + name) : null;
118 } catch (final MalformedURLException e) {
119 return null;
120 }
121 }
122
123 @Override
124 InputStream getResourceAsStream(final String name) {
125 final ZipEntry entry = zipFile.getEntry(name);
126 try {
127 return entry != null ? zipFile.getInputStream(entry) : null;
128 } catch (final IOException e) {
129 return null;
130 }
131 }
132
133 protected abstract String toEntryName(final String name, final String suffix);
134
135 @Override
136 public String toString() {
137 return zipFile.getName();
138 }
139
140 }
141
142
143
144
145 public interface ClassFile {
146
147
148
149
150
151 String getBase();
152
153
154
155
156
157 InputStream getInputStream() throws IOException;
158
159
160
161
162 String getPath();
163
164
165
166
167 long getSize();
168
169
170
171
172 long getTime();
173 }
174
175 private static final class Dir extends AbstractPathEntry {
176
177 private final String dir;
178
179 Dir(final String d) {
180 dir = d;
181 }
182
183 @Override
184 public void close() throws IOException {
185
186
187 }
188
189 @Override
190 ClassFile getClassFile(final String name, final String suffix) {
191 final File file = new File(dir + File.separatorChar + name.replace('.', File.separatorChar) + suffix);
192 return file.exists() ? new ClassFile() {
193
194 @Override
195 public String getBase() {
196 return dir;
197 }
198
199 @Override
200 public InputStream getInputStream() throws IOException {
201 return new FileInputStream(file);
202 }
203
204 @Override
205 public String getPath() {
206 try {
207 return file.getCanonicalPath();
208 } catch (final IOException e) {
209 return null;
210 }
211 }
212
213 @Override
214 public long getSize() {
215 return file.length();
216 }
217
218 @Override
219 public long getTime() {
220 return file.lastModified();
221 }
222 } : null;
223 }
224
225 @Override
226 URL getResource(final String name) {
227
228 final File file = toFile(name);
229 try {
230 return file.exists() ? file.toURI().toURL() : null;
231 } catch (final MalformedURLException e) {
232 return null;
233 }
234 }
235
236 @Override
237 InputStream getResourceAsStream(final String name) {
238
239 final File file = toFile(name);
240 try {
241 return file.exists() ? new FileInputStream(file) : null;
242 } catch (final IOException e) {
243 return null;
244 }
245 }
246
247 private File toFile(final String name) {
248 return new File(dir + File.separatorChar + name.replace('/', File.separatorChar));
249 }
250
251 @Override
252 public String toString() {
253 return dir;
254 }
255 }
256
257 private static final class Jar extends AbstractZip {
258
259 Jar(final ZipFile zip) {
260 super(zip);
261 }
262
263 @Override
264 protected String toEntryName(final String name, final String suffix) {
265 return Utility.packageToPath(name) + suffix;
266 }
267
268 }
269
270 private static final class JrtModule extends AbstractPathEntry {
271
272 private final Path modulePath;
273
274 public JrtModule(final Path modulePath) {
275 this.modulePath = Objects.requireNonNull(modulePath, "modulePath");
276 }
277
278 @Override
279 public void close() throws IOException {
280
281
282 }
283
284 @Override
285 ClassFile getClassFile(final String name, final String suffix) {
286 final Path resolved = modulePath.resolve(Utility.packageToPath(name) + suffix);
287 if (Files.exists(resolved)) {
288 return new ClassFile() {
289
290 @Override
291 public String getBase() {
292 return Objects.toString(resolved.getFileName(), null);
293 }
294
295 @Override
296 public InputStream getInputStream() throws IOException {
297 return Files.newInputStream(resolved);
298 }
299
300 @Override
301 public String getPath() {
302 return resolved.toString();
303 }
304
305 @Override
306 public long getSize() {
307 try {
308 return Files.size(resolved);
309 } catch (final IOException e) {
310 return 0;
311 }
312 }
313
314 @Override
315 public long getTime() {
316 try {
317 return Files.getLastModifiedTime(resolved).toMillis();
318 } catch (final IOException e) {
319 return 0;
320 }
321 }
322 };
323 }
324 return null;
325 }
326
327 @Override
328 URL getResource(final String name) {
329 final Path resovled = modulePath.resolve(name);
330 try {
331 return Files.exists(resovled) ? new URL("jrt:" + modulePath + "/" + name) : null;
332 } catch (final MalformedURLException e) {
333 return null;
334 }
335 }
336
337 @Override
338 InputStream getResourceAsStream(final String name) {
339 try {
340 return Files.newInputStream(modulePath.resolve(name));
341 } catch (final IOException e) {
342 return null;
343 }
344 }
345
346 @Override
347 public String toString() {
348 return modulePath.toString();
349 }
350
351 }
352
353 private static final class JrtModules extends AbstractPathEntry {
354
355 private final ModularRuntimeImage modularRuntimeImage;
356 private final JrtModule[] modules;
357
358 public JrtModules(final String path) throws IOException {
359 this.modularRuntimeImage = new ModularRuntimeImage();
360 this.modules = modularRuntimeImage.list(path).stream().map(JrtModule::new).toArray(JrtModule[]::new);
361 }
362
363 @Override
364 public void close() throws IOException {
365 if (modules != null) {
366
367 for (final JrtModule module : modules) {
368 module.close();
369 }
370 }
371 if (modularRuntimeImage != null) {
372 modularRuntimeImage.close();
373 }
374 }
375
376 @Override
377 ClassFile getClassFile(final String name, final String suffix) {
378
379 for (final JrtModule module : modules) {
380 final ClassFile classFile = module.getClassFile(name, suffix);
381 if (classFile != null) {
382 return classFile;
383 }
384 }
385 return null;
386 }
387
388 @Override
389 URL getResource(final String name) {
390
391 for (final JrtModule module : modules) {
392 final URL url = module.getResource(name);
393 if (url != null) {
394 return url;
395 }
396 }
397 return null;
398 }
399
400 @Override
401 InputStream getResourceAsStream(final String name) {
402
403 for (final JrtModule module : modules) {
404 final InputStream inputStream = module.getResourceAsStream(name);
405 if (inputStream != null) {
406 return inputStream;
407 }
408 }
409 return null;
410 }
411
412 @Override
413 public String toString() {
414 return Arrays.toString(modules);
415 }
416
417 }
418
419 private static final class Module extends AbstractZip {
420
421 Module(final ZipFile zip) {
422 super(zip);
423 }
424
425 @Override
426 protected String toEntryName(final String name, final String suffix) {
427 return "classes/" + Utility.packageToPath(name) + suffix;
428 }
429
430 }
431
432 private static final FilenameFilter ARCHIVE_FILTER = (dir, name) -> {
433 name = name.toLowerCase(Locale.ENGLISH);
434 return name.endsWith(".zip") || name.endsWith(".jar");
435 };
436
437 private static final FilenameFilter MODULES_FILTER = (dir, name) -> {
438 name = name.toLowerCase(Locale.ENGLISH);
439 return name.endsWith(org.apache.bcel.classfile.Module.EXTENSION);
440 };
441
442 public static final ClassPath SYSTEM_CLASS_PATH = new ClassPath(getClassPath());
443
444 private static void addJdkModules(final String javaHome, final List<String> list) {
445 String modulesPath = System.getProperty("java.modules.path");
446 if (modulesPath == null || modulesPath.trim().isEmpty()) {
447
448 modulesPath = javaHome + File.separator + "jmods";
449 }
450 final File modulesDir = new File(modulesPath);
451 if (modulesDir.exists()) {
452 final String[] modules = modulesDir.list(MODULES_FILTER);
453 if (modules != null) {
454 for (final String module : modules) {
455 list.add(modulesDir.getPath() + File.separatorChar + module);
456 }
457 }
458 }
459 }
460
461
462
463
464
465
466
467
468 public static String getClassPath() {
469 final String classPathProp = System.getProperty("java.class.path");
470 final String bootClassPathProp = System.getProperty("sun.boot.class.path");
471 final String extDirs = System.getProperty("java.ext.dirs");
472
473
474
475
476 final String javaHome = System.getProperty("java.home");
477 final List<String> list = new ArrayList<>();
478
479
480 final Path modulesPath = Paths.get(javaHome).resolve("lib/modules");
481 if (Files.exists(modulesPath) && Files.isRegularFile(modulesPath)) {
482 list.add(modulesPath.toAbsolutePath().toString());
483 }
484
485 addJdkModules(javaHome, list);
486
487 getPathComponents(classPathProp, list);
488 getPathComponents(bootClassPathProp, list);
489 final List<String> dirs = new ArrayList<>();
490 getPathComponents(extDirs, dirs);
491 for (final String d : dirs) {
492 final File extDir = new File(d);
493 final String[] extensions = extDir.list(ARCHIVE_FILTER);
494 if (extensions != null) {
495 for (final String extension : extensions) {
496 list.add(extDir.getPath() + File.separatorChar + extension);
497 }
498 }
499 }
500
501 return list.stream().collect(Collectors.joining(File.pathSeparator));
502 }
503
504 private static void getPathComponents(final String path, final List<String> list) {
505 if (path != null) {
506 final StringTokenizer tokenizer = new StringTokenizer(path, File.pathSeparator);
507 while (tokenizer.hasMoreTokens()) {
508 final String name = tokenizer.nextToken();
509 final File file = new File(name);
510 if (file.exists()) {
511 list.add(name);
512 }
513 }
514 }
515 }
516
517 private final String classPathString;
518
519 private final ClassPath parent;
520
521 private final List<AbstractPathEntry> paths;
522
523
524
525
526
527
528 @Deprecated
529 public ClassPath() {
530 this(getClassPath());
531 }
532
533 @SuppressWarnings("resource")
534 public ClassPath(final ClassPath parent, final String classPathString) {
535 this.parent = parent;
536 this.classPathString = Objects.requireNonNull(classPathString, "classPathString");
537 this.paths = new ArrayList<>();
538 for (final StringTokenizer tokenizer = new StringTokenizer(classPathString, File.pathSeparator); tokenizer.hasMoreTokens();) {
539 final String path = tokenizer.nextToken();
540 if (!path.isEmpty()) {
541 final File file = new File(path);
542 try {
543 if (file.exists()) {
544 if (file.isDirectory()) {
545 paths.add(new Dir(path));
546 } else if (path.endsWith(org.apache.bcel.classfile.Module.EXTENSION)) {
547 paths.add(new Module(new ZipFile(file)));
548 } else if (path.endsWith(ModularRuntimeImage.MODULES_PATH)) {
549 paths.add(new JrtModules(ModularRuntimeImage.MODULES_PATH));
550 } else {
551 paths.add(new Jar(new ZipFile(file)));
552 }
553 }
554 } catch (final IOException e) {
555 if (path.endsWith(".zip") || path.endsWith(".jar")) {
556 System.err.println("CLASSPATH component " + file + ": " + e);
557 }
558 }
559 }
560 }
561 }
562
563
564
565
566
567
568 public ClassPath(final String classPath) {
569 this(null, classPath);
570 }
571
572 @Override
573 public void close() throws IOException {
574 for (final AbstractPathEntry path : paths) {
575 path.close();
576 }
577 }
578
579 @Override
580 public boolean equals(final Object obj) {
581 if (this == obj) {
582 return true;
583 }
584 if (obj == null) {
585 return false;
586 }
587 if (getClass() != obj.getClass()) {
588 return false;
589 }
590 final ClassPath other = (ClassPath) obj;
591 return Objects.equals(classPathString, other.classPathString);
592 }
593
594
595
596
597
598
599 public byte[] getBytes(final String name) throws IOException {
600 return getBytes(name, JavaClass.EXTENSION);
601 }
602
603
604
605
606
607
608
609 public byte[] getBytes(final String name, final String suffix) throws IOException {
610 DataInputStream dis = null;
611 try (InputStream inputStream = getInputStream(name, suffix)) {
612 if (inputStream == null) {
613 throw new IOException("Couldn't find: " + name + suffix);
614 }
615 dis = new DataInputStream(inputStream);
616 final byte[] bytes = new byte[inputStream.available()];
617 dis.readFully(bytes);
618 return bytes;
619 } finally {
620 if (dis != null) {
621 dis.close();
622 }
623 }
624 }
625
626
627
628
629
630
631 public ClassFile getClassFile(final String name) throws IOException {
632 return getClassFile(name, JavaClass.EXTENSION);
633 }
634
635
636
637
638
639
640
641 public ClassFile getClassFile(final String name, final String suffix) throws IOException {
642 ClassFile cf = null;
643
644 if (parent != null) {
645 cf = parent.getClassFileInternal(name, suffix);
646 }
647
648 if (cf == null) {
649 cf = getClassFileInternal(name, suffix);
650 }
651
652 if (cf != null) {
653 return cf;
654 }
655
656 throw new IOException("Couldn't find: " + name + suffix);
657 }
658
659 private ClassFile getClassFileInternal(final String name, final String suffix) {
660 for (final AbstractPathEntry path : paths) {
661 final ClassFile cf = path.getClassFile(name, suffix);
662 if (cf != null) {
663 return cf;
664 }
665 }
666 return null;
667 }
668
669
670
671
672
673
674
675
676
677
678 public InputStream getInputStream(final String name) throws IOException {
679 return getInputStream(Utility.packageToPath(name), JavaClass.EXTENSION);
680 }
681
682
683
684
685
686
687
688
689
690
691
692
693 public InputStream getInputStream(final String name, final String suffix) throws IOException {
694 try {
695 final java.lang.ClassLoader classLoader = getClass().getClassLoader();
696 @SuppressWarnings("resource")
697 final
698 InputStream inputStream = classLoader == null ? null : classLoader.getResourceAsStream(name + suffix);
699 if (inputStream != null) {
700 return inputStream;
701 }
702 } catch (final Exception ignored) {
703
704 }
705 return getClassFile(name, suffix).getInputStream();
706 }
707
708
709
710
711
712
713 public String getPath(String name) throws IOException {
714 final int index = name.lastIndexOf('.');
715 String suffix = "";
716 if (index > 0) {
717 suffix = name.substring(index);
718 name = name.substring(0, index);
719 }
720 return getPath(name, suffix);
721 }
722
723
724
725
726
727
728
729 public String getPath(final String name, final String suffix) throws IOException {
730 return getClassFile(name, suffix).getPath();
731 }
732
733
734
735
736
737
738 public URL getResource(final String name) {
739 for (final AbstractPathEntry path : paths) {
740 URL url;
741 if ((url = path.getResource(name)) != null) {
742 return url;
743 }
744 }
745 return null;
746 }
747
748
749
750
751
752
753 public InputStream getResourceAsStream(final String name) {
754 for (final AbstractPathEntry path : paths) {
755 InputStream is;
756 if ((is = path.getResourceAsStream(name)) != null) {
757 return is;
758 }
759 }
760 return null;
761 }
762
763
764
765
766
767
768 public Enumeration<URL> getResources(final String name) {
769 final Vector<URL> results = new Vector<>();
770 for (final AbstractPathEntry path : paths) {
771 URL url;
772 if ((url = path.getResource(name)) != null) {
773 results.add(url);
774 }
775 }
776 return results.elements();
777 }
778
779 @Override
780 public int hashCode() {
781 return classPathString.hashCode();
782 }
783
784
785
786
787 @Override
788 public String toString() {
789 if (parent != null) {
790 return parent + File.pathSeparator + classPathString;
791 }
792 return classPathString;
793 }
794 }