1  /*
     2   * Copyright the original author or authors.
     3   * 
     4   * Licensed under the MOZILLA PUBLIC LICENSE, Version 1.1 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   * 
     8   *      http://www.mozilla.org/MPL/MPL-1.1.html
     9   * 
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  import org.as2lib.env.except.Throwable;
    18  import org.as2lib.env.except.IllegalArgumentException;
    19  import org.as2lib.env.except.IllegalStateException;
    20  import org.as2lib.env.except.StackTraceElement;
    21  import org.as2lib.env.except.ThrowableStringifier;
    22  import org.as2lib.util.Stringifier;
    23  import org.as2lib.env.log.Logger;
    24  import org.as2lib.env.log.LogManager;
    25  
    26  /**
    27   * {@code AbstractThrowable} is an abstract class that contains sourced out
    28   * functionalities used by the classes {@link Exception} and
    29   * {@link FatalException}.
    30   * 
    31   * <p>It is thought to be an abstract implementation of the {@link Throwable}
    32   * interface. Because of that sub-classes must implement the {@code Throwable}
    33   * interface if they are themselves not abstract.
    34   *
    35   * <p>This class extends the {@code Error} class. Thus you can use sub-classes of
    36   * it as throwable type in catch-blocks in Flex.
    37   *
    38   * @author Simon Wacker
    39   * @see org.as2lib.env.except.Throwable
    40   */
    41  class org.as2lib.env.except.AbstractThrowable extends Error {
    42  	
    43  	/** Stringifier used to stringify throwables. */
    44  	private static var stringifier:Stringifier;
    45  	
    46  	/** Logger used to output throwables. */
    47  	private static var logger:Logger;
    48  	
    49  	/**
    50  	 * Returns the stringifier to stringify throwables.
    51  	 *
    52  	 * <p>The returned stringifier is either the default
    53  	 * {@link ThrowableStringifier} if no custom stringifier was set or if the
    54  	 * stringifier was set to {@code null}.
    55  	 *
    56  	 * @return the current stringifier
    57  	 */
    58  	public static function getStringifier(Void):Stringifier {
    59  		if (!stringifier) stringifier = new ThrowableStringifier();
    60  		return stringifier;
    61  	}
    62  	
    63  	/**
    64  	 * Sets the stringifier to stringify throwables.
    65  	 *
    66  	 * <p>If {@code throwableStringifier} is {@code null} the static
    67  	 * {@link #getStringifier} method will return the default stringifier.
    68  	 *
    69  	 * @param throwableStringifier the stringifier to stringify throwables
    70  	 */
    71  	public static function setStringifier(throwableStringifier:Stringifier):Void {
    72  		stringifier = throwableStringifier;
    73  	}
    74  	
    75  	/**
    76  	 * Returns the logger used to log throwables.
    77  	 * 
    78  	 * @return the logger used to log throwables
    79  	 */
    80  	private static function getLogger(Void):Logger {
    81  		if (!logger) {
    82  			logger = LogManager.getLogger("org.as2lib.env.except.Throwable");
    83  		}
    84  		return logger;
    85  	}
    86  	
    87  	/** The saved stack of method calls. */
    88  	private var stackTrace:Array;
    89  	
    90  	/** The throwable that caused this throwable to be thrown. */
    91  	private var cause;
    92  	
    93  	/** The message describing what went wrong. */
    94  	private var message:String;
    95  	
    96  	/** The error code to obtain localized client messages. */
    97  	private var errorCode:String;
    98  	
    99  	/**
   100  	 * Constructs a new {@code AbstractThrowable} instance.
   101  	 *
   102  	 * <p>All arguments are allowed to be {@code null} or {@code undefined}. But
   103  	 * if one is, the string representation returned by the {@code toString}
   104  	 * method will not be complete.
   105  	 *
   106  	 * <p>The {@code args} array should be the internal arguments array of the
   107  	 * method that throws the throwable. The internal arguments array exists in
   108  	 * every method and contains its parameters, the callee method and the caller
   109  	 * method. You can refernce it in every method using the name
   110  	 * {@code "arguments"}.
   111  	 *
   112  	 * @param message the message that describes the problem in detail
   113  	 * @param thrower the object that declares the method that throws this
   114  	 * throwable
   115  	 * @param args the arguments of the throwing method
   116  	 */
   117  	private function AbstractThrowable(message:String, thrower, args:Array) {
   118  		this.message = message;
   119  		stackTrace = new Array();
   120  		addStackTraceElement(thrower, args.callee, args);
   121  		// TODO: Implement findMethod to display the next line correctly.
   122  		// addStackTraceElement(undefined, args.caller, new Array());
   123  	}
   124  	
   125  	/**
   126  	 * Adds a stack trace element to the stack trace.
   127  	 *
   128  	 * <p>The new stack trace element is added to the end of the stack trace.
   129  	 *
   130  	 * <p>At some parts in your application you may want to add stack trace elements
   131  	 * manually. This can help you to get a clearer image of what went where wrong and
   132  	 * why. You can use this method to do so.
   133  	 *
   134  	 * @param thrower the object that threw, rethrew or forwarded (let pass) the
   135  	 * throwable
   136  	 * @param method the method that threw, rethrew or forwarded (let pass) the
   137  	 * throwable
   138  	 * @param args the arguments the method was invoked with when throwing, rethrowing
   139  	 * or forwarding (leting pass) the throwable
   140  	 */
   141  	public function addStackTraceElement(thrower, method:Function, args:Array):Void {
   142  		stackTrace.push(new StackTraceElement(thrower, method, args));
   143  	}
   144  	
   145  	/**
   146  	 * Returns an array that contains {@link StackTraceElement} instances of the
   147  	 * methods invoked before this throwable was thrown.
   148  	 *
   149  	 * <p>The last element is always the one that contains the actual method that
   150  	 * threw the throwable.
   151  	 *
   152  	 * <p>The stack trace helps you a lot because it says you where the throwing of
   153  	 * the throwable took place and also what arguments caused the throwing.
   154  	 *
   155  	 * <p>The returned stack trace is never {@code null} or {@code undefined}. If
   156  	 * no stack trace element has been set an empty array is returned.
   157  	 *
   158  	 * @return a stack containing the invoked methods until the throwable was thrown
   159  	 */
   160  	public function getStackTrace(Void):Array {
   161  		return stackTrace;
   162  	}
   163  	
   164  	/**
   165  	 * Returns the initialized cause.
   166  	 *
   167  	 * <p>The cause is the throwable that caused this throwable to be thrown.
   168  	 *
   169  	 * @return the initialized cause
   170  	 * @see #initCause
   171  	 */
   172  	public function getCause(Void) {
   173  		return cause;
   174  	}
   175  	
   176  	/**
   177  	 * Initializes the cause of this throwable.
   178  	 *
   179  	 * <p>The cause can only be initialized once. You normally initialize a cause
   180  	 * if you throw a throwable due to the throwing of another throwable. Thereby
   181  	 * you do not lose the information the cause offers.
   182  	 * 
   183  	 * <p>This method returns this throwable to have an easy way to initialize the
   184  	 * cause. Following is how you could use the cause mechanism.
   185  	 *
   186  	 * <code>
   187  	 *   try {
   188  	 *       myInstance.invokeMethodThatThrowsAThrowable();
   189  	 *   } catch (e:org.as2lib.env.except.Throwable) {
   190  	 *       throw new MyThrowable("myMessage", this, arguments).initCause(e);
   191  	 *   }
   192  	 * </code>
   193  	 * 
   194  	 * @param cause the throwable that caused the throwing of this throwable
   195  	 * @return this throwable itself
   196  	 * @throws org.as2lib.env.except.IllegalArgumentException if the passed-in
   197  	 * {@code newCause} is {@code null} or {@code undefined}
   198  	 * @throws org.as2lib.env.except.IllegalStateException if the cause has
   199  	 * already been initialized
   200  	 * @see #getCause
   201  	 */
   202  	public function initCause(newCause):Throwable {
   203  		if (!newCause) throw new IllegalArgumentException("Cause must not be null or undefined.", this, arguments);
   204  		if (cause) throw new IllegalStateException("The cause [" + cause + "] has already been initialized.", this, arguments);
   205  		cause = newCause;
   206  		return Throwable(this);
   207  	}
   208  	
   209  	/**
   210  	 * Returns the message that describes in detail what went wrong.
   211  	 *
   212  	 * <p>The message should be understandable, even for non-programmers. It should
   213  	 * contain detailed information about what went wrong. And maybe also how the user
   214  	 * that sees this message can solve the problem.
   215  	 *
   216  	 * <p>If the throwable was thrown for example because of a wrong collaborator or
   217  	 * an illegal string or something similar, provide the string representation of it
   218  	 * in the error message. It is recommended to put these between []-characters.
   219  	 *
   220  	 * @return the message that describes the problem in detail
   221  	 */
   222  	public function getMessage(Void):String {
   223  		return message;
   224  	}
   225  	
   226  	/**
   227  	 * Initializes the error code for this throwable.
   228  	 * 
   229  	 * <p>The initialization works only once. Any further initialization results in an
   230  	 * exception.
   231  	 * 
   232  	 * <p>Take a look at {@link #getErrorCode} to see what error codes are good for.
   233  	 * 
   234  	 * @param errorCode the error code to get localized client messages by
   235  	 * @return this throwable
   236  	 * @see #getErrorCode
   237  	 */
   238  	public function initErrorCode(errorCode:String):Throwable {
   239  		this.errorCode = errorCode;
   240  		return Throwable(this);
   241  	}
   242  	
   243  	/**
   244  	 * Returns the initialized error code.
   245  	 * 
   246  	 * <p>Error codes can be used to obtain localized messages appropriate for users;
   247  	 * while the {@link #getMessage} method returns messages inteded for developers to
   248  	 * get hands on the exception and fix bugs more easily.
   249  	 * The localized messages can for example be obtained through a global message
   250  	 * source and property files.
   251  	 * 
   252  	 * @return the error code to obtain an error message for users
   253  	 */
   254  	public function getErrorCode(Void):String {
   255  		return errorCode;
   256  	}
   257  	
   258  	/**
   259  	 * Returns the string representation of this throwable.
   260  	 *
   261  	 * <p>The string representation is obtained via the stringifier returned by
   262  	 * the static {@link #getStringifier} method.
   263  	 *
   264  	 * <p>If you want to change the string representation either set a new
   265  	 * stringifier via the static {@link #setStringifier} method or if you want
   266  	 * the string representation only change for one throwable and its
   267  	 * sub-classes overwrite this method.
   268  	 *
   269  	 * @return the string representation of this throwable
   270  	 */
   271  	private function doToString(Void):String {
   272  		return getStringifier().execute(this);
   273  	}
   274  	
   275  }