1/*
2 * Javassist, a Java-bytecode translator toolkit.
3 * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved.
4 *
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License.  Alternatively, the contents of this file may be used under
8 * the terms of the GNU Lesser General Public License Version 2.1 or later.
9 *
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
14 */
15
16package javassist;
17
18import java.io.*;
19import java.util.jar.*;
20import java.net.MalformedURLException;
21import java.net.URL;
22import java.util.Hashtable;
23
24final class ClassPathList {
25    ClassPathList next;
26    ClassPath path;
27
28    ClassPathList(ClassPath p, ClassPathList n) {
29        next = n;
30        path = p;
31    }
32}
33
34final class DirClassPath implements ClassPath {
35    String directory;
36
37    DirClassPath(String dirName) {
38        directory = dirName;
39    }
40
41    public InputStream openClassfile(String classname) {
42        try {
43            char sep = File.separatorChar;
44            String filename = directory + sep
45                + classname.replace('.', sep) + ".class";
46            return new FileInputStream(filename.toString());
47        }
48        catch (FileNotFoundException e) {}
49        catch (SecurityException e) {}
50        return null;
51    }
52
53    public URL find(String classname) {
54        char sep = File.separatorChar;
55        String filename = directory + sep
56            + classname.replace('.', sep) + ".class";
57        File f = new File(filename);
58        if (f.exists())
59            try {
60                return f.getCanonicalFile().toURI().toURL();
61            }
62            catch (MalformedURLException e) {}
63            catch (IOException e) {}
64
65        return null;
66    }
67
68    public void close() {}
69
70    public String toString() {
71        return directory;
72    }
73}
74
75final class JarDirClassPath implements ClassPath {
76    JarClassPath[] jars;
77
78    JarDirClassPath(String dirName) throws NotFoundException {
79        File[] files = new File(dirName).listFiles(new FilenameFilter() {
80            public boolean accept(File dir, String name) {
81                name = name.toLowerCase();
82                return name.endsWith(".jar") || name.endsWith(".zip");
83            }
84        });
85
86        if (files != null) {
87            jars = new JarClassPath[files.length];
88            for (int i = 0; i < files.length; i++)
89                jars[i] = new JarClassPath(files[i].getPath());
90        }
91    }
92
93    public InputStream openClassfile(String classname) throws NotFoundException {
94        if (jars != null)
95            for (int i = 0; i < jars.length; i++) {
96                InputStream is = jars[i].openClassfile(classname);
97                if (is != null)
98                    return is;
99            }
100
101        return null;    // not found
102    }
103
104    public URL find(String classname) {
105        if (jars != null)
106            for (int i = 0; i < jars.length; i++) {
107                URL url = jars[i].find(classname);
108                if (url != null)
109                    return url;
110            }
111
112        return null;    // not found
113    }
114
115    public void close() {
116        if (jars != null)
117            for (int i = 0; i < jars.length; i++)
118                jars[i].close();
119    }
120}
121
122final class JarClassPath implements ClassPath {
123    JarFile jarfile;
124    String jarfileURL;
125
126    JarClassPath(String pathname) throws NotFoundException {
127        try {
128            jarfile = new JarFile(pathname);
129            jarfileURL = new File(pathname).getCanonicalFile()
130                                           .toURI().toURL().toString();
131            return;
132        }
133        catch (IOException e) {}
134        throw new NotFoundException(pathname);
135    }
136
137    public InputStream openClassfile(String classname)
138        throws NotFoundException
139    {
140        try {
141            String jarname = classname.replace('.', '/') + ".class";
142            JarEntry je = jarfile.getJarEntry(jarname);
143            if (je != null)
144                return jarfile.getInputStream(je);
145            else
146                return null;    // not found
147        }
148        catch (IOException e) {}
149        throw new NotFoundException("broken jar file?: "
150                                    + jarfile.getName());
151    }
152
153    public URL find(String classname) {
154        String jarname = classname.replace('.', '/') + ".class";
155        JarEntry je = jarfile.getJarEntry(jarname);
156        if (je != null)
157            try {
158                return new URL("jar:" + jarfileURL + "!/" + jarname);
159            }
160            catch (MalformedURLException e) {}
161
162        return null;            // not found
163    }
164
165    public void close() {
166        try {
167            jarfile.close();
168            jarfile = null;
169        }
170        catch (IOException e) {}
171    }
172
173    public String toString() {
174        return jarfile == null ? "<null>" : jarfile.toString();
175    }
176}
177
178final class ClassPoolTail {
179    protected ClassPathList pathList;
180    private Hashtable packages;         // should be synchronized.
181
182    public ClassPoolTail() {
183        pathList = null;
184        packages = new Hashtable();
185    }
186
187    public String toString() {
188        StringBuffer buf = new StringBuffer();
189        buf.append("[class path: ");
190        ClassPathList list = pathList;
191        while (list != null) {
192            buf.append(list.path.toString());
193            buf.append(File.pathSeparatorChar);
194            list = list.next;
195        }
196
197        buf.append(']');
198        return buf.toString();
199    }
200
201    public synchronized ClassPath insertClassPath(ClassPath cp) {
202        pathList = new ClassPathList(cp, pathList);
203        return cp;
204    }
205
206    public synchronized ClassPath appendClassPath(ClassPath cp) {
207        ClassPathList tail = new ClassPathList(cp, null);
208        ClassPathList list = pathList;
209        if (list == null)
210            pathList = tail;
211        else {
212            while (list.next != null)
213                list = list.next;
214
215            list.next = tail;
216        }
217
218        return cp;
219    }
220
221    public synchronized void removeClassPath(ClassPath cp) {
222        ClassPathList list = pathList;
223        if (list != null)
224            if (list.path == cp)
225                pathList = list.next;
226            else {
227                while (list.next != null)
228                    if (list.next.path == cp)
229                        list.next = list.next.next;
230                    else
231                        list = list.next;
232            }
233
234        cp.close();
235    }
236
237    public ClassPath appendSystemPath() {
238        return appendClassPath(new ClassClassPath());
239    }
240
241    public ClassPath insertClassPath(String pathname)
242        throws NotFoundException
243    {
244        return insertClassPath(makePathObject(pathname));
245    }
246
247    public ClassPath appendClassPath(String pathname)
248        throws NotFoundException
249    {
250        return appendClassPath(makePathObject(pathname));
251    }
252
253    private static ClassPath makePathObject(String pathname)
254        throws NotFoundException
255    {
256        String lower = pathname.toLowerCase();
257        if (lower.endsWith(".jar") || lower.endsWith(".zip"))
258            return new JarClassPath(pathname);
259
260        int len = pathname.length();
261        if (len > 2 && pathname.charAt(len - 1) == '*'
262            && (pathname.charAt(len - 2) == '/'
263                || pathname.charAt(len - 2) == File.separatorChar)) {
264            String dir = pathname.substring(0, len - 2);
265            return new JarDirClassPath(dir);
266        }
267
268        return new DirClassPath(pathname);
269    }
270
271    /**
272     * You can record "System" so that java.lang.System can be quickly
273     * found although "System" is not a package name.
274     */
275    public void recordInvalidClassName(String name) {
276        packages.put(name, name);
277    }
278
279    /**
280     * This method does not close the output stream.
281     */
282    void writeClassfile(String classname, OutputStream out)
283        throws NotFoundException, IOException, CannotCompileException
284    {
285        InputStream fin = openClassfile(classname);
286        if (fin == null)
287            throw new NotFoundException(classname);
288
289        try {
290            copyStream(fin, out);
291        }
292        finally {
293            fin.close();
294        }
295    }
296
297    /*
298    -- faster version --
299    void checkClassName(String classname) throws NotFoundException {
300        if (find(classname) == null)
301            throw new NotFoundException(classname);
302    }
303
304    -- slower version --
305
306    void checkClassName(String classname) throws NotFoundException {
307        InputStream fin = openClassfile(classname);
308        try {
309            fin.close();
310        }
311        catch (IOException e) {}
312    }
313    */
314
315
316    /**
317     * Opens the class file for the class specified by
318     * <code>classname</code>.
319     *
320     * @param classname             a fully-qualified class name
321     * @return null                 if the file has not been found.
322     * @throws NotFoundException    if any error is reported by ClassPath.
323     */
324    InputStream openClassfile(String classname)
325        throws NotFoundException
326    {
327        if (packages.get(classname) != null)
328            return null;    // not found
329
330        ClassPathList list = pathList;
331        InputStream ins = null;
332        NotFoundException error = null;
333        while (list != null) {
334            try {
335                ins = list.path.openClassfile(classname);
336            }
337            catch (NotFoundException e) {
338                if (error == null)
339                    error = e;
340            }
341
342            if (ins == null)
343                list = list.next;
344            else
345                return ins;
346        }
347
348        if (error != null)
349            throw error;
350        else
351            return null;    // not found
352    }
353
354    /**
355     * Searches the class path to obtain the URL of the class file
356     * specified by classname.  It is also used to determine whether
357     * the class file exists.
358     *
359     * @param classname     a fully-qualified class name.
360     * @return null if the class file could not be found.
361     */
362    public URL find(String classname) {
363        if (packages.get(classname) != null)
364            return null;
365
366        ClassPathList list = pathList;
367        URL url = null;
368        while (list != null) {
369            url = list.path.find(classname);
370            if (url == null)
371                list = list.next;
372            else
373                return url;
374        }
375
376        return null;
377    }
378
379    /**
380     * Reads from an input stream until it reaches the end.
381     *
382     * @return          the contents of that input stream
383     */
384    public static byte[] readStream(InputStream fin) throws IOException {
385        byte[][] bufs = new byte[8][];
386        int bufsize = 4096;
387
388        for (int i = 0; i < 8; ++i) {
389            bufs[i] = new byte[bufsize];
390            int size = 0;
391            int len = 0;
392            do {
393                len = fin.read(bufs[i], size, bufsize - size);
394                if (len >= 0)
395                    size += len;
396                else {
397                    byte[] result = new byte[bufsize - 4096 + size];
398                    int s = 0;
399                    for (int j = 0; j < i; ++j) {
400                        System.arraycopy(bufs[j], 0, result, s, s + 4096);
401                        s = s + s + 4096;
402                    }
403
404                    System.arraycopy(bufs[i], 0, result, s, size);
405                    return result;
406                }
407            } while (size < bufsize);
408            bufsize *= 2;
409        }
410
411        throw new IOException("too much data");
412    }
413
414    /**
415     * Reads from an input stream and write to an output stream
416     * until it reaches the end.  This method does not close the
417     * streams.
418     */
419    public static void copyStream(InputStream fin, OutputStream fout)
420        throws IOException
421    {
422        int bufsize = 4096;
423        for (int i = 0; i < 8; ++i) {
424            byte[] buf = new byte[bufsize];
425            int size = 0;
426            int len = 0;
427            do {
428                len = fin.read(buf, size, bufsize - size);
429                if (len >= 0)
430                    size += len;
431                else {
432                    fout.write(buf, 0, size);
433                    return;
434                }
435            } while (size < bufsize);
436            fout.write(buf);
437            bufsize *= 2;
438        }
439
440        throw new IOException("too much data");
441    }
442}
443