//
// $Id: Exception.h,v 1.8 2007/03/08 19:59:21 will_mason Exp $
//
// vi: set ft=objc:

/*
 * ObjectiveLib - a library of containers and algorithms for Objective-C
 *
 * Copyright (c) 2004-2007
 * Will Mason
 *
 * Portions:
 *
 * Copyright (c) 1994
 * Hewlett-Packard Company
 *
 * Copyright (c) 1996,1997
 * Silicon Graphics Computer Systems, Inc.
 *
 * Copyright (c) 1997
 * Moscow Center for SPARC Technology
 *
 * Copyright (c) 1999 
 * Boris Fomitchev
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * You may contact the author at will_mason@users.sourceforge.net.
 */

#if !defined(EXCEPTION_OL_GUARD)
#if !defined(OL_GENERATING_DOCUMENTATION)
#define EXCEPTION_OL_GUARD
#endif

#include <ObjectiveLib/ObjectBase.h>

/**
 * @defgroup Exceptions
 *
 * @if OL_NO_OPENSTEP
 *
 * Classes, constants and macros for exception handling in @b ObjectiveLib. Although
 * keywords for handling exception in Objective-C don't exist on all platforms, the
 * same effect can be achieved with classes and macros. There is a group of macros
 * in @b ObjectiveLib for defining try and catch blocks, and these macros work with
 * the OLException class to provide a nestable system of exception handlers on the
 * call stack.
 *
 * The basic usage of the macros is as follows. A try block and a catch block are both
 * defined. If the catch block is entered, one knows that an exception was raised
 * either within this function or futher down the call stack. Once an exception is
 * caught, it may be referred to by the indentifier @c localException. It is always
 * of type OLException.
 * @code
 * OL_TRY
 *     [self takeToVet: dog];
 *     [dog setHealthy: YES];
 * OL_CATCH
 *     fprintf(stderr, [localException message]);
 * OL_END_CATCH
 * @endcode
 *
 * Other macros from Exception.h provide related services. For example, if you with to
 * return from a method from within a try block, you must use one of two macros:
 * OL_TRY_ABANDON or OL_TRY_RETURN. If the method returns @c void, you can always
 * use OL_TRY_ABANDON, but you must otherwise use OL_TRY_RETURN. The same principle
 * applies to the macros OL_CATCH_ABANDON and OL_CATCH_RETURN, but they are for use
 * from within a catch block.
 * @code
 * OL_TRY
 *     [self takeToVet: dog];
 *     OL_TRY_RETURN(YES);
 * OL_CATCH
 *     fprintf(stderr, [localException message]);
 *     OL_CATCH_RETURN(NO);
 * OL_END_CATCH
 * @endcode
 *
 * There are two additional macros for use in catch blocks. In order to raise another
 * exception from within a catch block you must use either OL_CATCH_RAISE or
 * OL_CATCH_RAISE_STRING. The two are identical, but the second macro takes instances
 * of OLConstantString for the first two arguments instead of C "strings".
 * @code
 * OL_TRY
 *     [self takeToVet: dog];
 *     OL_TRY_RETURN(YES);
 * OL_CATCH
 *     fprintf(stderr, [localException message]);
 *     OL_CATCH_RAISE("DogException", "My dog has %s", "fleas");
 * OL_END_CATCH
 * @endcode
 * Note that to re-raise the current @c localException you must only call its
 * OLException#raise method. Using OL_CATCH_RAISE to re-raise the current exception
 * will not work.
 * @code
 * OL_TRY
 *     [self takeToVet: dog];
 *     OL_TRY_RETURN(YES);
 * OL_CATCH
 *     fprintf(stderr, [localException message]);
 *     [localException raise];
 * OL_END_CATCH
 * @endcode
 *
 * @else
 *
 * Constants that define the names of exceptions that @b ObjectiveLib can raise.
 *
 * @endif
 */

/**
 * @file
 *
 * @if OL_NO_OPENSTEP
 *
 * Constants and definitions for propagating exceptions. The constant "strings"
 * that appear in this file represent the names of exceptions that can be raised
 * by @b ObjectiveLib. The file also contains macros which actually implement
 * the exception handling mechanism. The other stuff in the file is for internal
 * use only.
 *
 * @else
 *
 * Constants used for the naems of exceptions that @b ObjectiveLib can raise.
 *
 * @endif
 *
 * @ingroup Exceptions
 */

/**
 * The name of the exception raised when an I/O error occurs.
 */
#if defined(OL_NO_OPENSTEP)
extern OLConstantString* const OLInputOutputException;
#else
extern NSString* const OLInputOutputException;
#endif

/**
 * The name of the exception raised when the end of a stream is reached.
 */
#if defined(OL_NO_OPENSTEP)
extern OLConstantString* const OLEndOfStreamException;
#else
extern NSString* const OLEndOfStreamException;
#endif

/**
 * The name of the exception raised when an error occurs during a socket operation.
 */
#if defined(OL_NO_OPENSTEP)
extern OLConstantString* const OLSocketException;
#else
extern NSString* const OLSocketException;
#endif

/**
 * The name of the exception raised when a class in the stream could not be found.
 */
#if defined(OL_NO_OPENSTEP)
extern OLConstantString* const OLClassNotFoundException;
#else
extern NSString* const OLClassNotFoundException;
#endif

#if defined(OL_NO_OPENSTEP)

#include <setjmp.h>
#include <stdarg.h>

/**
 * The name of the exception raised when a an invalid argument is encountered.
 */
extern OLConstantString* const OLInvalidArgumentException;

/**
 * The name of the exception raised when something just went wrong but no one is
 * really sure what it was.
 */
extern OLConstantString* const OLGenericException;

/**
 * @class OLException Exception.h ObjectiveLib/Exception.h
 *
 * This class is used to propagate information about exceptions.
 *
 * @ingroup Exceptions
 */
@interface OLException : Object
{
@protected
    /**
     * The type or name of the exception
     */
    char*           type;

    /**
     * The message to be delivered
     */
    char*           message;
}

/**
 * Raise an exception. This is a convenience method for allocating, instantiating
 * and rasing a new exception. See #initWithType:message:arguments:.
 *
 * @param nm the name or type of exception
 * @param msg the message that the exception should convey
 * @param ... the arguments used to format the message
 */
+ (void) raiseType: (const char*)nm message: (const char*)msg, ...;

/**
 * Raise an exception. This is a convenience method for allocating, instantiating
 * and rasing a new exception. The method initializes the eception with
 * #initWithType:message:arguments: by converting @a nm and @a msg into c-style
 * "strings".
 *
 * @param nm the name or type of exception
 * @param msg the message that the exception should convey
 * @param ... the arguments used to format the message
 */
+ (void) raiseTypeString: (OLConstantString*)nm messageString: (OLConstantString*)msg, ...;

/**
 * @name Initializers and Deallocatros
 */
/* @{ */
/**
 * Initialize an exception. The exception is given the name and its message. The message
 * is formatted by inserting the arguments into the message using @c printf style
 * formatting.
 *
 * @param nm the name or type of the exception
 * @param msg the message to convey
 * @param args the variadic arguments to format into the message
 * @return a reference to this exception
 */
- (id) initWithType: (const char*)nm message: (const char*)msg arguments: (va_list)args;

/**
 * Initialize an exception. The exception is given the name and its message. This message
 * musters the variadic arguments and sends the message #initWithType:message:arguments:.
 *
 * @param nm the name or type of the exception
 * @param msg the message to convey
 * @param ... the variadic arguments to format into the message
 * @return a reference to this exception
 */
- (id) initWithType: (const char*)nm message: (const char*)msg, ...;

/**
 * Deallocate memory and free the exception.
 */
- (id) free;
/* @} */

/**
 * Return the message associated with this exception.
 *
 * @return the message
 */
- (const char*) message;

/**
 * Return the type or name of the exception.
 *
 * @return the type or name
 */
- (const char*) type;

/**
 * Raise the exception. The exception is raised and control is transferred to the
 * next available block of code defined by OL_CATCH and OL_END_CATCH. If there is
 * no catch block available, then the system will abort.
 *
 * @pre A block of code defined by OL_CATCH and OL_END_CATCH must exist somewhere
 * in the call stack.
 */
- (void) raise;

@end

/**
 * @struct _OLCatchInfo Exception.h ObjectiveLib/Exception.h
 *
 * This is the internal structure used to pass information about exceptions.
 * This is for internal use only and it is unlikely that you will ever need
 * to refer to this structure directly. All the functionality is expressed
 * through the use of the OLException class and the exception handling
 * macros.
 *
 * @ingroup Exceptions
 */
typedef struct _OLCatchInfo
{
    /**
     * The next node in the list
     */
    struct _OLCatchInfo*    next;

    /**
     * The information for the long jump required in case of an exception
     * being raised
     */
    jmp_buf                 jumpInfo;

    /**
     * The exception raised
     */
    OLException*            exception;
} OLCatchInfo; /**< The type definition for the internal catch information structure */

/**
 * Push a catch information structure. You never want to use this function. It is
 * for internal use only. Please don't use it.
 *
 * @param info the catch information structure
 */
extern void OLPushCatchInfo(OLCatchInfo* info);

/**
 * Pop a catch information structure. You never want to use this function. It is
 * for internal use only. Please don't use it.
 */
extern void OLPopCatchInfo();

/**
 * Start a try block.
 *
 * @hideinitializer
 */
#define OL_TRY \
    { \
        OLCatchInfo __currentCatchInfo; \
        OLPushCatchInfo(&__currentCatchInfo); \
        if (!setjmp(__currentCatchInfo.jumpInfo)) \
        {

/**
 * Start a catch block.
 *
 * @hideinitializer
 */
#define OL_CATCH \
            OLPopCatchInfo(); \
        } \
        else \
        { \
            OLException* localException =  __currentCatchInfo.exception; \
            {

/**
 * End a catch block.
 *
 * @hideinitializer
 */
#define OL_END_CATCH \
            } \
            [localException free]; \
        } \
    }

/**
 * Return a value from a try block.
 *
 * @hideinitializer
 * @param val the value to return
 */
#define OL_TRY_RETURN(val) \
        { \
            typeof(val) __tryReturnValue = val; \
            OLPopCatchInfo(); \
            return __tryReturnValue; \
        }

/**
 * Abandon a try block.
 *
 * @hideinitializer
 */
#define OL_TRY_ABANDON \
        OLPopCatchInfo(); \
        return;

/**
 * Return a value from a catch block.
 *
 * @hideinitializer
 * @param val the value to return
 */
#define OL_CATCH_RETURN(val) \
            { \
                typeof(val) __catchReturnValue = val; \
                [localException free]; \
                return __catchReturnValue; \
            }

/**
 * Abandon a catch block.
 *
 * @hideinitializer
 */
#define OL_CATCH_ABANDON \
            [localException free]; \
            return 

/**
 * Raise a new exception from within a catch block. The macro requires arguments
 * of type C "string". For example, OL_CATCH_RAISE("DogException", "My dog has %s", "fleas").
 *
 * @sa OLException#initWithType:message:arguments:
 *
 * @hideinitializer
 * @param typ the type or name of the exception to raise
 * @param ... the variadic argument list, which includes the message itself and any
 * arguments with which the final message should be formatted
 */
#define OL_CATCH_RAISE(typ, ...) \
            [localException free]; \
            [OLException raise: typ message: __VA_ARGS__]

/**
 * Raise a new exception from within a catch block. This macro takes arguments of
 * type OLConstantString instead of bare C "string" arguments. For example,
 * OL_CATCH_RASE_STRING(@"DogException", @"My dog has %s", "fleas").
 *
 * @hideinitializer
 * @param typ the type or name of the exception to raise
 * @param ... the variadic argument list, which includes the message itself and any
 * arguments with which the final message should be formatted
 */
#define OL_CATCH_RAISE_STRING(typ, ...) \
            [localException free]; \
            [OLException raiseTypeString: typ messageString: __VAR_ARGS__]

#endif

#endif
