package org.bouncycastle.jce.cert;

import java.io.ByteArrayInputStream;
import java.io.NotSerializableException;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

/**
 * An immutable sequence of certificates (a certification path).<br />
 * <br />
 * This is an abstract class that defines the methods common to all CertPaths.
 * Subclasses can handle different kinds of certificates (X.509, PGP, etc.).<br />
 * <br />
 * All CertPath objects have a type, a list of Certificates, and one or more
 * supported encodings. Because the CertPath class is immutable, a CertPath
 * cannot change in any externally visible way after being constructed. This
 * stipulation applies to all public fields and methods of this class and any
 * added or overridden by subclasses.<br />
 * <br />
 * The type is a String that identifies the type of Certificates in the
 * certification path. For each certificate cert in a certification path
 * certPath, cert.getType().equals(certPath.getType()) must be true.<br />
 * <br />
 * The list of Certificates is an ordered List of zero or more Certificates.
 * This List and all of the Certificates contained in it must be immutable.<br />
 * <br />
 * Each CertPath object must support one or more encodings so that the object
 * can be translated into a byte array for storage or transmission to other
 * parties. Preferably, these encodings should be well-documented standards
 * (such as PKCS#7). One of the encodings supported by a CertPath is considered
 * the default encoding. This encoding is used if no encoding is explicitly
 * requested (for the {@link #getEncoded()} method, for instance).<br />
 * <br />
 * All CertPath objects are also Serializable. CertPath objects are resolved
 * into an alternate {@link CertPathRep} object during serialization. This
 * allows a CertPath object to be serialized into an equivalent representation
 * regardless of its underlying implementation.<br />
 * <br />
 * CertPath objects can be created with a CertificateFactory or they can be
 * returned by other classes, such as a CertPathBuilder.<br />
 * <br />
 * By convention, X.509 CertPaths (consisting of X509Certificates), are ordered
 * starting with the target certificate and ending with a certificate issued by
 * the trust anchor. That is, the issuer of one certificate is the subject of
 * the following one. The certificate representing the
 * {@link TrustAnchor TrustAnchor} should not be included in the certification
 * path. Unvalidated X.509 CertPaths may not follow these conventions. PKIX
 * CertPathValidators will detect any departure from these conventions that
 * cause the certification path to be invalid and throw a
 * CertPathValidatorException.<br />
 * <br />
 * <strong>Concurrent Access</strong><br />
 * <br />
 * All CertPath objects must be thread-safe. That is, multiple threads may
 * concurrently invoke the methods defined in this class on a single CertPath
 * object (or more than one) with no ill effects. This is also true for the List
 * returned by CertPath.getCertificates.<br />
 * <br />
 * Requiring CertPath objects to be immutable and thread-safe allows them to be
 * passed around to various pieces of code without worrying about coordinating
 * access. Providing this thread-safety is generally not difficult, since the
 * CertPath and List objects in question are immutable.
 * 
 * @see CertificateFactory
 * @see CertPathBuilder
 */
public abstract class CertPath extends Object implements Serializable
{
    private String type;

    /**
     * Alternate <code>CertPath</code> class for serialization.
     */
    protected static class CertPathRep implements Serializable
    {
        private String type;

        private byte[] data;

        /**
         * Creates a <code>CertPathRep</code> with the specified type and
         * encoded form of a certification path.
         * 
         * @param type
         *            the standard name of a CertPath
         * @param typedata
         *            the encoded form of the certification path
         */
        protected CertPathRep(String type, byte[] data)
        {
            this.type = type;
            this.data = data;
        }

        /**
         * Returns a CertPath constructed from the type and data.
         * 
         * @return the resolved CertPath object
         * @exception ObjectStreamException
         *                if a CertPath could not be constructed
         */
        protected Object readResolve() throws ObjectStreamException
        {
            try
            {
                ByteArrayInputStream inStream = new ByteArrayInputStream(data);
                CertificateFactory cf = CertificateFactory.getInstance(type);
                return cf.generateCertPath(inStream);
            }
            catch (CertificateException ce)
            {
                throw new NotSerializableException(
                        " java.security.cert.CertPath: " + type);
            }
        }
    }

    /**
     * Creates a CertPath of the specified type. This constructor is protected
     * because most users should use a CertificateFactory to create CertPaths.
     * 
     * @param type
     *            the standard name of the type of Certificatesin this path
     */
    protected CertPath(String type)
    {
        this.type = type;
    }

    /**
     * Returns the type of Certificates in this certification path. This is the
     * same string that would be returned by
     * {@link java.security.cert.Certificate#getType()} for all Certificates in
     * the certification path.
     * 
     * @return the type of Certificates in this certification path (never null)
     */
    public String getType()
    {
        return type;
    }

    /**
     * Returns an iteration of the encodings supported by this certification
     * path, with the default encoding first. Attempts to modify the returned
     * Iterator via its remove method result in an
     * UnsupportedOperationException.
     * 
     * @return an Iterator over the names of the supported encodings (as
     *         Strings)
     */
    public abstract Iterator getEncodings();

    /**
     * Compares this certification path for equality with the specified object.
     * Two CertPaths are equal if and only if their types are equal and their
     * certificate Lists (and by implication the Certificates in those Lists)
     * are equal. A CertPath is never equal to an object that is not a CertPath.<br />
     * <br />
     * This algorithm is implemented by this method. If it is overridden, the
     * behavior specified here must be maintained.
     * 
     * @param other
     *            the object to test for equality with this certification path
     * 
     * @return true if the specified object is equal to this certification path,
     *         false otherwise
     * 
     * @see Object#hashCode() Object.hashCode()
     */
    public boolean equals(Object other)
    {
        if (!(other instanceof CertPath))
        {
            return false;
        }

        CertPath otherCertPath = (CertPath)other;
        if (!getType().equals(otherCertPath.getType()))
        {
            return false;
        }
        return getCertificates().equals(otherCertPath.getCertificates());
    }

    /**
     * Returns the hashcode for this certification path. The hash code of a
     * certification path is defined to be the result of the following
     * calculation:
     * 
     * <pre>
     * hashCode = path.getType().hashCode();
     * hashCode = 31 * hashCode + path.getCertificates().hashCode();
     * </pre>
     * 
     * This ensures that path1.equals(path2) implies that
     * path1.hashCode()==path2.hashCode() for any two certification paths, path1
     * and path2, as required by the general contract of Object.hashCode.
     * 
     * @return The hashcode value for this certification path
     * 
     * @see #equals(Object)
     */
    public int hashCode()
    {
        return getType().hashCode() * 31 + getCertificates().hashCode();
    }

    /**
     * Returns a string representation of this certification path. This calls
     * the toString method on each of the Certificates in the path.
     * 
     * @return a string representation of this certification path
     */
    public String toString()
    {
        StringBuffer s = new StringBuffer();
        List certs = getCertificates();
        ListIterator iter = certs.listIterator();
        s.append('\n').append(getType()).append(" Cert Path: length = ").append(certs.size())
                .append("\n[\n");
        while (iter.hasNext())
        {
            s
                    .append("=========================================================Certificate ")
                    .append(iter.nextIndex()).append('\n');
            s.append(iter.next()).append('\n');
            s
                    .append("========================================================Certificate end\n\n\n");
        }
        s.append("\n]");
        return s.toString();
    }

    /**
     * Returns the encoded form of this certification path, using the default
     * encoding.
     * 
     * @return the encoded bytes
     * 
     * @exception CertificateEncodingException
     *                if an encoding error occurs
     */
    public abstract byte[] getEncoded() throws CertificateEncodingException;

    /**
     * Returns the encoded form of this certification path, using the specified
     * encoding.
     * 
     * @param encoding
     *            the name of the encoding to use
     * 
     * @return the encoded bytes
     * 
     * @exception CertificateEncodingException
     *                if an encoding error occurs or the encoding requested is
     *                not supported
     */
    public abstract byte[] getEncoded(String encoding)
            throws CertificateEncodingException;

    /**
     * Returns the list of certificates in this certification path. The List
     * returned must be immutable and thread-safe.
     * 
     * @return an immutable List of Certificates (may be empty, but not null)
     */
    public abstract List getCertificates();

    /**
     * Replaces the CertPath to be serialized with a CertPathRep object.
     * 
     * @return the CertPathRep to be serialized
     * 
     * @exception ObjectStreamException
     *                if a CertPathRep object representing this certification
     *                path could not be created
     */
    protected Object writeReplace() throws ObjectStreamException
    {
        try
        {
            return new CertPathRep(getType(), getEncoded());
        }
        catch (CertificateException ce)
        {
            throw new NotSerializableException(" java.security.cert.CertPath: "
                    + getType());
        }
    }
}
