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