/***************************************
 *                                     *
 *  JBoss: The OpenSource J2EE WebOS   *
 *                                     *
 *  Distributable under LGPL license.  *
 *  See terms of license at gnu.org.   *
 *                                     *
 ***************************************/
package org.jboss.remoting.loading;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.ref.SoftReference;
import java.util.Collections;
import java.util.Map;

import org.jboss.logging.Logger;

/**
 * ClassByteClassLoader is a classloader that will allow dynamic adding of classes from a remote machine
 * to be added and visible locally.
 * 
 * @author <a href="mailto:jhaynie@vocalocity.net">Jeff Haynie</a>
 * @version $Revision: 1.3 $
 */
public class ClassByteClassLoader extends ClassLoader
{
    private static final boolean logExtra = Boolean.getBoolean("jboss.remoting.classloader.debug");
    private static final Logger log = Logger.getLogger(ClassByteClassLoader.class);
    private Map loadedClasses=Collections.synchronizedMap(new java.util.HashMap());
    private Map loadedResources=Collections.synchronizedMap(new java.util.HashMap());

    public ClassByteClassLoader ()
    {
        super();
    }

    public ClassByteClassLoader (ClassLoader parent)
    {
        super(parent);
    }

    public String toString()
    {
        return "ClassByteClassLoader ["+loadedClasses+"]";
    }

    protected synchronized void finalize () throws Throwable
    {
        java.util.Iterator iter=loadedResources.values().iterator();
        while(iter.hasNext())
        {
            ((java.io.File)iter.next()).delete();
        }
        loadedResources.clear();
        loadedClasses.clear();
        super.finalize ();
    }

    public Class loadClass (String className, ClassBytes bytes[])
        throws ClassNotFoundException, java.io.IOException
    {
        if (logExtra && log.isDebugEnabled())
        {
            log.debug("loadClass: "+className+", bytes: "+bytes);
        }
        if (bytes!=null)
        {
            for (int c=0;c<bytes.length;c++)
            {
                addClass(bytes[c]);
            }
        }
        SoftReference sr=(SoftReference)loadedClasses.get(className);
        if (sr!=null)
        {
            Class cl = (Class)sr.get();
            if (cl==null)
            {
                loadedClasses.remove(className);
                sr.clear();
                sr=null;
                cl=null;
            }
            else
            {
                return cl;
            }
        }
        Class cl = findLoadedClass(className);
        if (cl!=null)
        {
            return cl;
        }
        cl = ClassLoader.getSystemClassLoader().loadClass(className);
        if (cl!=null)
        {
            return cl;
        }
        cl = super.loadClass(className,true);
        if (cl!=null)
        {
            return cl;
        }
        throw new ClassNotFoundException(className);
    }
    private synchronized void addClassResource (String name, byte buf[])
        throws IOException
    {
        OutputStream out=null;
        File file=null;
        try
        {
            file=File.createTempFile("cbc",".class");
            file.deleteOnExit();
            if (logExtra && log.isDebugEnabled())
            {
                log.debug("adding resource at: "+name+" to file: "+file);
            }
            out = new BufferedOutputStream(new FileOutputStream(file));
            out.write(buf);
            out.flush();
        }
        catch (java.io.IOException ex)
        {
            file = null;
            throw ex;
        }
        finally
        {
            if (out!=null)
            {
                try { out.close(); } catch (Exception ig){ }
                out=null;
            }
            if (file!=null)
            {
                loadedResources.put(name,file);
            }
        }
    }

    public java.io.InputStream getResourceAsStream (String name)
    {
        String denormalized = name.replace('/','.').substring(0,name.length()-6);
        java.io.File file = (java.io.File)loadedResources.get(denormalized);
        if (logExtra && log.isDebugEnabled())
        {
            log.debug("getResourceAsStream =>"+denormalized+" = "+file);
        }
        if (file!=null && file.exists())
        {
            try
            {
                return new java.io.BufferedInputStream(new java.io.FileInputStream(file));
            }
            catch (Exception ex)
            {

            }
        }
        return super.getResourceAsStream(name);
    }

    public synchronized void addClass (ClassBytes classBytes)
        throws java.io.IOException
    {
        Class cl=null;
        String name = classBytes.getClassName();
        if (loadedClasses.containsKey(name)==false)
        {
            if (logExtra && log.isDebugEnabled())
            {
                log.debug("  add class: "+name);
            }
            byte buf[] = classBytes.getClassBytes();
            try
            {
                cl = defineClass(name,buf,0,buf.length);
            }
            catch (java.lang.NoClassDefFoundError ex)
            {
                if (ex.getMessage().indexOf("(wrong name:")>-1)
                {
                    // array
                    String n = ClassUtil.getArrayClassPart(name);
                    if (findLoadedClass(n)==null)
                    {
                        if (logExtra && log.isDebugEnabled())
                        {
                            log.debug("attempting to load as an array instead: "+n+", from: "+name);
                        }
                        cl=defineClass(n,buf,0,buf.length);
                        resolveClass(cl);
                        addClassResource(n,buf);
                        loadedClasses.put(n,new java.lang.ref.SoftReference(cl));
                    }
                    return;
                }
                else
                {
                    ex.printStackTrace();
                }
            }
            if (logExtra && log.isDebugEnabled())
            {
                log.debug("  adding class ..."+name);
            }
            addClassResource(name,buf);
            loadedClasses.put(name,new java.lang.ref.SoftReference(cl));
        }
    }

    /**
     * Finds the specified class. This method should be overridden
     * by class loader implementations that follow the new delegation model
     * for loading classes, and will be called by the <code>loadClass</code>
     * method after checking the parent class loader for the requested class.
     * The default implementation throws <code>ClassNotFoundException</code>.
     *
     * @param  name the name of the class
     * @return the resulting <code>Class</code> object
     * @exception ClassNotFoundException if the class could not be found
     * @since  1.2
     */
    protected Class findClass (String name) throws ClassNotFoundException
    {

//    protected synchronized Class loadClass (String name, boolean resolve)
//            throws ClassNotFoundException
//    {
        boolean array = ClassUtil.isArrayClass(name);
        String cn = (array) ? ClassUtil.getArrayClassPart(name) : name;
        if (logExtra && log.isDebugEnabled())
        {
            log.debug("++ loadClass: "+name+", array?"+array+", normalized: ["+cn+"]");
        }
        Class cl = null;

//        try
//        {
//            cl=findLoadedClass(name);
//            if (cl!=null)
//            {
//                return cl;
//            }
//        }
//        catch (Exception ex){}

        SoftReference sr=(SoftReference)loadedClasses.get(cn);
        if (logExtra && log.isDebugEnabled())
        {
            log.debug(cn+" get returned soft ref="+sr+", loadedClasses="+loadedClasses);
        }
        if (sr!=null)
        {
            cl = (Class)sr.get();
            if (cl==null)
            {
                loadedClasses.remove(cn);
                sr.clear();
                sr=null;
            }
            else
            {
                if (logExtra && log.isDebugEnabled())
                {
                    log.debug("found added class: "+cl);
                }
                if (array)
                {
                    if (logExtra && log.isDebugEnabled())
                    {
                        log.debug(cl+" is an array class: "+name);
                    }
                    cl = findLoadedClass(name);
                    loadedClasses.put(name,new java.lang.ref.SoftReference(cl));
                }
                return cl;
            }
        }
//        try
//        {
//            cl = getSystemClassLoader().loadClass(name);
//            return cl;
//        }
//        catch (Exception ex) {}
//        cl = super.loadClass(name,resolve);
//        return cl;
        throw new ClassNotFoundException(name);
    }
}
