/*
 * @(#)Identity.java	1.39 97/10/17
 * 
 * Copyright 1993-1997 Sun Microsystems, Inc. 901 San Antonio Road, 
 * Palo Alto, California, 94303, U.S.A.  All Rights Reserved.
 * 
 * This software is the confidential and proprietary information of Sun
 * Microsystems, Inc. ("Confidential Information").  You shall not
 * disclose such Confidential Information and shall use it only in
 * accordance with the terms of the license agreement you entered into
 * with Sun.
 * 
 * CopyrightVersion 1.2
 * 
 */
 
package com.novell.java.security;

import java.io.IOException;
import java.io.Serializable;
import java.util.*;

/**
 * Represents identities, which are real-world objects. Identities can
 * be people, companies, or organizations whose identities can be
 * authenticated using their public keys. Identities may also be more
 * abstract (or concrete) constructs, such as daemon threads or smart cards.
 *
 * <p>Identity objects have an immutable name and a public key. If an
 * Identity is specified to have a particular scope, then the name and
 * public key of the Identity are unique within that scope.
 *
 * <p>An Identity also has a set of certificates to certify its own
 * public key. The Principal names specified in these certificates need 
 * not be the same, only the key must be unique.
 *
 * <p>An Identity can be subclassed to include postal and e-mail addresses,
 * telephone numbers, images of faces and logos, and so on.
 *
 * @see IdentityScope
 * @see Principal
 *
 * @version 1.39 98/01/14
 * @author Benjamin Renaud 
 */
public abstract 
class Identity implements Principal, Serializable {

    /**
     * The name for this identity.
     */
    private String name;

    /**
     * The public key for this identity.
     */
    private PublicKey publicKey;

    /** use serialVersionUID from JDK 1.1. for interoperability */
    private static final long serialVersionUID = 3609922007826600659L;

    /**
     * Generic, descriptive information about the identity.
     */
    String info = "No further information available.";

    /**
     * The scope of the identity.
     */
    IdentityScope scope;

    /**
     * The certificates for this identity.
     */
    Vector certificates;

    /**
     * @internal
     * Constructor for serialization only.
     */
    protected Identity() {
	this("restoring...");
    }

    /**
     * Constructs an identity with the passed-in name and scope.
     *
     * @param name  The name of the identity.  
     * @param scope The scope of the identity.
     *
     * @exception KeyManagementException Thrown if there is already
     *            an identity with the same name in the scope.
     */
    public Identity(String name, IdentityScope scope) throws
    KeyManagementException {
	this(name);
	this.scope = scope;
    }

    /**
     * Constructs an identity with the passed-in name but no scope.
     *
     * @param name The name of the identity.
     */
    public Identity(String name) {
	this.name = name;
    }

    /**
     * Returns the name of this identity as a String.
     *
     * @return The name of this identity.
     */
    public final String getName() {
	return name;
    }

    /**
     * Returns the scope of this identity as an IdentityScope object.
     *
     * @return The scope of this identity.
     */
    public final IdentityScope getScope() {
	return scope;
    }

    /**
     * Returns the public key for this identity.
     * 
     * @return The public key for this identity.
     */
    public PublicKey getPublicKey() {
	return publicKey;
    }

    /**
     * Sets the public key for this identity. The old key and all of this
     * identity's certificates are removed by this operation. 
     *
     * @param key The public key for this identity.
     *
     * @exception KeyManagementException Thrown if another identity in 
     * the identity's scope has the same public key, or if another
     * exception occurs.  
     */
    /* Should we throw an exception if this is already set? */
    public void setPublicKey(PublicKey key) throws KeyManagementException {
	
	check("Identity.setPublicKey");
	this.publicKey = key;
    }

    /**
     * Specifies a general information string for this identity.
     *
     * @param info The information string to set.
     *
     * @see #getInfo
     */
    public void setInfo(String info) {
	check("Identity.setInfo");
	this.info = info;
    }

    /**
     * Returns the general information string previously specified
     * (set) for this identity.
     *
     * @return The general information about this identity.
     *
     * @see #setInfo
     */
    public String getInfo() {
	return info;
    }

    /**
     * Adds a certificate for this identity. If the identity has a public
     * key, the public key in the certificate must be the same, and if
     * the identity does not have a public key, the identity's
     * public key is set to the one specified in the certificate.
     *
     * @param certificate The certificate to be added.
     *
     * @exception KeyManagementException Thrown if the certificate is
     *            not valid, or if the public key in the certificate being
     *            added conflicts with this identity's public key, or if
     *            another exception occurs.
     * @since JDK1.2
     */
    public void addCertificate(com.novell.java.security.cert.Certificate certificate)
    throws KeyManagementException {

	check("Identity.addCertificate");

	if (certificates == null) {
	    certificates = new Vector();
	}
        PublicKey tmpKey = certificate.getPublicKey();
	if (publicKey != null && tmpKey != null) {
	    if (!keyEquals(publicKey, tmpKey)) {
		throw new KeyManagementException(
		    "public key different from cert public key");
	    }
	} else {
	    publicKey = tmpKey;
	}
	certificates.addElement(certificate);
    }

   private boolean keyEquals(Key aKey, Key anotherKey) {
	if (aKey.getFormat().equalsIgnoreCase(anotherKey.getFormat())) {
	    return MessageDigest.isEqual(aKey.getEncoded(), 
					 anotherKey.getEncoded());
	} else {
	    return false;
	}
    }

    /**
     * Removes a certificate from this identity.
     *
     * @param certificate The certificate to be removed.
     *
     * @exception KeyManagementException Thrown if the certificate is
     * missing, or if another exception occurs.
     * @since JDK1.2
     */
    public void removeCertificate(com.novell.java.security.cert.Certificate certificate)
    throws KeyManagementException {
	check("Identity.removeCertificate");
	if (certificates != null) {
	    certificates.removeElement(certificate);
	}
    }

    /**
     * Returns a copy of all the certificates for this identity.  
     * 
     * @return An array containing a copy of all the certificates for
     *         this identity.  
     * @since JDK1.2
     */
    public com.novell.java.security.cert.Certificate[] getCertificates() {
	if (certificates == null) {
	    return new com.novell.java.security.cert.Certificate[0];
	}

	int n = certificates.size();
	int len = 0, i;

	for (i=0; i < n; i++) {
	    if (certificates.elementAt(i) instanceof 
		com.novell.java.security.cert.Certificate) {
		len++;
	    }
	}

	com.novell.java.security.cert.Certificate[] certs =
                           new com.novell.java.security.cert.Certificate[len];
	len = 0;

	for (i=0; i < n; i++) {
	    Object obj = certificates.elementAt(i);
	    if (obj instanceof com.novell.java.security.cert.Certificate) {
		certs[len] = (com.novell.java.security.cert.Certificate) obj;
		len++;
	    }
	}
	return certs;
    }

    /**
     * Compares two Objects for equality. The equals method compares
     * this identity object value with the value of the reference object
     * specified in the Object parameter.
     *
     * <p>The equals() method first tests to see if the entities actually
     * refer to the same object, in which case it returns TRUE. Next, it
     * checks to see if the entities have the same name and the same scope.
     * If they do, the method returns TRUE. Otherwise, it calls the
     * identityEquals method. Identity subclasses should override the
     * identityEquals() method.
     *
     * @param identity The object with which to test for equality.  
     *
     * @return A boolean set to TRUE if and only if the argument is not
     *         NULL and contains the same value as this object, otherwise
     *         set to FALSE.
     *
     * @see #identityEquals 
     */
    public final boolean equals(Object identity) {

	if (identity == this) {
	    return true;
	}

	if (identity instanceof Identity) {
	    Identity i = (Identity)identity;
	    if (i.getScope() == scope && i.getName().equals(name)) {
		return true;
	    } else {
		return identityEquals(i);	    
	    }
	}
	return false;
    }

    /**
     * Tests for equality between the specified identity and this identity.
     * Subclasses should overriden this method to test for equality. The 
     * default behavior is to return TRUE if the names and public keys 
     * are equal.
     *
     * @param identity The Identity with which to test for equality.
     * 
     * @return A boolean set to TRUE if the identities are considered
     *         equal, otherwise set to FALSE.
     *
     * @see #equals 
     */
    protected boolean identityEquals(Identity identity) {
	return (name.equals(identity.name) && 
		publicKey.equals(identity.publicKey));
    }

    /**
     * Returns a parsable name for identity: identityName.scopeName
     */
    String fullName() {
	String parsable = name;
	if (scope != null) {
	    parsable += "." + scope.getName();
	}
	return parsable;
    }

    /**
     * Generates a short string representation of the object (identity)
     * containing its name and scope (if any). The returned string, which
     * textually represents the object, should be a concise but informative
     * representation that is easy to read.
     *
     * @return A String containing information about this object (identity),
     *         such as its name and the name of its scope (if any).
     */
    public String toString() {
	String printable = name;
	if (scope != null) {
	    printable += "[" + scope.getName() + "]";
	}
	return printable;
    }

    /**
     * Generates a short string representation of the object (identity)
     * containing optionally more details than provided by the
     * toString() method without any arguments. If the detailed parameter
     * is set to TRUE this method returns more information than that 
     * provided by the toString() method without any arguments.
     *
     * @param detailed A boolean indicating whether or not to provide the
     *                 optional detailed information.  
     *
     * @return The optional detailed information about this identity.
     *
     * @see #toString
     */
    public String toString(boolean detailed) {
	String out = toString();
	if (detailed) {
	    out += "\n";
	    out += printKeys();
	    out += "\n" + printCertificates();
	    if (info != null) {
		out += "\n\t" + info;
	    } else {
		out += "\n\tno additional information available.";
	    }
	}	  
	return out;
    }

    String printKeys() {
	String key = "";
	if (publicKey != null) {
	    key = "\tpublic key initialized";
	} else {
	    key = "\tno public key";
	}
	return key;
    }

    String printCertificates() {
	String out = "";
	if (certificates == null) {
	    return "\tno certificates";
	} else {
	    out += "\tcertificates: \n";
	    Enumeration e = certificates.elements();
	    int i = 1;
	    while (e.hasMoreElements()) {
		Object obj = e.nextElement();
/*		We don't include the deprecated certificate - jsn

      if (obj instanceof java.security.Certificate) {
			java.security.Certificate cert = 
			    (java.security.Certificate) obj;
			out += "\tcertificate " + i++ +
			    "\tfor  : " + cert.getPrincipal() + "\n";
			out += "\t\t\tfrom : " + 
			    cert.getGuarantor() + "\n";
		} else 
*/
      if (obj instanceof com.novell.java.security.cert.X509Certificate) {
			com.novell.java.security.cert.X509Certificate cert =
                            (com.novell.java.security.cert.X509Certificate)obj;
			out += "\tcertificate " + i++ +
                               "\tfor  : " + cert.getSubjectDN() + "\n";
			out += "\t\t\tfrom : " + 
			       cert.getIssuerDN() + "\n";
		} else {
			out += obj.toString();	
		}
	    }
	}
	return out;
    }
    
    /**
     * Returns a hash code value for this object (identity).
     * Whenever the hashCode() method is invoked on the same
     * object more than once during an execution of a Java
     * application, the hashCode() method must consistently return
     * the same integer. This integer need not remain consistent 
     * from one execution of an application to another execution
     * of the same application. If two objects are equal according
     * to the equals() method, then calling the hashCode() method on
     * each of the two objects must produce the same integer result.
     *
     * @return A hash code value for this object (identity).
     */
    public int hashCode() {
	String scopedName = name;
	if (scope != null) {
	    scopedName += scope.getName();
	}
	return scopedName.hashCode();
    }

    private static void check(String directive) {
	SecurityManager security = System.getSecurityManager();
	if (security != null) {
	    security.checkSecurityAccess(directive);
	}
    }
}
