package polyglot.types.reflect;

import polyglot.main.Report;

import java.io.*;
import java.util.*;
import java.util.zip.*;
import java.util.jar.*;

/**
 * We implement our own class loader.  All this pain is so
 * we can define the classpath on the command line.
 */
public class ClassFileLoader
{
    /**
     * Map from directories to maps from names to ClassFiles
     * Map[File -> Map[String -> (ClassFile or not_found)]]
     */
    Map cache;
    
    /**
     * Keep a cache of the zips and jars so we don't have to keep 
     * opening them from the file system. 
     */
    Map jarCache;
    
    final static Object not_found = new Object();

    public ClassFileLoader() {
        this.cache = new HashMap();
        this.jarCache = new HashMap();
    }

    /**
     * Try to find the class <code>name</code> in the directory or jar or zip 
     * file <code>dir</code>.
     * If the class does not exist in the specified file/directory, then 
     * <code>null</code> is returned.
     */
    public ClassFile loadClass(File dir, String name)
    {
        if (Report.should_report(verbose, 2))
	    Report.report(2, "attempting to load class " + name +
                      " from " + dir);

        Map dirMap = (Map)cache.get(dir);
        if (dirMap == null) {
            dirMap = new HashMap();
            cache.put(dir, dirMap);
        }
        
        Object o = dirMap.get(name);

        if (o != not_found) {
            ClassFile c = (ClassFile) o;

            if (c != null) {
                if (Report.should_report(verbose, 3))
		    Report.report(3, "already loaded " + c.name());
                return c;
            }

            c = findClass(dir, name);

            // We cache here since more than one type system may attempt
            // to load the same class file.  But, we use a weak hash map
            // to allow garbage collection of ClassFiles when we need it.
            if (c != null) {
                if (Report.should_report(verbose, 2))
		    Report.report(2, "loaded class " + c.name());
                dirMap.put(name, c);
                return c;
            }

            dirMap.put(name, not_found);
        }

        return null;
    }

    /**
     * Try to find the class <code>name</code> in the directory or jar or zip 
     * file <code>dir</code>.
     * If the class does not exist in the specified file/directory, then 
     * <code>null</code> is returned.
     */
    protected ClassFile findClass(File dir, String name) {
        if (Report.should_report(verbose, 3)) {
	    Report.report(3, "looking in " + dir + " for " + 
                             name.replace('.', File.separatorChar) + ".class");
        }

        try {
            if (dir.isDirectory()) {
                String fileName = name.replace('.', File.separatorChar) + ".class";
                File file = new File(dir, fileName);

                if (file.exists()) {
                    if (Report.should_report(verbose, 3))
			Report.report(3, "found " + file);
                    FileInputStream in = new FileInputStream(file);
                    ClassFile c = loadFromStream(in, name);
                    in.close();
                    return c;
                }
            }
            else if (dir.getName().endsWith(".jar")) {
                Object o = jarCache.get(dir);
                if (o != not_found) {
                    JarFile jar = (JarFile)o;
                    if (jar == null) { 
                        // the jar is not in the cache.
                        // try to get it.
                        if (!dir.exists()) {
                            // record that the file does not exist, 
                            jarCache.put(dir, not_found);
                        }
                        else {
                            // get the jar and put it in the cache.
                            if (Report.should_report(verbose, 2))
                                Report.report(2, "Opening jar " + dir);
                            jar = new JarFile(dir);
                            jarCache.put(dir, jar);                            
                        }
                    }
                    if (jar != null) {
                        String entryName = name.replace('.', '/') + ".class";
                        JarEntry entry = jar.getJarEntry(entryName);
                        if (entry != null) {
                            if (Report.should_report(verbose, 3))
        			Report.report(3, "found jar entry " + entry);
                            InputStream in = jar.getInputStream(entry);
                            ClassFile c = loadFromStream(in, name);
                            in.close();
                            return c;
                        }
                    }
                }
            }
            else if (dir.getName().endsWith(".zip")) {
                Object o = jarCache.get(dir);
                if (o != not_found) {
                    ZipFile zip = (ZipFile)o;
                    if (zip == null) { 
                        // the zip is not in the cache.
                        // try to get it.
                        if (!dir.exists()) {
                            // record that the file does not exist, 
                            jarCache.put(dir, not_found);
                        }
                        else {
                            // get the zip and put it in the cache.
                            if (Report.should_report(verbose, 2))
                                Report.report(2, "Opening zip " + dir);
                            zip = new ZipFile(dir);
                            jarCache.put(dir, zip);                            
                        }
                    }
                    if (zip != null) {
                        String entryName = name.replace('.', '/') + ".class";
                        ZipEntry entry = zip.getEntry(entryName);
                        if (entry != null) {
                            if (Report.should_report(verbose, 3))
                                Report.report(3, "found zip entry " + entry);
                            InputStream in = zip.getInputStream(entry);
                            ClassFile c = loadFromStream(in, name);
                            in.close();
                            return c;
                        }
                    }
                }
            }
        }
        catch (IOException e) {
        }

        return null;
    }

    /**
     * Load a class from an input stream.
     */
    ClassFile loadFromStream(InputStream in, String name) throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();

        byte[] buf = new byte[4096];
        int n = 0;

        do {
            n = in.read(buf);
            if (n >= 0) out.write(buf, 0, n);
        } while (n >= 0);

        byte[] bytecode = out.toByteArray();

        try {
            if (Report.should_report(verbose, 3))
		Report.report(3, "defining class " + name);
            return new ClassFile(bytecode);
        }
        catch (ClassFormatError e) {
            throw new IOException(e.getMessage());
        }
    }

    static Collection verbose;

    static {
        verbose = new HashSet();
        verbose.add("loader");
    }
}

@@ -305,165 +305,8 @@
 /**%0A
-     * Map from directories to maps from names to ClassFiles%0A     * Map%5BFile -%3E Map%5BString -%3E (ClassFile or not_found)%5D%5D%0A     */%0A    Map cache;%0A    %0A    /**%0A
     
@@ -368,16 +368,16 @@
 o keep %0A
+
      * o
@@ -528,44 +528,8 @@
 ) %7B%0A
-        this.cache = new HashMap();%0A
     
@@ -865,1458 +865,14 @@
 me)%0A
+
     %7B%0A
-        if (Report.should_report(verbose, 2))%0A%09    Report.report(2, %22attempting to load class %22 + name +%0A                      %22 from %22 + dir);%0A%0A        Map dirMap = (Map)cache.get(dir);%0A        if (dirMap == null) %7B%0A            dirMap = new HashMap();%0A            cache.put(dir, dirMap);%0A        %7D%0A        %0A        Object o = dirMap.get(name);%0A%0A        if (o != not_found) %7B%0A            ClassFile c = (ClassFile) o;%0A%0A            if (c != null) %7B%0A                if (Report.should_report(verbose, 3))%0A%09%09    Report.report(3, %22already loaded %22 + c.name());%0A                return c;%0A            %7D%0A%0A            c = findClass(dir, name);%0A%0A            // We cache here since more than one type system may attempt%0A            // to load the same class file.  But, we use a weak hash map%0A            // to allow garbage collection of ClassFiles when we need it.%0A            if (c != null) %7B%0A                if (Report.should_report(verbose, 2))%0A%09%09    Report.report(2, %22loaded class %22 + c.name());%0A                dirMap.put(name, c);%0A                return c;%0A            %7D%0A%0A            dirMap.put(name, not_found);%0A        %7D%0A%0A        return null;%0A    %7D%0A%0A    /**%0A     * Try to find the class %3Ccode%3Ename%3C/code%3E in the directory or jar or zip %0A     * file %3Ccode%3Edir%3C/code%3E.%0A     * If the class does not exist in the specified file/directory, then %0A     * %3Ccode%3Enull%3C/code%3E is returned.%0A     */%0A    protected ClassFile findClass(File dir, String name) %7B%0A
     

Remove caching classes and directories. Since there is more efficient caching at a higher level, every call to this method was a cache miss, and we were wasting a lot of memory maintaining the cache.