<?php

require_once 'common/IObject.php';
require_once 'common/Object.php';
require_once 'common/ObjectInspector.php';

/**
 * Base Exception class. It's the root to any other exception class.
 * 
 * This class adds 2 attributes. The first, <em>object</em>, allows to hold 
 * the Object that throws the Exception (passing $this). The second one, 
 * <em>nestedException</em>, permits keep a nested Exception, so Exceptions 
 * could be nested, holding more info.
 * <br />
 * The interface <em>IObject</em>, defined at <em>common/IObject.php</em>, is 
 * implemented by this class. This interface is also implemented by 
 * <em>Object</em>, that is the base class for this framework.
 * This class must extend PHP's own <em>Exception</em> class, and then can't 
 * extends <em>Object</em> too. 
 * <br />
 * Codes (according to PHP Exceptions) are defined for know internal 
 * BaseExceptions.
 * 
 * @author  $Author: pruna $
 * @package common
 * @version $Revision: 1.3 $
 * @created 04-Oct-2004
 * @modified $Date: 2005/12/06 02:56:45 $
 */
class BaseException extends Exception implements IObject {
    
    /**
     * @const 
     */
    const CODE_UNIMPLEMENTED_METHOD = 1;
    
    /**
     * @const 
     */
    const CODE_INSTANTIATE_ABSTRACT_CLASS = 2;
    
    /**
     * Internal holder for failed object.
     * 
     * @var      object
     * @access   protected
     * @see      setObject
     */
    protected $object = null;
    
    /**
     * Internal holder for nested exception.
     * 
     * @var      Exception
     * @access   protected
     * @see      setNestedException
     */
    protected $exception = null;
    
    /**
     * Throws an Exception for unimplemented methods.
     * This method could be extended to log the error.
     *
     * @access    public
     * @param     string    $name         Method called in this object
     * @param     array     $arguments    Parameters passed
     * @return    mixed
     */
    public function __call($name, $arguments) {
        throw new BaseException("Unimplemented method: '$name' in class " 
            . ObjectInspector::getClass($this) . " (called with " 
            . var_export($arguments, TRUE) . ")", 
            self::CODE_UNIMPLEMENTED_METHOD, $this);
    }
    
    /**
     * Base constructor.
     *
     * @access    public
     * @param     string     $message     Message for exception
     * @param     int        $code        Code for exception
     * @param     object     $object      Failed object
     * @param     Exception  $exception   Nested exception
     */
    public function __construct($message = null, $code = 0, $object = null, 
        $exception = null) {
        parent::__construct($message, $code);
        $this->setObject($object);
        $this->setNestedExeption($exception);
    }
    
    /**
     * Empty destructor.
     * It facilitates parent destructor invocation, because if the object extends
     * from this class, it won't throw an error for not having a destructor.
     *
     * @access    public
     */
    public function __destruct() {}
    
    /**
     * Compares the specified object with this object for equality.
     *
     * @access    public
     * @param     mixed     $object    Object to be compared whit $this
     * @return    boolean
     */
    public function equals($object) {
        return ($this === $object);
    }
    
    /**
     * Returns the nested exception.
     *
     * @access   public
     * @return   Exception
     */
    public function getNestedExeption() {
        return $this->exception;
    }
    
    /**
     * Returns the object that has failed.
     *
     * @access   public
     * @return   object
     */
    public function getObject() {
        return $this->object;
    }
    
    /**
     * Returns string hash representation for this object.
     *
     * @access    public
     * @return    string
     */
    public function hashCode() {
        return md5($this->__toString());
    }
    
    /**
     * Magic method to <em>sleep</em> an object.
     * This method is called prior to serialization.
     *
     * @access    public
     * @return    array
     */
    public function __sleep() {
        return array_keys(ObjectInspector::getAttributes($this));
    }
    
    /**
     * Stores the nested exception.
     *
     * @access   public
     * @param    Exception     $exception    Nested exception to store
     */
    public function setNestedExeption($exception) {
        $this->exception = $exception;
    }
    
    /**
     * Stores the object that has failed.
     *
     * @access   public
     * @param    object     $object    Object to store
     */
    public function setObject($object) {
        $this->object = $object;
    }
    
    /**
     * Returns a string representation of this object.
     *
     * @access    public
     * @return    string
     */
    public function __toString() {
        return __CLASS__ . ": in {$this->getFile()} ({$this->getLine()}): "
            . "[{$this->getCode()}]: {$this->getMessage()}" . PHP_EOL
            . ((!is_null($this->object) && is_a($this->object, 'Object')) ? 
            ObjectInspector::getInfo($this->object) : '') 
            . $this->getTraceAsString() . PHP_EOL
            . (ObjectInspector::isA($this->exception, 'Exception') ? 
            $this->exception->__toString() : '');
    }
    
    /**
     * Returns a SimpleXML representation of this object.
     * 
     * @access    public
     * @return    SimpleXML
     */
    public function toXML() {
        return simplexml_load_string($this->toXMLString());
    }
    
    /**
     * Returns a XML string representation of this object.
     * Implementation is based on Object::toXMLString() method.
     * 
     * @access    public
     * @return    string
     */
    public function toXMLString() {
        $sp = "\t";
        $attribs = '';
        $oI = new ObjectInspector();
        foreach ($oI->getAttributes($this) as $k => $v) {
            $attribs .= $sp . $sp . '<attrib name="' . $k . '" type="' 
                . gettype($v) . '">' . $v . '</attrib>' . PHP_EOL;
        }
        $methods = '';
        foreach ($oI->getMethods($this) as $v) {
            $methods .= $sp . $sp . '<method>' . $v . '</method>' . PHP_EOL;
        }
        $parents = '';
        foreach ($oI->getHierarchy($this) as $v) {
            $parents .= $sp . $sp . '<parent>' . $v . '</parent>' . PHP_EOL;
        }
        $interfaces = '';
        foreach ($oI->getInterfaces($this) as $v) {
            $interfaces .= $sp . $sp . '<interface>' . $v . '<interface>' 
                . PHP_EOL;
        }
        $nested = '';
        if ($oI->isA($this->getNestedExeption(), 'BaseException')) {
            $nested = $this->getNestedExeption()->toXMLString();
        }
        return '<class name="' . $oI->getClass($this) . 
            '">' . PHP_EOL . 
            ($parents ? $sp . '<parents>' . PHP_EOL . $parents . $sp . 
                '</parents>' . PHP_EOL : '') .
            ($interfaces ? $sp . '<interfaces>' . PHP_EOL . $interfaces . 
                $sp . '</interfaces>' . PHP_EOL : '') . 
            ($attribs ? $sp . '<attributes>' . PHP_EOL . $attribs . $sp . 
                '</attributes>' . PHP_EOL : '') .
            ($methods ? $sp . '<methods>' . PHP_EOL . $methods . $sp . 
                '</methods>' . PHP_EOL : '') . 
            ($nested ? $sp . '<nested_exception>' . PHP_EOL . $nested . 
                '</nested_exception>' . PHP_EOL : '') .
            '</class>' . PHP_EOL;
    }
    
    /**
     * Magic method to "unsleep" an object.
     * This method is called prior to unserialization.
     *
     * @access    public
     */
    public function __wakeup() {}
    
}

?>