Home | History | Annotate | Download | only in runner
      1 package junit.runner;
      2 
      3 import java.util.*;
      4 import java.io.*;
      5 import java.net.URL;
      6 import 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 
     25 public class TestCaseClassLoader extends ClassLoader {
     26 	/** scanned class path */
     27 	private Vector<String> 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<String> 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<String>(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(".zip") ||
    130                        pathEntry.endsWith(".apk");
    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<String>(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