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<String> 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<String> 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<String>(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") ||
pathEntry.endsWith(".apk");
}
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<String>(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);
}
}
}
}