/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.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://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.jini.loader.pref;

import com.sun.jini.action.GetPropertyAction;
import com.sun.jini.logging.Levels;
import com.sun.jini.logging.LogUtil;
import java.io.FilePermission;
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.URLConnection;
import java.net.URLClassLoader;
import java.rmi.server.RMIClassLoaderSpi;
import java.security.AccessController;
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.Logger;
import java.util.logging.Level;
import net.jini.loader.ClassAnnotation;
import net.jini.loader.CodebaseAccessClassLoader;
import net.jini.loader.DownloadPermission;

/**
 * An <code>CodebaseAccessClassLoader</code> provider that supports preferred
 * classes.
 *
 * <p>See the {@link CodebaseAccessClassLoader} specification for information
 * about how to install and configure the <code>CodebaseAccessClassLoader</code>
 * service provider.
 *
 * <p><code>PreferredClassProvider</code> uses instances of {@link
 * PreferredClassLoader} to load classes from codebase URL paths
 * supplied to <code>CodebaseAccessClassLoader.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(ClassLoader)
 * getClassAnnotation(ClassLoader)} 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}.
 *
 * <h3>Common Terms and Behaviors</h3>
 *
 * The following section defines terms and describes behaviors common
 * to how <code>PreferredClassProvider</code> implements the abstract
 * methods of <code>CodebaseAccessClassLoaderSpi</code>.  Where applicable, these
 * definitions and descriptions are relative to the instance of
 * <code>PreferredClassProvider</code> on which a method is invoked
 * and the context in which it is invoked.
 *
 * <p>The <i>annotation string</i> for a class loader is determined by
 * the following procedure:
 *
 * <ul>
 *
 * <li>If the loader is the system class loader or an ancestor of the
 * system class loader (including the bootstrap class loader), the
 * annotation string is the result of invoking {@link
 * #getClassAnnotation(ClassLoader) getClassAnnotation(ClassLoader)}
 * with the loader.
 *
 * <li>Otherwise, if the loader is an instance of {@link
 * ClassAnnotation}, the annotation string is the result of invoking
 * {@link ClassAnnotation#getClassAnnotation getClassAnnotation} on
 * the loader.
 *
 * <li>Otherwise, if the loader is an instance of {@link
 * URLClassLoader}, the annotation string is a space-separated list of
 * the URLs returned by an invocation of {@link URLClassLoader#getURLs
 * getURLs} on the loader.
 *
 * <li>Otherwise, the annotation string is the result of invoking
 * {@link #getClassAnnotation(ClassLoader)
 * getClassAnnotation(ClassLoader)} with the loader.
 *
 * </ul>
 *
 * The <i>annotation URL path</i> for a class loader is the path of
 * URLs obtained by parsing the annotation string for the loader as a
 * list of URLs separated by spaces, where each URL is parsed as with
 * the {@link URL#URL(String) URL(String)} constructor; if such
 * parsing would result in a {@link MalformedURLException}, then the
 * annotation URL path for the loader is only defined to the extent
 * that it is not equal to any other path of URLs.
 *
 * <p>A <code>PreferredClassProvider</code> maintains an internal
 * table of class loader instances indexed by keys that comprise a
 * path of URLs and a parent class loader.  The table does not
 * strongly reference the class loader instances, in order to allow
 * them (and the classes they have defined) to be garbage collected
 * when they are not otherwise reachable.
 *
 * <p>The methods {@link #loadClass loadClass}, {@link #loadProxyClass
 * loadProxyClass}, and {@link #getClassLoader getClassLoader}, which
 * each have a <code>String</code> parameter named
 * <code>codebase</code>, have the following behaviors in common:
 *
 * <ul>
 *
 * <li><code>codebase</code> may be <code>null</code>.  If it is not
 * <code>null</code>, it is interpreted as a path of URLs by parsing
 * it as a list of URLs separated by spaces, where each URL is parsed
 * as with the <code>URL(String)</code> constructor; this could result
 * in a {@link MalformedURLException}.  This path of URLs is the
 * <i>codebase URL path</i> for the invocation.
 *
 * <li>A class loader known as the <i>codebase loader</i> is chosen
 * based on <code>codebase</code> and the current thread's context
 * class loader as follows.  If <code>codebase</code> is
 * <code>null</code>, then the codebase loader is the current thread's
 * context class loader.  Otherwise, for each non-<code>null</code>
 * loader starting with the current thread's context class loader and
 * continuing with each successive parent class loader, if the
 * codebase URL path is equal to the loader's annotation URL path,
 * then the codebase loader is that loader.  If no such matching
 * loader is found, then the codebase loader is the loader in this
 * <code>PreferredClassProvider</code>'s internal table with the
 * codebase URL path as the key's path of URLs and the current
 * thread's context class loader as the key's parent class loader.  If
 * no such entry exists in the table, then one is created by invoking
 * {@link #createClassLoader createClassLoader} with the codebase URL
 * path, the current thread's context class loader, and the
 * <code>boolean</code> <code>requireDlPerm</code> value that this
 * <code>PreferredClassProvider</code> was constructed with; the
 * created loader is added to the table, and it is chosen as the
 * codebase loader.
 *
 * <li>The current security context has <i>permission to access the
 * codebase loader</i> if it has the appropriate permission for each
 * of the URLs in the codebase loader's annotation URL path, where the
 * appropriate permission for a URL is defined as follows.  If the
 * result of invoking {@link URL#openConnection
 * openConnection()}.{@link URLConnection#getPermission
 * getPermission()} on the <code>URL</code> object is not a {@link
 * FilePermission} or if it is a <code>FilePermission</code> whose
 * name does not contain a directory separator, then that permission
 * is the appropriate permission.  If it is a
 * <code>FilePermission</code> whose name contains a directory
 * separator, then the appropriate permission is a
 * <code>FilePermission</code> with action <code>"read"</code> and the
 * same name except with the last path segment replaced with
 * <code>"-"</code> (that is, permission to read all files in the same
 * directory and all subdirectories).
 *
 * </ul>
 *
 * <p>When <code>PreferredClassProvider</code> attempts to load a
 * class (or interface) named <code><i>N</i></code> using class loader
 * <code><i>L</i></code>, it does so in a manner equivalent to
 * evaluating the following expression:
 *
 * <pre>
 * 	Class.forName(<code><i>N</i></code>, false, <code><i>L</i></code>)
 * </pre>
 *
 * In particular, the case of <code><i>N</i></code> being the binary
 * name of an array class is supported.
 *
 * @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 Levels#HANDLED HANDLED} <td> exceptions caught
 * during class loading operations
 *
 * <tr> <td> {@link Level#FINE FINE} <td> invocations of {@link
 * #loadClass loadClass} and {@link #loadProxyClass loadProxyClass}
 *
 * <tr> <td> {@link Level#FINEST FINEST} <td> detailed activity of
 * <code>loadClass</code> and <code>loadProxyClass</code>
 * implementations
 *
 * </table>
 **/
public class PreferredClassProvider extends RMIClassLoaderSpi {

    /** encodings for primitive array class element types */
    private static final String PRIMITIVE_TYPES = "BCDFIJSZ";

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

    /** true if constructor has completed successfully */
    private final boolean initialized;

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

    private static final Permission getClassLoaderPermission =
	new RuntimePermission("getClassLoader");

    /**
     * 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 Map 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 CodebaseAccessClassLoader}
     * service provider location mechanism when
     * <code>PreferredClassProvider</code> is configured as the
     * <code>CodebaseAccessClassLoader</code> provider class.
     *
     * <p>If there is a security manager, its {@link
     * SecurityManager#checkCreateClassLoader checkCreateClassLoader}
     * method is invoked; this could result in a
     * <code>SecurityException</code>.
     *
     * <p>{@link DownloadPermission} is not enforced by the created
     * provider.
     *
     * @throws SecurityException if there is a security manager and
     * the invocation of its <code>checkCreateClassLoader</code>
     * method fails
     **/
    public PreferredClassProvider() {
	this(false);
    }

    /**
     * Creates a new <code>PreferredClassProvider</code>.
     *
     * <p>This constructor is used by subclasses to control whether
     * or not {@link DownloadPermission} is enforced.
     *
     * <p>If there is a security manager, its {@link
     * SecurityManager#checkCreateClassLoader checkCreateClassLoader}
     * method is invoked; this could result in a
     * <code>SecurityException</code>.
     *
     * @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}
     *
     * @throws SecurityException if there is a security manager and
     * the invocation of its <code>checkCreateClassLoader</code>
     * method fails
     **/
    protected PreferredClassProvider(boolean requireDlPerm) {
	SecurityManager sm = System.getSecurityManager();
	if (sm != null) {
	    sm.checkCreateClassLoader();
	}
	this.requireDlPerm = requireDlPerm;
	initialized = true;
    }

    private void checkInitialized() {
	if (!initialized) {
	    throw new SecurityException("uninitialized instance");
	}
    }

    /**
     * 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)) {
	    assert urlsMatchLoaderAnnotation(urls, loader);

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

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

    /**
     * Provides the implementation for {@link
     * CodebaseAccessClassLoaderSpi#loadClass(String,String,ClassLoader)}.
     *
     * <p><code>PreferredClassProvider</code> implements this method
     * as follows:
     *
     * <p>If <code>name</code> is the binary name of an array class
     * (of one or more dimensions) with a primitive element type, this
     * method returns the <code>Class</code> for that array class.
     *
     * <p>Otherwise, if <code>defaultLoader</code> is not
     * <code>null</code> and any of the following conditions are true:
     *
     * <ul>
     *
     * <li>There is no security manager.
     *
     * <li>The codebase loader is not the current thread's context
     * class loader and the current security context does not have
     * permission to access the codebase loader.
     *
     * <li><code>codebase</code> is <code>null</code>.
     *
     * <li>The specified codebase URL path is equal to the annotation
     * URL path of <code>defaultLoader</code>.
     *
     * <li>The codebase loader is not an instance of {@link
     * PreferredClassLoader}.
     *
     * <li>The codebase loader is an instance of
     * <code>PreferredClassLoader</code> and an invocation of {@link
     * PreferredClassLoader#isPreferredResource isPreferredResource}
     * on the codebase loader with the class name described below as
     * the first argument and <code>true</code> as the second argument
     * returns <code>false</code>.  If <code>name</code> is the binary
     * name of an array class (of one or more dimensions) with a
     * element type that is a reference type, the class name passed to
     * <code>isPreferredResource</code> is the binary name of that
     * element type; otherwise, the class name passed to
     * <code>isPreferredResource</code> is <code>name</code>.  This
     * invocation is only done if none of the previous conditions are
     * true.  If <code>isPreferredResource</code> throws an
     * <code>IOException</code>, this method throws a
     * <code>ClassNotFoundException</code>.
     *
     * </ul>
     *
     * then this method attempts to load the class with the specified
     * name using <code>defaultLoader</code>.  If this attempt
     * succeeds, this method returns the resulting <code>Class</code>;
     * if it throws a <code>ClassNotFoundException</code>, this method
     * proceeds as follows.
     *
     * <p>Otherwise, this method attempts to load the class with the
     * specified name using the codebase loader, if there is a
     * security manager and the current security context has
     * permission to access the codebase loader, or using the current
     * thread's context class loader otherwise.  If this attempt
     * succeeds, this method returns the resulting <code>Class</code>;
     * if it throws a <code>ClassNotFoundException</code>, this method
     * throws a <code>ClassNotFoundException</code>.
     *
     * @param codebase the codebase URL path as a space-separated list
     * of URLs, or <code>null</code>
     *
     * @param name the binary 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
     *
     * @throws ClassNotFoundException if a definition for the class
     * could not be loaded
     **/
    public Class loadClass(String codebase,
			   String name,
			   ClassLoader defaultLoader)
	throws MalformedURLException, ClassNotFoundException
    {
	checkInitialized();
	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
					// throws MalformedURLException

	/*
	 * Process array class names.
	 */
	String elementTypeName = null;
	int len = name.length();
	if (len > 0 && name.charAt(0) == '[') {
	    int i = 1;
	    char c = 0;
	    while (i < len && (c = name.charAt(i)) == '[') {
		i++;
	    }
	    if (len == i + 1 && PRIMITIVE_TYPES.indexOf(c) != -1) {
		return Class.forName(name);
	    }
	    if (len > i + 2 && c == 'L' && name.charAt(len - 1) == ';') {
		elementTypeName = name.substring(i + 1, len - 1);
	    }
	}

	/*
	 * Try defaultLoader cases that don't require determining the
	 * codebase loader.
	 */
	SecurityManager sm = System.getSecurityManager();
	if (defaultLoader != null &&
	    (sm == null || codebaseURLs == null ||
	     urlsMatchLoaderAnnotation(codebaseURLs, defaultLoader)))
	{
	    try {
		Class c = Class.forName(name, false, defaultLoader);
		if (logger.isLoggable(Level.FINEST)) {
		    logger.log(Level.FINEST, "class \"{0}\" found " +
			"via defaultLoader, defined by {1}",
			new Object[] { name, getClassLoader(c) });
		}
		return c;
	    } catch (ClassNotFoundException e) {
		defaultLoader = null;	// don't try defaultLoader again
	    }
	}

	/*
	 * Determine the codebase loader.
	 */
	ClassLoader contextLoader = getRMIContextClassLoader();
	if (logger.isLoggable(Level.FINEST)) {
	    logger.log(Level.FINEST,
		       "(thread context class loader: {0})", contextLoader);
	}
	ClassLoader codebaseLoader = lookupLoader(codebaseURLs, contextLoader);

	/*
	 * Try remaining defaultLoader cases that don't require
	 * checking permission to access the codebase loader.
	 */
	if (defaultLoader != null &&
	    !(codebaseLoader instanceof PreferredClassLoader))
	{
	    try {
		Class c = Class.forName(name, false, defaultLoader);
		if (logger.isLoggable(Level.FINEST)) {
		    logger.log(Level.FINEST, "class \"{0}\" found " +
			"via defaultLoader, defined by {1}",
			new Object[] { name, getClassLoader(c) });
		}
		return c;
	    } catch (ClassNotFoundException e) {
		defaultLoader = null;	// don't try defaultLoader again
	    }
	}

	/*
	 * Check permission to access the codebase loader.
	 */
	SecurityException secEx = null;
	if (sm != null) {
	    try {
		checkLoader(codebaseLoader, contextLoader, codebaseURLs);
	    } catch (SecurityException e) {
		secEx = e;
	    }
	}

	/*
	 * Try remaining defaultLoader cases.
	 */
	if (defaultLoader != null) {
	    boolean tryDL = secEx != null;
	    if (!tryDL) {
		try {
		    tryDL = !((PreferredClassLoader) codebaseLoader).
			isPreferredResource(elementTypeName != null ?
					    elementTypeName : name, true);
		} catch (IOException e) {
		    ClassNotFoundException cnfe =
			new ClassNotFoundException(name +
			    " (could not determine preferred setting;" +
			    " original codebase: \"" + codebase + "\")", e);
		    if (logger.isLoggable(Levels.FAILED)) {
			LogUtil.logThrow(logger, Levels.FAILED,
			    PreferredClassProvider.class, "loadClass",
			    "class \"{0}\" not found, " +
			    "could not obtain preferred value",
			    new Object[] { name }, cnfe);
		    }
		    throw cnfe;
		}
	    }
	    if (tryDL) {
		try {
		    Class c = Class.forName(name, false, defaultLoader);
		    if (logger.isLoggable(Level.FINEST)) {
			logger.log(Level.FINEST, "class \"{0}\" found " +
			    "via defaultLoader, defined by {1}",
			    new Object[] { name, getClassLoader(c) });
		    }
		    return c;
		} catch (ClassNotFoundException e) {
		}
	    }
	}

	/*
	 * Done with defaultLoader, try the codebase loader or the
	 * context class loader as appropriate.
	 */
	try {
	    Class c = Class.forName(name, false,
				    (sm != null && secEx == null ?
				     codebaseLoader : contextLoader));
	    if (logger.isLoggable(Level.FINEST)) {
		String message;
		if (sm == null) {
		    message = "class \"{0}\" found " +
			" via thread context class loader " +
			" (no security manager), defined by {1}";
		} else if (secEx != null) {
		    message = "class \"{0}\" found " +
			" via thread context class loader " +
			" (access to codebase loader denied), defined by {1}";
		} else {
		    message = "class \"{0}\" found " +
			"via codebase loader, defined by {1}";
		}
		logger.log(Level.FINEST, message,
			   new Object[] { name, getClassLoader(c) });
	    }
	    return c;
	} catch (ClassNotFoundException e) {
	    if (sm == null) {
		ClassNotFoundException cnfe =
		    new ClassNotFoundException(e.getMessage() + 
			" (no security manager: codebase loader disabled)", e);
		if (logger.isLoggable(Levels.FAILED)) {
		    LogUtil.logThrow(logger, Levels.FAILED,
			PreferredClassProvider.class, "loadClass",
			"class \"{0}\" not found " +
			"via thread context class loader " +
			"(no security manager)",
			new Object[] { name }, cnfe);
		}
		throw cnfe;
	    } else if (secEx != null) {
		/*
		 * Presumably the original security exception is
		 * preferable to throw, but log both exceptions.
		 */
		if (logger.isLoggable(Levels.HANDLED)) {
		    LogUtil.logThrow(logger, Levels.HANDLED,
			PreferredClassProvider.class, "loadClass",
			"class \"{0}\" not found " +
			"via thread context class loader " +
			"(access to codebase loader denied)",
			new Object[] { name }, e);
		}
		ClassNotFoundException cnfe =
		    new ClassNotFoundException(e.getMessage() +
			" (access to codebase loader denied)", secEx);
		if (logger.isLoggable(Levels.FAILED)) {
		    LogUtil.logThrow(logger, Levels.FAILED,
			PreferredClassProvider.class, "loadClass",
			"class \"{0}\" not found " +
			"via thread context class loader " +
			"(access to codebase loader denied)",
			new Object[] { name }, cnfe);
		}
		throw cnfe;
	    } else {
		if (logger.isLoggable(Levels.FAILED)) {
		    LogUtil.logThrow(logger, Levels.FAILED,
			PreferredClassProvider.class, "loadClass",
			"class \"{0}\" not found via codebase loader",
			new Object[] { name }, e);
		}
		throw e;
	    }
	}
    }

    /**
     * Provides the implementation for {@link
     * CodebaseAccessClassLoaderSpi#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 element type, this method returns
     * <code>null</code>.
     *
     * <p>Otherwise, this method returns the annotation string for the
     * defining class loader of <code>cl</code>, except that if the
     * annotation string would be determined by an invocation of
     * {@link URLClassLoader#getURLs URLClassLoader.getURLs} on that
     * loader and the current security context does not have the
     * permissions necessary to connect to each URL returned by that
     * invocation (where the permission to connect to a URL is
     * determined by invoking {@link URL#openConnection
     * openConnection()}.{@link URLConnection#getPermission
     * getPermission()} on the <code>URL</code> object), this method
     * returns the result of invoking {@link
     * #getClassAnnotation(ClassLoader)
     * getClassAnnotation(ClassLoader)} with the loader instead.
     *
     * @param cl the class to obtain the annotation string for
     *
     * @return the annotation string for the class, or
     * <code>null</code>
     **/
    public String getClassAnnotation(Class cl) {
	checkInitialized();
	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), true);
    }

    /**
     * Returns the annotation string for the specified class loader.
     *
     * <p>This method is invoked in order to determine the annotation
     * string for 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 (for an
     * invocation of {@link #getClassAnnotation(Class)
     * getClassAnnotation(Class)}) a <code>URLClassLoader</code> for
     * which the current security context does not have the
     * permissions necessary 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 class loader to obtain the annotation string
     * for
     *
     * @return the annotation string for the class loader, or
     * <code>null</code>
     **/
    protected String getClassAnnotation(ClassLoader loader) {
	checkInitialized();
	return codebaseProperty;
    }

    /**
     * Returns the annotation string for the specified class loader
     * (possibly null).  If check is true and the annotation would be
     * determined from an invocation of URLClassLoader.getURLs() on
     * the loader, only return the true annotation if the current
     * security context has permission to connect to all of the URLs.
     **/
    private String getLoaderAnnotation(ClassLoader loader, boolean check) {
	
	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 (check) {
			SecurityManager sm = System.getSecurityManager();
			if (sm != null) {
			    Permissions perms = new Permissions();
			    for (int i = 0; i < urls.length; i++) {
				Permission p =
				    urls[i].openConnection().getPermission();
				if (p != null) {
				    if (!perms.implies(p)) {
					sm.checkPermission(p);
					perms.add(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)}.
     *
     * <p><code>PreferredClassProvider</code> implements this method
     * as follows:
     *
     * <p>If there is a security manager, its
     * <code>checkPermission</code> method is invoked with a
     * <code>RuntimePermission("getClassLoader")</code> permission;
     * this could result in a <code>SecurityException</code>.  Also,
     * if there is a security manager, the codebase loader is not the
     * current thread's context class loader, and the current security
     * context does not have permission to access the codebase loader,
     * this method throws a <code>SecurityException</code>.
     *
     * <p>This method returns the codebase loader if there is a
     * security manager, or the current thread's context class loader
     * otherwise.
     *
     * @param codebase the codebase URL path as a space-separated list
     * of URLs, or <code>null</code>
     *
     * @return a class loader for the specified codebase URL path
     *
     * @throws MalformedURLException if <code>codebase</code> is
     * non-<code>null</code> and 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
    {
	checkInitialized();
    	URL[] codebaseURLs = pathToURLs(codebase);	// may be null
					// throws MalformedURLException

	ClassLoader contextLoader = getRMIContextClassLoader();
	SecurityManager sm = System.getSecurityManager();
	if (sm != null) {
	    sm.checkPermission(getClassLoaderPermission);
	} else {
	    return contextLoader;
	}

	ClassLoader codebaseLoader = lookupLoader(codebaseURLs, contextLoader);
	checkLoader(codebaseLoader, contextLoader, codebaseURLs);
	return codebaseLoader;
    }

    /**
     * Provides the implementation of {@link
     * RMIClassLoaderSpi#loadProxyClass(String,String[],ClassLoader)}.
     *
     * <p><code>PreferredClassProvider</code> implements this method
     * as follows:
     *
     * <p>If <code>defaultLoader</code> is not <code>null</code> and
     * any of the following conditions are true:
     *
     * <ul>
     *
     * <li>There is no security manager.
     *
     * <li>The codebase loader is not the current thread's context
     * class loader and the current security context does not have
     * permission to access the codebase loader.
     *
     * <li><code>codebase</code> is <code>null</code>.
     *
     * <li>The specified codebase URL path is equal to the annotation
     * URL path of <code>defaultLoader</code>.
     *
     * <li>The codebase loader is not an instance of {@link
     * PreferredClassLoader}.
     *
     * <li>The codebase loader is an instance of
     * <code>PreferredClassLoader</code> and an invocation of {@link
     * PreferredClassLoader#isPreferredResource isPreferredResource}
     * on the codebase loader for each element of
     * <code>interfaces</code>, with the element as the first argument
     * and <code>true</code> as the second argument, all return
     * <code>false</code>.  These invocations are only done if none of
     * the previous conditions are true.  If any invocation of
     * <code>isPreferredResource</code> throws an
     * <code>IOException</code>, this method throws a
     * <code>ClassNotFoundException</code>.
     *
     * </ul>
     *
     * then this method attempts to load all of the interfaces named
     * by the elements of <code>interfaces</code> using
     * <code>defaultLoader</code>.  If all of the interfaces are
     * loaded successfully, then
     *
     * <ul>
     *
     * <li>If all of the loaded interfaces are <code>public</code>: if
     * there is a security manager, the codebase loader is the current
     * thread's context class loader or the current security context
     * has permission to access the codebase loader, and the
     * annotation URL path for the codebase loader is not equal to the
     * annotation URL path for <code>defaultLoader</code>, this method
     * first attempts to get a dynamic proxy class (using {@link
     * Proxy#getProxyClass Proxy.getProxyClass}) that is defined by
     * the codebase loader and that implements all of the interfaces,
     * and if this attempt succeeds, this method returns the resulting
     * <code>Class</code>.  Otherwise, this method attempts to get a
     * dynamic proxy class that is defined by
     * <code>defaultLoader</code> and that implements all of the
     * interfaces.  If that attempt succeeds, this method returns the
     * resulting <code>Class</code>; if it throws an
     * <code>IllegalArgumentException</code>, this method throws a
     * <code>ClassNotFoundException</code>.
     *
     * <li>If all of the non-<code>public</code> interfaces are
     * defined by the same class loader: this method attempts to get a
     * dynamic proxy class that is defined by that loader and that
     * implements all of the interfaces.  If this attempt succeeds,
     * this method returns the resulting <code>Class</code>; if it
     * throws an <code>IllegalArgumentException</code>, this method
     * throws a <code>ClassNotFoundException</code>.
     *
     * <li>Otherwise (if there are two or more non-<code>public</code>
     * interfaces defined by different class loaders): this method
     * throws a <code>LinkageError</code>.
     *
     * </ul>
     *
     * If any of the attempts to load one of the interfaces throws a
     * <code>ClassNotFoundException</code>, this method proceeds as
     * follows.
     *
     * <p>Otherwise, this method attempts to load all of the
     * interfaces named by the elements of <code>interfaces</code>
     * using the codebase loader, if there is a security manager and
     * the current security context has permission to access the
     * codebase loader, or using the current thread's context class
     * loader otherwise.  If all of the interfaces are loaded
     * successfully, then
     *
     * <ul>
     *
     * <li>If all of the loaded interfaces are <code>public</code>:
     * this method attempts to get a dynamic proxy class that is
     * defined by the loader used to load the interfaces and that
     * implements all of the interfaces.  If this attempt succeeds,
     * this method returns the resulting <code>Class</code>; if it
     * throws an <code>IllegalArgumentException</code>, this method
     * throws a <code>ClassNotFoundException</code>.
     *
     * <li>If all of the non-<code>public</code> interfaces are
     * defined by the same class loader: this method attempts to get a
     * dynamic proxy class that is defined by that loader and that
     * implements all of the interfaces.  If this attempt succeeds,
     * this method returns the resulting <code>Class</code>; if it
     * throws an <code>IllegalArgumentException</code>, this method
     * throws a <code>ClassNotFoundException</code>.
     *
     * <li>Otherwise (if there are two or more non-<code>public</code>
     * interfaces defined by different class loaders): this method
     * throws a <code>LinkageError</code>.
     *
     * </ul>
     *
     * If any of the attempts to load one of the interfaces throws a
     * <code>ClassNotFoundException</code>, this method throws a
     * <code>ClassNotFoundException</code>.
     *
     * @param codebase the codebase URL path as a space-separated list
     * of URLs, or <code>null</code>
     *
     * @param interfaceNames the binary 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
     *
     * @throws ClassNotFoundException if a definition for one of the
     * named interfaces could not be loaded, or if creation of the
     * dynamic proxy class failed (such as if
     * <code>Proxy.getProxyClass</code> would throw an
     * <code>IllegalArgumentException</code> for the given interface
     * list)
     **/
    public Class loadProxyClass(String codebase,
				String[] interfaceNames,
				ClassLoader defaultLoader)
	throws MalformedURLException, ClassNotFoundException
    {
	checkInitialized();
	if (logger.isLoggable(Level.FINE)) {
	    logger.log(Level.FINE,
		"interfaces={0}, codebase={1}, defaultLoader={2}",
		new Object[] {
		    Arrays.asList(interfaceNames),
		    codebase != null ? "\"" + codebase + "\"" : null,
		    defaultLoader
		});
	}

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

	/*
	 * Determine the codebase loader.
	 */
	ClassLoader contextLoader = getRMIContextClassLoader();
	if (logger.isLoggable(Level.FINEST)) {
	    logger.log(Level.FINEST,
		       "(thread context class loader: {0})", contextLoader);
	}
	ClassLoader codebaseLoader = lookupLoader(codebaseURLs, contextLoader);

	/*
	 * Check permission to access the codebase loader.
	 */
	SecurityManager sm = System.getSecurityManager();
	SecurityException secEx = null;
	if (sm != null) {
	    try {
		checkLoader(codebaseLoader, contextLoader, codebaseURLs);
	    } catch (SecurityException e) {
		secEx = e;
	    }
	}

	/*
	 * Try all defaultLoader cases.
	 */
	if (defaultLoader != null) {
	    boolean codebaseMatchesDL = false;
	    boolean tryDL =
		sm == null || secEx != null ||
		codebaseURLs == null;
	    if (!tryDL) {
		codebaseMatchesDL =
		    urlsMatchLoaderAnnotation(codebaseURLs, defaultLoader);
		tryDL = codebaseMatchesDL ||
		    !(codebaseLoader instanceof PreferredClassLoader) ||
		    !interfacePreferred((PreferredClassLoader) codebaseLoader,
					interfaceNames, codebase);
		// throws ClassNotFoundException if IOException occurs
	    }
	    if (tryDL) {
		try {
		    boolean preferCodebaseLoader =
			sm != null && secEx == null && !codebaseMatchesDL;
		    Class c = loadProxyClass(interfaceNames,
					     defaultLoader, "defaultLoader",
					     codebaseLoader,
					     preferCodebaseLoader);
		    if (logger.isLoggable(Level.FINEST)) {
			logger.log(Level.FINEST,
				   getProxySuccessLogMessage(sm, secEx),
				   getClassLoader(c));
		    }
		    return c;
		} catch (ClassNotFoundException e) {
		} catch (IllegalArgumentException e) {
		    ClassNotFoundException cnfe = new ClassNotFoundException(
			"dynamic proxy class creation failed", e);
		    if (logger.isLoggable(Levels.FAILED)) {
			logger.log(Levels.FAILED,
				   "dynamic proxy class creation failed", e);
		    }
		    throw cnfe;
		}
	    }
	}

	/*
	 * Try the codebase loader or the context class loader as
	 * appropriate.
	 */
	ClassLoader loader;
	String loaderName;
	if (sm != null && secEx == null) {
	    loader = codebaseLoader;
	    loaderName = "codebase loader";
	} else {
	    loader = contextLoader;
	    loaderName = "thread context class loader";
	}
	try {
	    Class c = loadProxyClass(interfaceNames, loader, loaderName,
				     null, false);
	    if (logger.isLoggable(Level.FINEST)) {
		logger.log(Level.FINEST,
			   getProxySuccessLogMessage(sm, secEx),
			   getClassLoader(c));
	    }
	    return c;
	} catch (ClassNotFoundException e) {
	    if (sm == null) {
		ClassNotFoundException cnfe =
		    new ClassNotFoundException(e.getMessage() +
			" (no security manager: codebase loader disabled)", e);
		if (logger.isLoggable(Levels.FAILED)) {
		    logger.log(Levels.FAILED,
			"proxy class resolution failed (no security manager)",
			cnfe);
		}
		throw cnfe;
	    } else if (secEx != null) {
		/*
		 * Presumably the original security exception is
		 * preferable to throw, but log both exceptions.
		 */
		if (logger.isLoggable(Levels.HANDLED)) {
		    logger.log(Levels.HANDLED,
			"proxy class resolution failed " +
			"(access to codebase loader denied)", e);
		}
		ClassNotFoundException cnfe =
		    new ClassNotFoundException(e.getMessage() +
			" (access to codebase loader denied)", secEx);
		if (logger.isLoggable(Levels.FAILED)) {
		    logger.log(Levels.FAILED,
			"proxy class resolution failed " +
			"(access to codebase loader denied)", cnfe);
		}
		throw cnfe;
	    } else {
		if (logger.isLoggable(Levels.FAILED)) {
		    logger.log(Levels.FAILED,
			       "proxy class resolution failed", e);
		}
		throw e;
	    }
	} catch (IllegalArgumentException e) {
	    ClassNotFoundException cnfe = new ClassNotFoundException(
		"dynamic proxy class creation failed", e);
	    if (logger.isLoggable(Levels.FAILED)) {
		logger.log(Levels.FAILED,
			   "dynamic proxy class creation failed", e);
	    }
	    throw cnfe;
	}
    }

    private static String getProxySuccessLogMessage(SecurityManager sm,
						    SecurityException secEx)
    {
	if (sm == null) {
	    return "(no security manager) proxy class defined by {0}";
	} else if (secEx != null) {
	    return
		"(access to codebase loader denied) " +
		"proxy class defined by {0}";
	} else {
	    return "proxy class defined by {0}";
	}
    }

    /**
     * Attempts to load the specified interfaces by name using the
     * specified loader, and if that is successful, attempts to get a
     * dynamic proxy class that implements those interfaces.
     *
     * If tryOtherLoaderFirst is true, attempts to get the proxy class
     * defined by the specified other loader first, and if that fails,
     * falls back to get the proxy class defined by the same loader
     * used to load the interfaces; otherwise, only attempts to get
     * the proxy class defined by the same loader used to load the
     * interfaces.
     *
     * Throws a ClassNotFoundException if attempting to load any of
     * the interfaces throws a ClassNotFoundException.  Throws
     * IllegalArgumentException if the final attempt to get a proxy
     * class throws an IllegalArgumentException.
     **/
    private Class loadProxyClass(String[] interfaceNames,
				 ClassLoader interfaceLoader,
				 String interfaceLoaderName,
				 ClassLoader otherLoader,
				 boolean tryOtherLoaderFirst)
	throws ClassNotFoundException
    {
	Class[] classObjs = new Class[interfaceNames.length];
	boolean[] nonpublic = { false };
	ClassLoader proxyLoader =
	    loadProxyInterfaces(interfaceNames, interfaceLoader,
				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 loaded via {0}, defined by {1}",
		new Object[] {
		    interfaceLoaderName, Arrays.asList(definingLoaders)
		});
	}

	if (!nonpublic[0]) {
	    if (tryOtherLoaderFirst) {
		try {
		    return Proxy.getProxyClass(otherLoader, classObjs);
		} catch (IllegalArgumentException e) {
		}
	    }
	    proxyLoader = interfaceLoader;
	}

	return Proxy.getProxyClass(proxyLoader, classObjs);
    }

    /**
     * Returns true if at least one of the specified interface names
     * is preferred for the specified class loader; returns false if
     * none of them are preferred.  Throws ClassNotFoundException if
     * PreferredClassLoader.isPreferredResource throws IOException
     * (although isPreferredResource isn't necessarily invoked for all
     * of the specified names, because this method short circuits on
     * the first invocation that returns true).  The codebase argument
     * is the original codebase URL path passed to loadProxyClass,
     * which typically but not necessarily equals the loader's import
     * URL path.
     **/
    private boolean interfacePreferred(PreferredClassLoader codebaseLoader,
				       String[] interfaceNames,
				       String codebase)
	throws ClassNotFoundException
    {
	for (int p = 0; p < interfaceNames.length; p++) {
	    try {
		if (((PreferredClassLoader) codebaseLoader).
		    isPreferredResource(interfaceNames[p], true))
		{
		    return true;
		}
	    } catch (IOException e) {
		ClassNotFoundException cnfe =
		    new ClassNotFoundException(interfaceNames[p] +
			" (could not determine preferred setting;" +
			" original codebase: \"" + codebase + "\")", e);
		if (logger.isLoggable(Levels.FAILED)) {
		    LogUtil.logThrow(logger, Levels.FAILED,
			PreferredClassProvider.class, "loadProxyClass",
			"class \"{0}\" not found, " +
			"could not obtain preferred value",
			new Object[] { interfaceNames[p] }, cnfe);
		}
		throw cnfe;
	    }
	}
	return false;
    }

    /**
     * Returns an array of URLs corresponding to the annotation string
     * for the specified class loader, or null if the annotation
     * string is null.
     **/
    private URL[] getLoaderAnnotationURLs(ClassLoader loader)
	throws MalformedURLException
    {
	return pathToURLs(getLoaderAnnotation(loader, false));
    }

    /**
     * Returns true if the specified path of URLs is equal to the
     * annotation URLs of the specified loader, and false otherwise.
     **/
    private boolean urlsMatchLoaderAnnotation(URL[] urls, ClassLoader loader) {
	try {
	    return Arrays.equals(urls, getLoaderAnnotationURLs(loader));
	} catch (MalformedURLException e) {
	    return false;
	}
    }
    
    /*
     * 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,
			PreferredClassProvider.class.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 final 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() {
		return CodebaseAccessClassLoader.getParentContextClassLoader();
    }

    /**
     * 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;
	 * }
	 */

	synchronized (loaderTable) {
	    /*
	     * Take this opportunity to remove from the table entries
	     * whose weak references have been cleared.
	     */
	    Object ref;
	    while ((ref = refQueue.poll()) != null) {
		if (ref instanceof LoaderKey) {
		    LoaderKey key = (LoaderKey) ref;
		    loaderTable.remove(key);
		} else if (ref instanceof LoaderEntry) {
		    LoaderEntry entry = (LoaderEntry) ref;
		    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);
	    LoaderEntry entry = (LoaderEntry) loaderTable.get(key);

	    ClassLoader loader;
	    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) {
		    loader = createClassLoader(urls, parent, requireDlPerm);
		}

		/*
		 * 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;
	}
    }

    /**
     * Creates the class loader for this
     * <code>PreferredClassProvider</code> to use to load classes from
     * the specified path of URLs with the specified delegation
     * parent.
     *
     * <p><code>PreferredClassProvider</code> implements this method
     * as follows:
     *
     * <p>This method creates a new instance of {@link
     * PreferredClassLoader} that loads classes and resources from
     * <code>urls</code>, delegates to <code>parent</code>, and
     * enforces {@link DownloadPermission} if
     * <code>requireDlPerm</code> is <code>true</code>.  The created
     * loader uses a restricted security context to ensure that the
     * URL retrieval operations undertaken by the loader cannot
     * exercise a permission that is not implied by the permissions
     * necessary to access the loader as a codebase loader for the
     * specified path of URLs.
     *
     * @param urls the path of URLs to load classes and resources from
     *
     * @param parent the parent class loader for delegation
     *
     * @param requireDlPerm if <code>true</code>, the loader must only
     * define classes with a {@link CodeSource} that is granted
     * <code>DownloadPermission</code>
     *
     * @return the created class loader
     *
     * @since 2.1
     **/
    protected ClassLoader createClassLoader(final URL[] urls,
					    final ClassLoader parent,
					    final boolean requireDlPerm)
    {
	checkInitialized();
	return CodebaseAccessClassLoader.createClassLoader( urls, parent, requireDlPerm,
			PreferredClassLoader.getLoaderAccessControlContext(urls) );
//	return (ClassLoader)
//	    AccessController.doPrivileged(new PrivilegedAction() {
//		public Object run() {
//		    return new PreferredClassLoader(urls, parent, null,
//						    requireDlPerm);
//		}
//	    }, PreferredClassLoader.getLoaderAccessControlContext(urls));
    }

    /**
     * Loader table key: a codebase URL path and a weak reference to
     * a parent class loader (possibly null).  The weak reference is
     * registered with "refQueue" so that the entry can be removed
     * after the loader has become unreachable.
     **/
    private class LoaderKey extends WeakReference {
	private final URL[] urls;
	private final boolean nullParent;
	private final int hashValue;

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

	    int h = nullParent ? 0 : parent.hashCode();
	    for (int i = 0; i < urls.length; i++) {
		h ^= urls[i].hashCode();
	    }
	    hashValue = h;
	}
        
	public int hashCode() {
	    return hashValue;
	}

	public boolean equals(Object obj) {
	    if (obj == this) {
		return true;
	    } else if (!(obj instanceof LoaderKey)) {
		return false;
	    }
	    LoaderKey other = (LoaderKey) obj;
	    ClassLoader parent;
	    return (nullParent ? other.nullParent
			       : ((parent = (ClassLoader) get()) != null &&
				  parent == other.get()))
		&& Arrays.equals(urls, other.urls);
	}
    }

    /**
     * Loader table value: a weak reference to a class loader.  The
     * weak reference is registered with "refQueue" so that the entry
     * can be removed after the loader has become unreachable.
     **/
    private class LoaderEntry extends WeakReference {
	public final LoaderKey key;	// for efficient removal

	/**
	 * 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(); }
	    });
    }
}
