/*
 * 
 * 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.security;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.NotSerializableException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamField;
import java.io.StreamTokenizer;
import java.io.StringReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.security.AllPermission;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.UnresolvedPermission;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.jini.security.policy.DynamicPolicy;

/**
 * Permission required to dynamically grant permissions by security policy
 * providers which implement the {@link DynamicPolicy} interface.  Each
 * <code>GrantPermission</code> instance contains a set of permissions that can
 * be granted by code authorized with the <code>GrantPermission</code>.  When
 * the {@link DynamicPolicy#grant DynamicPolicy.grant} method is invoked, the
 * <code>checkPermission</code> method of the installed security manager (if
 * any) is called with a <code>GrantPermission</code> containing the
 * permissions to grant; if the calling context does not have any permissions
 * which imply the <code>GrantPermission</code>, then the grant operation will
 * fail.
 * <p>
 * In addition to authorizing granting of contained permissions, each
 * <code>GrantPermission</code> also authorizes granting of
 * <code>GrantPermission</code>s for contained permissions, as well as granting
 * of permissions contained within nested <code>GrantPermission</code>s.  For
 * example, if <code>GrantPermission g1</code> contains <code>Permission
 * p</code>, <code>g1</code> authorizes granting of both <code>p</code> and
 * <code>GrantPermission(p)</code>; if <code>GrantPermission g2</code> contains
 * <code>GrantPermission(p)</code>, then <code>g2</code> also authorizes
 * granting of both <code>p</code> and <code>GrantPermission(p)</code>.
 * <p>
 * The name (also referred to as the "target name") of each
 * <code>GrantPermission</code> instance carries a string representation of the
 * permissions contained by the <code>GrantPermission</code>, while the actions
 * string of each <code>GrantPermission</code> is always the empty string.  If
 * a <code>GrantPermission</code> is serialized, only its name string is sent
 * (i.e., contained permissions are not themselves serialized).  Upon
 * deserialization, the set of contained permissions is reconstituted based on
 * information in the name string.  <code>GrantPermission</code>s constructed
 * explicitly with {@link UnresolvedPermission}s (through either the {@link
 * #GrantPermission(Permission)} or {@link #GrantPermission(Permission[])}
 * constructor) will have incomplete target names that cannot be used to
 * instantiate other <code>GrantPermission</code>s, and will not be
 * serializable--attempting to serialize such a <code>GrantPermission</code>
 * will cause a <code>java.io.NotSerializableException</code> to be thrown.
 * <p>
 * The syntax of the target name approximates that used for specifying
 * permissions in the default security policy file; it is listed below using
 * the same grammar notation employed by <i>The Java(TM) Language
 * Specification</i>:
 * <pre>
 * <i>Target</i>:
 *   <i>DelimiterDeclaration</i><sub>opt</sub> <i>Permissions</i> ;<sub>opt</sub>
 *   
 * <i>DelimiterDeclaration</i>:
 *   delim = <i>DelimiterCharacter</i>
 *   
 * <i>Permissions</i>:
 *   <i>Permission</i>
 *   <i>Permissions</i> ; <i>Permission</i>
 *   
 * <i>Permission</i>:
 *   <i>PermissionClassName</i>
 *   <i>PermissionClassName Name</i>
 *   <i>PermissionClassName Name</i> , <i>Actions</i>
 *   
 * <i>PermissionClassName</i>:
 *   <i>ClassName</i>
 *   
 * <i>Name</i>:
 *   <i>DelimitedString</i>
 *   
 * <i>Actions</i>:
 *   <i>DelimitedString</i>
 * </pre>
 * The production for <i>ClassName</i> is the same as that used in <i>The
 * Java Language Specification</i>.  <i>DelimiterCharacter</i> can be any
 * unquoted non-whitespace character other than ';' (single and
 * double-quote characters themselves are allowed).  If
 * <i>DelimiterCharacter</i> is not specified, then the double-quote
 * character is the default delimiter.  <i>DelimitedString</i> is the same
 * as the <i>StringLiteral</i> production in <i>The Java Language
 * Specification</i>, except that it is delimited by the
 * <i>DelimiterDeclaration</i>-specified (or default) delimiter character
 * instead of the double-quote character exclusively.
 * <p>
 * Note that if the double-quote character is used as the delimiter and the
 * name or actions strings of specified permissions themselves contain nested
 * double-quote characters, then those characters must be escaped (or in some
 * cases doubly-escaped) appropriately.  For example, the following policy file
 * entry would yield a <code>GrantPermission</code> containing a
 * <code>FooPermission</code> in which the target name would include the word
 * "quoted" surrounded by double-quote characters:
 * <pre>
 * permission net.jini.security.GrantPermission
 *     "FooPermission \"a \\\"quoted\\\" string\"";
 * </pre>
 * For comparison, the following policy file entry which uses a custom
 * delimiter would yield an equivalent <code>GrantPermission</code>:
 * <pre>
 * permission net.jini.security.GrantPermission
 *     "delim=| FooPermission |a \"quoted\" string|";
 * </pre>
 * Some additional example policy file permissions:
 * <pre>
 * // allow granting of permission to listen for and accept connections
 * permission net.jini.security.GrantPermission
 *     "java.net.SocketPermission \"localhost:1024-\", \"accept,listen\"";
 *
 * // allow granting of permissions to read files under /foo, /bar directories
 * permission net.jini.security.GrantPermission 
 *     "delim=' java.io.FilePermission '/foo/-', 'read'; java.io.FilePermission '/bar/-', 'read'";
 *
 * // allow granting of permission for client authentication as jack, with or without delegation, to any server
 * permission net.jini.security.GrantPermission
 *     "delim=| net.jini.security.AuthenticationPermission |javax.security.auth.x500.X500Principal \"CN=jack\"|, |delegate|";
 * </pre>
 *
 * @author	Sun Microsystems, Inc.
 * @see		DynamicPolicy#grant(Class, Principal[], Permission[])
 * @since 2.0
 *
 * @com.sun.jini.impl <!-- Implementation Specifics -->
 *
 * This implementation uses the {@link Logger} named
 * <code>net.jini.security.policy</code> to log information at
 * the following levels:
 * <table summary="Describes what is logged by GrantPermission at various
 * logging levels" border=1 cellpadding=5>
 * <tr>
 * <th>Level</th>
 * <th>Description</th>
 * </tr>
 * <tr>
 * <td>{@link Level#FINE FINE}</td>
 * <td>failure to instantiate contained permission</td>
 * </tr>
 * <tr>
 * <td>{@link Level#FINEST FINEST}</td>
 * <td>resolution of contained permissions</td>
 * </tr>
 * </table>
 */
public final class GrantPermission extends Permission {

    private static final long serialVersionUID = 4668259055340724280L;
    
    private static final Class[] PARAMS0 = {};
    private static final Class[] PARAMS1 = { String.class };
    private static final Class[] PARAMS2 = { String.class, String.class };
    
    private static final Logger logger =
	Logger.getLogger("net.jini.security.policy");

    private transient Permission[] grants;
    private transient boolean unserializable;
    private transient volatile Implier implier;
    private transient volatile Integer hash;

    /**
     * Creates a <code>GrantPermission</code> for the permission(s) specified
     * in the name string.
     * 
     * @param	name string describing contained permissions
     * @throws	NullPointerException if <code>name</code> is <code>null</code>
     * @throws	IllegalArgumentException if unable to parse target name
     */
    public GrantPermission(String name) {
	super(name);
	initFromName(name);
    }
    
    /**
     * Creates a <code>GrantPermission</code> for the given permission.
     * 
     * @param	permission permission to allow to be granted
     * @throws	NullPointerException if <code>permission</code> is
     * 		<code>null</code>
     */
    public GrantPermission(Permission permission) {
	this(new Permission[]{ permission });
    }

    /**
     * Creates a <code>GrantPermission</code> for the given permissions.  The
     * permissions array passed in is neither modified nor retained; subsequent
     * changes to the array have no effect on the <code>GrantPermission</code>.
     * 
     * @param	permissions permissions to allow to be granted
     * @throws	NullPointerException if <code>permissions</code> array or any
     * 		element of <code>permissions</code> array is <code>null</code>
     */
    public GrantPermission(Permission[] permissions) {
	super(constructName(permissions = (Permission[]) permissions.clone()));
	grants = flatten(permissions);
	for (int i = 0; i < permissions.length; i++) {
	    if (permissions[i] instanceof UnresolvedPermission) {
		unserializable = true;
		return;
	    }
	}
    }
    
    /**
     * Returns canonical string representation of this permission's actions,
     * which for <code>GrantPermission</code> is always the empty string
     * <code>""</code>.
     * 
     * @return	the empty string <code>""</code>
     */
    public String getActions() {
	return "";
    }
    
    /**
     * Returns a newly created empty mutable permission collection for
     * <code>GrantPermission</code> instances.  The <code>implies</code> method
     * of the returned <code>PermissionCollection</code> instance is defined as
     * follows: for a given <code>GrantPermission g</code>, let
     * <code>c(g)</code> denote the set of all permissions contained within
     * <code>g</code> or within arbitrarily nested
     * <code>GrantPermission</code>s inside <code>g</code>, excluding nested
     * <code>GrantPermission</code>s themselves. Then, a <code>GrantPermission
     * g</code> is implied by the <code>PermissionCollection pc</code> if and
     * only if each permission in <code>c(g)</code> is implied by the union of
     * <code>c(p)</code> for all <code>p</code> in <code>pc</code>.  
     * <p>
     * Implication of contained
     * <code>java.security.UnresolvedPermission</code>s is special-cased: an
     * <code>UnresolvedPermission p1</code> is taken to imply another
     * <code>UnresolvedPermission p2</code> if and only if the serialized
     * representations of <code>p1</code> and <code>p2</code> are identical.
     * 
     * @return	newly created empty mutable permission collection for
     *		<code>GrantPermissions</code>
     */
    public PermissionCollection newPermissionCollection() {
	return new GrantPermissionCollection();
    }
    
    /**
     * Returns <code>true</code> if the given permission is a
     * <code>GrantPermission</code> implied by this permission, or
     * <code>false</code> otherwise.  Implication is defined as follows: for a
     * given <code>GrantPermission g</code>, let <code>c(g)</code> denote the
     * set of all permissions contained within <code>g</code> or within
     * arbitrarily nested <code>GrantPermission</code>s inside <code>g</code>,
     * excluding nested <code>GrantPermission</code>s themselves.  Then, a
     * <code>GrantPermission g1</code> is implied by another
     * <code>GrantPermission g2</code> if and only if each permission in
     * <code>c(g1)</code> is implied by <code>c(g2)</code>.
     * <p>
     * Implication of contained
     * <code>java.security.UnresolvedPermission</code>s is special-cased: an
     * <code>UnresolvedPermission p1</code> is taken to imply another
     * <code>UnresolvedPermission p2</code> if and only if the serialized
     * representations of <code>p1</code> and <code>p2</code> are identical.
     * 
     * @param	permission permission to check
     * @return	<code>true</code> if given permission is implied by this
     *		permission, <code>false</code> otherwise
     */
    public boolean implies(Permission permission) {
	if (!(permission instanceof GrantPermission)) {
	    return false;
	}
	// perm -> perm implies infrequent, so construct implier lazily
	if (implier == null) {
	    Implier imp = new Implier();
	    imp.add(this);
	    implier = imp;
	}
	return implier.implies(permission);
    }
    
    /**
     * Returns <code>true</code> if the given object is a
     * <code>GrantPermission</code> which both implies and is implied by this
     * permission; returns <code>false</code> otherwise.
     * 
     * @param	obj object to compare against
     * @return	<code>true</code> if given object is a
     * 		<code>GrantPermission</code> which both implies and is implied
     * 		by this permission, <code>false</code> otherwise
     */
    public boolean equals(Object obj) {
	if (this == obj) {
	    return true;
	}
	if (obj instanceof GrantPermission) {
	    GrantPermission gp = (GrantPermission) obj;
	    return this.implies(gp) && gp.implies(this);
	}
	return false;
    }
    
    public int hashCode() {
	if (hash == null) {
	    hash = new Integer(computeHashCode());
	}
	return hash.intValue();
    }

    /**
     * Returns hash code computed by summing hash codes of each distinct
     * permission class name.
     */
    private int computeHashCode() {
	int sum = 0;
	HashSet set = new HashSet(grants.length);
	for (int i = 0; i < grants.length; i++) {
	    Permission p = grants[i];
	    String pcn = p.getClass().getName();
	    if (p instanceof AllPermission) {
		return pcn.hashCode();
	    } else if (p instanceof UnresolvedPermission) {
		pcn += ":" + p.getName();    // add name of unresolved class
	    }
	    if (!set.contains(pcn)) {
		set.add(pcn);
		sum += pcn.hashCode();
	    }
	}
	return sum;
    }

    /**
     * Writes target name representing contained permissions.
     *
     * @throws	NotSerializableException if the <code>GrantPermission</code>
     * 		was constructed explicitly with
     * 		<code>java.security.UnresolvedPermission</code>s
     */
    private void writeObject(ObjectOutputStream out) throws IOException {
	if (unserializable) {
	    throw new NotSerializableException(
		GrantPermission.class.getName());
	}
	out.defaultWriteObject();
    }

    /**
     * Reconstitutes contained permissions based on the information in the
     * target name.
     * 
     * @throws	InvalidObjectException if the target name is <code>null</code>
     * 		or does not conform to the syntax specified in the
     * 		documentation for {@link GrantPermission}
     */
    private void readObject(ObjectInputStream in)
	throws IOException, ClassNotFoundException
    {
	in.defaultReadObject();
	try {
	    initFromName(getName());
	} catch (RuntimeException e) {
	    if (e instanceof NullPointerException ||
		e instanceof IllegalArgumentException)
	    {
		InvalidObjectException ee =
		    new InvalidObjectException(e.getMessage());
		ee.initCause(e);
		throw ee;
	    }
	    throw e;
	}
    }
    
    /**
     * Initializes GrantPermission to contain permissions described in the
     * given name.  Throws an IllegalArgumentException if the name is
     * misformatted, or specifies an invalid permission class.  Throws a
     * SecurityException if access to the class is not permitted.
     */
    private void initFromName(String name) {
	PermissionInfo[] pia = parsePermissions(name);
	ArrayList l = new ArrayList();
	for (int i = 0; i < pia.length; i++) {
	    PermissionInfo pi = pia[i];

	    SecurityManager sm = System.getSecurityManager();
	    if (sm != null) {
		int d = pi.type.lastIndexOf('.');
		if (d != -1) {
		    sm.checkPackageAccess(pi.type.substring(0, d));
		}
	    }
	    Class cl;
	    try {
		cl = Class.forName(pi.type);
	    } catch (ClassNotFoundException ex) {
		l.add(new UnresolvedPermission(
		    pi.type, pi.name, pi.actions, null));
		continue;
	    }
	    if (!Permission.class.isAssignableFrom(cl)) {
		throw new IllegalArgumentException(
		    "not a permission class: " + cl);
	    }
	    if (!Modifier.isPublic(cl.getModifiers())) {
		throw new IllegalArgumentException(
		    "non-public permission class: " + cl);
	    }
	    
	    if (pi.name == null) {
		try {
		    Constructor c = cl.getConstructor(PARAMS0);
		    l.add(c.newInstance(new Object[0]));
		    continue;
		} catch (Exception ex) {
		}
	    } 
	    if (pi.actions == null) {
		try {
		    Constructor c = cl.getConstructor(PARAMS1);
		    l.add(c.newInstance(new Object[]{ pi.name }));
		    continue;
		} catch (Exception ex) {
		}
	    } 
	    try {
		Constructor c = cl.getConstructor(PARAMS2);
		l.add(c.newInstance(new Object[]{ pi.name, pi.actions }));
		continue;
	    } catch (Exception ex) {
	    }
	    throw new IllegalArgumentException(
		"uninstantiable permission class: " + cl);
	}
	grants = flatten((Permission[]) l.toArray(new Permission[l.size()]));
	if (logger.isLoggable(Level.FINEST)) {
	    logger.log(Level.FINEST, "parsed {0} from {1}",
		       new Object[]{ l, name });
	}
    }

    /**
     * Parses permission information from given GrantPermission name string.
     * Throws an IllegalArgumentException if the name string is misformatted.
     */
    private static PermissionInfo[] parsePermissions(String s) {
	try {
	    ArrayList l = new ArrayList();
	    StreamTokenizer st = createTokenizer(s);
	    char delim = '"';

	    if (st.nextToken() == StreamTokenizer.TT_WORD &&
		st.sval.equals("delim"))
	    {
		if (st.nextToken() == '=') {
		    if (st.nextToken() == StreamTokenizer.TT_WORD) {
			if (st.sval.length() > 1) {
			    throw new IllegalArgumentException(
				"excess delimiter characters");
			}
			delim = st.sval.charAt(0);
		    } else {
			delim = (char) st.ttype;
		    }
		    if (delim == ';') {
			throw new IllegalArgumentException(
			    "illegal delimiter ';'");
		    }
		} else {	// rewind
		    st = createTokenizer(s);
		}
		st.nextToken();
	    }
	    st.quoteChar(delim);

	    do {
		String type, name = null, actions = null;

		if (st.ttype != StreamTokenizer.TT_WORD) {
		    throw new IllegalArgumentException(
			"expected permission type");
		}
		type = st.sval;
		
		// REMIND: allow unquoted name/actions?
		st.nextToken();
		if (st.ttype == StreamTokenizer.TT_EOF || st.ttype == ';') {
		    l.add(new PermissionInfo(type, null, null));
		    continue;
		} else if (st.ttype == delim) {
		    name = st.sval;
		} else {
		    throw new IllegalArgumentException(
			"expected permission name or ';'");
		}
		
		st.nextToken();
		if (st.ttype == StreamTokenizer.TT_EOF || st.ttype == ';') {
		    l.add(new PermissionInfo(type, name, null));
		    continue;
		} else if (st.ttype != ',') {
		    throw new IllegalArgumentException("expected ',' or ';'");
		}

		if (st.nextToken() != delim) {
		    throw new IllegalArgumentException(
			"expected permission actions");
		}
		actions = st.sval;
		
		st.nextToken();
		if (st.ttype == StreamTokenizer.TT_EOF || st.ttype == ';') {
		    l.add(new PermissionInfo(type, name, actions));
		    continue;
		} else {
		    throw new IllegalArgumentException("expected ';'");
		}

	    } while (st.nextToken() != StreamTokenizer.TT_EOF);

	    return (PermissionInfo[]) l.toArray(new PermissionInfo[l.size()]);
	} catch (IOException ex) {
	    throw (Error) new InternalError().initCause(ex);
	}
    }

    /**
     * Returns tokenizer for parsing given string.  The tokenizer is configured
     * similarly to that used by sun.security.provider.PolicyParser, except
     * that comments are disabled and no quote character is set (yet).
     */
    private static StreamTokenizer createTokenizer(String s) {
	StreamTokenizer st = new StreamTokenizer(new StringReader(s));
	st.resetSyntax();
	st.wordChars('a', 'z');
	st.wordChars('A', 'Z');
	st.wordChars('.', '.');
	st.wordChars('0', '9');
	st.wordChars('_', '_');
	st.wordChars('$', '$');
	st.wordChars(128 + 32, 255);
	st.whitespaceChars(0, ' ');
	st.lowerCaseMode(false);
	st.ordinaryChar('/');
	st.slashSlashComments(false);
	st.slashStarComments(false);
	return st;
    }

    /**
     * Constructs GrantPermission name/target string appropriate for given list
     * of permissions.
     */
    private static String constructName(Permission[] pa) {
	StringBuffer sb = new StringBuffer();
	for (int i = 0; i < pa.length; i++) {
	    Permission p = pa[i];
	    if (p instanceof UnresolvedPermission) {
		sb.append(p + "; ");
	    } else {
		Class cl = p.getClass();
		int nargs = maxConsArgs(cl);
		String t = cl.getName(), n = p.getName(), a = p.getActions();
		if (nargs == 2 && a != null) {
		    // REMIND: handle null name?
		    sb.append(t + " " + quote(n) + ", " + quote(a) + "; ");
		} else if (nargs >= 1 && n != null) {
		    sb.append(t + " " + quote(n) + "; ");
		} else {
		    sb.append(t + "; ");
		}
	    }
	}
	return sb.toString().trim();
    }

    /**
     * Returns the maximum number of String parameters (up to 2) accepted by a
     * constructor of the given class.  Returns -1 if no matching constructor
     * (including no-arg constructor) is defined by given class.
     */
    private static int maxConsArgs(Class cl) {
	try {
	    cl.getConstructor(PARAMS2);
	    return 2;
	} catch (Exception ex) {
	}
	try {
	    cl.getConstructor(PARAMS1);
	    return 1;
	} catch (Exception ex) {
	}
	try {
	    cl.getConstructor(PARAMS0);
	    return 0;
	} catch (Exception ex) {
	}
	return -1;
    }

    /**
     * Returns quoted string literal that, if parsed by
     * java.io.StreamTokenizer, would yield the given string.  This method is
     * essentially a copy of com.sun.jini.config.ConfigUtil.stringLiteral; the
     * two methods are kept separate since ConfigUtil.stringLiteral could
     * conceivably escape unicode characters, while such escaping would be
     * incorrect for GrantPermission.
     */
    private static String quote(String s) {
	StringBuffer sb = new StringBuffer(s.length() + 2);
	sb.append('"');
	char[] ca = s.toCharArray();
	for (int i = 0; i < ca.length; i++) {
	    char c = ca[i];
	    if (c == '\\' || c == '"') {
		sb.append("\\").append(c);
	    } else if (c == '\n') {
		sb.append("\\n");
	    } else if (c == '\r') {
		sb.append("\\r");
	    } else if (c == '\t') {
		sb.append("\\t");
	    } else if (c == '\f') {
		sb.append("\\f");
	    } else if (c == '\b') {
		sb.append("\\b");
	    } else if (c < 0x20) {
		sb.append("\\").append(Integer.toOctalString(c));
	    } else {
		sb.append(c);
	    }
	}
	return sb.append('"').toString();
    }

    /**
     * Returns an array containing all non-GrantPermission permissions in the
     * given permission array, including those contained in nested
     * GrantPermissions in the array.
     */
    private static Permission[] flatten(Permission[] pa) {
	List l = new ArrayList(pa.length);
	for (int i = 0; i < pa.length; i++) {
	    Permission p = pa[i];
	    if (p instanceof GrantPermission) {
		l.addAll(Arrays.asList(((GrantPermission) p).grants));
	    } else {
		l.add(p);
	    }
	}
	return (Permission[]) l.toArray(new Permission[l.size()]);
    }

    /**
     * Parsed information about a permission.
     */
    private static class PermissionInfo {
	
	final String type;
	final String name;
	final String actions;
	
	PermissionInfo(String type, String name, String actions) {
	    this.type = type;
	    this.name = name;
	    this.actions = actions;
	}
    }
    
    /**
     * Class for checking implication of contained permissions.
     */
    private static class Implier {
	
	private final PermissionCollection perms = new Permissions();
	private final ArrayList unresolved = new ArrayList();

	void add(GrantPermission gp) {
	    for (int i = 0; i < gp.grants.length; i++) {
		Permission p = gp.grants[i];
		if (!impliesContained(p)) {
		    perms.add(p);
		    if (p instanceof UnresolvedPermission) {
			unresolved.add(p);
		    }
		}
	    }
	}
	
	boolean implies(Permission p) {
	    if (!(p instanceof GrantPermission)) {
		return false;
	    }
	    Permission[] pa = ((GrantPermission) p).grants;
	    for (int i = 0; i < pa.length; i++) {
		if (!impliesContained(pa[i])) {
		    return false;
		}
	    }
	    return true;
	}

	private boolean impliesContained(Permission p) {
	    if (p instanceof UnresolvedPermission) {
		for (Iterator i = unresolved.iterator(); i.hasNext();) {
		    if (implies((UnresolvedPermission) i.next(),
				(UnresolvedPermission) p))
		    {
			return true;
		    }
		}
		return false;
	    } else {
		return perms.implies(p);
	    }
	}
	
	private static boolean implies(UnresolvedPermission p1,
				       UnresolvedPermission p2)
	{
	    if (p1 == p2) {
		return true;
	    }
	    // REMIND: use UnresolvedPermission.equals() once 4513737 fixed
	    try {
		ByteArrayOutputStream bout = new ByteArrayOutputStream();
		ObjectOutputStream oout = new ObjectOutputStream(bout);

		bout.reset();
		oout.writeObject(p1);
		oout.flush();
		byte[] b1 = bout.toByteArray();
		
		oout.reset();
		bout.reset();
		oout.writeObject(p2);
		oout.flush();
		byte[] b2 = bout.toByteArray();
		
		return Arrays.equals(b1, b2);
	    } catch (IOException ex) {
		throw (Error) new InternalError().initCause(ex);
	    }
	}
    }

    /**
     * PermissionCollection variant returned by newPermissionCollection().
     *
     * @serial include
     */
    static class GrantPermissionCollection extends PermissionCollection {

	private static final long serialVersionUID = 8227621799817733985L;

	/**
	 * @serialField perms List The permissions.
	 */
	private static final ObjectStreamField[] serialPersistentFields = {
	    new ObjectStreamField("perms", List.class, true)
	};

	private List perms = new ArrayList();
	private Implier implier = new Implier();

	public synchronized void add(Permission p) {
	    if (!(p instanceof GrantPermission)) {
		throw new IllegalArgumentException("invalid permission: " + p);
	    }
	    if (isReadOnly()) {
		throw new SecurityException(
		    "can't add to read-only PermissionCollection");
	    }
	    perms.add(p);
	    implier.add((GrantPermission) p);
	}
	
	public synchronized Enumeration elements() {
	    return Collections.enumeration(perms);
	}
	
	public synchronized boolean implies(Permission p) {
	    return implier.implies(p);
	}

	public synchronized void setReadOnly() {
	    super.setReadOnly();
	}

	public synchronized boolean isReadOnly() {
	    return super.isReadOnly();
	}

	/**
	 * Writes the permissions list.
	 */
	private synchronized void writeObject(ObjectOutputStream s)
	    throws IOException
	{
	    s.defaultWriteObject();
	}

	/**
	 * Verifies the permissions list.
	 *
	 * @throws InvalidObjectException if the list is
	 * 	   <code>null</code> or any element is not an instance of
	 * 	   <code>GrantPermission</code>
	 */
	private void readObject(ObjectInputStream s)
	    throws IOException, ClassNotFoundException
	{
	    s.defaultReadObject();
	    if (perms == null) {
		throw new InvalidObjectException("list cannot be null");
	    }
	    if (!perms.getClass().equals(ArrayList.class)) {
		perms = new ArrayList(perms);
	    }
	    if (perms.contains(null)) {
		throw new InvalidObjectException(
		    "element must be a GrantPermission");
	    }

	    GrantPermission[] pa;
	    try {
		pa = (GrantPermission[]) 
		    perms.toArray(new GrantPermission[perms.size()]);
	    } catch (ArrayStoreException e) {
		throw new InvalidObjectException(
		    "element must be a GrantPermission");
	    }

	    implier = new Implier();
	    for (int i = 0; i < pa.length; i++) {
		implier.add(pa[i]);
	    }
	}
    }
}
