/**
 * Redistribution and use of this software and associated documentation
 * ("Software"), with or without modification, are permitted provided
 * that the following conditions are met:
 *
 * 1. Redistributions of source code must retain copyright
 *    statements and notices.  Redistributions must also contain a
 *    copy of this document.
 *
 * 2. Redistributions in binary form must reproduce the
 *    above copyright notice, this list of conditions and the
 *    following disclaimer in the documentation and/or other
 *    materials provided with the distribution.
 *
 * 3. The name "Exolab" must not be used to endorse or promote
 *    products derived from this Software without prior written
 *    permission of Exoffice Technologies.  For written permission,
 *    please contact info@exolab.org.
 *
 * 4. Products derived from this Software may not be called "Exolab"
 *    nor may "Exolab" appear in their names without prior written
 *    permission of Exoffice Technologies. Exolab is a registered
 *    trademark of Exoffice Technologies.
 *
 * 5. Due credit should be given to the Exolab Project
 *    (http://www.exolab.org/).
 *
 * THIS SOFTWARE IS PROVIDED BY EXOFFICE TECHNOLOGIES AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
 * EXOFFICE TECHNOLOGIES OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Copyright 1999 (C) Exoffice Technologies Inc. All Rights Reserved.
 *
 * $Id: ProxyClassLoader.java,v 1.2 2002/02/25 20:31:01 dblevins Exp $
 */
package org.openejb.util.proxy;

import java.net.*;
import java.io.*;

/**
 * ClassLoader for dynamically generating byte-code and loading classes.
 *
 * @author David Blevins
 */
public class ProxyClassLoader extends URLClassLoader{

    private ProxyByteCodeStreamHandler streamHandler;


    public ProxyClassLoader(){
        super(new URL[]{}, ProxyClassLoader.class.getClassLoader());

        streamHandler = new ProxyByteCodeStreamHandler();
    }

    /**
     * Converts an array of bytes into an instance of class <code>Class</code>.
     * Before the Class can be used it must be resolved.
     * <p>
     * This method assigns a default <code>ProtectionDomain</code> to
     * the newly defined class. The <code>ProtectionDomain</code>
     * contains the set of permissions granted when
     * a call to <code>Policy.getPolicy().getPermissions()</code> is made with
     * a Codesource of <code>null,null</code>. The default domain is
     * created on the first invocation of <code>defineClass</code>, and
     * re-used on subsequent calls.
     * <p>
     * To assign a specific <code>ProtectionDomain</code> to the class,
     * use the <code>defineClass</code> method that takes a
     * <code>ProtectionDomain</code> as one of its arguments.
     *
     * @param	   name the expected name of the class, or <code>null</code>
     *                  if not known, using '.' and not '/' as the separator
     *                  and without a trailing ".class" suffix.
     * @param      b    the bytes that make up the class data. The bytes in
     *             positions <code>off</code> through <code>off+len-1</code>
     *             should have the format of a valid class file as defined
     *             by the
     *             <a href="http://java.sun.com/docs/books/vmspec/">Java
     *             Virtual Machine Specification</a>.
     * @return     the <code>Class</code> object that was created from the
     *             specified class data
     * @exception  ClassFormatError if the data did not contain a valid class
     * @exception  SecurityException if an attempt is made to add this class
     *             to a package that contains classes that were signed by
     *             a different set of certificates then this class, which
     *             is unsigned.
     *
     * @see        ClassLoader#loadClass(java.lang.String, boolean)
     * @see        ClassLoader#resolveClass(java.lang.Class)
     * @see        java.security.ProtectionDomain
     * @see        java.security.Policy
     * @see        java.security.CodeSource
     * @see        java.security.SecureClassLoader
     */
    public Class defineClass( String name, byte[] byteCode ) throws ClassFormatError{
        Class clazz = null;
        try{
            clazz = defineClass(name, byteCode, 0, byteCode.length);
            resolveClass(clazz);
            cacheByteCode(clazz, byteCode);
        } catch (ClassFormatError cfe){
            System.out.println("[Proxy Class Loader] ERROR: "+name);
            throw cfe;
        }
        return clazz;
    }

    /**
     * Finds the resource with the given name. Class loader
     * implementations should override this method to specify where to
     * find resources.
     *
     * Find the byte-code for the specified class.
     *
     * The proxy classes are generated at runtime and are created from a byte
     * array and not a .class file.  The byte array containing the byte-code
     * is discarded by the VM after a Class is created using
     * <CODE>ClassLoader.defineClass(...)</CODE>. Calling
     * Class.getResourceAsStream(proxyClassName) will return null because
     * there is no .class definition in the file system.  In this situation,
     * it is the responsibility of the class loader (ProxyClassLoader) that
     * loaded the class definition to implement ClassLoader.findResource(...)
     * and return a URL that can be used to retreive the proxy class byte-code.
     *
     * @param  name the resource name
     * @return a URL for reading the resource, or <code>null</code>
     *         if the resource could not be found
     * @since  JDK1.2
     * @see java.lang.ClassLoader.defineClass
     * @see java.lang.ClassLoader.getSystemResourceAsStream()
     * @see org.openejb.util.proxy.ProxyClassLoader
     * @see org.openejb.util.proxy.ProxyClassLoader.findResource
     */
    public URL findResource(String name) {
        URL resource = super.findResource(name);
        if (resource != null) return resource;

        if (!name.endsWith(".class")) return null;

        try{
            name = name.substring(0,name.lastIndexOf(".class"));
            byte[] byteCode = getCachedByteCode(name);
            if (byteCode == null || byteCode.length < 1) return null;
            resource = new URL(null,null,0, name, streamHandler);
        } catch (MalformedURLException mue){
            // This exception will never be thrown.
        }
        return resource;
    }





    public void addURL(File directory) throws java.net.MalformedURLException{
        super.addURL(directory.toURL());
    }

    public class ProxyByteCodeStreamHandler extends URLStreamHandler {
        /**
         * Opens a connection to the object referenced by the
         * <code>URL</code> argument.
         * This method should be overridden by a subclass.
         *
         * <p>If for the handler's protocol (such as HTTP or JAR), there
         * exists a public, specialized URLConnection subclass belonging
         * to one of the following packages or one of their subpackages:
         * java.lang, java.io, java.util, java.net, the connection
         * returned will be of that subclass. For example, for HTTP an
         * HttpURLConnection will be returned, and for JAR a
         * JarURLConnection will be returned.
         *
         * @param      u   the URL that this connects to.
         * @return     a <code>URLConnection</code> object for the <code>URL</code>.
         * @exception  IOException  if an I/O error occurs while opening the
         *               connection.
         */
        protected URLConnection openConnection(URL u) throws IOException {
            byte[] byteCode = getCachedByteCode(u.getFile());
            return new ProxyByteCodeURLConnection(byteCode);
        }

    }

    public class ProxyByteCodeURLConnection extends URLConnection {


        byte[] byteCode;

        public ProxyByteCodeURLConnection(byte[] byteCode){
            super(null);
            this.byteCode = byteCode;
        }

        /**
         * Required to be implemented by subclasses of URLConnection
         * @exception  IOException  if an I/O error occurs while opening the
         *               connection.
         * @see java.net.URLConnection#connected */
        public void connect() throws IOException {
        }

        /**
         * Returns an input stream that reads from this open connection.
         *
         * @return     an input stream that reads from this open connection.
         * @exception  IOException              if an I/O error occurs while
         *               creating the input stream.
         * @exception  UnknownServiceException  if the protocol does not support
         *               input.
         */
        public InputStream getInputStream() throws IOException {
            return new ByteArrayInputStream(byteCode);
        }
    }


    /**
     * This method should not need to be overridden by subclasses.
     * It is done here because of a bug in the VM.  This method was
     * overriden using the algorithm of it's superclass with one
     * workaround that reliably calls the findClass method.
     * No other changes should be made in this method.
     *
     * @param name
     * @param resolve
     * @return
     * @exception ClassNotFoundException
     */
    protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
    // First, check if the class has already been loaded
    Class c = findLoadedClass(name);
    if (c == null) {
        try {
                ClassLoader parent = getParent();
        if (parent != null) {
                    c = parent.loadClass(name);
        } else {
            c = findSystemClass(name);
        }

        } catch (ClassNotFoundException e) {
            // If still not found, then call findClass in order
            // to find the class.
                c = findClass(name);
            } catch (NoClassDefFoundError bug) {
                /* [DMB] Problem:
                 *       The VM does not consistently throw ClassNotFoundException
                 *       when classes are not found.  The sublcass' findClass method
                 *       is not called in these situations.  This behavior is not in
                 *       accordance with the ClassLoader delegation model.
                 *       Workaround:
                 *       Catch the NoClassDefFoundError that is actually thrown and
                 *       call the findClass method according to the delegation model.
                 */
                c = findClass(name);
        }
    }
    if (resolve) {
            resolveClass(c);
    }
    return c;
    }

    //=========================================================
    // Protected support for getting/setting cached byte-code
    //

    /**
     * Find the byte-code for the specified class.
     *
     * The proxy classes are generated at runtime and are created from a byte
     * array and not a .class file.  The byte array containing the byte-code
     * is discarded by the VM after a Class is created using
     * <CODE>ClassLoader.defineClass(...)</CODE>. Calling
     * Class.getResourceAsStream(proxyClassName) will return null because
     * there is no .class definition in the file system.  In this situation,
     * it is the responsibility of the class loader (ProxyClassLoader) that
     * loaded the class definition to implement ClassLoader.findResource(...)
     * and return a URL that can be used to retreive the proxy class byte-code.
     *
     * @param className
     * @return
     * @see java.lang.ClassLoader.defineClass
     * @see java.lang.ClassLoader.getSystemResourceAsStream()
     * @see org.openejb.util.proxy.ProxyClassLoader
     * @see org.openejb.util.proxy.ProxyClassLoader.findResource
     */
    protected static byte[] getCachedByteCode(String className) {

        try {
            return getCachedByteCode( DynamicProxyFactory.loader.loadClass(className) );
        } catch ( ClassNotFoundException cnfe ) {
            /* If this is a problem, it will be discovered and
             * hanled appropriately by callers of ProxyFactory.newProxyInstance()
             * or  ProxyManager.newProxyInstance()
             */
        }
        return null;
    }

    /*
     * Find the byte-code for the specified class.
     */
    protected static byte[] getCachedByteCode(Class clazz) {
        /* Synchronize on the hashtable so no two threads will do
         * this at the same time.
         */
        synchronized (byteCodeFor) {
            /* Find the matching byte-code if it already known */
            ClassByteCode classByteCode = findByteCodeFor(clazz);
            if ( classByteCode != null ) {
                return classByteCode.getByteCode();
            }

        }
        return null;
    }

    protected static void cacheByteCode(Class clazz, byte[] byteCode) {
        insertClassByteCodeFor(clazz, byteCode);
    }

    //
    // Protected support for getting/setting cached byte-code
    //=========================================================


    //=========================================
    // Private byte-code cache implementation
    //

    /*
     * findByteCodeFor a Class.  This looks in the cache for a
     * mapping from Class -> byte-code mappings.  The hashCode
     * of the Class is used for the lookup since the Class is the key.
     * The entries are extended from java.lang.ref.SoftReference so the
     * gc will be able to free them if needed.
     */
    private static ClassByteCode findByteCodeFor(Class clazz) {

        int hash = clazz.hashCode();
        int index = (hash & 0x7FFFFFFF) % byteCodeFor.length;
        ClassByteCodeEntry e;
        ClassByteCodeEntry prev;

        /* Free any initial entries whose refs have been cleared */
        while ( (e = byteCodeFor[index]) != null && e.get() == null ) {
            byteCodeFor[index] = e.next;
        }

        /* Traverse the chain looking for a byte-code with forClass == clazz.
         * unlink entries that are unresolved.
         */
        prev = e;
        while ( e != null ) {
            ClassByteCode classByteCode = (ClassByteCode)(e.get());
            if ( classByteCode == null ) {
                // This entry has been cleared,  unlink it
                prev.next = e.next;
            } else {
                if ( classByteCode.forClass() == clazz )
                    return classByteCode;
                prev = e;
            }
            e = e.next;
        }
        return null;
    }


    /*
     * Creates and inserts a new ClassByteCodeEntry for the
     * Class -> byte-code mapping.
     * The hashCode of the Class is used to index the entry.
     * The entries are extended from java.lang.ref.SoftReference so the
     * gc will be able to free them if needed.
     */
    private static void insertClassByteCodeFor(Class clazz, byte[] byteCode) {

        // Make sure not already present
        if ( findByteCodeFor(clazz) != null ) {
            return;
        }

        int hash = clazz.hashCode();
        int index = (hash & 0x7FFFFFFF) % byteCodeFor.length;
        ClassByteCodeEntry e = new ClassByteCodeEntry(new ClassByteCode(clazz, byteCode));
        e.next = byteCodeFor[index];
        byteCodeFor[index] = e;
    }

    /*
     * Entries held in the Cache of known ClassByteCode objects.
     * Entries are chained together with the same hash value (modulo array size).
     * The entries are extended from java.lang.ref.SoftReference so the
     * gc will be able to free them if needed.
     */
    private static class ClassByteCodeEntry extends java.lang.ref.SoftReference {
        ClassByteCodeEntry next;

        ClassByteCodeEntry(ClassByteCode bc) {
            super(bc);
        }
    }

    /**
     * The chache that holds byte-code for previously generated class definitions.
     * Entries are chained together with the same hash value (modulo array size).
     */
    private static ClassByteCodeEntry[] byteCodeFor = new ClassByteCodeEntry[61];


    /**
     * Wrapper for class to byte-code mappings.
     */
    static class ClassByteCode {
        Class clazz;
        byte[] byteCode;

        public ClassByteCode(Class clazz, byte[] byteCode) {
            this.clazz = clazz;
            this.byteCode = byteCode;
        }

        public Class forClass() {
            return clazz;
        }

        public byte[] getByteCode() {
            return byteCode;
        }
    }

    //
    // Private byte-code cache implementation
    //=========================================
}

