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