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