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);
                        }
                }
        }
}