/*
 * @(#)JDKClassLoader.java	1.18 01/12/03
 *
 * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/*
 * Licensed Materials - Property of IBM
 * RMI-IIOP v1.0
 * Copyright IBM Corp. 1998 1999  All Rights Reserved
 *
 * US Government Users Restricted Rights - Use, duplication or
 * disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
 */

package com.sun.corba.se.internal.util;

import java.io.IOException;
import java.io.*;
import java.util.*;
import java.lang.ref.WeakReference;
import java.lang.reflect.*;
import java.security.AccessController;
import java.security.PrivilegedAction;

/**
 *  Utility method for crawling call stack to load class
 */
class JDKClassLoader {

    private static final JDKClassLoaderCache classCache
        = new JDKClassLoaderCache();
    // latestUserDefinedLoader() is a private static method
    // in java.io.ObjectInputStream in JDK 1.3 and 1.4.
    // We use reflection in a doPrivileged block to get a
    // Method reference.
    //
    // We should be very careful about what happens when this
    // method no longer exists!  Right now, Errors will be
    // thrown.
    private static Method latestUserDefinedLoaderMethod = null;

    private static final Class[] NO_ARGS = new Class[] {};

    // Uses reflection on java.io.ObjectInputStream to call
    // its latestUserDefinedLoader native code.
    //
    // The reflection is only performed once -- we store a
    // Method reference
    private static ClassLoader getLatestUserDefinedLoader()
    {
        if (latestUserDefinedLoaderMethod == null) {
            AccessController.doPrivileged(new PrivilegedAction() {
                public Object run() {
                    try {
                        // Try to dig up the method we want
                        Class io = java.io.ObjectInputStream.class;
                        latestUserDefinedLoaderMethod
                            = io.getDeclaredMethod("latestUserDefinedLoader", NO_ARGS);
                        latestUserDefinedLoaderMethod.setAccessible(true);
                    } catch (NoSuchMethodException nsme) {
                        // Throw Errors right now.  This is a serious problem
                        throw new Error("java.io.ObjectInputStream"
                                        + " latestUserDefinedLoader "
                                        + nsme.getMessage());
                    }
                    return null;
                }
            });
        }

        try {
            // Invoke the ObjectInputStream.latestUserDefinedLoader method
            return (ClassLoader)latestUserDefinedLoaderMethod.invoke(null, NO_ARGS);
        } catch (InvocationTargetException ite) {
            throw new Error("java.io.ObjectInputStream"
                            + " latestUserDefinedLoader "
                            + ite.getMessage());
        } catch (IllegalAccessException iae) {
            throw new Error("java.io.ObjectInputStream"
                            + " latestUserDefinedLoader "
                            + iae.getMessage());
        }
    }

    static Class loadClass(Class aClass, String className)
	throws ClassNotFoundException {

        // Maintain the same error semantics as Class.forName()
        if (className == null) {
            throw new NullPointerException();
        }
        if (className.length() == 0) {
            throw new ClassNotFoundException();
        }

        // It would be nice to bypass JDKClassLoader's attempts completely
        // if it's known that the latest user defined ClassLoader will
        // fail.
        //
        // Otherwise, we end up calling Class.forName here as well as in
        // the next step in JDKBridge.  That can take a long time depending
        // on the length of the classpath.

        // Note: Looking at the only place in JDKBridge where this code
        // is invoked, it is clear that aClass will always be null.
        ClassLoader loader;
        if (aClass != null) {
            loader = aClass.getClassLoader();
        } else {
            loader = getLatestUserDefinedLoader();
        }
        // See createKey for a description of what's involved
        Object key = classCache.createKey(className, loader);

        if (classCache.knownToFail(key)) {
            throw new ClassNotFoundException(className);
        } else {
            try {
                // Loading this class with the call stack
                // loader isn't known to fail, so try
                // to load it.
                return Class.forName(className, false, loader);
            } catch(ClassNotFoundException cnfe) {
                // Record that we failed to find the class
                // with this particular loader.  This way, we won't
                // waste time looking with this loader, again.
                classCache.recordFailure(key);
                throw cnfe;
            }
        }
    }
	
    /**
     * Private cache implementation specific to JDKClassLoader.
     */
    private static class JDKClassLoaderCache
    {
        // JDKClassLoader couldn't find the class with the located
        // ClassLoader.  Note this in our cache so JDKClassLoader
        // can abort early next time.
        public final void recordFailure(Object key) {
            cache.put(key, JDKClassLoaderCache.KNOWN_TO_FAIL);
        }

        // Factory for a key (CacheKey is an implementation detail
        // of JDKClassLoaderCache).
        //
        // A key currently consists of the class name as well as
        // the latest user defined class loader, so it's fairly
        // expensive to create.
        public final Object createKey(String className, ClassLoader latestLoader) {          
            return new CacheKey(className, latestLoader);
        }

        // Determine whether or not this combination of class name
        // and ClassLoader is known to fail.
        public final boolean knownToFail(Object key) {
            return cache.get(key) == JDKClassLoaderCache.KNOWN_TO_FAIL;
        }

        // Synchronized WeakHashMap
        private final Map cache
            = Collections.synchronizedMap(new WeakHashMap());

        // Cache result used to mark the caches when there is
        // no way JDKClassLoader could succeed with the given
        // key
        private static final Object KNOWN_TO_FAIL = new Object();

        // Key consisting of the class name and the latest
        // user defined class loader
        private static class CacheKey
        {
            String className;
            ClassLoader loader;
        
            public CacheKey(String className, ClassLoader loader) {
                this.className = className;
                this.loader = loader;
            }

            // Try to incorporate both class name and loader
            // into the hashcode
            public int hashCode() {
                if (loader == null)
                    return className.hashCode();
                else
                    return className.hashCode() ^ loader.hashCode();
            }

            public boolean equals(Object obj) {
                try {

                    // WeakHashMap may compare null keys
                    if (obj == null)
                        return false;

                    CacheKey other = (CacheKey)obj;

                    // I've made a decision to actually compare the
                    // loader references.  I don't want a case when
                    // two loader instances override their equals
                    // methods and only compare code base.
                    //
                    // This way, at worst, our performance will
                    // be slower, but we know we'll do the correct
                    // loading.
                    return (className.equals(other.className) &&
                            loader == other.loader);
                    
                } catch (ClassCastException cce) {
                    return false;
                }
            }
        }
    }
}
