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