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