package junit.runner; | |
import java.util.*; | |
import java.io.*; | |
import java.net.URL; | |
import java.util.zip.*; | |
/** | |
* A custom class loader which enables the reloading | |
* of classes for each test run. The class loader | |
* can be configured with a list of package paths that | |
* should be excluded from loading. The loading | |
* of these packages is delegated to the system class | |
* loader. They will be shared across test runs. | |
* <p> | |
* The list of excluded package paths is specified in | |
* a properties file "excluded.properties" that is located in | |
* the same place as the TestCaseClassLoader class. | |
* <p> | |
* <b>Known limitation:</b> the TestCaseClassLoader cannot load classes | |
* from jar files. | |
*/ | |
public class TestCaseClassLoader extends ClassLoader { | |
/** scanned class path */ | |
private Vector fPathItems; | |
/** default excluded paths */ | |
private String[] defaultExclusions= { | |
"junit.framework.", | |
"junit.extensions.", | |
"junit.runner." | |
}; | |
/** name of excluded properties file */ | |
static final String EXCLUDED_FILE= "excluded.properties"; | |
/** excluded paths */ | |
private Vector fExcluded; | |
/** | |
* Constructs a TestCaseLoader. It scans the class path | |
* and the excluded package paths | |
*/ | |
public TestCaseClassLoader() { | |
this(System.getProperty("java.class.path")); | |
} | |
/** | |
* Constructs a TestCaseLoader. It scans the class path | |
* and the excluded package paths | |
*/ | |
public TestCaseClassLoader(String classPath) { | |
scanPath(classPath); | |
readExcludedPackages(); | |
} | |
private void scanPath(String classPath) { | |
String separator= System.getProperty("path.separator"); | |
fPathItems= new Vector(10); | |
StringTokenizer st= new StringTokenizer(classPath, separator); | |
while (st.hasMoreTokens()) { | |
fPathItems.addElement(st.nextToken()); | |
} | |
} | |
public URL getResource(String name) { | |
return ClassLoader.getSystemResource(name); | |
} | |
public InputStream getResourceAsStream(String name) { | |
return ClassLoader.getSystemResourceAsStream(name); | |
} | |
public boolean isExcluded(String name) { | |
for (int i= 0; i < fExcluded.size(); i++) { | |
if (name.startsWith((String) fExcluded.elementAt(i))) { | |
return true; | |
} | |
} | |
return false; | |
} | |
public synchronized Class loadClass(String name, boolean resolve) | |
throws ClassNotFoundException { | |
Class c= findLoadedClass(name); | |
if (c != null) | |
return c; | |
// | |
// Delegate the loading of excluded classes to the | |
// standard class loader. | |
// | |
if (isExcluded(name)) { | |
try { | |
c= findSystemClass(name); | |
return c; | |
} catch (ClassNotFoundException e) { | |
// keep searching | |
} | |
} | |
if (c == null) { | |
byte[] data= lookupClassData(name); | |
if (data == null) | |
throw new ClassNotFoundException(); | |
c= defineClass(name, data, 0, data.length); | |
} | |
if (resolve) | |
resolveClass(c); | |
return c; | |
} | |
private byte[] lookupClassData(String className) throws ClassNotFoundException { | |
byte[] data= null; | |
for (int i= 0; i < fPathItems.size(); i++) { | |
String path= (String) fPathItems.elementAt(i); | |
String fileName= className.replace('.', '/')+".class"; | |
if (isJar(path)) { | |
data= loadJarData(path, fileName); | |
} else { | |
data= loadFileData(path, fileName); | |
} | |
if (data != null) | |
return data; | |
} | |
throw new ClassNotFoundException(className); | |
} | |
boolean isJar(String pathEntry) { | |
return pathEntry.endsWith(".jar") || pathEntry.endsWith(".zip"); | |
} | |
private byte[] loadFileData(String path, String fileName) { | |
File file= new File(path, fileName); | |
if (file.exists()) { | |
return getClassData(file); | |
} | |
return null; | |
} | |
private byte[] getClassData(File f) { | |
try { | |
FileInputStream stream= new FileInputStream(f); | |
ByteArrayOutputStream out= new ByteArrayOutputStream(1000); | |
byte[] b= new byte[1000]; | |
int n; | |
while ((n= stream.read(b)) != -1) | |
out.write(b, 0, n); | |
stream.close(); | |
out.close(); | |
return out.toByteArray(); | |
} catch (IOException e) { | |
} | |
return null; | |
} | |
private byte[] loadJarData(String path, String fileName) { | |
ZipFile zipFile= null; | |
InputStream stream= null; | |
File archive= new File(path); | |
if (!archive.exists()) | |
return null; | |
try { | |
zipFile= new ZipFile(archive); | |
} catch(IOException io) { | |
return null; | |
} | |
ZipEntry entry= zipFile.getEntry(fileName); | |
if (entry == null) | |
return null; | |
int size= (int) entry.getSize(); | |
try { | |
stream= zipFile.getInputStream(entry); | |
byte[] data= new byte[size]; | |
int pos= 0; | |
while (pos < size) { | |
int n= stream.read(data, pos, data.length - pos); | |
pos += n; | |
} | |
zipFile.close(); | |
return data; | |
} catch (IOException e) { | |
} finally { | |
try { | |
if (stream != null) | |
stream.close(); | |
} catch (IOException e) { | |
} | |
} | |
return null; | |
} | |
private void readExcludedPackages() { | |
fExcluded= new Vector(10); | |
for (int i= 0; i < defaultExclusions.length; i++) | |
fExcluded.addElement(defaultExclusions[i]); | |
InputStream is= getClass().getResourceAsStream(EXCLUDED_FILE); | |
if (is == null) | |
return; | |
Properties p= new Properties(); | |
try { | |
p.load(is); | |
} | |
catch (IOException e) { | |
return; | |
} finally { | |
try { | |
is.close(); | |
} catch (IOException e) { | |
} | |
} | |
for (Enumeration e= p.propertyNames(); e.hasMoreElements(); ) { | |
String key= (String)e.nextElement(); | |
if (key.startsWith("excluded.")) { | |
String path= p.getProperty(key); | |
path= path.trim(); | |
if (path.endsWith("*")) | |
path= path.substring(0, path.length()-1); | |
if (path.length() > 0) | |
fExcluded.addElement(path); | |
} | |
} | |
} | |
} |