/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License.
 */

/*
 * JDOException.java
 *
 */

package javax.jdo;

import javax.jdo.spi.I18NHelper;

/** This is the root of all JDO Exceptions.  It contains an optional detail
 * message, an optional nested <code>Throwable</code> array and an optional failed object.
 * @author Craig Russell
 * @version 1.0.2
 */
public class JDOException extends java.lang.RuntimeException {
  
  /** This exception was generated because of an exception in the runtime library.
   * @serial the nested <code>Throwable</code> array
   */
  Throwable[] nested;
  
  /** This exception may be the result of incorrect parameters supplied
   * to an API.  This is the object from which the user can determine
   * the cause of the problem.
   * @serial the failed <code>Object</code>
   */
  Object failed;

    /** The Internationalization message helper.
     */
    private static I18NHelper msg = I18NHelper.getInstance ("javax.jdo.Bundle"); //NOI18N

    /** Flag indicating whether printStackTrace is being executed.
     */
    private boolean inPrintStackTrace = false;
    
  /**
   * Constructs a new <code>JDOException</code> without a detail message.
   */
  public JDOException() {
  }
  

  /**
   * Constructs a new <code>JDOException</code> with the specified detail message.
   * @param msg the detail message.
   */
  public JDOException(String msg) {
    super(msg);
  }

  /** Constructs a new <code>JDOException</code> with the specified detail message
   * and nested <code>Throwable</code>s.
   * @param msg the detail message.
   * @param nested the nested <code>Throwable[]</code>.
   */
  public JDOException(String msg, Throwable[] nested) {
    super(msg);
    this.nested = nested;
  }
  
  /** Constructs a new <code>JDOException</code> with the specified detail message
   * and nested <code>Throwable</code>.
   * @param msg the detail message.
   * @param nested the nested <code>Throwable</code>.
   */
  public JDOException(String msg, Throwable nested) {
    super(msg);
    this.nested = new Throwable[] {nested};
  }
  
  /** Constructs a new <code>JDOException</code> with the specified detail message
   * and failed object.
   * @param msg the detail message.
   * @param failed the failed object.
   */
  public JDOException(String msg, Object failed) {
    super(msg);
    this.failed = failed;
  }
  
  /** Constructs a new <code>JDOException</code> with the specified detail message,
   * nested <code>Throwable</code>s, and failed object.
   * @param msg the detail message.
   * @param nested the nested <code>Throwable[]</code>.
   * @param failed the failed object.
   */
  public JDOException(String msg, Throwable[] nested, Object failed) {
    super(msg);
    this.nested = nested;
    this.failed = failed;
  }
  
  /** Constructs a new <code>JDOException</code> with the specified detail message,
   * nested <code>Throwable</code>, and failed object.
   * @param msg the detail message.
   * @param nested the nested <code>Throwable</code>.
   * @param failed the failed object.
   */
  public JDOException(String msg, Throwable nested, Object failed) {
    super(msg);
    this.nested = new Throwable[] {nested};
    this.failed = failed;
  }
  
  /** The exception may include a failed object.
   * @return the failed object.
   */
  public Object getFailedObject() {
    return failed;
  }
  
  /** The exception may have been caused by multiple exceptions in the runtime.
   * If multiple objects caused the problem, each failed object will have
   * its own <code>Exception</code>.
   * @return the nested Throwable array.
   */
  public Throwable[] getNestedExceptions() {
    return nested;
  }
  
  /** Often there is only one nested exception, and this method returns it.
   * If there are more than one, then this method returns the first nested
   * exception. If there is no nested exception, then null is returned.
   * @return the first or only nested Throwable.
   * @since 1.0.1
   */
  public synchronized Throwable getCause() {
      // super.printStackTrace calls getCause to handle the cause. 
      // Returning null prevents the superclass from handling the cause;
      // instead the local implementation of printStackTrace should
      // handle the cause. Otherwise, the cause is printed twice.
      if (nested == null || nested.length == 0 || inPrintStackTrace) {
          return null;
      } else {
          return nested[0];
      }
  }
  
  /** JDK 1.4 includes a new chaining mechanism for Throwable, but since
   * JDO has its own "legacy" chaining mechanism, the "standard" mechanism
   * cannot be used. This method always throws a JDOFatalInternalException.
   * @param cause ignored.
   * @return never.
   */
  public Throwable initCause(Throwable cause) {
      throw new JDOFatalInternalException(msg.msg("ERR_CannotInitCause"));
  }
  
  /** The <code>String</code> representation includes the name of the class,
   * the descriptive comment (if any),
   * the <code>String</code> representation of the failed <code>Object</code> (if any),
   * and the <code>String</code> representation of the nested <code>Throwable</code>s (if any).
   * @return the <code>String</code>.
   */
  public synchronized String toString() {
    int len = nested==null?0:nested.length;
    // calculate approximate size of the String to return
    StringBuffer sb = new StringBuffer (10 + 100 * len);
    sb.append (super.toString());
    // include failed object information
    if (failed != null) {
        sb.append ("\n").append (msg.msg ("MSG_FailedObject"));
      String failedToString = null;
      try {
          failedToString = failed.toString();
      } catch (Exception ex) {
          // include the information from the exception thrown by failed.toString
          Object objectId = JDOHelper.getObjectId(failed);
          if (objectId == null) {
              failedToString = msg.msg("MSG_ExceptionGettingFailedToString", //NOI18N
                                       exceptionToString(ex));
          }
          else {
              // include the ObjectId information
              String objectIdToString = null;
              try {
                  objectIdToString = objectId.toString();
              }
              catch (Exception ex2) {
                  objectIdToString = exceptionToString(ex2);
              }
              failedToString = msg.msg("MSG_ExceptionGettingFailedToStringObjectId", //NOI18N
                                       exceptionToString(ex), objectIdToString);
          }
      }
      sb.append (failedToString);
    }
    // include nested Throwable information, but only if not called by
    // printStackTrace; the stacktrace will include the cause anyway.
    if (len > 0 && !inPrintStackTrace) {
      sb.append ("\n").append (msg.msg ("MSG_NestedThrowables")).append ("\n");
      Throwable exception = nested[0];
      sb.append (exception==null?"null":exception.toString()); //NOI18N
      for (int i=1; i<len; ++i) {
        sb.append ("\n"); //NOI18N
        exception = nested[i];
      sb.append (exception==null?"null":exception.toString()); //NOI18N
      }
    }
    return sb.toString();
  }    
  
    /**
     * Prints this <code>JDOException</code> and its backtrace to the 
     * standard error output.
     * Print nested Throwables' stack trace as well.
     */
    public void printStackTrace() { 
        printStackTrace (System.err);
    }

    /**
     * Prints this <code>JDOException</code> and its backtrace to the 
     * specified print stream.
     * Print nested Throwables' stack trace as well.
     * @param s <code>PrintStream</code> to use for output
     */
    public synchronized void printStackTrace(java.io.PrintStream s) { 
    int len = nested==null?0:nested.length;
        synchronized (s) {
            inPrintStackTrace = true;
            super.printStackTrace(s);
            if (len > 0) {
                s.println (msg.msg ("MSG_NestedThrowablesStackTrace"));
                for (int i=0; i<len; ++i) {
                    Throwable exception = nested[i];
                    if (exception != null) {
                        exception.printStackTrace(s);
                    }
                }
            }
            inPrintStackTrace = false;
        }
    }

    /**
     * Prints this <code>JDOException</code> and its backtrace to the specified
     * print writer.
     * Print nested Throwables' stack trace as well.
     * @param s <code>PrintWriter</code> to use for output
     */
    public synchronized void printStackTrace(java.io.PrintWriter s) { 
    int len = nested==null?0:nested.length;
        synchronized (s) {
            inPrintStackTrace = true;
            super.printStackTrace(s);
            if (len > 0) {
                s.println (msg.msg ("MSG_NestedThrowablesStackTrace"));
                for (int i=0; i<len; ++i) {
                    Throwable exception = nested[i];
                    if (exception != null) {
                        exception.printStackTrace(s);
                    }
                }
            }
            inPrintStackTrace = false;
        }
    }

    /**
     * Helper method returning a short description of the exception passed
     * as an argument. The returned string has the format defined by
     * Throwable.toString. If the exception has a non-null detail message 
     * string, then it returns the name of exception class concatenated
     * with ": " concatenated with the detailed message. Otherwise it
     * returns the name of exception class.
     * @param ex the exception to be represented.
     * @return a string representation of the exception passed as an argument.
     */
    private static String exceptionToString(Exception ex)
    {
        if (ex == null) return null;
        String s = ex.getClass().getName();
        String message = ex.getMessage();
        return (message != null) ? (s + ": " + message) : s;
    }
}

