/*
 * @(#)Subject.java	1.128 10/03/23
 *
 * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package javax.security.auth;

import java.util.*;
import java.io.*;
import java.lang.reflect.*;
import java.text.MessageFormat;
import java.security.AccessController;
import java.security.AccessControlContext;
import java.security.DomainCombiner;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.security.PrivilegedExceptionAction;
import java.security.PrivilegedActionException;
import java.security.ProtectionDomain;
import sun.security.util.ResourcesMgr;
import sun.security.util.SecurityConstants;

/**
 * <p> A <code>Subject</code> represents a grouping of related information
 * for a single entity, such as a person.
 * Such information includes the Subject's identities as well as
 * its security-related attributes
 * (passwords and cryptographic keys, for example).
 *
 * <p> Subjects may potentially have multiple identities.
 * Each identity is represented as a <code>Principal</code>
 * within the <code>Subject</code>.  Principals simply bind names to a
 * <code>Subject</code>.  For example, a <code>Subject</code> that happens
 * to be a person, Alice, might have two Principals:
 * one which binds "Alice Bar", the name on her driver license,
 * to the <code>Subject</code>, and another which binds,
 * "999-99-9999", the number on her student identification card,
 * to the <code>Subject</code>.  Both Principals refer to the same
 * <code>Subject</code> even though each has a different name.
 *
 * <p> A <code>Subject</code> may also own security-related attributes,
 * which are referred to as credentials.
 * Sensitive credentials that require special protection, such as
 * private cryptographic keys, are stored within a private credential
 * <code>Set</code>.  Credentials intended to be shared, such as
 * public key certificates or Kerberos server tickets are stored
 * within a public credential <code>Set</code>.  Different permissions
 * are required to access and modify the different credential Sets.
 *
 * <p> To retrieve all the Principals associated with a <code>Subject</code>,
 * invoke the <code>getPrincipals</code> method.  To retrieve
 * all the public or private credentials belonging to a <code>Subject</code>,
 * invoke the <code>getPublicCredentials</code> method or
 * <code>getPrivateCredentials</code> method, respectively.
 * To modify the returned <code>Set</code> of Principals and credentials,
 * use the methods defined in the <code>Set</code> class.
 * For example:
 * <pre>
 *	Subject subject;
 *	Principal principal;
 *	Object credential;
 *
 *	// add a Principal and credential to the Subject
 *	subject.getPrincipals().add(principal);
 *	subject.getPublicCredentials().add(credential);
 * </pre>
 *
 * <p> This <code>Subject</code> class implements <code>Serializable</code>.
 * While the Principals associated with the <code>Subject</code> are serialized,
 * the credentials associated with the <code>Subject</code> are not.
 * Note that the <code>java.security.Principal</code> class
 * does not implement <code>Serializable</code>.  Therefore all concrete
 * <code>Principal</code> implementations associated with Subjects
 * must implement <code>Serializable</code>.
 *
 * @version 1.128, 03/23/10
 * @see java.security.Principal
 * @see java.security.DomainCombiner
 */
public final class Subject implements java.io.Serializable {

    private static final long serialVersionUID = -8308522755600156056L;

    /**
     * A <code>Set</code> that provides a view of all of this
     * Subject's Principals
     *
     * <p>
     *
     * @serial Each element in this set is a
     *		<code>java.security.Principal</code>.
     *		The set is a <code>Subject.SecureSet</code>.
     */
    Set principals;

    /**
     * Sets that provide a view of all of this
     * Subject's Credentials
     */
    transient Set pubCredentials;
    transient Set privCredentials;

    /**
     * Whether this Subject is read-only
     *
     * @serial
     */
    private volatile boolean readOnly = false;

    private static final int PRINCIPAL_SET = 1;
    private static final int PUB_CREDENTIAL_SET = 2;
    private static final int PRIV_CREDENTIAL_SET = 3;

    /**
     * Create an instance of a <code>Subject</code>
     * with an empty <code>Set</code> of Principals and empty
     * Sets of public and private credentials.
     *
     * <p> The newly constructed Sets check whether this <code>Subject</code>
     * has been set read-only before permitting subsequent modifications.
     * The newly created Sets also prevent illegal modifications
     * by ensuring that callers have sufficient permissions.
     *
     * <p> To modify the Principals Set, the caller must have
     * <code>AuthPermission("modifyPrincipals")</code>.
     * To modify the public credential Set, the caller must have
     * <code>AuthPermission("modifyPublicCredentials")</code>.
     * To modify the private credential Set, the caller must have
     * <code>AuthPermission("modifyPrivateCredentials")</code>.
     */
    public Subject() {

	this.principals = Collections.synchronizedSet
				(new SecureSet(this, PRINCIPAL_SET));
	this.pubCredentials = Collections.synchronizedSet
				(new SecureSet(this, PUB_CREDENTIAL_SET));
	this.privCredentials = Collections.synchronizedSet
				(new SecureSet(this, PRIV_CREDENTIAL_SET));
    }

    /**
     * Create an instance of a <code>Subject</code> with
     * Principals and credentials.
     *
     * <p> The Principals and credentials from the specified Sets
     * are copied into newly constructed Sets.
     * These newly created Sets check whether this <code>Subject</code>
     * has been set read-only before permitting subsequent modifications.
     * The newly created Sets also prevent illegal modifications
     * by ensuring that callers have sufficient permissions.
     *
     * <p> To modify the Principals Set, the caller must have
     * <code>AuthPermission("modifyPrincipals")</code>.
     * To modify the public credential Set, the caller must have
     * <code>AuthPermission("modifyPublicCredentials")</code>.
     * To modify the private credential Set, the caller must have
     * <code>AuthPermission("modifyPrivateCredentials")</code>.
     * <p>
     *
     * @param readOnly true if the <code>Subject</code> is to be read-only,
     *		and false otherwise. <p>
     *
     * @param principals the <code>Set</code> of Principals
     *		to be associated with this <code>Subject</code>. <p>
     *
     * @param pubCredentials the <code>Set</code> of public credentials
     *		to be associated with this <code>Subject</code>. <p>
     *
     * @param privCredentials the <code>Set</code> of private credentials
     *		to be associated with this <code>Subject</code>.
     *
     * @exception NullPointerException if the specified
     *		<code>principals</code>, <code>pubCredentials</code>,
     *		or <code>privCredentials</code> are <code>null</code>.
     */
    public Subject(boolean readOnly, Set<? extends Principal> principals,
		   Set<?> pubCredentials, Set<?> privCredentials)
    {

	if (principals == null ||
	    pubCredentials == null ||
	    privCredentials == null)
	    throw new NullPointerException
		(ResourcesMgr.getString("invalid null input(s)"));

	this.principals = Collections.synchronizedSet(new SecureSet
				(this, PRINCIPAL_SET, principals));
	this.pubCredentials = Collections.synchronizedSet(new SecureSet
				(this, PUB_CREDENTIAL_SET, pubCredentials));
	this.privCredentials = Collections.synchronizedSet(new SecureSet
				(this, PRIV_CREDENTIAL_SET, privCredentials));
	this.readOnly = readOnly;
    }

    /**
     * Set this <code>Subject</code> to be read-only.
     *
     * <p> Modifications (additions and removals) to this Subject's
     * <code>Principal</code> <code>Set</code> and
     * credential Sets will be disallowed.
     * The <code>destroy</code> operation on this Subject's credentials will
     * still be permitted.
     *
     * <p> Subsequent attempts to modify the Subject's <code>Principal</code>
     * and credential Sets will result in an
     * <code>IllegalStateException</code> being thrown.
     * Also, once a <code>Subject</code> is read-only,
     * it can not be reset to being writable again.
     *
     * <p>
     *
     * @exception SecurityException if the caller does not have permission
     *		to set this <code>Subject</code> to be read-only.
     */
    public void setReadOnly() {
	java.lang.SecurityManager sm = System.getSecurityManager();
	if (sm != null) {
	    sm.checkPermission(new AuthPermission("setReadOnly"));
	}

	this.readOnly = true;
    }

    /**
     * Query whether this <code>Subject</code> is read-only.
     *
     * <p>
     *
     * @return true if this <code>Subject</code> is read-only, false otherwise.
     */
    public boolean isReadOnly() {
	return this.readOnly;
    }

    /**
     * Get the <code>Subject</code> associated with the provided
     * <code>AccessControlContext</code>.
     *
     * <p> The <code>AccessControlContext</code> may contain many
     * Subjects (from nested <code>doAs</code> calls).
     * In this situation, the most recent <code>Subject</code> associated
     * with the <code>AccessControlContext</code> is returned.
     *
     * <p>
     *
     * @param  acc the <code>AccessControlContext</code> from which to retrieve
     *		the <code>Subject</code>.
     *
     * @return  the <code>Subject</code> associated with the provided
     *		<code>AccessControlContext</code>, or <code>null</code>
     *		if no <code>Subject</code> is associated
     *		with the provided <code>AccessControlContext</code>.
     *
     * @exception SecurityException if the caller does not have permission
     *		to get the <code>Subject</code>. <p>
     *
     * @exception NullPointerException if the provided
     *		<code>AccessControlContext</code> is <code>null</code>.
     */
    public static Subject getSubject(final AccessControlContext acc) {

	java.lang.SecurityManager sm = System.getSecurityManager();
	if (sm != null) {
	    sm.checkPermission(new AuthPermission("getSubject"));
	}

	if (acc == null) {
	    throw new NullPointerException(ResourcesMgr.getString
		("invalid null AccessControlContext provided"));
	}

	// return the Subject from the DomainCombiner of the provided context
	return (Subject)AccessController.doPrivileged
	    (new java.security.PrivilegedAction() {
	    public Object run() {
		DomainCombiner dc = acc.getDomainCombiner();
		if (!(dc instanceof SubjectDomainCombiner))
		    return null;
		SubjectDomainCombiner sdc = (SubjectDomainCombiner)dc;
		return sdc.getSubject();
	    }
	});
    }

    /**
     * Perform work as a particular <code>Subject</code>.
     *
     * <p> This method first retrieves the current Thread's
     * <code>AccessControlContext</code> via
     * <code>AccessController.getContext</code>,
     * and then instantiates a new <code>AccessControlContext</code>
     * using the retrieved context along with a new
     * <code>SubjectDomainCombiner</code> (constructed using
     * the provided <code>Subject</code>).
     * Finally, this method invokes <code>AccessController.doPrivileged</code>,
     * passing it the provided <code>PrivilegedAction</code>,
     * as well as the newly constructed <code>AccessControlContext</code>.
     *
     * <p>
     *
     * @param subject the <code>Subject</code> that the specified
     *			<code>action</code> will run as.  This parameter
     *			may be <code>null</code>. <p>
     *
     * @param action the code to be run as the specified
     *			<code>Subject</code>. <p>
     *
     * @return the value returned by the PrivilegedAction's
     *			<code>run</code> method.
     *
     * @exception NullPointerException if the <code>PrivilegedAction</code>
     *			is <code>null</code>. <p>
     *
     * @exception SecurityException if the caller does not have permission
     *			to invoke this method.
     */
    public static <T> T doAs(final Subject subject,
			final java.security.PrivilegedAction<T> action) {

	java.lang.SecurityManager sm = System.getSecurityManager();
	if (sm != null) {
	    sm.checkPermission(SecurityConstants.DO_AS_PERMISSION);
	}
	if (action == null)
	    throw new NullPointerException
		(ResourcesMgr.getString("invalid null action provided"));

	// set up the new Subject-based AccessControlContext
	// for doPrivileged
	final AccessControlContext currentAcc = AccessController.getContext();

	// call doPrivileged and push this new context on the stack
	return java.security.AccessController.doPrivileged
					(action,
					createContext(subject, currentAcc));
    }

    /**
     * Perform work as a particular <code>Subject</code>.
     *
     * <p> This method first retrieves the current Thread's
     * <code>AccessControlContext</code> via
     * <code>AccessController.getContext</code>,
     * and then instantiates a new <code>AccessControlContext</code>
     * using the retrieved context along with a new
     * <code>SubjectDomainCombiner</code> (constructed using
     * the provided <code>Subject</code>).
     * Finally, this method invokes <code>AccessController.doPrivileged</code>,
     * passing it the provided <code>PrivilegedExceptionAction</code>,
     * as well as the newly constructed <code>AccessControlContext</code>.
     *
     * <p>
     *
     * @param subject the <code>Subject</code> that the specified
     *			<code>action</code> will run as.  This parameter
     *			may be <code>null</code>. <p>
     *
     * @param action the code to be run as the specified
     *			<code>Subject</code>. <p>
     *
     * @return the value returned by the
     *			PrivilegedExceptionAction's <code>run</code> method.
     *
     * @exception PrivilegedActionException if the
     *			<code>PrivilegedExceptionAction.run</code>
     *			method throws a checked exception. <p>
     *
     * @exception NullPointerException if the specified
     *			<code>PrivilegedExceptionAction</code> is
     *			<code>null</code>. <p>
     *
     * @exception SecurityException if the caller does not have permission
     *			to invoke this method.
     */
    public static <T> T doAs(final Subject subject,
			final java.security.PrivilegedExceptionAction<T> action)
			throws java.security.PrivilegedActionException {

	java.lang.SecurityManager sm = System.getSecurityManager();
	if (sm != null) {
	    sm.checkPermission(SecurityConstants.DO_AS_PERMISSION);
	}

	if (action == null)
	    throw new NullPointerException
		(ResourcesMgr.getString("invalid null action provided"));

	// set up the new Subject-based AccessControlContext for doPrivileged
	final AccessControlContext currentAcc = AccessController.getContext();

	// call doPrivileged and push this new context on the stack
	return java.security.AccessController.doPrivileged
					(action,
					createContext(subject, currentAcc));
    }

    /**
     * Perform privileged work as a particular <code>Subject</code>.
     *
     * <p> This method behaves exactly as <code>Subject.doAs</code>,
     * except that instead of retrieving the current Thread's
     * <code>AccessControlContext</code>, it uses the provided
     * <code>AccessControlContext</code>.  If the provided
     * <code>AccessControlContext</code> is <code>null</code>,
     * this method instantiates a new <code>AccessControlContext</code>
     * with an empty collection of ProtectionDomains.
     *
     * <p>
     *
     * @param subject the <code>Subject</code> that the specified
     *			<code>action</code> will run as.  This parameter
     *			may be <code>null</code>. <p>
     *
     * @param action the code to be run as the specified
     *			<code>Subject</code>. <p>
     *
     * @param acc the <code>AccessControlContext</code> to be tied to the
     *			specified <i>subject</i> and <i>action</i>. <p>
     *
     * @return the value returned by the PrivilegedAction's
     *			<code>run</code> method.
     *
     * @exception NullPointerException if the <code>PrivilegedAction</code>
     *			is <code>null</code>. <p>
     *
     * @exception SecurityException if the caller does not have permission
     *			to invoke this method.
     */
    public static <T> T doAsPrivileged(final Subject subject,
			final java.security.PrivilegedAction<T> action,
			final java.security.AccessControlContext acc) {

	java.lang.SecurityManager sm = System.getSecurityManager();
	if (sm != null) {
	    sm.checkPermission(SecurityConstants.DO_AS_PRIVILEGED_PERMISSION);
	}

	if (action == null)
	    throw new NullPointerException
		(ResourcesMgr.getString("invalid null action provided"));

	// set up the new Subject-based AccessControlContext
	// for doPrivileged
	final AccessControlContext callerAcc =
		(acc == null ?
		new AccessControlContext(new ProtectionDomain[0]) :
		acc);

	// call doPrivileged and push this new context on the stack
	return java.security.AccessController.doPrivileged
					(action,
					createContext(subject, callerAcc));
    }

    /**
     * Perform privileged work as a particular <code>Subject</code>.
     *
     * <p> This method behaves exactly as <code>Subject.doAs</code>,
     * except that instead of retrieving the current Thread's
     * <code>AccessControlContext</code>, it uses the provided
     * <code>AccessControlContext</code>.  If the provided
     * <code>AccessControlContext</code> is <code>null</code>,
     * this method instantiates a new <code>AccessControlContext</code>
     * with an empty collection of ProtectionDomains.
     *
     * <p>
     *
     * @param subject the <code>Subject</code> that the specified
     *			<code>action</code> will run as.  This parameter
     *			may be <code>null</code>. <p>
     *
     * @param action the code to be run as the specified
     *			<code>Subject</code>. <p>
     *
     * @param acc the <code>AccessControlContext</code> to be tied to the
     *			specified <i>subject</i> and <i>action</i>. <p>
     *
     * @return the value returned by the
     *			PrivilegedExceptionAction's <code>run</code> method.
     *
     * @exception PrivilegedActionException if the
     *			<code>PrivilegedExceptionAction.run</code>
     *			method throws a checked exception. <p>
     *
     * @exception NullPointerException if the specified
     *			<code>PrivilegedExceptionAction</code> is
     *			<code>null</code>. <p>
     *
     * @exception SecurityException if the caller does not have permission
     *			to invoke this method.
     */
    public static <T> T doAsPrivileged(final Subject subject,
			final java.security.PrivilegedExceptionAction<T> action,
			final java.security.AccessControlContext acc)
			throws java.security.PrivilegedActionException {

	java.lang.SecurityManager sm = System.getSecurityManager();
	if (sm != null) {
	    sm.checkPermission(SecurityConstants.DO_AS_PRIVILEGED_PERMISSION);
	}

	if (action == null)
	    throw new NullPointerException
		(ResourcesMgr.getString("invalid null action provided"));

	// set up the new Subject-based AccessControlContext for doPrivileged
	final AccessControlContext callerAcc =
		(acc == null ?
		new AccessControlContext(new ProtectionDomain[0]) :
		acc);

	// call doPrivileged and push this new context on the stack
	return java.security.AccessController.doPrivileged
					(action,
					createContext(subject, callerAcc));
    }

    private static AccessControlContext createContext(final Subject subject,
					final AccessControlContext acc) {


	return (AccessControlContext)
	    java.security.AccessController.doPrivileged
	    (new java.security.PrivilegedAction() {
	    public Object run() {
		if (subject == null)
	            return new AccessControlContext(acc, null);
		else
		    return new AccessControlContext
					(acc,
					new SubjectDomainCombiner(subject));
	    }
	});
    }

    /**
     * Return the <code>Set</code> of Principals associated with this
     * <code>Subject</code>.  Each <code>Principal</code> represents
     * an identity for this <code>Subject</code>.
     *
     * <p> The returned <code>Set</code> is backed by this Subject's
     * internal <code>Principal</code> <code>Set</code>.  Any modification
     * to the returned <code>Set</code> affects the internal
     * <code>Principal</code> <code>Set</code> as well.
     *
     * <p>
     *
     * @return	The <code>Set</code> of Principals associated with this
     *		<code>Subject</code>.
     */
    public Set<Principal> getPrincipals() {

	// always return an empty Set instead of null
	// so LoginModules can add to the Set if necessary
	return principals;
    }

    /**
     * Return a <code>Set</code> of Principals associated with this
     * <code>Subject</code> that are instances or subclasses of the specified
     * <code>Class</code>.
     *
     * <p> The returned <code>Set</code> is not backed by this Subject's
     * internal <code>Principal</code> <code>Set</code>.  A new
     * <code>Set</code> is created and returned for each method invocation.
     * Modifications to the returned <code>Set</code>
     * will not affect the internal <code>Principal</code> <code>Set</code>.
     *
     * <p>
     *
     * @param c the returned <code>Set</code> of Principals will all be
     *		instances of this class.
     *
     * @return a <code>Set</code> of Principals that are instances of the
     *		specified <code>Class</code>.
     *
     * @exception NullPointerException if the specified <code>Class</code> 
     *			is <code>null</code>.
     */
    public <T extends Principal> Set<T> getPrincipals(Class<T> c) {

	if (c == null)
	    throw new NullPointerException
		(ResourcesMgr.getString("invalid null Class provided"));

	// always return an empty Set instead of null
	// so LoginModules can add to the Set if necessary
	return new ClassSet(PRINCIPAL_SET, c);
    }

    /**
     * Return the <code>Set</code> of public credentials held by this
     * <code>Subject</code>.
     *
     * <p> The returned <code>Set</code> is backed by this Subject's
     * internal public Credential <code>Set</code>.  Any modification
     * to the returned <code>Set</code> affects the internal public
     * Credential <code>Set</code> as well.
     *
     * <p>
     *
     * @return	A <code>Set</code> of public credentials held by this
     *		<code>Subject</code>.
     */
    public Set<Object> getPublicCredentials() {

	// always return an empty Set instead of null
	// so LoginModules can add to the Set if necessary
	return pubCredentials;
    }

    /**
     * Return the <code>Set</code> of private credentials held by this
     * <code>Subject</code>.
     *
     * <p> The returned <code>Set</code> is backed by this Subject's
     * internal private Credential <code>Set</code>.  Any modification
     * to the returned <code>Set</code> affects the internal private
     * Credential <code>Set</code> as well.
     *
     * <p> A caller requires permissions to access the Credentials
     * in the returned <code>Set</code>, or to modify the
     * <code>Set</code> itself.  A <code>SecurityException</code>
     * is thrown if the caller does not have the proper permissions.
     * 
     * <p> While iterating through the <code>Set</code>,
     * a <code>SecurityException</code> is thrown
     * if the caller does not have permission to access a
     * particular Credential.  The <code>Iterator</code>
     * is nevertheless advanced to next element in the <code>Set</code>.
     *
     * <p>
     *
     * @return	A <code>Set</code> of private credentials held by this
     *		<code>Subject</code>.
     */
    public Set<Object> getPrivateCredentials() {

	// XXX
	// we do not need a security check for
	// AuthPermission(getPrivateCredentials)
	// because we already restrict access to private credentials
	// via the PrivateCredentialPermission.  all the extra AuthPermission
	// would do is protect the set operations themselves
	// (like size()), which don't seem security-sensitive.

	// always return an empty Set instead of null
	// so LoginModules can add to the Set if necessary
	return privCredentials;
    }

    /**
     * Return a <code>Set</code> of public credentials associated with this
     * <code>Subject</code> that are instances or subclasses of the specified
     * <code>Class</code>.
     *
     * <p> The returned <code>Set</code> is not backed by this Subject's
     * internal public Credential <code>Set</code>.  A new
     * <code>Set</code> is created and returned for each method invocation.
     * Modifications to the returned <code>Set</code>
     * will not affect the internal public Credential <code>Set</code>.
     *
     * <p>
     *
     * @param c the returned <code>Set</code> of public credentials will all be
     *		instances of this class.
     *
     * @return a <code>Set</code> of public credentials that are instances
     *		of the	specified <code>Class</code>.
     *
     * @exception NullPointerException if the specified <code>Class</code>
     *		is <code>null</code>.
     */
    public <T> Set<T> getPublicCredentials(Class<T> c) {

	if (c == null)
	    throw new NullPointerException
		(ResourcesMgr.getString("invalid null Class provided"));

	// always return an empty Set instead of null
	// so LoginModules can add to the Set if necessary
	return new ClassSet<T>(PUB_CREDENTIAL_SET, c);
    }

    /**
     * Return a <code>Set</code> of private credentials associated with this
     * <code>Subject</code> that are instances or subclasses of the specified
     * <code>Class</code>. 
     *
     * <p> The caller must have permission to access all of the
     * requested Credentials, or a <code>SecurityException</code>
     * will be thrown.
     *
     * <p> The returned <code>Set</code> is not backed by this Subject's
     * internal private Credential <code>Set</code>.  A new
     * <code>Set</code> is created and returned for each method invocation.
     * Modifications to the returned <code>Set</code>
     * will not affect the internal private Credential <code>Set</code>.
     *
     * <p>
     *
     * @param c the returned <code>Set</code> of private credentials will all be
     *		instances of this class.
     *
     * @return a <code>Set</code> of private credentials that are instances
     *		of the	specified <code>Class</code>.
     *
     * @exception NullPointerException if the specified <code>Class</code>
     *		is <code>null</code>.
     */
    public <T> Set<T> getPrivateCredentials(Class<T> c) {

	// XXX
	// we do not need a security check for
	// AuthPermission(getPrivateCredentials)
	// because we already restrict access to private credentials
	// via the PrivateCredentialPermission.  all the extra AuthPermission
	// would do is protect the set operations themselves
	// (like size()), which don't seem security-sensitive.

	if (c == null)
	    throw new NullPointerException
		(ResourcesMgr.getString("invalid null Class provided"));

	// always return an empty Set instead of null
	// so LoginModules can add to the Set if necessary
	return new ClassSet<T>(PRIV_CREDENTIAL_SET, c);
    }

    /**
     * Compares the specified Object with this <code>Subject</code>
     * for equality.  Returns true if the given object is also a Subject
     * and the two <code>Subject</code> instances are equivalent.
     * More formally, two <code>Subject</code> instances are
     * equal if their <code>Principal</code> and <code>Credential</code>
     * Sets are equal.
     *
     * <p>
     *
     * @param o Object to be compared for equality with this
     *		<code>Subject</code>.
     *
     * @return true if the specified Object is equal to this
     *		<code>Subject</code>.
     *
     * @exception SecurityException if the caller does not have permission
     *		to access the private credentials for this <code>Subject</code>,
     *		or if the caller does not have permission to access the
     *		private credentials for the provided <code>Subject</code>.
     */
    public boolean equals(Object o) {

	if (o == null)
	    return false;

	if (this == o)
	    return true;

	if (o instanceof Subject) {

	    final Subject that = (Subject)o;

	    // check the principal and credential sets
	    Set thatPrincipals;
	    synchronized(that.principals) {
		// avoid deadlock from dual locks
		thatPrincipals = new HashSet(that.principals);
	    }
	    if (!principals.equals(thatPrincipals)) {
		return false;
	    }

	    Set thatPubCredentials;
	    synchronized(that.pubCredentials) {
		// avoid deadlock from dual locks
		thatPubCredentials = new HashSet(that.pubCredentials);
	    }
	    if (!pubCredentials.equals(thatPubCredentials)) {
		return false;
	    }

	    Set thatPrivCredentials;
	    synchronized(that.privCredentials) {
		// avoid deadlock from dual locks
		thatPrivCredentials = new HashSet(that.privCredentials);
	    }
	    if (!privCredentials.equals(thatPrivCredentials)) {
		return false;
	    }
	    return true;
	}
	return false;
    }

    /**
     * Return the String representation of this <code>Subject</code>.
     *
     * <p>
     *
     * @return the String representation of this <code>Subject</code>.
     */
    public String toString() {
	return toString(true);
    }

    /**
     * package private convenience method to print out the Subject
     * without firing off a security check when trying to access
     * the Private Credentials
     */
    String toString(boolean includePrivateCredentials) {

	String s = ResourcesMgr.getString("Subject:\n");
	String suffix = "";

	synchronized(principals) {
	    Iterator pI = principals.iterator();
	    while (pI.hasNext()) {
		Principal p = (Principal)pI.next();
		suffix = suffix + ResourcesMgr.getString("\tPrincipal: ") +
			p.toString() + ResourcesMgr.getString("\n");
	    }
	}

	synchronized(pubCredentials) {
	    Iterator pI = pubCredentials.iterator();
	    while (pI.hasNext()) {
		Object o = pI.next();
		suffix = suffix +
			ResourcesMgr.getString("\tPublic Credential: ") +
			o.toString() + ResourcesMgr.getString("\n");
	    }
	}
		   
	if (includePrivateCredentials) {
	    synchronized(privCredentials) {
		Iterator pI = privCredentials.iterator();
		while (pI.hasNext()) {
		    try {
			Object o = pI.next();
			suffix += ResourcesMgr.getString
					("\tPrivate Credential: ") +
					o.toString() +
					ResourcesMgr.getString("\n");
		    } catch (SecurityException se) {
			suffix += ResourcesMgr.getString
				("\tPrivate Credential inaccessible\n");
			break;
		    }
		}
	    }
	}
	return s + suffix;
    }

    /**
     * Returns a hashcode for this <code>Subject</code>.
     *
     * <p>
     *
     * @return a hashcode for this <code>Subject</code>.
     *
     * @exception SecurityException if the caller does not have permission
     *		to access this Subject's private credentials.
     */
    public int hashCode() {

	/** 
	 * The hashcode is derived exclusive or-ing the
	 * hashcodes of this Subject's Principals and credentials.
	 *
	 * If a particular credential was destroyed
	 * (<code>credential.hashCode()</code> throws an
	 * <code>IllegalStateException</code>),
	 * the hashcode for that credential is derived via:
	 * <code>credential.getClass().toString().hashCode()</code>.
	 */

	int hashCode = 0;

	synchronized(principals) {
	    Iterator pIterator = principals.iterator();
	    while (pIterator.hasNext()) {
		Principal p = (Principal)pIterator.next();
		hashCode ^= p.hashCode();
	    }
	}

	synchronized(pubCredentials) {
	    Iterator pubCIterator = pubCredentials.iterator();
	    while (pubCIterator.hasNext()) {
		hashCode ^= getCredHashCode(pubCIterator.next());
	    }
	}
	return hashCode;
    }

    /**
     * get a credential's hashcode
     */
    private int getCredHashCode(Object o) {
	try {
	    return o.hashCode();
	} catch (IllegalStateException ise) {
	    return o.getClass().toString().hashCode();
	}
    }

    /**
     * Writes this object out to a stream (i.e., serializes it).
     */
    private void writeObject(java.io.ObjectOutputStream oos)
		throws java.io.IOException {
	synchronized(principals) {
	    oos.defaultWriteObject();
	}
    }

    /**
     * Reads this object from a stream (i.e., deserializes it)
     */
    private void readObject(java.io.ObjectInputStream s)
		throws java.io.IOException, ClassNotFoundException {

	s.defaultReadObject();

	// The Credential <code>Set</code> is not serialized, but we do not
	// want the default deserialization routine to set it to null.
	this.pubCredentials = Collections.synchronizedSet
				(new SecureSet(this, PUB_CREDENTIAL_SET));
	this.privCredentials = Collections.synchronizedSet
				(new SecureSet(this, PRIV_CREDENTIAL_SET));
    }

    /**
     * Prevent modifications unless caller has permission.
     *
     * @serial include
     */
    private static class SecureSet
	extends AbstractSet
	implements java.io.Serializable {

	private static final long serialVersionUID = 7911754171111800359L;

	/**
	 * @serialField this$0 Subject The outer Subject instance.
	 * @serialField elements LinkedList The elements in this set.
	 */
	private static final ObjectStreamField[] serialPersistentFields = {
	    new ObjectStreamField("this$0", Subject.class),
	    new ObjectStreamField("elements", LinkedList.class),
	    new ObjectStreamField("which", int.class)
	};

	Subject subject;
	LinkedList elements;

	/**
	 * @serial An integer identifying the type of objects contained
	 *	in this set.  If <code>which == 1</code>,
	 *	this is a Principal set and all the elements are
	 *	of type <code>java.security.Principal</code>.
	 *	If <code>which == 2</code>, this is a public credential
	 *	set and all the elements are of type <code>Object</code>.
	 *	If <code>which == 3</code>, this is a private credential
	 *	set and all the elements are of type <code>Object</code>.
	 */
	private int which;

	SecureSet(Subject subject, int which) {
	    this.subject = subject;
	    this.which = which;
	    this.elements = new LinkedList();
	}

	SecureSet(Subject subject, int which, Set set) {
	    this.subject = subject;
	    this.which = which;
	    this.elements = new LinkedList(set);
	}

	public int size() {
	    return elements.size();
	}

	public Iterator iterator() {
	    final LinkedList list = elements;
	    return new Iterator() {
		ListIterator i = list.listIterator(0);

		public boolean hasNext() {return i.hasNext();}
	    
		public Object next() {
		    if (which != Subject.PRIV_CREDENTIAL_SET) {
			return i.next();
		    }

		    SecurityManager sm = System.getSecurityManager();
		    if (sm != null) {
			try {
			    sm.checkPermission(new PrivateCredentialPermission
				(list.get(i.nextIndex()).getClass().getName(),
				subject.getPrincipals()));
			} catch (SecurityException se) {
			    i.next();
			    throw (se);
			}
		    }
		    return i.next();
		}
	    
		public void remove() {

		    if (subject.isReadOnly()) {
			throw new IllegalStateException(ResourcesMgr.getString
				("Subject is read-only"));
		    }

		    java.lang.SecurityManager sm = System.getSecurityManager();
		    if (sm != null) {
			switch (which) {
			case Subject.PRINCIPAL_SET:
			    sm.checkPermission(new AuthPermission
					("modifyPrincipals"));
			    break;
			case Subject.PUB_CREDENTIAL_SET:
			    sm.checkPermission(new AuthPermission
					("modifyPublicCredentials"));
			    break;
			default:
			    sm.checkPermission(new AuthPermission
					("modifyPrivateCredentials"));
			    break;
			}
		    }
		    i.remove();
		}
	    };
	}
	  
	public boolean add(Object o) {
	
	    if (subject.isReadOnly()) {
		throw new IllegalStateException
			(ResourcesMgr.getString("Subject is read-only"));
	    }
	
	    java.lang.SecurityManager sm = System.getSecurityManager();
	    if (sm != null) {
		switch (which) {
		case Subject.PRINCIPAL_SET:
		    sm.checkPermission
			(new AuthPermission("modifyPrincipals"));
		    break;
		case Subject.PUB_CREDENTIAL_SET:
		    sm.checkPermission
			(new AuthPermission("modifyPublicCredentials"));
		    break;
		default:
		    sm.checkPermission
			(new AuthPermission("modifyPrivateCredentials"));
		    break;
		}
	    }
	
	    switch (which) {
	    case Subject.PRINCIPAL_SET:
		if (!(o instanceof Principal)) {
		    throw new SecurityException(ResourcesMgr.getString
			("attempting to add an object which is not an " +
			"instance of java.security.Principal to a " +
			"Subject's Principal Set"));
		}
		break;
	    default:
		// ok to add Objects of any kind to credential sets
		break;
	    }
	
	    // check for duplicates
	    if (!elements.contains(o))
		return elements.add(o);
	    else
		return false;
	}
	  
	public boolean remove(Object o) {
	
	    final Iterator e = iterator();
	    while (e.hasNext()) {
		Object next;
		if (which != Subject.PRIV_CREDENTIAL_SET) {
		    next = e.next();
		} else {
		    next = (Object)java.security.AccessController.doPrivileged
			(new java.security.PrivilegedAction() {
			public Object run() {
			    return e.next();
			}
		    });
		}
	  
		if (next == null) {
		    if (o == null) {
			e.remove();
			return true;
		    }
		} else if (next.equals(o)) {
		    e.remove();
		    return true;
		}
	    }
	    return false;
	}
	
	public boolean contains(Object o) {
	    final Iterator e = iterator();
	    while (e.hasNext()) {
		Object next;
		if (which != Subject.PRIV_CREDENTIAL_SET) {
		    next = e.next();
		} else {

		    // For private credentials:
		    // If the caller does not have read permission for
		    // for o.getClass(), we throw a SecurityException.
		    // Otherwise we check the private cred set to see whether
		    // it contains the Object
	
		    SecurityManager sm = System.getSecurityManager();
		    if (sm != null) {
			sm.checkPermission(new PrivateCredentialPermission
						(o.getClass().getName(),
						subject.getPrincipals()));
		    }
		    next = (Object)java.security.AccessController.doPrivileged
			(new java.security.PrivilegedAction() {
			public Object run() {
			    return e.next();
			}
		    });
		}
	  
		if (next == null) {
		    if (o == null) {
			return true;
		    }
		} else if (next.equals(o)) {
		    return true;
		}
	    }
	    return false;
	}
	  
	public boolean removeAll(Collection c) {
	
	    boolean modified = false;
	    final Iterator e = iterator();
	    while (e.hasNext()) {
		Object next;
		if (which != Subject.PRIV_CREDENTIAL_SET) {
		    next = e.next();
		} else {
		    next = (Object)java.security.AccessController.doPrivileged
			(new java.security.PrivilegedAction() {
			public Object run() {
			    return e.next();
			}
		    });
		}

		Iterator ce = c.iterator();
		while (ce.hasNext()) {
		    Object o = ce.next();
		    if (next == null) {
			if (o == null) {
			    e.remove();
			    modified = true;
			    break;
			}
		    } else if (next.equals(o)) {
			e.remove();
			modified = true;
			break;
		    }
		}
	    }
	    return modified;
	}

	public boolean retainAll(Collection c) {

	    boolean modified = false;
	    boolean retain = false;
	    final Iterator e = iterator();
	    while (e.hasNext()) {
		retain = false;
		Object next;
		if (which != Subject.PRIV_CREDENTIAL_SET) {
		    next = e.next();
		} else {
		    next = (Object)java.security.AccessController.doPrivileged
			(new java.security.PrivilegedAction() {
			public Object run() {
			    return e.next();
			}
		    });
		} 

		Iterator ce = c.iterator();
		while (ce.hasNext()) {
		    Object o = ce.next();	    
		    if (next == null) {
			if (o == null) {
			    retain = true;
			    break;
			}
		    } else if (next.equals(o)) {		
			retain = true;
			break;
		    }
		}
	  
		if (!retain) {
		    e.remove();
		    retain = false;
		    modified = true;
	        }
	    }
	    return modified;
	}
	  
	public void clear() {
	    final Iterator e = iterator();
	    while (e.hasNext()) {
		Object next;
		if (which != Subject.PRIV_CREDENTIAL_SET) {
		    next = e.next();
		} else {
		    next = (Object)java.security.AccessController.doPrivileged
			(new java.security.PrivilegedAction() {
			public Object run() {
			    return e.next();
			}
		    });
		}
		e.remove();
	    }	
	}

	/**
	 * Writes this object out to a stream (i.e., serializes it).
	 *
	 * <p>
	 *
	 * @serialData If this is a private credential set,
	 *	a security check is performed to ensure that
	 *	the caller has permission to access each credential
	 *	in the set.  If the security check passes,
	 *	the set is serialized.
	 */
	private void writeObject(java.io.ObjectOutputStream oos)
		throws java.io.IOException {
	
	    if (which == Subject.PRIV_CREDENTIAL_SET) {
		// check permissions before serializing
		Iterator i = iterator();
		while (i.hasNext()) {
		    i.next();
		}
	    }
	    ObjectOutputStream.PutField fields = oos.putFields();
	    fields.put("this$0", subject);
	    fields.put("elements", elements);
	    fields.put("which", which);
	    oos.writeFields();
	}

	private void readObject(ObjectInputStream ois)
	    throws IOException, ClassNotFoundException
	{
	    ObjectInputStream.GetField fields = ois.readFields();
	    subject = (Subject) fields.get("this$0", null);
	    elements = (LinkedList) fields.get("elements", null);
	    which = fields.get("which", 0);
	}
    }

    /**
     * This class implements a <code>Set</code> which returns only 
     * members that are an instance of a specified Class. 
     */
    private class ClassSet<T> extends AbstractSet<T> {

	private int which;
	private Class c;
	private Set<T> set;

	ClassSet(int which, Class c) {
	    this.which = which;
	    this.c = c;
	    set = new HashSet();

	    switch (which) {
	    case Subject.PRINCIPAL_SET:
		synchronized(principals) { populateSet(); }
		break;
	    case Subject.PUB_CREDENTIAL_SET:
		synchronized(pubCredentials) { populateSet(); }
		break;
	    default:
		synchronized(privCredentials) { populateSet(); }
		break;
	    }
	}

	private void populateSet() {
	    final Iterator iterator;
	    switch(which) {
	    case Subject.PRINCIPAL_SET:
		iterator = Subject.this.principals.iterator();
		break;
	    case Subject.PUB_CREDENTIAL_SET:
		iterator = Subject.this.pubCredentials.iterator();
		break;
	    default:
		iterator = Subject.this.privCredentials.iterator();
		break;
	    }

	    // Check whether the caller has permisson to get
	    // credentials of Class c 
	   
	    while (iterator.hasNext()) {
		Object next;
		if (which == Subject.PRIV_CREDENTIAL_SET) {
		    next = (Object)java.security.AccessController.doPrivileged
			(new java.security.PrivilegedAction() {
			public Object run() {
    			    return iterator.next();
			}
		    });
		} else {
    		    next = iterator.next();
		}
		if (c.isAssignableFrom(next.getClass())) {
		    if (which != Subject.PRIV_CREDENTIAL_SET) {
			set.add((T)next);
		    } else {
			// Check permission for private creds
			SecurityManager sm = System.getSecurityManager();
			if (sm != null) {
			    sm.checkPermission(new PrivateCredentialPermission
						(next.getClass().getName(),
						Subject.this.getPrincipals()));
			}
			set.add((T)next);
		    }
		}
	    }
	}

	public int size() {
	    return set.size();
	}
	
	public Iterator<T> iterator() {
	    return set.iterator();
	}
	
	public boolean add(T o) {
	    
	    if (!o.getClass().isAssignableFrom(c)) {
		MessageFormat form = new MessageFormat(ResourcesMgr.getString
			("attempting to add an object which is not an " +
			"instance of class"));
		Object[] source = {c.toString()};
		throw new SecurityException(form.format(source));
	    }
	    
	    return set.add(o);
	}
    }
}
