Home | History | Annotate | Download | only in util
      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$
     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 }
    547