/*
 * $Id: ExceptionUtils.java,v 1.19 2008/07/04 10:22:53 agoubard Exp $
 *
 * Copyright 2003-2008 Online Breedband B.V.
 * See the COPYRIGHT file for redistribution and use restrictions.
 */
package org.xins.logdoc;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import java.util.WeakHashMap;

/**
 * Utility functions related to exceptions.
 *
 * @version $Revision: 1.19 $ $Date: 2008/07/04 10:22:53 $
 * @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a>
 *
 * @since XINS 1.2.0
 */
public final class ExceptionUtils {

   /**
    * Reference to the <code>getCause()</code> method in class
    * <code>Throwable</code>. This reference will be <code>null</code> on Java
    * 1.3.
    */
   private static Method GET_CAUSE;

   /**
    * Reference to the <code>initCause()</code> method in class
    * <code>Throwable</code>. This reference will be <code>null</code> on Java
    * 1.3.
    */
   private static Method SET_CAUSE;

   /**
    * Table that maps from exception to cause. This table will only be used on
    * Java 1.3. On Java 1.4 and up it will be <code>null</code>.
    */
   private static WeakHashMap CAUSE_TABLE;

   /**
    * Placeholder for the <code>null</code> object. This object will be stored
    * in the {@link #CAUSE_TABLE} on Java 1.3 if the cause for an exception is
    * set to <code>null</code>.
    */
   private static final Object NULL = new Object();

   /**
    * Initializes this class.
    */
   static {

      Class[] args = new Class[] { Throwable.class };

      try {
         GET_CAUSE = Throwable.class.getDeclaredMethod("getCause", null);
         SET_CAUSE = Throwable.class.getDeclaredMethod("initCause", args);
         CAUSE_TABLE = null;

      // Method does not exist, this is not Java 1.4
      } catch (NoSuchMethodException exception) {
         GET_CAUSE   = null;
         SET_CAUSE   = null;
         CAUSE_TABLE = new WeakHashMap();

      // Access denied
      } catch (SecurityException exception) {
         throw new RuntimeException("Unable to get getCause() method of class Throwable: Access denied by security manager.");
      }
   }

   /**
    * Constructs a new <code>ExceptionUtils</code> object.
    */
   private ExceptionUtils() {
      // empty
   }

   /**
    * Determines the root cause for the specified exception.
    *
    * @param exception
    *    the exception to determine the root cause for, can be
    *    <code>null</code>.
    *
    * @return
    *    the root cause exception, can be <code>null</code>.
    */
   public static Throwable getRootCause(Throwable exception) {

      // Check preconditions
      if (exception == null) {
         return null;
      }

      // Get the root cause of the exception
      Throwable cause = getCause(exception);
      while (cause != null) {
         exception = cause;
         cause = getCause(exception);
      }

      return exception;
   }

   /**
    * Determines the cause for the specified exception.
    *
    * @param exception
    *    the exception to determine the cause for, cannot be
    *    <code>null</code>.
    *
    * @return
    *    the cause exception, can be <code>null</code>.
    *
    * @throws IllegalArgumentException
    *    if <code>exception == null</code>.
    */
   public static Throwable getCause(Throwable exception)
   throws IllegalArgumentException {

      // Check preconditions
      if (exception == null) {
         throw new IllegalArgumentException("exception  == null");
      }

      // On Java 1.4 (and up) use the Throwable.getCause() method
      if (GET_CAUSE != null) {
         try {
            return (Throwable) GET_CAUSE.invoke(exception, null);
         } catch (IllegalAccessException e) {
            throw new RuntimeException("Unable to invoke Throwable.getCause() method. Caught IllegalAccessException.");
         } catch (IllegalArgumentException e) {
            throw new RuntimeException("Unable to invoke Throwable.getCause() method. Caught IllegalArgumentException");
         } catch (InvocationTargetException e) {
            throw new RuntimeException("Unable to invoke Throwable.getCause() method. Caught InvocationTargetException");
         }

      // On Java 1.3 use the static table
      } else {
         Object cause = CAUSE_TABLE.get(exception);
         return (cause == NULL) ? null : (Throwable) cause;
      }
   }

   /**
    * Sets the cause for the specified exception.
    *
    * @param exception
    *    the exception to set the cause for, cannot be <code>null</code>.
    *
    * @param cause
    *    the cause exception, can be <code>null</code> but cannot be the
    *    same as <code>exception</code>.
    *
    * @throws IllegalArgumentException
    *    if <code>exception == null || exception == cause</code>.
    *
    * @throws IllegalStateException
    *    if the cause exception was already set.
    */
   public static void setCause(Throwable exception, Throwable cause)
   throws IllegalArgumentException, IllegalStateException {

      // Check preconditions
      if (exception == null) {
         throw new IllegalArgumentException("exception  == null");
      }
      if (exception == cause) {
         throw new IllegalArgumentException("exception == cause");
      }

      // On Java 1.4 (and up) use the Throwable.initCause() method
      if (SET_CAUSE != null) {
         try {
            Object[] args = { cause };
            SET_CAUSE.invoke(exception, args);
         } catch (IllegalAccessException e) {
            throw new RuntimeException("Unable to invoke Throwable.initCause() method. Caught IllegalAccessException.");
         } catch (IllegalArgumentException e) {
            throw new RuntimeException("Unable to invoke Throwable.initCause() method. Caught IllegalArgumentException");
         } catch (InvocationTargetException e) {
            Throwable targetException = e.getTargetException();
            if (targetException instanceof RuntimeException) {
               throw (RuntimeException) targetException;
            } else if (targetException instanceof Error) {
               throw (Error) targetException;
            } else {
               throw new RuntimeException("Unable to invoke Throwable.initCause() method. Throwable.initCause() has thrown an unexpected exception. Exception class is " + targetException.getClass().getName() + ".  Message is: " + targetException.getMessage() + '.');
            }
         }

      // On Java 1.3 use the static table
      } else {
         if (CAUSE_TABLE.get(exception) != null) {
            throw new IllegalStateException("Cause for exception already set.");
         }

         Object value = (cause == null) ? NULL : cause;
         CAUSE_TABLE.put(exception, value);
      }
   }
}