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