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