/*
 * 
 * Copyright 2004 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * The contents of this file are subject to the Sun Community Source
 * License v3.0/Jini Technology Specific Attachment v 1.0 (the
 * "License"). You may not use this file except in compliance with the
 * License. You may obtain a copy of the License at
 * http://wwws.sun.com/software/jini/licensing/SCSL3_JiniTSA1.html.
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Reference Code is Jini Technology Core Platform code, v2.0. The
 * Developer of the Reference Code is Sun Microsystems, Inc.
 * 
 * Contributor(s): Sun Microsystems, Inc.
 * 
 * The contents of this file comply with the Jini Technology Core
 * Platform Compatibility Kit, v2.0A.
 * 
 * Tester(s): Sun Microsystems, Inc.
 * 
 * Test Platform(s):
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Solaris(TM)SPARC/x86
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Linux (Intel x86)
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Windows (Intel Platform)
 * 
 */

package net.jini.loader.pref;

import com.sun.jini.action.GetPropertyAction;
import com.sun.jini.logging.Levels;
import java.io.IOException;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.rmi.server.RMIClassLoader;
import java.rmi.server.RMIClassLoaderSpi;
import java.security.AccessController;
import java.security.AccessControlContext;
import java.security.CodeSource;
import java.security.Permission;
import java.security.Permissions;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.WeakHashMap;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.logging.Level;
import net.jini.loader.ClassAnnotation;
import net.jini.loader.DownloadPermission;

/**
 * An <code>RMIClassLoader</code> provider that supports preferred
 * classes.
 *
 * <p>See the {@link RMIClassLoader} specification for information
 * about how to install and configure the <code>RMIClassLoader</code>
 * service provider.
 *
 * <p><code>PreferredClassProvider</code> uses instances of {@link
 * PreferredClassLoader} to load classes from codebase URL paths
 * supplied to <code>RMIClassLoader.loadClass</code> methods.
 *
 * <p><code>PreferredClassProvider</code> does not enforce {@link
 * DownloadPermission} by default, but a subclass can configure it to
 * do so by passing <code>true</code> as the argument to the
 * <code>protected</code> constructor.
 *
 * <p>By overriding the {@link #getClassAnnotation getClassAnnotation}
 * method, a subclass can also configure the class annotations to be
 * used for classes defined by the system class loader, its ancestor
 * class loaders, and any class loader that is not an instance of
 * {@link ClassAnnotation} or {@link URLClassLoader}.
 *
 * @author Sun Microsystems, Inc.
 * @since 2.0
 *
 * @com.sun.jini.impl
 *
 * <p>This implementation uses the {@link Logger} named
 * <code>net.jini.loader.pref.PreferredClassProvider</code> to log
 * information at the following levels:
 *
 * <table summary="Describes what is logged by PreferredClassProvider
 * at various logging levels" border=1 cellpadding=5>
 *
 * <tr> <th> Level <th> Description
 *
 * <tr> <td> {@link Levels#FAILED FAILED} <td> class loading failures
 *
 * <tr> <td> {@link Level#FINE FINE} <td> invocations of {@link
 * #loadClass loadClass} and {@link #loadProxyClass loadProxyClass}
 *
 * <tr> <td> {@link Level#FINE FINEST} <td> detailed activity of
 * <code>loadClass</code> and <code>loadProxyClass</code>
 * implementations
 *
 * </table>
 **/
public class PreferredClassProvider extends RMIClassLoaderSpi {

    /** download from codebases with no dl perm allowed? */
    private boolean requireDlPerm = false;

    /** provider logger */
    private static final Logger logger =
	Logger.getLogger("net.jini.loader.pref.PreferredClassProvider");

    /**
     * value of "java.rmi.server.codebase" property, as cached at class
     * initialization time.  It may contain malformed URLs.
     */
    private static String codebaseProperty = null;
    static {
	String prop = (String) AccessController.doPrivileged(
            new GetPropertyAction("java.rmi.server.codebase"));
	if (prop != null && prop.trim().length() > 0) {
	    codebaseProperty = prop;
	}
    }

    /** table of "local" class loaders */
    private static final Map localLoaders =
	Collections.synchronizedMap(new WeakHashMap());
    static {
	AccessController.doPrivileged(new PrivilegedAction() {
	    public Object run() {
		for (ClassLoader loader = ClassLoader.getSystemClassLoader();
		     loader != null;
		     loader = loader.getParent())
		{
		    localLoaders.put(loader, null);
		}
		return null;
	    }
	});
    }

    /**
     * table mapping codebase URL path and context class loader pairs
     * to class loader instances.  Entries hold class loaders with weak
     * references, so this table does not prevent loaders from being
     * garbage collected.
     */
    private final HashMap loaderTable = new HashMap();

    /** reference queue for cleared class loader entries */
    private final ReferenceQueue refQueue = new ReferenceQueue();

    /**
     * Creates a new <code>PreferredClassProvider</code>.
     *
     * <p>This constructor is used by the {@link RMIClassLoader}
     * service provider location mechanism when
     * <code>PreferredClassProvider</code> is configured as the
     * <code>RMIClassLoader</code> provider class.
     *
     * <p>{@link DownloadPermission} is not enforced by the created
     * provider.
     **/
    public PreferredClassProvider() {}

    /**
     * Creates a new <code>PreferredClassProvider</code>.
     *
     * <p>This constructor is used by subclasses to control whether
     * or not {@link DownloadPermission} is enforced.
     *
     * @param requireDlPerm if <code>true</code>, the class loaders
     * created by the provider will only define classes with a {@link
     * CodeSource} that is granted {@link DownloadPermission}
     **/
    protected PreferredClassProvider(boolean requireDlPerm) {
	this.requireDlPerm = requireDlPerm;
    }

    /**
     * Map to hold permissions needed to check the URLs of
     * URLClassLoader objects.
     */
    private final Map classLoaderPerms = new WeakHashMap();
    
    /*
     * Check permissions to load from the specified loader.  The
     * supplied URLs must be the loader's annotation URLs unless the
     * loader is identical to the "parent" argument-- this method
     * checks for permission to access those URLs.
     *
     * If the specified loader is identical to the "parent" argument,
     * no permissions are checked, because the caller could have used
     * an empty codebase to achieve the same effect anyway.
     */
    private void checkLoader(ClassLoader loader, ClassLoader parent,
			     URL[] urls)
    {
	SecurityManager sm = System.getSecurityManager();

	if ((sm != null) && (loader != null) && (loader != parent)) {
	    try {
		assert(Arrays.equals(urls, getLoaderAnnotationURLs(loader)));
	    } catch (MalformedURLException e) {
		throw new AssertionError(e);
	    }

	    if (loader.getClass() == PreferredClassLoader.class) {
		((PreferredClassLoader) loader).checkPermissions();
		
	    } else {
		Permissions perms = null;
		
		synchronized (classLoaderPerms) {
		    perms = (Permissions) classLoaderPerms.get(loader);
		    if (perms == null) {
			perms = new Permissions();
			PreferredClassLoader.addPermissionsForURLs(
			    urls, perms, false);
			classLoaderPerms.put(loader, perms);
		    }
		}

		Enumeration enum = perms.elements();
		while (enum.hasMoreElements()) {
		    sm.checkPermission((Permission) enum.nextElement());
		}
	    }
	}
    }

    /**
     * Provides the implementation for {@link
     * RMIClassLoaderSpi#loadClass(String,String,ClassLoader)}.
     *
     * Loads a class from a codebase URL path, only using the supplied
     * loader if the given name is not preferred by the class loader
     * for the codebase path.
     *
     * If the name parameter is marked as preferred in the preferred
     * class loader which is requested to load the named class, that
     * class loader will not delegate to its parent to load the
     * requested class.  Instead the class loader will only search its
     * own resources to find the class.
     *
     * If a definition for the class cannot be found, a
     * <code>ClassNotFoundException</code> is thrown, otherwise the
     * class object for the requested class is returned.
     * 
     * <p>If <code>codebase</code> is <code>null</code>, then the
     * value of the system property
     * <code>java.rmi.server.codebase</code> (or possibly an earlier
     * cached value) is used as the codebase URL path.
     *
     * @param codebase the list of URLs (separated by spaces) from
     * which the class should be loaded , or <code>null</code>
     *
     * @param name the name of the class to load
     *
     * @param defaultLoader additional contextual class loader
     * to use, or <code>null</code>
     *
     * @return	the <code>Class</code> object representing the loaded class
     *
     * @throws MalformedURLException if <code>codebase</code> is
     * non-<code>null</code> and contains an invalid URL, or if
     * <code>codebase</code> is <code>null</code> and the value of
     * <code>java.rmi.server.codebase</code> contains an invalid URL
     *
     * @throws	ClassNotFoundException if a definition for the class
     * could not be found at the specified location
     **/
    public Class loadClass(String codebase, String name,
			   ClassLoader defaultLoader)
	throws MalformedURLException, ClassNotFoundException
    {
	if (logger.isLoggable(Level.FINE)) {
	    logger.log(Level.FINE,
		       "name=\"{0}\", codebase={1}, defaultLoader={2}",
		       new Object[] {
			   name,
			   codebase != null ? "\"" + codebase + "\"" : null,
			   defaultLoader
		       });
	}

    	URL[] codebaseURLs = pathToURLs(codebase);	// may be null

	/*
	 * If the defaultLoader's annotation URLs match the codebase
	 * (boomerang class), or if the requested codebase is null
	 * (presumed platform class), check the defaultLoader first.
	 */
	if (defaultLoader != null) {
	    try {
		if (codebaseURLs == null ||
		    Arrays.equals(codebaseURLs,
				  getLoaderAnnotationURLs(defaultLoader)))
		{
		    try {
			Class c = Class.forName(name, false, defaultLoader);
			if (logger.isLoggable(Level.FINEST)) {
			    logDefaultLoader(name, defaultLoader, c);
			}
			return c;
		    } catch (ClassNotFoundException e) {
			// remember not to try this loader again
			defaultLoader = null;
		    }
		}
	    } catch (MalformedURLException e) {
		// default loader's annotation must not match codebase URLs
	    }
	}

	SecurityException secEx = null;
    	boolean childPrefers = false;
    	ClassLoader parent = getRMIContextClassLoader();
	if (logger.isLoggable(Level.FINEST)) {
	    logContextLoader(parent, "loadClass");
	}

	/*
	 * Get or create the preferred class loader for this codebase
	 * URL path and parent class loader pair (or get the
	 * corresponding ancestor loader if a boomerang is detected).
	 */
	ClassLoader childLoader = lookupLoader(codebaseURLs, parent);

	/*
	 * Get the preferred setting for the class (false if the
	 * matching loader is not even a PreferredClassLoader).  At
	 * this point (with the defaultLoader's annotation URLs not
	 * matching the requested URLs above), we only try to load
	 * from the defaultLoader (if any) if the preferred setting is
	 * false.  We must check permission to access the loader's
	 * URLs before getting the preferred setting, to avoid
	 * performing network communication that the caller doesn't
	 * have permission to do.  But if the matching loader is not a
	 * PreferredClassLoader, then the preferred setting must be
	 * false, so we can put off the permission check until after
	 * trying the defaultLoader.
	 */
	if (childLoader instanceof PreferredClassLoader) {
	    // isPreferredResource doesn't support array class names
	    String strippedName = stripArrayClassName(name);
	    try {
		checkLoader(childLoader, parent, codebaseURLs);
		childPrefers = ((PreferredClassLoader) childLoader).
		    isPreferredResource(strippedName, true);
	    } catch (IOException e) {
		throw new ClassNotFoundException("could not obtain " +
		    "preferred value for: " + strippedName, e);
	    } catch (SecurityException e) {
		secEx = e;
	    }
	}

	try {
	    if ((defaultLoader != null) && !childPrefers) {
		Class c1 = Class.forName(name, false, defaultLoader);
		if (logger.isLoggable(Level.FINEST)) {
		    logDefaultLoader(name, defaultLoader, c1);
		}
		return c1;
	    }
	} catch (ClassNotFoundException e) {
	}

	if (!(childLoader instanceof PreferredClassLoader)) {
	    try {
		checkLoader(childLoader, parent, codebaseURLs);
	    } catch (SecurityException e) {
		secEx = e;
	    }
	}

	Class c2 = disabledUseParent(name, parent, secEx);

        return (c2 == null ? attemptChildLoad(name, childLoader) : c2);
    }

    /**
     * If the specified class name designates an array class with a
     * reference type as its element type, returns the name of that
     * reference type; otherwise, returns the specified name.
     **/
    private static String stripArrayClassName(String name) {
	int len = name.length();
	int n = 0;
	while (n < len && name.charAt(n) == '[') {
	    n++;
	}
	if (n == 0 || n >= (len - 2) ||
	    name.charAt(n) != 'L' || name.charAt(len - 1) != ';')
	{
	    return name;
	}
	return name.substring(n + 1, len - 1);  // strip 'L' and trailing ';'
    }

    /**
     * If no security manager is installed or if the caller does not
     * have permission to load from the loader urls, try to load from
     * the parent loader.
     */
    Class disabledUseParent(String name, ClassLoader parent,
			    SecurityException secEx)
	throws ClassNotFoundException
    {
	/*
	 * If no security manager is set, disable access to RMI class
	 * loaders and simply delegate request to the parent loader
	 * (see bugid 4140511).
	 *
	 * or if a security exception was thrown above, check to see
	 * if the named class could have been resolved without the
	 * security-offending codebase anyway; if so, return
	 * successfully (see bugids 4191926 & 4349670).
	 */
	SecurityManager sm = System.getSecurityManager();
	if ((sm == null) || (secEx != null)) {
	    try {
		Class c = Class.forName(name, false, parent);
		if (logger.isLoggable(Level.FINEST)) {
		    logger.logp(Level.FINEST,
			this.getClass().getName(), "loadClass",
			"class \"{0}\" found via " +
			"thread context class loader " +
			"(access to codebase denied), " +
			"defined by {1}",
			new Object[] { name, getClassLoader(c) });
		}
		return c;
	    } catch (ClassNotFoundException e) {
		String message = e.getMessage() +
		    " (no security manager: RMI class loader disabled";
		Exception exception = e;
		if (secEx != null) {
		    if (logger.isLoggable(Levels.HANDLED)) {
			LogRecord lr = new LogRecord(Levels.HANDLED,
			    "class \"{0}\" not found via " +
			    "thread context class loader " +
			    "(access to codebase denied)");
			lr.setLoggerName(logger.getName());
			lr.setSourceClassName(this.getClass().getName());
			lr.setSourceMethodName("loadClass");
			lr.setParameters(new Object[] { name });
			lr.setThrown(exception);
			logger.log(lr);
		    }
		    message = "access to class loader denied";
		    /*	
		     * Presumably the security exception is the more important
		     * exception to report in this case.
		     */
		    exception = secEx;
		    if (logger.isLoggable(Levels.FAILED)) {
			LogRecord lr = new LogRecord(Levels.FAILED,
			    "class \"{0}\" not found via " +
			    "thread context class loader " +
			    "(access to codebase denied)");
			lr.setLoggerName(logger.getName());
			lr.setSourceClassName(this.getClass().getName());
			lr.setSourceMethodName("loadClass");
			lr.setParameters(new Object[] { name });
			lr.setThrown(exception);
			logger.log(lr);
		    }
		}
		throw new ClassNotFoundException(message, exception);
	    }
	}
	return null;
    }

    /**
     * Attempt to load the requested class from a child class loader
     */ 
    Class attemptChildLoad(String name, ClassLoader childLoader)
	throws ClassNotFoundException
    {
	/*
	 * Attempt to load the class from a child class loader.
	 */
	try {
	    Class c = Class.forName(name, false, childLoader);
	    if (logger.isLoggable(Level.FINEST)) {
		logger.logp(Level.FINEST,
		    this.getClass().getName(), "loadClass",
		    "class \"{0}\" found via codebase, defined by {1}",
		    new Object[] { name, getClassLoader(c) });
	    }
	    return c;
	} catch (ClassNotFoundException e) {
	    if (logger.isLoggable(Levels.FAILED)) {
		LogRecord lr = new LogRecord(Levels.FAILED,
		    "class \"{0}\" not found via codebase");
		lr.setLoggerName(logger.getName());
		lr.setSourceClassName(this.getClass().getName());
		lr.setSourceMethodName("loadClass");
		lr.setParameters(new Object[] { name });
		lr.setThrown(e);
		logger.log(lr);
	    }
	    throw e;
	}
    }
    
    /**
     * Log that a class was loaded from the default loader
     */
    private void logDefaultLoader(String name, ClassLoader defaultLoader,
				  Class c) {
	logger.logp(Level.FINEST, this.getClass().getName(), "loadClass",
		    "class \"{0}\" found via defaultLoader, defined by {1}",
		    new Object[] { name, getClassLoader(c) });
    }

    /**
     * Provides the implementation for {@link
     * RMIClassLoaderSpi#getClassAnnotation(Class)}.
     *
     * <p><code>PreferredClassProvider</code> implements this method
     * as follows:
     *
     * <p>If <code>cl</code> is an array class (of one or more
     * dimensions) with a primitive component type, then this method
     * returns <code>null</code>.
     *
     * <p>If the defining class loader of <code>cl</code> is the
     * system class loader or an ancestor of the system class loader,
     * then this method invokes {@link
     * #getClassAnnotation(ClassLoader) getClassAnnotation} with the
     * loader and returns the result.
     *
     * <p>If the defining class loader of <code>cl</code> is an
     * instance of {@link ClassAnnotation}, then this method invokes
     * {@link ClassAnnotation#getClassAnnotation getClassAnnotation}
     * on that loader and returns the result.
     *
     * <p>If the defining class loader of <code>cl</code> is an
     * instance of {@link URLClassLoader} and the current security
     * context has the permissions necessary to connect to all of the
     * URLs returned by an invocation of {@link URLClassLoader#getURLs
     * getURLs} on the loader, then this method returns a
     * space-separated list of those URLs.
     *
     * <p>Otherwise, this method invokes {@link
     * #getClassAnnotation(ClassLoader) getClassAnnotation} with the
     * defining class loader of <code>cl</code> and returns the
     * result.
     *
     * @param cl the class to obtain the annotation for
     *
     * @return the annotation string for the class, or
     * <code>null</code>
     **/
    public String getClassAnnotation(Class cl) {
	String name = cl.getName();

	/*
	 * Class objects for arrays of primitive types never need an
	 * annotation, because they never need to be (or can be) downloaded.
	 *
	 * REMIND: should we (not) be annotating classes that are in
	 * "java.*" packages?
	 */
	int nameLength = name.length();
	if (nameLength > 0 && name.charAt(0) == '[') {
	    // skip past all '[' characters (see bugid 4211906)
	    int i = 1;
	    while (nameLength > i && name.charAt(i) == '[') {
		i++;
	    }
	    if (nameLength > i && name.charAt(i) != 'L') {
		return null;
	    }
	}

	return getLoaderAnnotation(getClassLoader(cl));
    }

    /**
     * Returns the class annotation string to use for classes defined
     * by the specified class loader.
     *
     * <p>This method is invoked by {@link #getClassAnnotation(Class)
     * PreferredClassProvider.getClassAnnotation(Class)} when it is
     * passed a class defined by the system class loader, an ancestor
     * of the system class loader, any class loader that is not an
     * instance of {@link ClassAnnotation} or {@link URLClassLoader},
     * or a <code>URLClassLoader</code> for which the current security
     * context does not have permission to connect to all of its URLs.
     *
     * <p><code>PreferredClassProvider</code> implements this method
     * as follows:
     *
     * <p>This method returns the value of the system property
     * <code>java.rmi.server.codebase</code> (or possibly an earlier
     * cached value).
     *
     * @param loader the defining class loader of the class to obtain
     * the annotation for
     *
     * @return the class annotation string for classes defined by the
     * loader, or <code>null</code>
     **/
    protected String getClassAnnotation(ClassLoader loader) {
	return codebaseProperty;
    }

    /**
     * Returns the annotation string for the specified class loader
     * (possibly null).
     **/
    private String getLoaderAnnotation(ClassLoader loader) {
	
	if (isLocalLoader(loader)) {
	    return getClassAnnotation(loader);
	}

        /*
	 * Get the codebase URL path for the class loader, if it supports
	 * such a notion (i.e., if it is a URLClassLoader or subclass).
	 */
	String annotation = null;
	if (loader instanceof ClassAnnotation) {
	    /*
	     * If the class loader is one of our RMI class loaders, we have
	     * already computed the class annotation string, and no
	     * permissions are required to know the URLs.
	     */
	    annotation = ((ClassAnnotation) loader).getClassAnnotation();

	} else if (loader instanceof URLClassLoader) {
	    try {
		URL[] urls = ((URLClassLoader) loader).getURLs();
		if (urls != null) {
		    /*
		     * If the class loader is not one of our RMI class loaders,
		     * we must verify that the current access control context
		     * has permission to know all of these URLs.
		     */
		    SecurityManager sm = System.getSecurityManager();
		    if (sm != null) {
			for (int i = 0; i < urls.length; i++) {
			    Permission p =
				urls[i].openConnection().getPermission();
			    if (p != null) {
				sm.checkPermission(p);
			    }
			}
		    }
		    annotation = PreferredClassLoader.urlsToPath(urls);
		}
	    } catch (SecurityException e) {
		/*
		 * If access was denied to the knowledge of the class
		 * loader's URLs, fall back to the default behavior.
		 */
	    } catch (IOException e) {
		/*
		 * This shouldn't happen, although it is declared to be
		 * thrown by openConnection() and getPermission().  If it
		 * does happen, forget about this class loader's URLs and
		 * fall back to the default behavior.
		 */
	    }
	}

	if (annotation != null) {
	    return annotation;
	} else {
	    return getClassAnnotation(loader);
	}
    }

    /**
     * Return true if the given loader is the system class loader or
     * its parent (i.e. the loader for installed extensions) or the null
     * class loader
     */
    private static boolean isLocalLoader(ClassLoader loader) {
	return (loader == null || localLoaders.containsKey(loader));
    }
    
    /**
     * Provides the implementation for {@link
     * RMIClassLoaderSpi#getClassLoader(String)}.
     *
     * Returns a class loader that loads classes from the given
     * codebase URL path.
     *
     * <p>If <code>codebase</code> is <code>null</code>, then the
     * value of the system property
     * <code>java.rmi.server.codebase</code> (or possibly an earlier
     * cached value) is used as the codebase URL path.
     *
     * <p>If there is a security manager, its
     * <code>checkPermission</code> method will be invoked with a
     * <code>RuntimePermission("getClassLoader")</code> permission;
     * this could result in a <code>SecurityException</code>.  This
     * method also verifies that the current security context has all
     * of the security permissions necessary to connect to all of the
     * URLs in the codebase URL path.
     *
     * @param codebase the list of URLs (space-separated) that the
     * returned class loader will load classes from, or
     * <code>null</code>
     *
     * @return a class loader that loads classes from the given
     * codebase URL path
     *
     * @throws MalformedURLException if <code>codebase</code> is
     * non-<code>null</code> and contains an invalid URL, or if
     * <code>codebase</code> is <code>null</code> and the value of
     * <code>java.rmi.server.codebase</code> contains an invalid URL
     *
     * @throws SecurityException if there is a security manager and
     * the invocation of its <code>checkPermission</code> method
     * fails, or if the current security context does not have the
     * permissions necessary to connect to all of the URLs in the
     * codebase URL path
     **/
    public ClassLoader getClassLoader(String codebase)
	throws MalformedURLException
    {
	ClassLoader parent = getRMIContextClassLoader();

	/*
	 * If there is a security manager, the current access control
	 * context must have the "getClassLoader" RuntimePermission.
	 */
	SecurityManager sm = System.getSecurityManager();
	if (sm != null) {
	    sm.checkPermission(new RuntimePermission("getClassLoader"));
	} else {
	    /*
	     * But if no security manager is set, disable access to
	     * RMI class loaders and simply return the parent loader.
	     */
	    return parent;
	}

	URL[] urls = pathToURLs(codebase);		// may be null
	ClassLoader loader = lookupLoader(urls, parent);

	/*
	 * Verify that the caller has permission to access this loader.
	 */
	checkLoader(loader, parent, urls);

	return loader;
    }

    /**
     * Provides the implementation of {@link
     * RMIClassLoaderSpi#loadProxyClass(String,String[],ClassLoader)}.
     *
     * Defines and returns a dynamic proxy class in a class loader
     * with URLs supplied in the given location.  The proxy class will
     * implement interface classes named by the given array of
     * interface names.
     * 
     * <p>If <code>codebase</code> is <code>null</code>, then the
     * value of the system property
     * <code>java.rmi.server.codebase</code> (or possibly an earlier
     * cached value) is used as the codebase URL path.
     *
     * @param	codebase the list of URLs (space-separated) to load
     * classes from, or <code>null</code>
     *
     * @param	interfaces the names of the interfaces for the proxy class
     * to implement
     *
     * @return	a dynamic proxy class that implements the named interfaces
     *
     * @param	defaultLoader additional contextual class loader
     * to use, or <code>null</code>
     *
     * @throws MalformedURLException if <code>codebase</code> is
     * non-<code>null</code> and contains an invalid URL, or if
     * <code>codebase</code> is <code>null</code> and the value of
     * <code>java.rmi.server.codebase</code> contains an invalid URL
     *
     * @throws	ClassNotFoundException if a definition for one of
     * the named interfaces could not be found at the specified location,
     * or if creation of the dynamic proxy class failed (such as if
     * {@link java.lang.reflect.Proxy#getProxyClass Proxy.getProxyClass}
     * would throw an <code>IllegalArgumentException</code> for the given
     * interface list)
     **/
    public Class loadProxyClass(String codebase, String[] interfaces,
				ClassLoader defaultLoader)
	throws MalformedURLException, ClassNotFoundException
    {
	if (logger.isLoggable(Level.FINE)) {
	    logger.log(Level.FINE,
		"interfaces={0}, codebase={1}, defaultLoader={2}",
		new Object[] {
		    Arrays.asList(interfaces),
		    codebase != null ? "\"" + codebase + "\"" : null,
		    defaultLoader
		});
	}

	/*
	 * This method uses a fairly complex algorithm to load the
	 * proxy class and its interface classes in order to maximize
	 * the likelihood that the proxy's codebase annotation will be
	 * preserved.  The algorithm is (assuming that all of the
	 * proxy interface classes are public):
	 *
	 * If the default loader is not null, try to load the proxy
	 * interfaces through that loader. If the interfaces can be
	 * loaded in that loader, try to define the proxy class in an
	 * RMI class loader (child of the context class loader) before
	 * trying to define the proxy in the default loader.  If the
	 * attempt to define the proxy class succeeds, the codebase
	 * annotation is preserved.  If the attempt fails, try to
	 * define the proxy class in the default loader.
	 *
	 * If the interface classes can not be loaded from the default
	 * loader or the default loader is null, try to load them from
	 * the RMI class loader.  Then try to define the proxy class
	 * in the RMI class loader.
	 *
	 * Additionally, if any of the proxy interface classes are not
	 * public, all of the non-public interfaces must reside in the
	 * same class loader or it will be impossible to define the
	 * proxy class (an IllegalAccessError will be thrown).  An
	 * attempt to load the interfaces from the default loader is
	 * made.  If the attempt fails, a second attempt will be made
	 * to load the interfaces from the RMI loader. If all of the
	 * non-public interfaces classes do reside in the same class
	 * loader, then we attempt to define the proxy class in the
	 * class loader of the non-public interfaces.  No other
	 * attempt to define the proxy class will be made.
	 */
	ClassLoader parent = getRMIContextClassLoader();
	if (logger.isLoggable(Level.FINEST)) {
	    logContextLoader(parent, "loadProxyClass");
	}

        /*
         * If no security manager is set, disable access to RMI class
         * loaders and use the would-de parent instead.
         */
        SecurityManager sm = System.getSecurityManager();
        if (sm == null) {
            try {
		Class c = loadProxyClass(interfaces, defaultLoader, parent,
					 false);
		if (logger.isLoggable(Level.FINEST)) {
		    logger.log(Level.FINEST,
			"(no security manager: codebase disabled) " +
			"proxy class defined by {0}", getClassLoader(c));
		}
		return c;
            } catch (ClassNotFoundException e) {
		if (logger.isLoggable(Levels.FAILED)) {
		    logger.log(Levels.FAILED,
			"(no security manager: codebase disabled) " +
			"proxy class resolution failed", e);
		}
                throw new ClassNotFoundException(e.getMessage() +
                    " (no security manager: RMI class loader disabled)",
                    e.getException());
            }
        }

	/*
	 * Get or create the preferred class loader for this codebase
	 * URL path and parent class loader pair (or get the
	 * corresponding ancestor loader if a boomerang is detected).
	 */
        URL[] urls = pathToURLs(codebase);		// may be null
        ClassLoader loader = lookupLoader(urls, parent);

        try {
	    /*
	     * Verify that the caller has permission to access this loader.
	     */
	    checkLoader(loader, parent, urls);
        } catch (SecurityException e) {
            /*
             * If the current access control context does not have permission
             * to access all of the URLs in the codebase path, wrap the
             * resulting security exception in a ClassNotFoundException, so
             * the caller can handle this outcome just like any other class
             * loading failure (see bugid 4146529).
             */
            try {
                /*
                 * But first, check to see if the proxy class could have been
                 * resolved without the security-offending codebase anyway;
                 * if so, return successfully (see bugids 4191926 & 4349670).
                 */
                Class c = loadProxyClass(interfaces, defaultLoader, parent,
					 false);
		if (logger.isLoggable(Level.FINEST)) {
		    logger.log(Level.FINEST,
			"(access to codebase denied) " +
			"proxy class defined by {0}", getClassLoader(c));
		}
		return c;
            } catch (ClassNotFoundException unimportant) {
		if (logger.isLoggable(Levels.HANDLED)) {
		    logger.log(Levels.HANDLED,
			"(access to codebase denied) " +
			"proxy class resolution failed", unimportant);
		}
                /*
                 * Presumably the security exception is the more important
                 * exception to report in this case.
                 */
		if (logger.isLoggable(Levels.FAILED)) {
		    logger.log(Levels.FAILED,
			"(access to codebase denied) " +
			"proxy class resolution failed", e);
		}
                throw new ClassNotFoundException(
                    "access to class loader denied", e);
            }
        }

	try {
	    Class c = loadProxyClass(interfaces, defaultLoader, loader, true);
	    if (logger.isLoggable(Level.FINEST)) {
		logger.log(Level.FINEST,
		    "proxy class defined by {0}", getClassLoader(c));
	    }
	    return c;
	} catch (ClassNotFoundException e) {
	    if (logger.isLoggable(Levels.FAILED)) {
		logger.log(Levels.FAILED,
		    "proxy class resolution failed", e);
	    }
	    throw e;
	}
    }
    
    /**
     * Define a proxy class in the default loader if appropriate.
     * Define the class in an RMI class loader otherwise.  The proxy
     * class will implement classes which are named in the supplied
     * interfaceNames
     */
    private Class loadProxyClass(String[] interfaceNames,
				 ClassLoader defaultLoader,
				 ClassLoader codebaseLoader,
				 boolean preferCodebase)
        throws MalformedURLException, ClassNotFoundException
    {
        ClassLoader proxyLoader = null;
        Class[] classObjs = new Class[interfaceNames.length];
        boolean[] nonpublic = { false };

	/* result of comparing loaders URLs */
	Boolean[] matchResult = new Boolean[1];

	if ((defaultLoader != null) &&
	    (codebaseLoader instanceof PreferredClassLoader) &&
	    !annotationsMatch(matchResult, defaultLoader, codebaseLoader) &&
	    interfacePreferred((PreferredClassLoader) codebaseLoader,
			       interfaceNames))
	{
	    /* dont violate preferred list, ignore default loader */
	    defaultLoader = null;
	}

      defaultLoaderCase:
        if (defaultLoader != null) {
            try {
                proxyLoader =
		    loadProxyInterfaces(interfaceNames, defaultLoader,
					classObjs, nonpublic);
		if (logger.isLoggable(Level.FINEST)) {
		    ClassLoader[] definingLoaders =
			new ClassLoader[classObjs.length];
		    for (int i = 0; i < definingLoaders.length; i++) {
			definingLoaders[i] = getClassLoader(classObjs[i]);
		    }
		    logger.log(Level.FINEST,
			"proxy interfaces found via defaultLoader, " +
			"defined by {0}", Arrays.asList(definingLoaders));
		}
            } catch (ClassNotFoundException e) {
                break defaultLoaderCase;
            }
            if (!nonpublic[0]) {
                if (preferCodebase && !annotationsMatch(matchResult,
				       defaultLoader, codebaseLoader))
		{
		    try {
			return Proxy.getProxyClass(codebaseLoader,
						   classObjs);
		    } catch (IllegalArgumentException e) {
		    }
		}
                proxyLoader = defaultLoader;
            }
            return loadProxyClass(proxyLoader, classObjs);
        }

        nonpublic[0] = false;
        proxyLoader = loadProxyInterfaces(interfaceNames, codebaseLoader,
                                          classObjs, nonpublic);
	if (logger.isLoggable(Level.FINEST)) {
	    ClassLoader[] definingLoaders = new ClassLoader[classObjs.length];
	    for (int i = 0; i < definingLoaders.length; i++) {
		definingLoaders[i] = getClassLoader(classObjs[i]);
	    }
	    logger.log(Level.FINEST,
		"proxy interfaces found via codebase, " +
		"defined by {0}", Arrays.asList(definingLoaders));
	}
        if (!nonpublic[0]) {
            proxyLoader = codebaseLoader;
        }
        return loadProxyClass(proxyLoader, classObjs);
    }

    /*
     * Compare annotation arrays of two default and codebase loaders.
     * Store class loader annotation arrays and computed result
     *
     * Use a Boolean[] as a single mutable Boolean value
     */
    private boolean annotationsMatch(Boolean[] matchResult,
				     ClassLoader defaultLoader,
				     ClassLoader codebaseLoader)
    {
	if (matchResult[0] == null) {
	    try {
		URL[] defaultURLs = getLoaderAnnotationURLs(defaultLoader);
		URL[] codebaseURLs = getLoaderAnnotationURLs(codebaseLoader);

		matchResult[0] =
		    Boolean.valueOf(Arrays.equals(defaultURLs, codebaseURLs));
	    } catch (MalformedURLException e) {
		// assume this means no match
		matchResult[0] = Boolean.FALSE;
	    }
	}
	return matchResult[0].booleanValue();
    }
    
    /*
     * Detect if one of the interfaceNames is preferred in the
     * codebaseLoader
     */
    private boolean interfacePreferred(PreferredClassLoader codebaseLoader,
				       String[] interfaceNames)
	throws ClassNotFoundException
    {
	boolean interfacePreferred = false;
	
	for (int p = 0; p < interfaceNames.length; p++) {
	    try {
		if (((PreferredClassLoader) codebaseLoader).
		    isPreferredResource(interfaceNames[p], true))
		{
		    interfacePreferred = true;
		    break;
		}
	    } catch (IOException e) {
		throw new ClassNotFoundException("could not obtain " +
		    "preferred value for: " + interfaceNames[p], e);
	    }
	}
	return interfacePreferred;
    }

    /**
     * Define a proxy class in the given class loader.  The proxy
     * class will implement the given interfaces Classes
     */
    private static Class loadProxyClass(ClassLoader loader, Class[] interfaces)
        throws ClassNotFoundException
    {
        try {
            return Proxy.getProxyClass(loader, interfaces);
        } catch (IllegalArgumentException e) {
            throw new ClassNotFoundException(
                "error creating dynamic proxy class", e);
        }
    }

    /**
     * Returns an array of URLs corresponding to the annotation string
     * for the specified class loader, or null if the annotation
     * string is null.
     **/
    URL[] getLoaderAnnotationURLs(ClassLoader loader)
	throws MalformedURLException
    {
	return pathToURLs(getLoaderAnnotation(loader));
    }
    
    /*
     * Load Class objects for the names in the interfaces array from
     * the given class loader.
     *
     * We pass classObjs and useNonpublic array to avoid needing a
     * multi-element return value. useNonpublic is an array to enable
     * the method to take a boolean argument by reference.
     *
     * useNonpublic array is needed to signal when the return value of
     * this method should be used as the proxy class loader.  Because
     * null represents a valid class loader, that value is
     * insufficient to signal that the return value should not be used
     * as the proxy class loader.
     */
    private ClassLoader loadProxyInterfaces(String[] interfaces,
					    ClassLoader loader,
					    Class[] classObjs,
					    boolean[] useNonpublic)

	throws ClassNotFoundException
    {
	/* loader of a non-public interface class */
	ClassLoader nonpublic = null;

	for (int i = 0; i < interfaces.length; i++) {
	    Class cl =
		(classObjs[i] = Class.forName(interfaces[i], false, loader));
		
	    if (!Modifier.isPublic(cl.getModifiers())) {
		ClassLoader current = getClassLoader(cl);
		if (logger.isLoggable(Level.FINEST)) {
		    logger.logp(Level.FINEST,
			this.getClass().getName(), "loadProxyClass",
			"non-public interface \"{0}\" defined by {1}",
			new Object[] { interfaces[i], current });
		}
		if (!useNonpublic[0]) {
		    nonpublic = current;
		    useNonpublic[0] = true;
		} else if (current != nonpublic) {
		    throw new IllegalAccessError(
			"non-public interfaces defined in different " +
			"class loaders");
		}
	    }
	}
	return nonpublic;
    }

    /**
     * Convert a string containing a space-separated list of URLs into a
     * corresponding array of URL objects, throwing a MalformedURLException
     * if any of the URLs are invalid.  This method returns null if the
     * specified string is null.
     *
     * @param path the string path to be converted to an array of urls
     * @return the string path converted to an array of URLs, or null
     * @throws MalformedURLException if the string path of urls contains a
     *         mal-formed url which can not be converted into a url object.
     */
    private static URL[] pathToURLs(String path) throws MalformedURLException {
	if (path == null) {
	    return null;
	}
	synchronized (pathToURLsCache) {
	    Object[] v = (Object[]) pathToURLsCache.get(path);
	    if (v != null) {
		return ((URL[])v[0]);
	    }
	}
	StringTokenizer st = new StringTokenizer(path);	// divide by spaces
	URL[] urls = new URL[st.countTokens()];
	for (int i = 0; st.hasMoreTokens(); i++) {
	    urls[i] = new URL(st.nextToken());
	}
	synchronized (pathToURLsCache) {
	    pathToURLsCache.put(path,
				new Object[] {urls, new SoftReference(path)});
	}
	return urls;
    }

    /** map from weak(key=string) to [URL[], soft(key)] */
    private static Map pathToURLsCache = new WeakHashMap(5);

    /**
     * Return the class loader to be used as the parent for an RMI class
     * loader used in the current execution context.
     */
    private static ClassLoader getRMIContextClassLoader() {
	/*
	 * The current implementation simply uses the current thread's
	 * context class loader.
	 */
	return (ClassLoader) AccessController.doPrivileged(
	    new PrivilegedAction() {
		public Object run() {
		    return Thread.currentThread().getContextClassLoader();
		}
	    });
    }

    /**
     * log information about the context class loader
     */
    void logContextLoader(ClassLoader ctxLoader, String methodName) {
	logger.logp(Level.FINEST, this.getClass().getName(), methodName,
		    "(thread context class loader: {0})", ctxLoader);
    }

    
    /**
     * Return the origin class loader for the <code>pathURLs</code> or
     * null if the origin loader was not present in the delegation
     * hierarchy.
     *
     * Preferred classes introduces the "class boomerang" problem to
     * RMI class loading.  A class boomerang occurs when a class which
     * is marked preferred is accessible from the codebase of a
     * virtual machine (VM) and is loaded by that VM.  Since the VM
     * has a copy of the class in its own resources and the class is
     * "returning to its origin" the class should not be preferred.
     * If the class is preferred, it will be loaded in a class loader
     * for the local codebase annotation.  As a result, the class'
     * type will not be compatible with types defined from the local
     * definition of the class file in the relevant VM.
     *
     * A boomerang can also occur when a new child loader for a URL
     * path is created but an ancestor of the new class loader has the
     * same URL path as the new class loader.  In such cases the new
     * class loader should not be created.  The incoming class should
     * be loaded from the origin ancestor instead.
     *
     * A simple example of a class boomerang occurs when when a VM
     * makes a remote method call to itself.  Suppose an object whose
     * class was loaded locally in that VM and is preferred in the
     * codebase of the VM is passed in the call.  When the VM receives
     * its own call, an instance of the unmarshalled parameter will
     * not be assignable to instances that were defined by local
     * classes (i.e. never unmarshalled).
     *
     * In order to work around class boomerang problems, the preferred
     * class provider lookupLoader algorithm is different from the
     * analogous algorithm in LoaderHandler.  To avoid boomerangs, the
     * lookupLoader method of this class attempts to locate the
     * "origin" class loader of an incoming class in a remote method
     * call.  Loading classes from their origin loader instead of in a
     * preferred circumvents type compatibility conflicts.
     *
     * To find origin loaders, the lookupLoader method calls
     * findOriginLoader() before locating or creating new
     * PreferredClassLoaders.  An origin loader is found by searching
     * up the delegation hierarchy above the parent (context) class
     * loader for a loader that has an export path which matches the
     * parameter path.
     */
    private ClassLoader findOriginLoader(final URL[] pathURLs,
					 final ClassLoader parent)
    {
	return (ClassLoader) AccessController.doPrivileged(
	    new PrivilegedAction() {
		public Object run() {
		    return findOriginLoader0(pathURLs, parent);
		}
	    });
    }

    private ClassLoader findOriginLoader0(URL[] pathURLs, ClassLoader parent) {
	for (ClassLoader ancestor = parent;
	     ancestor != null;
	     ancestor = ancestor.getParent())
	{
	    URL[] ancestorURLs;
	    try {
		ancestorURLs = getLoaderAnnotationURLs(ancestor);
	    } catch (MalformedURLException e) {
		// this ancestor's annotation must not match pathURLs
		continue;
	    }

	    /* check if found a matching ancestor loader */
	    if (Arrays.equals(pathURLs, ancestorURLs)) {
		if (logger.isLoggable(Level.FINEST)) {
		    logger.log(Level.FINEST,
			"using an existing ancestor class loader " +
			"which serves the requested codebase urls: {0}, " +
			"urls: {1}",
			new Object[] {
			    ancestor,
			    (ancestorURLs != null ?
			     Arrays.asList(ancestorURLs) : null)
			});
		}

		return ancestor;
	    }
	}
	return null;
    }

    /**
     * Look up the class loader for the given codebase URL path
     * and the given parent class loader.  A new class loader instance
     * will be created and returned if no match is found.
     */
    private ClassLoader lookupLoader(final URL[] urls,
				     final ClassLoader parent)
    {
	/*
	 * If the requested codebase is null, then PreferredClassProvider
	 * assumes that the class is expected to be a "platform" class
	 * with respect to the parent class loader.
	 */
	if (urls == null) {
	    return parent;
	}

	/*
	 * If the requested codebase URL path is empty, the supplied
	 * parent class loader will be sufficient.
	 *
	 * REMIND: To be conservative, this optimization is commented out
	 * for now so that it does not open a security hole in the future
	 * by providing untrusted code with direct access to the public
	 * loadClass() method of a class loader instance that it cannot
	 * get a reference to.  (It's an unlikely optimization anyway.)
	 *
	 * if (urls.length == 0) {
	 *     return parent;
	 * }
	 */

	LoaderEntry entry;
	ClassLoader loader = null;

	synchronized (loaderTable) {
	    /*
	     * Take this opportunity to remove from the table entries
	     * whose weak references have been cleared.
	     */
	    while ((entry = (LoaderEntry) refQueue.poll()) != null) {
		if (!entry.removed) {	// ignore entries removed below
		    loaderTable.remove(entry.key);
		}
	    }

	    /*
	     * Look up the codebase URL path and parent class loader pair
	     * in the table of RMI class loaders.
	     */
	    LoaderKey key = new LoaderKey(urls, parent);
	    entry = (LoaderEntry) loaderTable.get(key);

	    if (entry == null ||
		(loader = (ClassLoader) entry.get()) == null)
	    {
		/*
		 * If entry was in table but it's weak reference was cleared,
		 * remove it from the table and mark it as explicitly cleared,
		 * so that new matching entry that we put in the table will
		 * not be erroneously removed when this entry is processed
		 * from the weak reference queue.
		 */
		if (entry != null) {
		    loaderTable.remove(key);
		    entry.removed = true;
		}

		/*
		 * An existing loader with the given URL path and
		 * parent was not found.  Perform the following steps
		 * to obtain an appropriate loader:
		 * 
		 * Search for an ancestor of the parent class loader
		 * whose export urls match the parameter URL path
		 * 
		 * If a matching ancestor could not be found, create a
		 * new class loader instance for the requested
		 * codebase URL path and parent class loader.  The
		 * loader instance is created within an access control
		 * context restricted to the permissions necessary to
		 * load classes from its codebase URL path.
		 */
		loader = findOriginLoader(urls, parent);

		if (loader == null) {
		    AccessControlContext acc = PreferredClassLoader.
			getLoaderAccessControlContext(urls);
		    loader = (PreferredClassLoader)
			AccessController.doPrivileged(new PrivilegedAction() {
				public Object run() {
				    return new PreferredClassLoader(urls,
					parent,	null, requireDlPerm, false);
				}
			}, acc);
		}

		/*
		 * Finally, create an entry to hold the new loader with a
		 * weak reference and store it in the table with the key.
		 */
		entry = new LoaderEntry(key, loader);
		loaderTable.put(key, entry);
	    }
	}

	return loader;
    }

    /**
     * LoaderKey holds a codebase URL path and parent class loader pair
     * used to look up RMI class loader instances in its class loader cache.
     */
    private static class LoaderKey {

	private URL[] urls;

	private ClassLoader parent;

	private int hashValue;

	public LoaderKey(URL[] urls, ClassLoader parent) {
	    this.urls = urls;
	    this.parent = parent;

	    if (parent != null) {
		hashValue = parent.hashCode();
	    }
	    for (int i = 0; i < urls.length; i++) {
		hashValue ^= urls[i].hashCode();
	    }
	}

	public int hashCode() {
	    return hashValue;
	}

	public boolean equals(Object obj) {
	    if (obj instanceof LoaderKey) {
		LoaderKey other = (LoaderKey) obj;
		if (parent != other.parent) {
		    return false;
		}
		return Arrays.equals(urls, other.urls);
	    } else {
		return false;
	    }
	}
    }

    /**
     * LoaderEntry contains a weak reference to an RMIClassLoader.  The
     * weak reference is registered with the private static "refQueue"
     * queue.  The entry contains the codebase URL path and parent class
     * loader key for the loader so that the mapping can be removed from
     * the table efficiently when the weak reference is cleared.
     */
    private class LoaderEntry extends WeakReference {

	public final LoaderKey key;

	/**
	 * set to true if the entry has been removed from the table
	 * because it has been replaced, so it should not be attempted
	 * to be removed again
	 */
	public boolean removed = false;

	public LoaderEntry(LoaderKey key, ClassLoader loader) {
	    super(loader, refQueue);
	    this.key = key;
	}
    }

    private static ClassLoader getClassLoader(final Class c) {
	return (ClassLoader) AccessController.doPrivileged(
	    new PrivilegedAction() {
		public Object run() { return c.getClassLoader(); }
	    });
    }
}
