View Javadoc
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.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.util.ArrayList;
29  import java.util.Enumeration;
30  import java.util.List;
31  import java.util.Locale;
32  import java.util.StringTokenizer;
33  import java.util.Vector;
34  import java.util.zip.ZipEntry;
35  import java.util.zip.ZipFile;
36  
37  /**
38   * Responsible for loading (class) files from the CLASSPATH. Inspired by
39   * sun.tools.ClassPath.
40   *
41   * @version $Id: ClassPath.html 992795 2016-07-14 11:53:52Z britter $
42   */
43  public class ClassPath {
44  
45      public static final ClassPath SYSTEM_CLASS_PATH = new ClassPath(getClassPath());
46  
47      private static final FilenameFilter ARCHIVE_FILTER = new FilenameFilter() {
48  
49          @Override
50          public boolean accept( final File dir, String name ) {
51              name = name.toLowerCase(Locale.ENGLISH);
52              return name.endsWith(".zip") || name.endsWith(".jar");
53          }
54      };
55  
56      private final PathEntry[] paths;
57      private final String class_path;
58      private ClassPath parent;
59  
60      public ClassPath(final ClassPath parent, final String class_path) {
61          this(class_path);
62          this.parent = parent;
63      }
64  
65      /**
66       * Search for classes in given path.
67       * 
68       * @param class_path
69       */
70      public ClassPath(final String class_path) {
71          this.class_path = class_path;
72          final List<PathEntry> list = new ArrayList<>();
73          for (final StringTokenizer tok = new StringTokenizer(class_path, File.pathSeparator); tok.hasMoreTokens();) {
74              final String path = tok.nextToken();
75              if (!path.isEmpty()) {
76                  final File file = new File(path);
77                  try {
78                      if (file.exists()) {
79                          if (file.isDirectory()) {
80                              list.add(new Dir(path));
81                          } else {
82                              list.add(new Zip(new ZipFile(file)));
83                          }
84                      }
85                  } catch (final IOException e) {
86                      if (path.endsWith(".zip") || path.endsWith(".jar")) {
87                          System.err.println("CLASSPATH component " + file + ": " + e);
88                      }
89                  }
90              }
91          }
92          paths = new PathEntry[list.size()];
93          list.toArray(paths);
94      }
95  
96      /**
97       * Search for classes in CLASSPATH.
98       * @deprecated Use SYSTEM_CLASS_PATH constant
99       */
100     @Deprecated
101     public ClassPath() {
102         this(getClassPath());
103     }
104 
105     /** @return used class path string
106      */
107     @Override
108     public String toString() {
109         if (parent != null) {
110             return parent + File.pathSeparator + class_path;
111         }
112         return class_path;
113     }
114 
115     @Override
116     public int hashCode() {
117         if (parent != null) {
118             return class_path.hashCode() + parent.hashCode();            
119         }
120         return class_path.hashCode();
121     }
122 
123 
124     @Override
125     public boolean equals( final Object o ) {
126         if (o instanceof ClassPath) {
127             final ClassPath cp = (ClassPath)o;
128             return class_path.equals(cp.toString());
129         }
130         return false;
131     }
132 
133 
134     private static void getPathComponents( final String path, final List<String> list ) {
135         if (path != null) {
136             final StringTokenizer tok = new StringTokenizer(path, File.pathSeparator);
137             while (tok.hasMoreTokens()) {
138                 final String name = tok.nextToken();
139                 final File file = new File(name);
140                 if (file.exists()) {
141                     list.add(name);
142                 }
143             }
144         }
145     }
146 
147 
148     /** Checks for class path components in the following properties:
149      * "java.class.path", "sun.boot.class.path", "java.ext.dirs"
150      *
151      * @return class path as used by default by BCEL
152      */
153     // @since 6.0 no longer final
154     public static String getClassPath() {
155         final String class_path = System.getProperty("java.class.path");
156         final String boot_path = System.getProperty("sun.boot.class.path");
157         final String ext_path = System.getProperty("java.ext.dirs");
158         final List<String> list = new ArrayList<>();
159         getPathComponents(class_path, list);
160         getPathComponents(boot_path, list);
161         final List<String> dirs = new ArrayList<>();
162         getPathComponents(ext_path, dirs);
163         for (final String d : dirs) {
164             final File ext_dir = new File(d);
165             final String[] extensions = ext_dir.list(ARCHIVE_FILTER);
166             if (extensions != null) {
167                 for (final String extension : extensions) {
168                     list.add(ext_dir.getPath() + File.separatorChar + extension);
169                 }
170             }
171         }
172         final StringBuilder buf = new StringBuilder();
173         String separator = "";
174         for (final String path : list) {
175             buf.append(separator);
176             separator = File.pathSeparator;
177             buf.append(path);
178         }
179         return buf.toString().intern();
180     }
181 
182 
183     /**
184      * @param name fully qualified class name, e.g. java.lang.String
185      * @return input stream for class
186      */
187     public InputStream getInputStream( final String name ) throws IOException {
188         return getInputStream(name.replace('.', '/'), ".class");
189     }
190 
191 
192     /**
193      * Return stream for class or resource on CLASSPATH.
194      *
195      * @param name fully qualified file name, e.g. java/lang/String
196      * @param suffix file name ends with suff, e.g. .java
197      * @return input stream for file on class path
198      */
199     public InputStream getInputStream( final String name, final String suffix ) throws IOException {
200         InputStream is = null;
201         try {
202             is = getClass().getClassLoader().getResourceAsStream(name + suffix); // may return null
203         } catch (final Exception e) {
204             // ignored
205         }
206         if (is != null) {
207             return is;
208         }
209         return getClassFile(name, suffix).getInputStream();
210     }
211 
212     /**
213      * @param name fully qualified resource name, e.g. java/lang/String.class
214      * @return InputStream supplying the resource, or null if no resource with that name.
215      * @since 6.0
216      */
217     public InputStream getResourceAsStream(final String name) {
218         for (final PathEntry path : paths) {
219             InputStream is;
220             if ((is = path.getResourceAsStream(name)) != null) {
221                 return is;
222             }
223         }
224         return null;
225     }
226 
227     /**
228      * @param name fully qualified resource name, e.g. java/lang/String.class
229      * @return URL supplying the resource, or null if no resource with that name.
230      * @since 6.0
231      */
232     public URL getResource(final String name) {
233         for (final PathEntry path : paths) {
234             URL url;
235             if ((url = path.getResource(name)) != null) {
236                 return url;
237             }
238         }
239         return null;
240     }
241 
242     /**
243      * @param name fully qualified resource name, e.g. java/lang/String.class
244      * @return An Enumeration of URLs supplying the resource, or an
245      * empty Enumeration if no resource with that name.
246      * @since 6.0
247      */
248     public Enumeration<URL> getResources(final String name) {
249         final Vector<URL> results = new Vector<>();
250         for (final PathEntry path : paths) {
251             URL url;
252             if ((url = path.getResource(name)) != null) {
253                 results.add(url);
254             }
255         }
256         return results.elements();
257     }
258 
259     /**
260      * @param name fully qualified file name, e.g. java/lang/String
261      * @param suffix file name ends with suff, e.g. .java
262      * @return class file for the java class
263      */
264     public ClassFile getClassFile( final String name, final String suffix ) throws IOException {
265         ClassFile cf = null;
266 
267         if (parent != null) {
268             cf = parent.getClassFileInternal(name, suffix);
269         }
270 
271         if (cf == null) {
272             cf = getClassFileInternal(name, suffix);
273         }
274 
275         if (cf != null) {
276             return cf;
277         }
278 
279         throw new IOException("Couldn't find: " + name + suffix);
280     }
281 
282     private ClassFile getClassFileInternal(final String name, final String suffix) throws IOException {
283 
284       for (final PathEntry path : paths) {
285           final ClassFile cf = path.getClassFile(name, suffix);
286 
287           if(cf != null) {
288               return cf;
289           }
290       }
291 
292       return null;
293    }
294 
295 
296     /**
297      * @param name fully qualified class name, e.g. java.lang.String
298      * @return input stream for class
299      */
300     public ClassFile getClassFile( final String name ) throws IOException {
301         return getClassFile(name, ".class");
302     }
303 
304 
305     /**
306      * @param name fully qualified file name, e.g. java/lang/String
307      * @param suffix file name ends with suffix, e.g. .java
308      * @return byte array for file on class path
309      */
310     public byte[] getBytes(final String name, final String suffix) throws IOException {
311         DataInputStream dis = null;
312         try (InputStream is = getInputStream(name, suffix)) {
313             if (is == null) {
314                 throw new IOException("Couldn't find: " + name + suffix);
315             }
316             dis = new DataInputStream(is);
317             final byte[] bytes = new byte[is.available()];
318             dis.readFully(bytes);
319             return bytes;
320         } finally {
321             if (dis != null) {
322                 dis.close();
323             }
324         }
325     }
326 
327 
328     /**
329      * @return byte array for class
330      */
331     public byte[] getBytes( final String name ) throws IOException {
332         return getBytes(name, ".class");
333     }
334 
335 
336     /**
337      * @param name name of file to search for, e.g. java/lang/String.java
338      * @return full (canonical) path for file
339      */
340     public String getPath( String name ) throws IOException {
341         final int index = name.lastIndexOf('.');
342         String suffix = "";
343         if (index > 0) {
344             suffix = name.substring(index);
345             name = name.substring(0, index);
346         }
347         return getPath(name, suffix);
348     }
349 
350 
351     /**
352      * @param name name of file to search for, e.g. java/lang/String
353      * @param suffix file name suffix, e.g. .java
354      * @return full (canonical) path for file, if it exists
355      */
356     public String getPath( final String name, final String suffix ) throws IOException {
357         return getClassFile(name, suffix).getPath();
358     }
359 
360     private abstract static class PathEntry {
361 
362         abstract ClassFile getClassFile( String name, String suffix ) throws IOException;
363         abstract URL getResource(String name);
364         abstract InputStream getResourceAsStream(String name);
365     }
366 
367     /** Contains information about file/ZIP entry of the Java class.
368      */
369     public interface ClassFile {
370 
371         /** @return input stream for class file.
372          */
373         InputStream getInputStream() throws IOException;
374 
375 
376         /** @return canonical path to class file.
377          */
378         String getPath();
379 
380 
381         /** @return base path of found class, i.e. class is contained relative
382          * to that path, which may either denote a directory, or zip file
383          */
384         String getBase();
385 
386 
387         /** @return modification time of class file.
388          */
389         long getTime();
390 
391 
392         /** @return size of class file.
393          */
394         long getSize();
395     }
396 
397     private static class Dir extends PathEntry {
398 
399         private final String dir;
400 
401 
402         Dir(final String d) {
403             dir = d;
404         }
405 
406         @Override
407         URL getResource(final String name) {
408             // Resource specification uses '/' whatever the platform
409             final File file = new File(dir + File.separatorChar + name.replace('/', File.separatorChar));
410             try {
411                 return file.exists() ? file.toURI().toURL() : null;
412             } catch (final MalformedURLException e) {
413                return null;
414             }
415         }
416 
417         @Override
418         InputStream getResourceAsStream(final String name) {
419             // Resource specification uses '/' whatever the platform
420             final File file = new File(dir + File.separatorChar + name.replace('/', File.separatorChar));
421             try {
422                return file.exists() ? new FileInputStream(file) : null;
423             } catch (final IOException e) {
424                return null;
425             }
426         }
427 
428         @Override
429         ClassFile getClassFile( final String name, final String suffix ) throws IOException {
430             final File file = new File(dir + File.separatorChar
431                     + name.replace('.', File.separatorChar) + suffix);
432             return file.exists() ? new ClassFile() {
433 
434                 @Override
435                 public InputStream getInputStream() throws IOException {
436                     return new FileInputStream(file);
437                 }
438 
439 
440                 @Override
441                 public String getPath() {
442                     try {
443                         return file.getCanonicalPath();
444                     } catch (final IOException e) {
445                         return null;
446                     }
447                 }
448 
449 
450                 @Override
451                 public long getTime() {
452                     return file.lastModified();
453                 }
454 
455 
456                 @Override
457                 public long getSize() {
458                     return file.length();
459                 }
460 
461 
462                 @Override
463                 public String getBase() {
464                     return dir;
465                 }
466             } : null;
467         }
468 
469 
470         @Override
471         public String toString() {
472             return dir;
473         }
474     }
475 
476     private static class Zip extends PathEntry {
477 
478         private final ZipFile zip;
479 
480 
481         Zip(final ZipFile z) {
482             zip = z;
483         }
484 
485         @Override
486         URL getResource(final String name) {
487             final ZipEntry entry = zip.getEntry(name);
488             try {
489                 return (entry != null) ? new URL("jar:file:" + zip.getName() + "!/" + name) : null;
490             } catch (final MalformedURLException e) {
491                 return null;
492            }
493         }
494 
495         @Override
496         InputStream getResourceAsStream(final String name) {
497             final ZipEntry entry = zip.getEntry(name);
498             try {
499                 return (entry != null) ? zip.getInputStream(entry) : null;
500             } catch (final IOException e) {
501                 return null;
502             }
503         }
504 
505         @Override
506         ClassFile getClassFile( final String name, final String suffix ) throws IOException {
507             final ZipEntry entry = zip.getEntry(name.replace('.', '/') + suffix);
508 
509             if (entry == null) {
510                 return null;
511             }
512 
513             return new ClassFile() {
514 
515                 @Override
516                 public InputStream getInputStream() throws IOException {
517                     return zip.getInputStream(entry);
518                 }
519 
520 
521                 @Override
522                 public String getPath() {
523                     return entry.toString();
524                 }
525 
526 
527                 @Override
528                 public long getTime() {
529                     return entry.getTime();
530                 }
531 
532 
533                 @Override
534                 public long getSize() {
535                     return entry.getSize();
536                 }
537 
538 
539                 @Override
540                 public String getBase() {
541                     return zip.getName();
542                 }
543             };
544         }
545     }
546 }