1package junit.runner;
2
3import java.util.*;
4import java.io.*;
5import java.net.URL;
6import java.util.zip.*;
7
8/**
9 * A custom class loader which enables the reloading
10 * of classes for each test run. The class loader
11 * can be configured with a list of package paths that
12 * should be excluded from loading. The loading
13 * of these packages is delegated to the system class
14 * loader. They will be shared across test runs.
15 * <p>
16 * The list of excluded package paths is specified in
17 * a properties file "excluded.properties" that is located in
18 * the same place as the TestCaseClassLoader class.
19 * <p>
20 * <b>Known limitation:</b> the TestCaseClassLoader cannot load classes
21 * from jar files.
22 * {@hide} - Not needed for 1.0 SDK
23 */
24public class TestCaseClassLoader extends ClassLoader {
25    /** scanned class path */
26    private Vector fPathItems;
27    /** default excluded paths */
28    private String[] defaultExclusions= {
29            "junit.framework.",
30            "junit.extensions.",
31            "junit.runner."
32    };
33    /** name of excluded properties file */
34    static final String EXCLUDED_FILE= "excluded.properties";
35    /** excluded paths */
36    private Vector fExcluded;
37
38    /**
39     * Constructs a TestCaseLoader. It scans the class path
40     * and the excluded package paths
41     */
42    public TestCaseClassLoader() {
43        this(System.getProperty("java.class.path"));
44    }
45
46    /**
47     * Constructs a TestCaseLoader. It scans the class path
48     * and the excluded package paths
49     */
50    public TestCaseClassLoader(String classPath) {
51        scanPath(classPath);
52        readExcludedPackages();
53    }
54
55    private void scanPath(String classPath) {
56        String separator= System.getProperty("path.separator");
57        fPathItems= new Vector(10);
58        StringTokenizer st= new StringTokenizer(classPath, separator);
59        while (st.hasMoreTokens()) {
60            fPathItems.addElement(st.nextToken());
61        }
62    }
63
64    public URL getResource(String name) {
65        return ClassLoader.getSystemResource(name);
66    }
67
68    public InputStream getResourceAsStream(String name) {
69        return ClassLoader.getSystemResourceAsStream(name);
70    }
71
72    public boolean isExcluded(String name) {
73        for (int i= 0; i < fExcluded.size(); i++) {
74            if (name.startsWith((String) fExcluded.elementAt(i))) {
75                return true;
76            }
77        }
78        return false;
79    }
80
81    public synchronized Class loadClass(String name, boolean resolve)
82            throws ClassNotFoundException {
83
84        Class c= findLoadedClass(name);
85        if (c != null)
86            return c;
87        //
88        // Delegate the loading of excluded classes to the
89        // standard class loader.
90        //
91        if (isExcluded(name)) {
92            try {
93                c= findSystemClass(name);
94                return c;
95            } catch (ClassNotFoundException e) {
96                // keep searching
97            }
98        }
99        if (c == null) {
100            byte[] data= lookupClassData(name);
101            if (data == null)
102                throw new ClassNotFoundException();
103            c= defineClass(name, data, 0, data.length);
104        }
105        if (resolve)
106            resolveClass(c);
107        return c;
108    }
109
110    private byte[] lookupClassData(String className) throws ClassNotFoundException {
111        byte[] data= null;
112        for (int i= 0; i < fPathItems.size(); i++) {
113            String path= (String) fPathItems.elementAt(i);
114            String fileName= className.replace('.', '/')+".class";
115            if (isJar(path)) {
116                data= loadJarData(path, fileName);
117            } else {
118                data= loadFileData(path, fileName);
119            }
120            if (data != null)
121                return data;
122        }
123        throw new ClassNotFoundException(className);
124    }
125
126    boolean isJar(String pathEntry) {
127        return pathEntry.endsWith(".jar") ||
128                pathEntry.endsWith(".apk") ||
129                pathEntry.endsWith(".zip");
130    }
131
132    private byte[] loadFileData(String path, String fileName) {
133        File file= new File(path, fileName);
134        if (file.exists()) {
135            return getClassData(file);
136        }
137        return null;
138    }
139
140    private byte[] getClassData(File f) {
141        try {
142            FileInputStream stream= new FileInputStream(f);
143            ByteArrayOutputStream out= new ByteArrayOutputStream(1000);
144            byte[] b= new byte[1000];
145            int n;
146            while ((n= stream.read(b)) != -1)
147                out.write(b, 0, n);
148            stream.close();
149            out.close();
150            return out.toByteArray();
151
152        } catch (IOException e) {
153        }
154        return null;
155    }
156
157    private byte[] loadJarData(String path, String fileName) {
158        ZipFile zipFile= null;
159        InputStream stream= null;
160        File archive= new File(path);
161        if (!archive.exists())
162            return null;
163        try {
164            zipFile= new ZipFile(archive);
165        } catch(IOException io) {
166            return null;
167        }
168        ZipEntry entry= zipFile.getEntry(fileName);
169        if (entry == null)
170            return null;
171        int size= (int) entry.getSize();
172        try {
173            stream= zipFile.getInputStream(entry);
174            byte[] data= new byte[size];
175            int pos= 0;
176            while (pos < size) {
177                int n= stream.read(data, pos, data.length - pos);
178                pos += n;
179            }
180            zipFile.close();
181            return data;
182        } catch (IOException e) {
183        } finally {
184            try {
185                if (stream != null)
186                    stream.close();
187            } catch (IOException e) {
188            }
189        }
190        return null;
191    }
192
193    private void readExcludedPackages() {
194        fExcluded= new Vector(10);
195        for (int i= 0; i < defaultExclusions.length; i++)
196            fExcluded.addElement(defaultExclusions[i]);
197
198        InputStream is= getClass().getResourceAsStream(EXCLUDED_FILE);
199        if (is == null)
200            return;
201        Properties p= new Properties();
202        try {
203            p.load(is);
204        }
205        catch (IOException e) {
206            return;
207        } finally {
208            try {
209                is.close();
210            } catch (IOException e) {
211            }
212        }
213        for (Enumeration e= p.propertyNames(); e.hasMoreElements(); ) {
214            String key= (String)e.nextElement();
215            if (key.startsWith("excluded.")) {
216                String path= p.getProperty(key);
217                path= path.trim();
218                if (path.endsWith("*"))
219                    path= path.substring(0, path.length()-1);
220                if (path.length() > 0)
221                    fExcluded.addElement(path);
222            }
223        }
224    }
225}
226