001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     *  Unless required by applicable law or agreed to in writing, software
012     *  distributed under the License is distributed on an "AS IS" BASIS,
013     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     *  See the License for the specific language governing permissions and
015     *  limitations under the License. 
016     *
017     */
018    package org.apache.bcel.util;
019    
020    import java.io.DataInputStream;
021    import java.io.File;
022    import java.io.FileInputStream;
023    import java.io.FilenameFilter;
024    import java.io.IOException;
025    import java.io.InputStream;
026    import java.io.Serializable;
027    import java.net.MalformedURLException;
028    import java.net.URL;
029    import java.util.ArrayList;
030    import java.util.Enumeration;
031    import java.util.List;
032    import java.util.Locale;
033    import java.util.StringTokenizer;
034    import java.util.Vector;
035    import java.util.zip.ZipEntry;
036    import java.util.zip.ZipFile;
037    
038    /**
039     * Responsible for loading (class) files from the CLASSPATH. Inspired by
040     * sun.tools.ClassPath.
041     *
042     * @version $Id: ClassPath.java 1152077 2011-07-29 02:29:42Z dbrosius $
043     * @author  <A HREF="mailto:m.dahm@gmx.de">M. Dahm</A>
044     */
045    public class ClassPath implements Serializable {
046    
047        private static final long serialVersionUID = 2099441438483340671L;
048        public static final ClassPath SYSTEM_CLASS_PATH = new ClassPath();
049        private PathEntry[] paths;
050        private String class_path;
051        private ClassPath parent;
052    
053        public ClassPath(ClassPath parent, String class_path) {
054            this(class_path);
055            this.parent = parent;
056        }
057    
058        /**
059         * Search for classes in given path.
060         * 
061         * @param class_path
062         */
063        public ClassPath(String class_path) {
064            this.class_path = class_path;
065            List<PathEntry> vec = new ArrayList<PathEntry>();
066            for (StringTokenizer tok = new StringTokenizer(class_path, System
067                    .getProperty("path.separator")); tok.hasMoreTokens();) {
068                String path = tok.nextToken();
069                if (!path.equals("")) {
070                    File file = new File(path);
071                    try {
072                        if (file.exists()) {
073                            if (file.isDirectory()) {
074                                vec.add(new Dir(path));
075                            } else {
076                                vec.add(new Zip(new ZipFile(file)));
077                            }
078                        }
079                    } catch (IOException e) {
080                        System.err.println("CLASSPATH component " + file + ": " + e);
081                    }
082                }
083            }
084            paths = new PathEntry[vec.size()];
085            vec.toArray(paths);
086        }
087    
088    
089        /**
090         * Search for classes in CLASSPATH.
091         * @deprecated Use SYSTEM_CLASS_PATH constant
092         */
093        @Deprecated
094        public ClassPath() {
095            this(getClassPath());
096        }
097    
098    
099        /** @return used class path string
100         */
101        @Override
102        public String toString() {
103            if (parent != null) {
104                return parent.toString() + File.pathSeparator + class_path;
105            }
106            return class_path;
107        }
108    
109        @Override
110        public int hashCode() {
111            if (parent != null) {
112                return class_path.hashCode() + parent.hashCode();            
113            }
114            return class_path.hashCode();
115        }
116    
117    
118        @Override
119        public boolean equals( Object o ) {
120            if (o instanceof ClassPath) {
121                ClassPath cp = (ClassPath)o;
122                return class_path.equals(cp.toString());
123            }
124            return false;
125        }
126    
127    
128        private static final void getPathComponents( String path, List<String> list ) {
129            if (path != null) {
130                StringTokenizer tok = new StringTokenizer(path, File.pathSeparator);
131                while (tok.hasMoreTokens()) {
132                    String name = tok.nextToken();
133                    File file = new File(name);
134                    if (file.exists()) {
135                        list.add(name);
136                    }
137                }
138            }
139        }
140    
141    
142        /** Checks for class path components in the following properties:
143         * "java.class.path", "sun.boot.class.path", "java.ext.dirs"
144         *
145         * @return class path as used by default by BCEL
146         */
147        public static final String getClassPath() {
148            String class_path = System.getProperty("java.class.path");
149            String boot_path = System.getProperty("sun.boot.class.path");
150            String ext_path = System.getProperty("java.ext.dirs");
151            List<String> list = new ArrayList<String>();
152            getPathComponents(class_path, list);
153            getPathComponents(boot_path, list);
154            List<String> dirs = new ArrayList<String>();
155            getPathComponents(ext_path, dirs);
156            for (String d : dirs) {
157                File ext_dir = new File(d);
158                String[] extensions = ext_dir.list(new FilenameFilter() {
159    
160                    public boolean accept( File dir, String name ) {
161                        name = name.toLowerCase(Locale.ENGLISH);
162                        return name.endsWith(".zip") || name.endsWith(".jar");
163                    }
164                });
165                if (extensions != null) {
166                    for (int i = 0; i < extensions.length; i++) {
167                        list.add(ext_dir.getPath() + File.separatorChar + extensions[i]);
168                    }
169                }
170            }
171            StringBuilder buf = new StringBuilder();
172            String separator = "";
173            for (String path : list) {
174                buf.append(separator);
175                separator = File.pathSeparator;
176                buf.append(path);
177            }
178            return buf.toString().intern();
179        }
180    
181    
182        /**
183         * @param name fully qualified class name, e.g. java.lang.String
184         * @return input stream for class
185         */
186        public InputStream getInputStream( String name ) throws IOException {
187            return getInputStream(name.replace('.', '/'), ".class");
188        }
189    
190    
191        /**
192         * Return stream for class or resource on CLASSPATH.
193         *
194         * @param name fully qualified file name, e.g. java/lang/String
195         * @param suffix file name ends with suff, e.g. .java
196         * @return input stream for file on class path
197         */
198        public InputStream getInputStream( String name, String suffix ) throws IOException {
199            InputStream is = null;
200            try {
201                is = getClass().getClassLoader().getResourceAsStream(name + suffix);
202            } catch (Exception e) {
203            }
204            if (is != null) {
205                return is;
206            }
207            return getClassFile(name, suffix).getInputStream();
208        }
209    
210        /**
211         * @param name fully qualified resource name, e.g. java/lang/String.class
212         * @return InputStream supplying the resource, or null if no resource with that name.
213         */
214        public InputStream getResourceAsStream(String name) {
215            for (int i = 0; i < paths.length; i++) {
216                InputStream is;
217                if ((is = paths[i].getResourceAsStream(name)) != null) {
218                    return is;
219                }
220            }
221            return null;
222        }
223        
224        /**
225         * @param name fully qualified resource name, e.g. java/lang/String.class
226         * @return URL supplying the resource, or null if no resource with that name.
227         */
228        public URL getResource(String name) {
229            for (int i = 0; i < paths.length; i++) {
230                URL url;
231                if ((url = paths[i].getResource(name)) != null) {
232                    return url;
233                }
234            }
235            return null;
236        }
237    
238        /**
239         * @param name fully qualified resource name, e.g. java/lang/String.class
240         * @return An Enumeration of URLs supplying the resource, or an
241         * empty Enumeration if no resource with that name.
242         */
243        public Enumeration<URL> getResources(String name) {
244            Vector<URL> results = new Vector<URL>();
245            for (int i = 0; i < paths.length; i++) {
246                URL url;
247                if ((url = paths[i].getResource(name)) != null) {
248                    results.add(url);
249                }
250            }
251            return results.elements();
252        }
253    
254        /**
255         * @param name fully qualified file name, e.g. java/lang/String
256         * @param suffix file name ends with suff, e.g. .java
257         * @return class file for the java class
258         */
259        public ClassFile getClassFile( String name, String suffix ) throws IOException {
260            for (int i = 0; i < paths.length; i++) {
261                ClassFile cf = null;
262    
263                if(parent != null) {
264                    cf = parent.getClassFileInternal(name, suffix);
265                }
266                
267                if(cf == null) {
268                    cf = getClassFileInternal(name,suffix);
269                }
270                
271                if(cf != null) {
272                    return cf;
273                }
274            }
275    
276            throw new IOException("Couldn't find: " + name + suffix);
277        }
278    
279        private ClassFile getClassFileInternal(String name, String suffix) throws IOException {
280    
281          for(int i=0; i < paths.length; i++) {
282              ClassFile cf = paths[i].getClassFile(name, suffix);
283              
284              if(cf != null) {
285                  return cf;
286              }
287          }
288    
289          return null;
290       }
291    
292    
293        /**
294         * @param name fully qualified class name, e.g. java.lang.String
295         * @return input stream for class
296         */
297        public ClassFile getClassFile( String name ) throws IOException {
298            return getClassFile(name, ".class");
299        }
300    
301    
302        /**
303         * @param name fully qualified file name, e.g. java/lang/String
304         * @param suffix file name ends with suffix, e.g. .java
305         * @return byte array for file on class path
306         */
307        public byte[] getBytes( String name, String suffix ) throws IOException {
308            DataInputStream dis = null;
309            try {
310                InputStream is = getInputStream(name, suffix);
311                if (is == null) {
312                    throw new IOException("Couldn't find: " + name + suffix);
313                }
314                dis = new DataInputStream(is);
315                byte[] bytes = new byte[is.available()];
316                dis.readFully(bytes);
317                return bytes;
318            } finally {
319                if (dis != null) {
320                    dis.close();
321                }
322            }
323        }
324    
325    
326        /**
327         * @return byte array for class
328         */
329        public byte[] getBytes( String name ) throws IOException {
330            return getBytes(name, ".class");
331        }
332    
333    
334        /**
335         * @param name name of file to search for, e.g. java/lang/String.java
336         * @return full (canonical) path for file
337         */
338        public String getPath( String name ) throws IOException {
339            int index = name.lastIndexOf('.');
340            String suffix = "";
341            if (index > 0) {
342                suffix = name.substring(index);
343                name = name.substring(0, index);
344            }
345            return getPath(name, suffix);
346        }
347    
348    
349        /**
350         * @param name name of file to search for, e.g. java/lang/String
351         * @param suffix file name suffix, e.g. .java
352         * @return full (canonical) path for file, if it exists
353         */
354        public String getPath( String name, String suffix ) throws IOException {
355            return getClassFile(name, suffix).getPath();
356        }
357    
358        private static abstract class PathEntry implements Serializable {
359    
360            private static final long serialVersionUID = 6828494485207666122L;
361            abstract ClassFile getClassFile( String name, String suffix ) throws IOException;
362            abstract URL getResource(String name);
363            abstract InputStream getResourceAsStream(String name);
364        }
365    
366        /** Contains information about file/ZIP entry of the Java class.
367         */
368        public interface ClassFile {
369    
370            /** @return input stream for class file.
371             */
372            public abstract InputStream getInputStream() throws IOException;
373    
374    
375            /** @return canonical path to class file.
376             */
377            public abstract String getPath();
378    
379    
380            /** @return base path of found class, i.e. class is contained relative
381             * to that path, which may either denote a directory, or zip file
382             */
383            public abstract String getBase();
384    
385    
386            /** @return modification time of class file.
387             */
388            public abstract long getTime();
389    
390    
391            /** @return size of class file.
392             */
393            public abstract long getSize();
394        }
395    
396        private static class Dir extends PathEntry {
397    
398            private static final long serialVersionUID = 4374062802142373088L;
399            private String dir;
400    
401    
402            Dir(String d) {
403                dir = d;
404            }
405    
406            @Override
407            URL getResource(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.toURL() : null;
412                } catch (MalformedURLException e) {
413                   return null;
414                }
415            }
416            
417            @Override
418            InputStream getResourceAsStream(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 (IOException e) {
424                   return null;
425                }
426            }
427    
428            @Override
429            ClassFile getClassFile( String name, 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                    public InputStream getInputStream() throws IOException {
435                        return new FileInputStream(file);
436                    }
437    
438    
439                    public String getPath() {
440                        try {
441                            return file.getCanonicalPath();
442                        } catch (IOException e) {
443                            return null;
444                        }
445                    }
446    
447    
448                    public long getTime() {
449                        return file.lastModified();
450                    }
451    
452    
453                    public long getSize() {
454                        return file.length();
455                    }
456    
457    
458                    public String getBase() {
459                        return dir;
460                    }
461                } : null;
462            }
463    
464    
465            @Override
466            public String toString() {
467                return dir;
468            }
469        }
470    
471        private static class Zip extends PathEntry {
472    
473            private static final long serialVersionUID = -2210747632897905532L;
474            private ZipFile zip;
475    
476    
477            Zip(ZipFile z) {
478                zip = z;
479            }
480    
481            @Override
482            URL getResource(String name) {
483                final ZipEntry entry = zip.getEntry(name);
484                try {
485                    return (entry != null) ? new URL("jar:file:" + zip.getName() + "!/" + name) : null;
486                } catch (MalformedURLException e) {
487                    return null;
488               }
489            }
490            
491            @Override
492            InputStream getResourceAsStream(String name) {
493                final ZipEntry entry = zip.getEntry(name);
494                try {
495                    return (entry != null) ? zip.getInputStream(entry) : null;
496                } catch (IOException e) {
497                    return null;
498                }
499            }
500                    
501            @Override
502            ClassFile getClassFile( String name, String suffix ) throws IOException {
503                final ZipEntry entry = zip.getEntry(name.replace('.', '/') + suffix);
504                
505                if (entry == null)
506                    return null;
507                
508                return new ClassFile() {
509    
510                    public InputStream getInputStream() throws IOException {
511                        return zip.getInputStream(entry);
512                    }
513    
514    
515                    public String getPath() {
516                        return entry.toString();
517                    }
518    
519    
520                    public long getTime() {
521                        return entry.getTime();
522                    }
523    
524    
525                    public long getSize() {
526                        return entry.getSize();
527                    }
528    
529    
530                    public String getBase() {
531                        return zip.getName();
532                    }
533                };
534            }
535        }
536    }