/*
 * 
 * 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.loader.pref.internal.PreferredResources;
import com.sun.jini.logging.Levels;
import java.io.IOException;
import java.io.InputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FilePermission;
import java.net.HttpURLConnection;
import java.net.SocketPermission;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.ProtectionDomain;
import java.security.Policy;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.cert.Certificate;
import java.util.Enumeration;
import java.util.Set;
import java.util.HashSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.jini.loader.ClassAnnotation;
import net.jini.loader.DownloadPermission;

/**
 * A class loader that supports preferred classes.
 *
 * <p>A preferred class is a class that is to be loaded by a class
 * loader without the loader delegating to its parent class loader
 * first.  Resources may also be preferred.
 *
 * <p>Like {@link java.net.URLClassLoader},
 * <code>PreferredClassLoader</code> loads classes and resources from
 * a search path of URLs.  If a URL in the path ends with a
 * <code>'/'</code>, it is assumed to refer to a directory; otherwise,
 * the URL is assumed to refer to a JAR file.
 *
 * <p>The location of the first URL in the path can contain a
 * <i>preferred list</i> for the entire path.  A preferred list
 * declares names of certain classes and other resources throughout
 * the path as being <i>preferred</i> or not.  When a
 * <code>PreferredClassLoader</code> is asked to load a class or
 * resource that is preferred (according to the preferred list) and
 * the class or resource exists in the loader's path of URLs, the
 * loader will not delegate first to its parent class loader as it
 * otherwise would do; instead, it will attempt to load the class or
 * resource from its own path of URLs only.
 *
 * <p>The preferred list for a path of URLs, if one exists, is located
 * relative to the first URL in the path.  If the first URL refers to
 * a JAR file, then the preferred list is the contents of the file
 * named <code>"META-INF/PREFERRED.LIST"</code> within that JAR file.
 * If the first URL refers to a directory, then the preferred list is
 * the contents of the file at the location
 * <code>"META-INF/PREFERRED.LIST"</code> relative to that directory
 * URL.  If there is no preferred list at the required location, then
 * no classes or resources are preferred for the path of URLs.  A
 * preferred list at any other location (such as relative to one of
 * the other URLs in the path) is ignored.
 *
 * <p>Note that a class or resource is only considered to be preferred
 * if the preferred list declares the name of the class or resource as
 * being preferred and the class or resource actually exists in the
 * path of URLs.
 *
 * <h3>Preferred List Syntax</h3>
 *
 * A preferred list is a UTF-8 encoded text file, with lines separated
 * by CR&nbsp;LF, LF, or CR (not followed by an LF).  Multiple
 * whitespace characters in a line are equivalent to a single
 * whitespace character, and whitespace characters at the beginning or
 * end of a line are ignored.  If the first non-whitespace character
 * of a line is <code>'#'</code>, the line is a comment and is
 * equivalent to a blank line.
 *
 * <p>The first line of a preferred list must contain a version
 * number in the following format:
 *
 * <pre>
 *     PreferredResources-Version: 1.<i>x</i>
 * </pre>
 *
 * This specification defines only version 1.0, but
 * <code>PreferredClassLoader</code> will parse any version
 * 1.<i>x</i>, <i>x</i>>=0 with the format and semantics specified
 * here.
 *
 * <p>After the version number line, a preferred list comprises an
 * optional default preferred entry followed by zero or more named
 * preferred entries.  A preferred list must contain either a default
 * preferred entry or at least one named preferred entry.  Blank lines
 * are allowed before and after preferred entries, as well as between
 * the lines of a named preferred entry.
 *
 * <p>A default preferred entry is a single line in the following
 * format:
 *
 * <pre>
 *     Preferred: <i>preferred-setting</i>
 * </pre>
 *
 * where <i>preferred-setting</i> is a non-empty sequence of
 * characters.  If <i>preferred-setting</i> equals <code>"true"</code>
 * (case insensitive), then resource names not matched by any of the
 * named preferred entries are by default preferred; otherwise,
 * resource names not matched by any of the named preferred entries
 * are by default not preferred.  If there is no default preferred
 * entry, then resource names are by default not preferred.
 *
 * <p>A named preferred entry is two lines in the following format:
 *
 * <pre>
 *     Name: <i>name-expression</i>
 *     Preferred: <i>preferred-setting</i>
 * </pre>
 *
 * where <i>name-expression</i> and <i>preferred-setting</i> are
 * non-empty sequences of characters.  If <i>preferred-setting</i>
 * equals <code>"true"</code> (case insensitive), then resource names
 * that are matched by <i>name-expression</i> (and not any more
 * specific named preferred entries) are preferred; otherwise,
 * resource names that are matched by <i>name-expression</i> (and not
 * any more specific named preferred entries) are not preferred.
 * 
 * <p>If <i>name-expression</i> ends with <code>".class"</code>, it
 * matches a class whose name is <i>name-expression</i> without the
 * <code>".class"</code> suffix and with each <code>'/'</code>
 * character replaced with a <code>'.'</code>, and it also matches any
 * nested classes with an enclosing class of that name.  Therefore,
 * the preferred settings of a class and all of its nested classes are
 * the same by default.  It is possible, but strongly discouraged, to
 * override the preferred setting of a nested class with a named
 * preferred entry that explicitly matches the nested class's binary
 * name.  Named preferred entries for classes with binary names that
 * start with <code>"java."</code> are ignored.
 *
 * <p><i>name-expression</i> may match arbitrary resource names as
 * well as class names, with path elements separated by
 * <code>'/'</code> characters.
 *
 * <p>If <i>name-expression</i> ends with <code>"/"</code> or
 * <code>"/*"</code>, then the entry is a directory wildcard entry
 * that matches all resources (including classes) in the named
 * directory.  If <i>name-expression</i> ends with <code>"/-"</code>,
 * then the entry is a namespace wildcard entry that matches all
 * resources (including classes) in the named directory and all of its
 * subdirectories.
 *
 * <p>When more than one named preferred entry matches a class or
 * resource name, then the most specific entry takes precedence.  A
 * non-wildcard entry is more specific than a wildcard entry.  A
 * directory wildcard entry is more specific than a namespace wildcard
 * entry.  A namespace wildcard entry with more path elements is more
 * specific than a namespace wildcard entry with fewer path elements.
 * The order of named preferred entries is insignificant.
 *
 * <h3>Example Preferred List</h3>
 *
 * <p>Following is an example preferred list:
 *
 * <pre>
 *     PreferredResources-Version: 1.0
 *     Preferred: false
 *
 *     Name: com/foo/FooBar.class
 *     Preferred: true
 *
 *     Name: com/foo/*
 *     Preferred: false
 *
 *     Name: com/foo/-
 *     Preferred: true
 *
 *     Name: image-files/*
 *     Preferred: mumble
 * </pre>
 *
 * <p>The class <code>com.foo.FooBar</code> is preferred, as well as
 * any nested classes that have it as an enclosing class.  All other
 * classes in the <code>com.foo</code> package are not preferred
 * because of the directory wildcard entry.  Classes in subpackages of
 * <code>com.foo</code> are preferred because of the namespace
 * wildcard entry.  Resources in the directory <code>"com/foo/"</code>
 * are not preferred, and resources in subdirectories of
 * <code>"com/foo/"</code> are preferred.  Resources in the directory
 * <code>"image-files/"</code> are not preferred because preferred
 * settings other than <code>"true"</code> are interpreted as false.
 * Classes that are in a package named <code>com.bar</code> are not
 * preferred because of the default preferred entry.
 *
 * @author Sun Microsystems, Inc.
 * @since 2.0
 *
 * @com.sun.jini.impl
 *
 * <p>This implementation uses the {@link Logger} named
 * <code>net.jini.loader.pref.PreferredClassLoader.preferred</code> to
 * log information at the following levels:
 *
 * <table summary="Describes what is logged by PreferredClassLoader to
 * the preferred logger at various logging levels" border=1
 * cellpadding=5>
 *
 * <tr> <th> Level <th> Description
 *
 * <tr> <td> {@link Level#FINE FINE} <td> names of preferred resources
 * encountered
 *
 * </table>
 *
 * <p>This implementation uses the {@link Logger} named
 * <code>net.jini.loader.pref.PreferredClassLoader.exception</code> to
 * log information at the following levels:
 *
 * <table summary="Describes what is logged by PreferredClassLoader to
 * the exception logger at various logging levels" border=1
 * cellpadding=5>
 * 
 * <tr> <th> Level <th> Description
 *
 * <tr> <td> {@link Levels#HANDLED HANDLED} <td> I/O exceptions caught
 * trying to access a preferred list that are interpreted to indicate
 * that the preferred list definitely does not exist
 *
 * </table>
 **/
public class PreferredClassLoader extends URLClassLoader
    implements ClassAnnotation
{
    /**
     * well known name of resource that contains the preferred list in
     * a path of URLs
     **/
    private static final String PREF_NAME = "META-INF/PREFERRED.LIST";

    /** parent class loader, kept here as an optimization */
    private ClassLoader parent;

    /** string form of loader's codebase URL path, also an optimization */
    private String exportAnnotation;

    /** permissions required to access loader through public API */
    private Permissions permissions;

    /** PreferredResources for this loader */
    private PreferredResources preferredResources;

    /** The context to be used when loading classes and resources */
    private AccessControlContext acc;

    /** true if createPreferred() has successfully returned */
    private boolean createAttempted = false;

    /** cause this class loader to ignore preferred information */
    private boolean preferredInactive = false;

    /** permission required to download code? */
    private boolean requireDlPerm = false;

    /** permission to check is download not permitted by default */
    private static final Permission downloadPermission =
	new DownloadPermission();

    /** "preferred" logger */
    private static final Logger preferredLogger = Logger.getLogger(
	"net.jini.loader.pref.PreferredClassLoader.preferred");

    /** "exception" logger */
    private static final Logger exceptionLogger = Logger.getLogger(
	"net.jini.loader.pref.PreferredClassLoader.exception");

    /**
     * Creates a new <code>PreferredClassLoader</code> that loads
     * classes and resources from the specified path of URLs and
     * delegates to the specified parent class loader.
     *
     * <p>If <code>exportAnnotation</code> is not <code>null</code>,
     * then it will be used as the return value of the loader's {@link
     * #getClassAnnotation getClassAnnotation} method.  If
     * <code>exportAnnotation</code> is <code>null</code>, the
     * loader's <code>getClassAnnotation</code> method will return a
     * space-separated list of the URLs in the specified path.  The
     * <code>exportAnnotation</code> parameter can be used to specify
     * so-called "export" URLs, from which other parties should load
     * classes defined by the loader and which are different from the
     * "import" URLs that the classes are actually loaded from.
     *
     * <p>If <code>requireDlPerm</code> is <code>true</code>, the
     * loader's {@link #getPermissions getPermissions} method will
     * require that the {@link CodeSource} of any class defined by the
     * loader is granted {@link DownloadPermission}.
     *
     * @param urls the path of URLs to load classes and resources from
     *
     * @param parent the parent class loader for delegation
     *
     * @param exportAnnotation the export class annotation string to
     * use for classes defined by this loader, or <code>null</code>
     *
     * @param requireDlPerm if <code>true</code>, the loader will only
     * define classes with a {@link CodeSource} that is granted {@link
     * DownloadPermission}
     *
     * @throws SecurityException if there is a security manager and an
     * invocation of its {@link SecurityManager#checkCreateClassLoader
     * checkCreateClassLoader} method fails
     **/
    public PreferredClassLoader(URL[] urls, ClassLoader parent,
				String exportAnnotation,
				boolean requireDlPerm)
    {
	this(urls, parent, exportAnnotation, requireDlPerm, false);
    }

    /**
     * Package-internal PreferredClassLoader constructor that allows
     * the preferred list to be ignored.
     **/
    PreferredClassLoader(URL[] urls, ClassLoader parent,
			 String exportAnnotation,
			 boolean requireDlPerm,
			 boolean preferredInactive)
    {
	super(urls, parent);
	this.parent = parent;
	this.exportAnnotation = exportAnnotation;
	this.requireDlPerm = requireDlPerm;
	this.preferredInactive = preferredInactive;

	acc = AccessController.getContext();
	    
	/*
	 * Precompute the permissions required to access the loader.
	 */
	permissions = new Permissions();
	addPermissionsForURLs(urls, permissions, false);

	/*
	 * Caching the value of class annotation string here assumes
	 * that the protected method addURL() is never called on this
	 * class loader.
	 */
	if (this.exportAnnotation == null) {
	    this.exportAnnotation = urlsToPath(urls);
	}
    }

    /**
     * Convert an array of URL objects into a corresponding string
     * containing a space-separated list of URLs.
     *
     * Note that if the array has zero elements, the return value is
     * null, not the empty string.
     */
    static String urlsToPath(URL[] urls) {
	if (urls.length == 0) {
	    return null;
	} else if (urls.length == 1) {
	    return urls[0].toExternalForm();
	} else {
	    StringBuffer path = new StringBuffer(urls[0].toExternalForm());
	    for (int i = 1; i < urls.length; i++) {
		path.append(' ');
		path.append(urls[i].toExternalForm());
	    }
	    return path.toString();
	}
    }

    /**
     * Creates a new instance of <code>PreferredClassLoader</code>
     * that loads classes and resources from the specified path of
     * URLs and delegates to the specified parent class loader.
     *
     * <p>The <code>exportAnnotation</code> and
     * <code>requireDlPerm</code> parameters have the same semantics
     * as they do for the constructor.
     *
     * <p>The {@link #loadClass loadClass} method of the returned
     * <code>PreferredClassLoader</code> will, if there is a security
     * manager, invoke its {@link SecurityManager#checkPackageAccess
     * checkPackageAccess} method with the package name of the class
     * to load before attempting to load the class; this could result
     * in a <code>SecurityException</code> being thrown from
     * <code>loadClass</code>.
     *
     * @param urls the path of URLs to load classes and resources from
     *
     * @param parent the parent class loader for delegation
     *
     * @param exportAnnotation the export class annotation string to
     * use for classes defined by this loader, or <code>null</code>
     *
     * @param requireDlPerm if <code>true</code>, the loader will only
     * define classes with a {@link CodeSource} that is granted {@link
     * DownloadPermission}
     *
     * @return the new <code>PreferredClassLoader</code> instance
     *
     * @throws SecurityException if the current security context does
     * not have the permissions necessary to connect to all of the
     * URLs in <code>urls</code>
     **/
    public static PreferredClassLoader
	newInstance(final URL[] urls,
		    final ClassLoader parent,
		    final String exportAnnotation,
		    final boolean requireDlPerm)
    {
	/* ensure caller has permission to access all urls */
	Permissions perms = new Permissions();
	addPermissionsForURLs(urls, perms, false);
	checkPermissions(perms);

	AccessControlContext acc = getLoaderAccessControlContext(urls);
	/* Use privileged status to return a new class loader instance */
	return (PreferredClassLoader)
	    AccessController.doPrivileged(new PrivilegedAction() {
		public Object run() {
		    return new PreferredFactoryClassLoader(urls, parent,
			exportAnnotation, requireDlPerm);
		}
	    }, acc);
    }

    /**
     * If a preferred list exists in the resources of this class
     * loader, then create preferred list for this class loader from
     * that resource.  If no resource exists, then use a null
     * preferred resources object to signal that this class loader has
     * no preferred list.
     *
     * Note, method should be invoked while synchronized on 'this'
     */
    private void createPreferred() throws IOException {
	URL[] urls = getURLs();
	InputStream prefIn = null;
    
	if ((urls.length > 0) && (urls[0]) != null) {
		
	    String firstURLString = urls[0].toString();
	    try {
		prefIn = getPreferredInputStream(firstURLString);
	   
	    } catch (IOException e) {
		if (!firstURLString.startsWith("file:") &&
		    !(e instanceof FileNotFoundException))
  		{
		    throw e;
		}
		/* Assume that all IOExceptions thrown while
		 * trying to access "file:" urls and all
		 * FileNotFoundExceptions imply that the preferred
		 * list is definitely not found.  It is safe to
		 * exceptions with these characteristics.
		 */
		if (exceptionLogger.isLoggable(Levels.HANDLED)) {
		    logException(e, firstURLString);
		}
	    }
	    if (prefIn != null) {
		preferredResources = new PreferredResources(prefIn);
	    }
	}
    }

    /**
     * Obtain an input stream from which the preferred list for
     * this class loader can be read.  The location for the
     * preferred list is determined as follows:
     *
     * If a resource exists in a URL of the following form:
     *
     * if getURLs()[0] ends with '/' :
     * 
     *     getURLs()[0] + "META-INF/PREFERRED.LIST"
     * 
     * else
     *
     *     "jar:" + getURLs()[0] + "!/META-INF/PREFERRED.LIST"
     *
     * Returns a connection to the preferred list or throws any
     * exceptions that occurred while trying to access this
     * list. Returns null if getPreferredConnection returns null.
     */
    private InputStream getPreferredInputStream(String firstURL)
	throws IOException
    {
	String preferredURLString = null;
	URLConnection preferredConnection = null;
	InputStream prefIn = null;

	if (firstURL.endsWith("/")) {
	    preferredURLString = firstURL + PREF_NAME;
	    preferredConnection =
		getPreferredConnection(preferredURLString, false);
	    if (preferredConnection != null) {
		prefIn = preferredConnection.getInputStream();
	    }
	} else {
	    /*
	     * Note: The structure of URL class loader forces the
	     * preferred class loader to have to download jar files in
	     * particular urls two times in order to access the
	     * PREFERRED.LIST resource file in a jar.
	     *
	     * The first download is needed to know if a JAR file
	     * definitely exists in the given URL.  The second
	     * download is used to actually load the bytes for the jar
	     * file.
	     *
	     * Unfortunately when we download the jar from a "jar:"
	     * url, the Jar file factory code masks errors that occur
	     * while trying to access the jar.  As a result, its not
	     * possible to tell if a PREFERRED.LIST is only missing
	     * because of a transient/indefinite network failure or if
	     * the given jar file really does not contain a
	     * PREFERRED.LIST.
	     */
	    if (jarExists(firstURL)) {
		preferredURLString = "jar:" + firstURL + "!/" + PREF_NAME;
		preferredConnection =
		    getPreferredConnection(preferredURLString, false);
	    }
	}
			    
	if (preferredConnection != null) {
	    prefIn = preferredConnection.getInputStream();
	}
	return prefIn;
    }

    /* cache existence of jar files referenced by codebase urls */
    private static final Set existSet = new HashSet(11);

    /*
     * Determine if a jar file in a given URL location exists.  If the
     * jar exists record the jar file's URL in a cache of URL strings.
     *
     * Recording the existence of the jar prevents the need to
     * re-determine the jar's existence on subsequent downloads of the
     * jar in potentially different preferred class loaders.
     */
    private boolean jarExists(String firstURL) throws IOException {
	boolean exists;

	synchronized (existSet) {
	    exists = existSet.contains(firstURL);
	}

	if (!exists) {
	    exists = (getPreferredConnection(firstURL, true) != null);
	    if (exists) {
		synchronized (existSet) {
		    existSet.add(firstURL);
		}
	    }
	}
	return exists;
    }
    
    /**
     * Obtain a url connection from which an input stream that
     * contains a preferred list can be obtained.
     *
     * For http urls, attempts to use http response codes to
     * determine if a preferred list exists or is definitely not
     * found.  Simply attempts to open a connection to other kinds
     * of non-file urls.  If the attempt fails, an IOException is
     * thrown to user code.
     *
     * Returns null if the preferred list definitely does not
     * exist.  Rethrows all indefinite IOExceptions generated
     * while trying to open a connection to the preferred list.
     *
     * The caller has the option to close the connection after the
     * resource has been detected (as will happen when probing for a
     * PREFERRED.LIST).
     */
    private URLConnection getPreferredConnection(String first,
						 boolean closeAfter)
	throws IOException
    {
	URLConnection preferredConnection = null;
	final URL url = new URL(first);

	if (first.startsWith("file:")) {
	    return url.openConnection();
	}
	URLConnection closeConn = null;
	URLConnection conn = null;
	try {
	    closeConn = url.openConnection();
	    conn = closeConn;
						 
	    /* check status of http urls  */
	    if (conn instanceof HttpURLConnection) {
		HttpURLConnection hconn = (HttpURLConnection) conn;
		if (closeAfter) {
		    hconn.setRequestMethod("HEAD");
		}
		int responseCode = hconn.getResponseCode();
		
		switch (responseCode) {
		case HttpURLConnection.HTTP_OK:
		case HttpURLConnection.HTTP_NOT_AUTHORITATIVE:
		    /* the preferred list exists */
		    break;

		    /* 404, not found appears to be handled by
		     * HttpURLConnection (FileNotFoundException is
		     * thrown), but to be safe do the right thing here as
		     * well.
		     */
		case HttpURLConnection.HTTP_NOT_FOUND:
		case HttpURLConnection.HTTP_FORBIDDEN:
		case HttpURLConnection.HTTP_GONE:
		    /* list definitely does not exist */
		    conn = null;
		    break;
		default:
		    /* indefinite response code */
		    throw new IOException("Indefinite http response for " +
			"preferred list request:" +
			hconn.getResponseMessage());
		}
	    }
	} finally {
	    if (closeAfter && (closeConn != null)) {
		/* clean up after... */
		try {
		    closeConn.getInputStream().close();
		} catch (IOException e) {
		}
	    }
	}

	return conn;
    }

    private void logException(IOException e, String firstURL) {
	String preferredURLString = firstURL.endsWith("/") ?
	    firstURL + PREF_NAME :
	    "jar:" + firstURL + "!/" + PREF_NAME;

	exceptionLogger.logp(Levels.HANDLED,
			     this.getClass().getName(),
			     "isPreferredResource",
			     preferredURLString, e);
    }
	
    /**
     * Returns <code>true</code> if a class or resource with the
     * specified name is preferred for this class loader, and
     * <code>false</code> if a class or resource with the specified
     * name is not preferred for this loader.
     *
     * <p>If <code>isClass</code> is <code>true</code>, then
     * <code>name</code> is interpreted as the binary name of a class;
     * otherwise, <code>name</code> is interpreted as the full path of
     * a resource.
     *
     * <p>This method only returns <code>true</code> if a class or
     * resource with the specified name exists in the this loader's
     * path of URLs and the name is preferred in the preferred list.
     * This method returns <code>false</code> if the name is not
     * preferred in the preferred list or if the name is preferred
     * with the default preferred entry or a wildcard preferred entry
     * and the class or resource does not exist in the path of URLs.
     *
     * @param name the name of the class or resource
     *
     * @param isClass <code>true</code> if <code>name</code> is a
     * binary class name, and <code>false</code> if <code>name</code>
     * is the full path of a resource
     *
     * @return <code>true</code> if a class or resource named
     * <code>name</code> is preferred for this loader, and
     * <code>false</code> if a class or resource named
     * <code>name</code> is not preferred for this loader
     *
     * @throws IOException if the preferred list cannot definitely be
     * determined to exist or not exist, or if the preferred list
     * contains a syntax error, or if the name is preferred with the
     * default preferred entry or a wildcard preferred entry and the
     * class or resource cannot definitely be determined to exist or
     * not exist in the path of URLs, or if the name is preferred with
     * a non-wildcard entry and the class or resource does not exist
     * or cannot definitely be determined to exist in the path of URLs
     **/
    protected boolean isPreferredResource(final String name,
					  final boolean isClass)
	throws IOException
    {
	if (preferredInactive) {
	    return false;
	}
	if (isClass && name.startsWith("java.")) {
	    return false;
	}

	try {
	    return ((Boolean) AccessController.doPrivileged(
	        new PrivilegedExceptionAction() {
		    public Object run() throws IOException {
			boolean b = isPreferredResource0(name, isClass);
			return Boolean.valueOf(b);
		    }
	        }, acc)).booleanValue();

	} catch (PrivilegedActionException e) {
	    throw (IOException) e.getException();
	}
    }

    /*
     * Perform the work to determine if a resource name is preferred.
     */
    private boolean isPreferredResource0(String name, boolean isClass)
	throws IOException
    {
	/* attempt to create the preferred resource object */
	synchronized (this) {
	    if (!createAttempted) {
		createPreferred();
		createAttempted = true;
	    }
	    if (preferredResources == null) {
		return false;
	    }
	}
	    
	String resourceName = name;
	if (isClass) {
	    /* class name -> resource name */
	    resourceName = name.replace('.', '/') + ".class";
	}

	/*
	 * Determine if the class name is preferred. Making this
	 * distinction is somewhat tricky because we need to cache the
	 * preferred state (i.e. if the name is preferred and its
	 * resource exists) in a way that avoids duplication of
	 * preferred information - state information is stored back
	 * into the preferred resources object for this class loader
	 * and not held in a separate preferred settings cache.
	 */
	boolean resourcePreferred = false;

	synchronized (this) {
	    int state = preferredResources.getNameState(resourceName, isClass);
	    switch (state) {
	    case PreferredResources.NAME_NOT_PREFERRED:
		resourcePreferred = false;
		break;

	    case PreferredResources.NAME_PREFERRED_RESOURCE_EXISTS:
		resourcePreferred = true;
		break;

	    case PreferredResources.NAME_NO_PREFERENCE:
		Boolean wildcardPref =
		    preferredResources.getWildcardPreference(resourceName);
		if (wildcardPref == null) {
		    /* preferredDefault counts as a wild card */
		    wildcardPref = preferredResources.getDefaultPreference();
		}
		if (wildcardPref.booleanValue()) {
		    resourcePreferred =
			findResourceUpdateState(name, resourceName);
		}
		break;

	    case PreferredResources.NAME_PREFERRED:
		resourcePreferred =
		    findResourceUpdateState(name, resourceName);
		if (!resourcePreferred) {
		    throw new IOException("no resource found for " +
					  "complete preferred name");
		}
		break;

	    default:
		throw new Error("unknown preference state");
	    }
	}
	return resourcePreferred;
    }

    /*
     * Determine if a resource for a given preferred name exists.  If
     * the resource exists record its new state in the
     * preferredResources object.
     *
     * REMIND: Only call while synchronized on this
     */
    private boolean findResourceUpdateState(String name,
					    String resourceName)
	throws IOException
    {
	boolean resourcePreferred = false;

	if (findResource(resourceName) != null) {
	    /* the resource is know to exist */
	    preferredResources.setNameState(resourceName,
	        PreferredResources.NAME_PREFERRED_RESOURCE_EXISTS);
	    resourcePreferred = true;
	    if (preferredLogger.isLoggable(Level.FINE)) {
		logPreferred(name);
	    }
	}

	return resourcePreferred;
    }

    private void logPreferred(String name) {
	preferredLogger.logp(Level.FINE,
			     this.getClass().getName(),
			     "isPreferredResource",
			     "resource named \"{0}\" is preferred", name);
    }
	
    /**
     * Loads a class with the specified name.
     *
     * <p><code>PreferredClassLoader</code> implements this method as
     * follows:
     *
     * <p>This method first invokes {@link #findLoadedClass
     * findLoadedClass} with <code>name</code>; if
     * <code>findLoadedClass</code> returns a non-<code>null</code>
     * <code>Class</code>, then this method returns that
     * <code>Class</code>.
     *
     * <p>Otherwise, this method invokes {@link #isPreferredResource
     * isPreferredResource} with <code>name</code> as the first
     * argument and <code>true</code> as the second argument:
     *
     * <ul>
     *
     * <li>If <code>isPreferredResource</code> throws an
     * <code>IOException</code>, then this method throws a
     * <code>ClassNotFoundException</code> containing the
     * <code>IOException</code> as its cause.
     *
     * <li>If <code>isPreferredResource</code> returns
     * <code>true</code>, then this method invokes {@link #findClass
     * findClass} with <code>name</code>.  If <code>findClass</code>
     * throws an exception, then this method throws that exception.
     * Otherwise, this method returns the <code>Class</code> returned
     * by <code>findClass</code>, and if <code>resolve</code> is
     * <code>true</code>, {@link #resolveClass resolveClass} is
     * invoked with the <code>Class</code> before returning.
     *
     * <li>If <code>isPreferredResource</code> returns
     * <code>false</code>, then this method invokes the superclass
     * implementation of {@link ClassLoader#loadClass(String,boolean)
     * loadClass} with <code>name</code> and <code>resolve</code> and
     * returns the result.  If the superclass's <code>loadClass</code>
     * throws an exception, then this method throws that exception.
     *
     * </ul>
     *
     * @param name the binary name of the class to load
     *
     * @param resolve if <code>true</code>, then {@link #resolveClass
     * resolveClass} will be invoked with the loaded class before
     * returning
     *
     * @return the loaded class
     *
     * @throws ClassNotFoundException if the class could not be found
     **/
    protected synchronized Class loadClass(String name, boolean resolve)
	throws ClassNotFoundException
    {
	// First, check if the class has already been loaded
	Class c = findLoadedClass(name);
	if (c == null) {
	    boolean preferred;
	    try {
		preferred = isPreferredResource(name, true);
	    } catch (IOException e) {
		throw new ClassNotFoundException("Could not access" +
		    " preferred list for " + name, e);
	    }
	    if (preferred) {
		c = findClass(name);
		if (resolve) {
		    resolveClass(c);
		}
		return c;
	    } else {
		return super.loadClass(name, resolve);
	    }
	}

	return c;
    }
	
    /**
     * Gets a resource with the specified name.
     *
     * <p><code>PreferredClassLoader</code> implements this method as
     * follows:
     *
     * <p>This method invokes {@link #isPreferredResource
     * isPreferredResource} with <code>name</code> as the first
     * argument and <code>false</code> as the second argument:
     *
     * <ul>
     *
     * <li>If <code>isPreferredResource</code> throws an
     * <code>IOException</code>, then this method returns
     * <code>null</code>.
     *
     * <li>If <code>isPreferredResource</code> returns
     * <code>true</code>, then this method invokes {@link
     * #findResource findResource} with <code>name</code> and returns
     * the result.
     *
     * <li>If <code>isPreferredResource</code> returns
     * <code>false</code>, then this method invokes the superclass
     * implementation of {@link ClassLoader#getResource getResource}
     * with <code>name</code> and returns the result.
     *
     * </ul>
     *
     * @param name the name of the resource to get
     *
     * @return a <code>URL</code> for the resource, or
     * <code>null</code> if the resource could not be found
     **/
    public URL getResource(String name) {
	try {
	    return (isPreferredResource(name, false) ?
	        findResource(name) : super.getResource(name));
	} catch (IOException e) {
	}
	return null;
    }

    /*
     * Work around 4841786: wrap ClassLoader.definePackage so that if
     * it throws an IllegalArgumentException because an ancestor
     * loader "defined" the named package since the last time that
     * ClassLoader.getPackage was invoked, then just return the
     * result of invoking ClassLoader.getPackage again here.
     * Fortunately, URLClassLoader.defineClass ignores the value
     * returned by this method.
     */
    protected Package definePackage(String name, String specTitle,
				    String specVersion, String specVendor,
				    String implTitle, String implVersion,
				    String implVendor, URL sealBase)
    {
	try {
	    return super.definePackage(name, specTitle,
				       specVersion, specVendor,
				       implTitle, implVersion,
				       implVendor, sealBase);
	} catch (IllegalArgumentException e) {
	    return getPackage(name);
	}
    }

    /**
     * {@inheritDoc}
     *
     * <p><code>PreferredClassLoader</code> implements this method as
     * follows:
     *
     * <p>If this <code>PreferredClassLoader</code> was constructed
     * with a non-<code>null</code> export class annotation string,
     * then this method returns that string.  Otherwise, this method
     * returns a space-separated list of this loader's path of URLs.
     **/
    public String getClassAnnotation() {
	return exportAnnotation;
    }

    /**
     * Check that the current access control context has all of the
     * permissions necessary to load classes from this loader.
     */
    void checkPermissions() {
	checkPermissions(permissions);
    }

    /**
     * Check that the current access control context has all of the
     * given permissions.
     */
    private static void checkPermissions(Permissions perms) {
	SecurityManager sm = System.getSecurityManager();
	if (sm != null) {		// should never be null?
	    Enumeration enum = perms.elements();
	    while (enum.hasMoreElements()) {
		sm.checkPermission((Permission) enum.nextElement());
	    }
	}
    }

    /**
     * Returns the static permissions to be automatically granted to
     * classes loaded from the specified {@link CodeSource} and
     * defined by this class loader.
     *
     * <p><code>PreferredClassLoader</code> implements this method as
     * follows:
     *
     * <p>If there is a security manager and this
     * <code>PreferredClassLoader</code> was constructed to enforce
     * {@link DownloadPermission}, then this method checks that the
     * current security policy grants the specified
     * <code>CodeSource</code> the permission
     * <code>DownloadPermission("permit")</code>; if that check fails,
     * then this method throws a <code>SecurityException</code>.
     *
     * <p>Then this method invokes the superclass implementation of
     * {@link #getPermissions getPermissions} and returns the result.
     *
     * @param codeSource the <code>CodeSource</code> to return the
     * permissions to be granted to
     *
     * @return the permissions to be granted to the
     * <code>CodeSource</code>
     *
     * @throws SecurityException if there is a security manager, this
     * <code>PreferredClassLoader</code> was constructed to enforce
     * <code>DownloadPermission</code>, and the current security
     * policy does not grant the specified <code>CodeSource</code> the
     * permission <code>DownloadPermission("permit")</code>
     **/
    protected PermissionCollection getPermissions(CodeSource codeSource) {
	if (requireDlPerm) {
	    SecurityManager sm = System.getSecurityManager();
	    if (sm != null) {
		ProtectionDomain pd =
		    new ProtectionDomain(codeSource, null, this, null);

		if (!pd.implies(downloadPermission)) {
		    throw new SecurityException(
			"CodeSource not permitted to define class: " +
			codeSource);
		}
	    }
	}
	
  	return super.getPermissions(codeSource);
    }

    /**
     * Returns a string representation of this class loader.
     **/
    public String toString() {
	return super.toString() + "[\"" + exportAnnotation + "\"]";
    }
    
    /**
     * Return the access control context that a loader for the given
     * codebase URL path should execute with.
     */
    static AccessControlContext getLoaderAccessControlContext(URL[] urls)
    {
	/*
	 * The approach used here is taken from the similar method
	 * getAccessControlContext() in the sun.applet.AppletPanel class.
	 */
	// begin with permissions granted to all code in current policy
	PermissionCollection perms = (PermissionCollection)
	    AccessController.doPrivileged(new PrivilegedAction() {
		public Object run() {
		    CodeSource codesource =
			new CodeSource(null, (Certificate[]) null);
		    Policy p = java.security.Policy.getPolicy();
		    if (p != null) {
			return p.getPermissions(codesource);
		    } else {
			return new Permissions();
		    }
		}
	    });

	// createClassLoader permission needed to create loader in context
	perms.add(new RuntimePermission("createClassLoader"));

	// add permissions to read any "java.*" property
	perms.add(new java.util.PropertyPermission("java.*","read"));

	// add permissions required to load from codebase URL path
	addPermissionsForURLs(urls, perms, true);

	/*
	 * Create an AccessControlContext that consists of a single
	 * protection domain with only the permissions calculated above.
	 */
	ProtectionDomain pd = new ProtectionDomain(
	    new CodeSource((urls.length > 0 ? urls[0] : null),
			   (Certificate[]) null), perms);
	return new AccessControlContext(new ProtectionDomain[] { pd });
    }

    /**
     * Add the permissions necessary to load classes from a loader with
     * the given URLs for a codebase to the given permissions object.
     */
    static void addPermissionsForURLs(URL[] urls,
				      PermissionCollection perms,
				      boolean forLoader)
    {
	/*
	 * The approach used here was derived from the method
	 * getAccessControlContext() in the sun.applet.AppletPanel class.
	 */
	for (int i = 0; i < urls.length; i++) {
	    URL url = urls[i];

	    try {
		/*
		 * Get the permission required to connect to the particular
		 * codebase URL, according to the URL's connection object.
		 */
		Permission p = url.openConnection().getPermission();


		if (p != null) {
		    if (p instanceof FilePermission) {
			/*
			 * If the codebase is a file, the permission required
			 * to actually read classes from the codebase URL is
			 * the permission to read all files beneath the last
			 * directory in the file path, either because JAR
			 * files can refer to other JAR files in the same
			 * directory, or because permission to read a
			 * directory is not implied by permission to read the
			 * contents of a directory, which all that might be
			 * granted.
			 */
			String path = p.getName();
			int endIndex = path.lastIndexOf(File.separatorChar);
			if (endIndex != -1) {
			    path = path.substring(0, endIndex+1);
			    if (path.endsWith(File.separator)) {
				path += "-";
			    }
			    perms.add(new FilePermission(path, "read"));
			} else {
			    /*
			     * No directory separator: use permission to
			     * read the file.
			     */
			    perms.add(p);
			}
		    } else {
			/*
			 * Otherwise, assume that the permission we have is
			 * the real permission required to download classes
			 * from beneath the codebase URL.
			 */
			perms.add(p);

	                /*
			 * If the purpose of these permissions is to grant
			 * them to an instance of a URLClassLoader subclass,
			 * we must add permission to connect to and accept
			 * from the host of non-"file:" URLs, otherwise the
			 * getPermissions() method of URLClassLoader will
			 * throw a security exception.
			 */
			if (forLoader) {
			    String host = url.getHost();
			    if (host != null) {
				perms.add(new SocketPermission(host,
				    "connect, accept"));
			    }
			}
		    }
		}
	    } catch (IOException e) {
		/*
		 * This shouldn't happen, although it is declared to be
		 * thrown by openConnection() and getPermission().  If it
		 * does, don't bother granting or requiring any permissions
		 * for this URL.
		 */
	    }
	}
    }
}
