package org.bouncycastle.jce.cert;

import java.io.PrintStream;
import java.io.PrintWriter;
import java.security.GeneralSecurityException;

/**
 * An exception indicating one of a variety of problems encountered when 
 * validating a certification path. <br />
 * <br />
 * A <code>CertPathValidatorException</code> provides support for wrapping
 * exceptions. The {@link #getCause getCause} method returns the throwable, 
 * if any, that caused this exception to be thrown. <br />
 * <br />
 * A <code>CertPathValidatorException</code> may also include the 
 * certification path that was being validated when the exception was thrown 
 * and the index of the certificate in the certification path that caused the 
 * exception to be thrown. Use the {@link #getCertPath getCertPath} and
 * {@link #getIndex getIndex} methods to retrieve this information.<br />
 * <br />
 * <b>Concurrent Access</b><br />
 * <br />
 * Unless otherwise specified, the methods defined in this class are not
 * thread-safe. Multiple threads that need to access a single
 * object concurrently should synchronize amongst themselves and
 * provide the necessary locking. Multiple threads each manipulating
 * separate objects need not synchronize.
 *
 * @see CertPathValidator
 **/
public class CertPathValidatorException extends GeneralSecurityException
{
    private Throwable cause;
    private CertPath certPath;
    private int index = -1;

    /**
     * Creates a <code>CertPathValidatorException</code> with no detail
     * message.
     */
    public CertPathValidatorException()
    {
        super();
    }

    /**
     * Creates a <code>CertPathValidatorException</code> with the given detail
     * message. A detail message is a <code>String</code> that describes this
     * particular exception.
     * 
     * @param messag
     *            the detail message
     */
    public CertPathValidatorException(String message)
    {
        super(message);
    }

    /**
     * Creates a <code>CertPathValidatorException</code> with the specified
     * detail message and cause.
     * 
     * @param msg
     *            the detail message
     * @param cause
     *            the cause (which is saved for later retrieval by the
     *            {@link #getCause getCause()} method). (A <code>null</code>
     *            value is permitted, and indicates that the cause is
     *            nonexistent or unknown.)
     */
    public CertPathValidatorException(String message, Throwable cause)
    {
        super(message);
        this.cause = cause;
    }

    /**
     * Creates a <code>CertPathValidatorException</code> with the specified
     * detail message, cause, certification path, and index.
     * 
     * @param msg
     *            the detail message (or <code>null</code> if none)
     * @param cause
     *            the cause (or <code>null</code> if none)
     * @param certPath
     *            the certification path that was in the process of being
     *            validated when the error was encountered
     * @param index
     *            the index of the certificate in the certification path that
     *            caused the error (or -1 if not applicable). Note that the list
     *            of certificates in a <code>CertPath</code> is zero based.
     * 
     * @exception IndexOutOfBoundsException
     *                if the index is out of range
     *                <code>(index < -1 || (certPath != null && index >=
     * certPath.getCertificates().size())</code>
     * @exception IllegalArgumentException
     *                if <code>certPath</code> is <code>null</code> and
     *                <code>index</code> is not -1
     */
    public CertPathValidatorException(
        String message,
        Throwable cause,
        CertPath certPath,
        int index)
    {
        super(message);

        if (certPath == null && index != -1)
        {
            throw new IllegalArgumentException(
                    "certPath = null and index != -1");
        }
        if (index < -1
                || (certPath != null && index >= certPath.getCertificates()
                        .size()))
        {
            throw new IndexOutOfBoundsException(
                    " index < -1 or out of bound of certPath.getCertificates()");
        }

        this.cause = cause;
        this.certPath = certPath;
        this.index = index;
    }

    /**
     * Creates a <code>CertPathValidatorException</code> that wraps the
     * specified throwable. This allows any exception to be converted into a
     * <code>CertPathValidatorException</code>, while retaining information
     * about the wrapped exception, which may be useful for debugging. The
     * detail message is set to (<code>cause==null ? null : cause.toString()
     * </code>)
     * (which typically contains the class and detail message of cause).
     * 
     * @param cause
     *            the cause (which is saved for later retrieval by the
     *            {@link #getCause getCause()} method). (A <code>null</code>
     *            value is permitted, and indicates that the cause is
     *            nonexistent or unknown.)
     */
    public CertPathValidatorException(Throwable cause)
    {
        this.cause = cause;
    }

    /**
     * Returns the detail message for this
     * <code>CertPathValidatorException</code>.
     * 
     * @return the detail message, or <code>null</code> if neither the message
     *         nor cause were specified
     */
    public String getMessage()
    {
        String message = super.getMessage();

        if (message != null)
        {
            return message;
        }

        if (cause != null)
        {
            return cause.getMessage();
        }
        
        return null;
    }

    /**
     * Returns the certification path that was being validated when the
     * exception was thrown.
     * 
     * @return the <code>CertPath</code> that was being validated when the
     *         exception was thrown (or <code>null</code> if not specified)
     */
    public CertPath getCertPath()
    {
        return certPath;
    }

    /**
     * Returns the index of the certificate in the certification path that
     * caused the exception to be thrown. Note that the list of certificates in
     * a <code>CertPath</code> is zero based. If no index has been set, -1 is
     * returned.
     * 
     * @return the index that has been set, or -1 if none has been set
     */
    public int getIndex()
    {
        return index;
    }

    /**
     * Returns the cause of this <code>CertPathValidatorException</code> or
     * <code>null</code> if the cause is nonexistent or unknown.
     * 
     * @return the cause of this throwable or <code>null</code> if the cause
     *         is nonexistent or unknown.
     */
    public Throwable getCause()
    {
        return cause;
    }

    /**
     * Returns a string describing this exception, including a description of
     * the internal (wrapped) cause if there is one.
     * 
     * @return a string representation of this
     *         <code>CertPathValidatorException</code>
     */
    public String toString()
    {
        StringBuffer sb = new StringBuffer();
        String s = getMessage();
        if (s != null)
        {
            sb.append(s);
        }
        if (getIndex() >= 0)
        {
            sb.append("index in certpath: ").append(getIndex()).append('\n');
            sb.append(getCertPath());
        }
        return sb.toString();
    }

    /**
     * Prints a stack trace to <code>System.err</code>, including the
     * backtrace of the cause, if any.
     */
    public void printStackTrace()
    {
        printStackTrace(System.err);
    }

    /**
     * Prints a stack trace to a <code>PrintStream</code>, including the
     * backtrace of the cause, if any.
     * 
     * @param ps
     *            the <code>PrintStream</code> to use for output
     */
    public void printStackTrace(PrintStream ps)
    {
        super.printStackTrace(ps);
        if (getCause() != null)
        {
            getCause().printStackTrace(ps);
        }
    }

    /**
     * Prints a stack trace to a <code>PrintWriter</code>, including the
     * backtrace of the cause, if any.
     * 
     * @param pw
     *            the <code>PrintWriter</code> to use for output
     */
    public void printStackTrace(PrintWriter pw)
    {
        super.printStackTrace(pw);
        if (getCause() != null)
        {
            getCause().printStackTrace(pw);
        }
    }
}
