001: /*BEGIN_COPYRIGHT_BLOCK
002: *
003: * Copyright (c) 2001-2007, JavaPLT group at Rice University (javaplt@rice.edu)
004: * All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions are met:
008: * * Redistributions of source code must retain the above copyright
009: * notice, this list of conditions and the following disclaimer.
010: * * Redistributions in binary form must reproduce the above copyright
011: * notice, this list of conditions and the following disclaimer in the
012: * documentation and/or other materials provided with the distribution.
013: * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
014: * names of its contributors may be used to endorse or promote products
015: * derived from this software without specific prior written permission.
016: *
017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
018: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
019: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
020: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
021: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
022: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
023: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
024: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
025: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
026: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
027: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028: *
029: * This software is Open Source Initiative approved Open Source Software.
030: * Open Source Initative Approved is a trademark of the Open Source Initiative.
031: *
032: * This file is part of DrJava. Download the current version of this project
033: * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
034: *
035: * END_COPYRIGHT_BLOCK*/
036:
037: package edu.rice.cs.drjava.model.junit;
038:
039: import junit.runner.*;
040: import java.util.*;
041: import java.io.*;
042: import java.net.*;
043: import java.util.zip.*;
044:
045: import edu.rice.cs.util.FileOps;
046:
047: import static edu.rice.cs.plt.debug.DebugUtil.debug;
048:
049: /** A custom classloader for use in running test cases. this will load all classes for the test case. This loader also
050: * provides an excludes list. any class that matches an entry in the list will be loaded by the system class loader
051: * instead.
052: *
053: * This class extends junit.runner.TestCaseClassLoader. however, since the junit version kept all non public code as private, we
054: * had to duplicate the class to add the features we need.
055: *
056: * getResource and getResourceAsStream will not defer to the system class loader, as the junit version does, but instead will use
057: * the custom internal classpath to get resources.
058: *
059: * This allows this class to be used an a remote classloader to test cases that spawn multiple jvms.
060: */
061:
062: public final class DrJavaTestCaseClassLoader extends
063: TestCaseClassLoader {
064: /** the class loader that will load from the custom inner classpath */
065: ClassLoader _loader;
066:
067: /** scanned class path */
068: private Vector<String> fPathItems;
069:
070: /** default excluded paths */
071: private String[] defaultExclusions = { "junit.framework.",
072: "junit.extensions.", "junit.runner.", "java." };
073:
074: /** Name of excluded properties file */
075: static final String EXCLUDED_FILE = "excluded.properties";
076:
077: /** Excluded paths */
078: private Vector<String> fExcluded;
079:
080: /** Constructs a TestCaseLoader. It scans the class path and the excluded package paths. */
081: public DrJavaTestCaseClassLoader() {
082: this (System.getProperty("java.class.path"));
083: }
084:
085: /** Constructs a TestCaseLoader. It scans the class path and the excluded package paths. */
086: public DrJavaTestCaseClassLoader(String classPath) {
087: _loader = getClass().getClassLoader();
088: scanPath(classPath);
089: readExcludedPackages();
090: }
091:
092: /** Scans the classpath, and creates an internal IR for the classpath and initializes the
093: * internal loader
094: */
095: private void scanPath(String classPath) {
096: String separator = System.getProperty("path.separator");
097: fPathItems = new Vector<String>(10);
098: StringTokenizer st = new StringTokenizer(classPath, separator);
099: String item;
100: while (st.hasMoreTokens()) {
101: item = st.nextToken();
102: fPathItems.addElement(item);
103: try {
104: _loader = new DrJavaURLClassLoader(new URL[] { FileOps
105: .toURL(new File(item)) }, _loader);
106: } catch (MalformedURLException e) {
107: /* do nothing */
108: }
109: }
110: }
111:
112: /** Gets a resource from the custom classpath. */
113: public URL getResource(String name) {
114: return _loader.getResource(name);
115: }
116:
117: /** Gets a resource stream from the custom classpath. */
118: public InputStream getResourceAsStream(String name) {
119: return _loader.getResourceAsStream(name);
120: }
121:
122: /** Teturns true if the classname should be excluded
123: * (loaded from system), false otherwise
124: */
125: public boolean isExcluded(String name) {
126: for (int i = 0; i < fExcluded.size(); i++) {
127: if (name.startsWith(fExcluded.elementAt(i)))
128: return true;
129: }
130: return false;
131: }
132:
133: /** Loads the class. 1st. checks if the class is already loaded. 2nd. checks if it should be loaded by system.
134: * 3rd. try to load the class myself
135: */
136: public synchronized Class<?> loadClass(String name, boolean resolve)
137: throws ClassNotFoundException {
138: Class<?> c = findLoadedClass(name);
139: if (c != null)
140: return c;
141: //
142: // Delegate the loading of excluded classes to the
143: // standard class loader.
144: //
145: if (isExcluded(name)) {
146: try {
147: c = findSystemClass(name);
148: return c;
149: } catch (ClassNotFoundException e) {
150: // keep searching
151: }
152: }
153:
154: try {
155: if (c == null) {
156: byte[] data = lookupClassData(name);
157: if (data == null) {
158: throw new ClassNotFoundException();
159: }
160: c = defineClass(name, data, 0, data.length);
161: }
162: if (resolve)
163: resolveClass(c);
164: } catch (ClassNotFoundException e) {
165: // we couldn't load it, try the system
166: return findSystemClass(name);
167: }
168: return c;
169: }
170:
171: /** Reads in and returns data from class file for the given classname. */
172: private byte[] lookupClassData(String className)
173: throws ClassNotFoundException {
174: byte[] data = null;
175: for (int i = 0; i < fPathItems.size(); i++) {
176: String path = fPathItems.elementAt(i);
177: String fileName = className.replace('.', '/') + ".class";
178: if (isJar(path)) {
179: data = loadJarData(path, fileName);
180: } else {
181: data = loadFileData(path, fileName);
182: }
183: if (data != null) {
184: return data;
185: }
186: }
187: throw new ClassNotFoundException(className);
188: }
189:
190: /**
191: * returns true if the pathEntry points to a jar file
192: */
193: private boolean isJar(String pathEntry) {
194: return pathEntry.endsWith(".jar") || pathEntry.endsWith(".zip");
195: }
196:
197: /**
198: * reads in data from a class file and returns it
199: */
200: private byte[] loadFileData(String path, String fileName) {
201: File file = new File(path, fileName);
202: if (file.exists()) {
203: return getClassData(file);
204: }
205: return null;
206: }
207:
208: /**
209: * returns the contents of the file as an array of bytes
210: */
211: private byte[] getClassData(File f) {
212: try {
213: FileInputStream stream = new FileInputStream(f);
214: ByteArrayOutputStream out = new ByteArrayOutputStream(1000);
215: byte[] b = new byte[1000];
216: int n;
217: while ((n = stream.read(b)) != -1)
218: out.write(b, 0, n);
219: stream.close();
220: out.close();
221: return out.toByteArray();
222:
223: } catch (IOException e) {
224: }
225: return null;
226: }
227:
228: /**
229: * searches the contents of the jar for the specified fileName
230: * and returns an array of bytes
231: */
232: private byte[] loadJarData(String path, String fileName) {
233: ZipFile zipFile = null;
234: InputStream stream = null;
235: File archive = new File(path);
236: if (!archive.exists())
237: return null;
238: try {
239: zipFile = new ZipFile(archive);
240: } catch (IOException io) {
241: return null;
242: }
243: ZipEntry entry = zipFile.getEntry(fileName);
244: if (entry == null)
245: return null;
246: int size = (int) entry.getSize();
247: try {
248: stream = zipFile.getInputStream(entry);
249: byte[] data = new byte[size];
250: int pos = 0;
251: while (pos < size) {
252: int n = stream.read(data, pos, data.length - pos);
253: pos += n;
254: }
255: zipFile.close();
256: return data;
257: } catch (IOException e) {
258: } finally {
259: try {
260: if (stream != null)
261: stream.close();
262: } catch (IOException e) {
263: }
264: }
265: return null;
266: }
267:
268: /**
269: * reads in a list of excluded packages from a config file
270: */
271: private void readExcludedPackages() {
272: fExcluded = new Vector<String>(10);
273: for (String de : defaultExclusions)
274: fExcluded.addElement(de);
275:
276: InputStream is = getClass().getResourceAsStream(EXCLUDED_FILE);
277: if (is == null)
278: return;
279: Properties p = new Properties();
280: try {
281: p.load(is);
282: } catch (IOException e) {
283: return;
284: } finally {
285: try {
286: is.close();
287: } catch (IOException e) {
288: /* do nothing */
289: }
290: }
291:
292: // Raw type because Properties is not generified.
293: Enumeration pnames = p.propertyNames();
294:
295: while (pnames.hasMoreElements()) {
296: String key = (String) pnames.nextElement();
297: if (key.startsWith("excluded.")) {
298: String path = p.getProperty(key);
299: path = path.trim();
300: if (path.endsWith("*"))
301: path = path.substring(0, path.length() - 1);
302: if (path.length() > 0)
303: fExcluded.addElement(path);
304: }
305: }
306: }
307:
308: /** Allows more control over the URLClassLoader. specifically,
309: * allows us to view what's loaded when
310: */
311: private static class DrJavaURLClassLoader extends URLClassLoader {
312:
313: public DrJavaURLClassLoader(URL[] urls, ClassLoader c) {
314: super (urls, c);
315: }
316:
317: public URL getResource(String name) {
318: URL ret = getParent().getResource(name);
319: if (ret == null)
320: ret = super.getResource(name);
321: return ret;
322: }
323: }
324: }
|