<?php
 
namespace Co3\CodeGen;
 
use \InvalidArgumentException as InvalidArgumentException;
 
use \BadMethodCallException as BadMethodCallException;
 
/**
 
 * @package Co3
 
 * @subpackage CodeGen
 
 * @author Oliver Anan <[email protected]>
 
 */ 
 
/** 
 
 * Base class of enumerations. 
 
 * Service provider for generating Enumerations.
 
 * 
 
 * <p>This class can generate other enum classes at runtime and provides methods to work with enums.
 
 * All enum classes are derived from this class.</p> 
 
 * <p>Creation and managment are not seperated to allow the usage 
 
 * of a protected constructor.</p>
 
 * <p>Creating a new enumeration involves craeting the source code and one instance
 
 * for each member.</p> 
 
 * <p><b>Manually deriving from this class will not work</b>.</p> 
 
 * <p>Supports type hinting
 
 * <code>
 
 * //create the enumearion 'Direction'
 
 *  Enum::create('Direction',array('Up','Down','Left','Right'));
 
 *  
 
 * function move(Direction $dir,$distance){
 
 *   //code
 
 * } 
 
 * </code></p>
 
 * <p>implements __toString()</p> 
 
 * @author Oliver Anan <[email protected]>
 
 * @version 1.0
 
 * @since 0.1
 
 * @copyright 2011 oliver anan  
 
 * @license LGPL 3 http://www.gnu.org/licenses/lgpl.html    
 
 */
 
class Enum{
 
    //------ STATIC ------//
 
    private static $_members = array();
 
    private static $_arrays = array();
 
    
 
    /**
 
     * Create a new enumeration class.
 
     * 
 
     * <p>if the class name contains a \ character the class will be generated in a namespace.
 
     * Everything before the last \ is considered as namespace</p>
 
     * <p>All values of the $members array must be valid php identifiers.
 
     * Values o the $members array must not be PHP keywords.
 
     * The keys of the $members array are preserved unless $baseTwoKeysm is true</p>  
 
     * <code>
 
     *  //create the enumearion 'Direction' in the namespace 'My\Namespace'
 
     *  Enum::create('My\Namespae\Direction',array('Up','Down','Left','Right'));
 
     * </code>
 
     * @param string $name the name of the class including the namespace
 
     * @param array $members an array  containng all memebers of the enumeration.
 
     * The keys are preserved unless baseTwoKeys is true. 
 
     * @param boolean $baseTwoKeys If this is true all keys will be replaced with powers of 2.
 
     * @return void;
 
     * @static
 
     * @throws \InvalidArgumentException
 
     * @throws Co3\Exception\FormatException (Only if class is available)
 
     * @access public 
 
     */
 
    public static function create($name, $members,$baseTwoKeys=false){
 
        $code = self::generateSourceCode($name,$members);
 
        eval($code);
 
        //create instances
 
        self::$_arrays[$name]  = array();
 
        self::$_members[$name] = array();
 
        if($baseTwoKeys){
 
            $i = 0;
 
        }
 
        foreach($members as $key => $member){
 
            if($baseTwoKeys){
 
                $key = pow(2,$i++);
 
            }
 
            $instance = new $name($key,$member);
 
            self::$_members[$name][$member] = $instance;
 
            self::$_arrays[$name][$key] = $instance;
 
        }     
 
    }
 
    /**
 
     * get an array with all instances of this enumeration.
 
     * @return array
 
     * @access public
 
     * @static
 
     * @throws \BadMethodCallException 
 
     */
 
    public static function toArray(){
 
        $calledClass = get_called_class();
 
        if($calledClass==__CLASS__){
 
            throw new BadMethodCallException(__CLASS__ . '::' . __METHOD__ . ' may only be called from deriver classes.');
 
        }
 
        return self::$_arrays[get_called_class()];
 
    }
 
    
 
    /**
 
     * create the php source code for an enum class
 
     * 
 
     * @param $name the name o the new class including the namespace.
 
     * @param $members 
 
     * @return string
 
     * @access private
 
     * @static
 
     * @throws \InvalidArgumentException
 
     * @throws Co3\Exception\FormatException (Only if class is available)
 
     */
 
    private static function generateSourceCode($name,$members){
 
                $code      = "";
 
        //remove leading '\'
 
        $name = ltrim($name,'\\');
 
        $parts     = explode('\\',$name);
 
        //validate class name (including namespace)
 
        foreach($parts as $part){
 
            if(!self::isValidIdentifier($part)){
 
                    $message = "Invalid class/namespace name '{$className}'";
 
                    if(class_exists('Co3\Exception\FormatException',true)){
 
                        throw new Co3\Exception\FormatException($message);
 
                    } else {
 
                        throw new InvalidArgumentException($message);
 
                    }
 
            }
 
        }
 
        $className      = array_pop($parts);;
 
        $namespace = implode('\\',$parts);        
 
        //create source code
 
        if($namespace){
 
            $code .= "namespace {$namespace};\n";
 
        }
 
        $code .= "final class {$className} extends \\" . __CLASS__ . "{\n";
 
        foreach($members as $key => $member){
 
            if(!self::isValidIdentifier($member)){
 
                $message = "Invalid member name '{$member}' at index {$key}.";
 
                if(class_exists('Co3\Exception\FormatException')){
 
                    throw new Co3\Exception\FormatException($message);
 
                } else {
 
                    throw new InvalidArgumentException($message);
 
                }
 
            }
 
            $code .= "const {$member} = '{$member}';\n";
 
        }
 
        $code .= "}";
 
        return $code;
 
    }
 
    
 
    /**
 
     * Get an instance of the called enum class.
 
     * 
 
     * <p>Return an instance of the called class if there is an instance
 
     * with an name that equals $name.</p>
 
     * <p>Enumeations define class constants for every member. 
 
     * You can use the name or the constant to get an instance</p> 
 
     * <p>Subsequent calls to get with the same $name will return the same instance</p>
 
     * <code>
 
     * Enum::create('Direction',array('Up','Down','Left','Right'));
 
     * $up = Direction::get(Direction::Up);
 
     * //is the same as
 
     * $upToo = Direction::get('Up');
 
     * //There is just one instance for every member.
 
     * $up === $upToo //true
 
     * </code>
 
     * <p>If there is no matching instance a InvalidArgumentException is thrown.</p> 
 
     * @param string $name the name of the instance.
 
     * @return unknown_type an instance of the called class
 
     * @access public
 
     * @static
 
     * @throws \BadMethodCallException
 
     */
 
    public static function get($name){
 
            $calledClass = get_called_class();
 
        if($calledClass==__CLASS__){
 
            throw new BadMethodCallException(__CLASS__ . '::' . __METHOD__ . ' may only br called from deriver classes.');
 
        }
 
        if ($calledClass::isMember($name)){
 
            return Enum::$_members[$calledClass][$name];
 
        } else {
 
            throw new InvalidArgumentException("{$name} is no valid member of {$calledClass}");
 
        }
 
    }
 
    
 
    /** 
 
     * Get an instance of the called enum class.
 
     * 
 
     * Return an instance of the called class if there is an instance
 
     * with an key that equals $key.
 
     * If there is no matching instance a InvalidArgumentException is thrown.    
 
     * @param $key the key of the instance. 
 
     * @access public
 
     * @static
 
     * @return unknown_type an instance o the called class
 
     * @throws \BadMethodCallException
 
     */
 
    public static function getByKey($key){
 
        $calledClass = get_called_class();
 
        if($calledClass==__CLASS__){
 
            throw new BadMethodCallException(__CLASS__ . '::' . __METHOD__ . ' may only br called from deriver classes.');
 
        }
 
        if($calledClass::isKey($key)){
 
            return self::$_arrays[$calledClass][$key]; 
 
        } else {
 
            throw new InvalidArgumentException("{$name} is no valid key of {$calledClass}");
 
        }
 
    }
 
    
 
    /**
 
     * Returns an boolean indicating if there is a member of this enum type with this name.
 
     * @param $name
 
     * @return boolean
 
     * @access public
 
     * @static
 
     * @throws \BadMethodCallException
 
     */
 
    public static function isMember($name){
 
        $calledClass = get_called_class();
 
        if($calledClass==__CLASS__){
 
            throw new BadMethodCallException(__CLASS__ . '::' . __METHOD__ . ' may only be called from deriver classes.');
 
        }
 
        return array_key_exists($name,Enum::$_members[$calledClass]);
 
    }
 
    
 
    /**
 
     * Returns an boolean indicating if there is a member of this enum type with this key.
 
     * @return boolean
 
     * @access public
 
     * @static 
 
     * @throws \BadMethodCallException
 
     */
 
    public static function isKey($key){
 
        $calledClass = get_called_class();
 
        if($calledClass==__CLASS__){
 
            throw new BadMethodCallException(__CLASS__ . '::' . __METHOD__ . ' may only br called from deriver classes.');
 
        }
 
        return array_key_exists($key, self::$_arrays[$calledClass]);
 
    }
 
    
 
    /**
 
     * Returns an boolean indicating if the given string is a valid PHP identifier
 
     * 
 
     * @todo move this method to another class 
 
     * @param $string
 
     * @return unknown_type
 
     * @access protected
 
     * @static
 
     */
 
    private static function isValidIdentifier($string){
 
        return preg_match("|^[a-zA-Z_][a-zA-Z0-9_]*$|i", $string); 
 
    }
 
    //------ INSTANCE ------//
 
    /**
 
     * The internal key of this instance
 
     * @var unknown_type
 
     * @access protected
 
     */
 
    private $key;
 
    
 
    /**
 
     * The name of this instance
 
     * @var unknown_type
 
     * @access protected
 
     */
 
    private $name;
 
    
 
    /**
 
     * constructs a new instance
 
     * @param $key the key o the instance
 
     * @param $name the name o the instance
 
     * @return unknown_type
 
     * @access protected
 
     */
 
    protected function __construct($key, $name){
 
        $this->key = $key;
 
        $this->name = $name;
 
    }
 
    
 
    /**
 
     * Returns the internal key of this instance.
 
     * @return unknown_type
 
     * @access public
 
     */
 
    public function getKey(){
 
        return $this->key;
 
    }
 
    
 
    /**
 
     * returns the name of this instance. 
 
     * @return string 
 
     * @access public
 
     */
 
    public function __toString(){
 
        return $this->name;
 
    }
 
     
 
}
 
?>
 
 
 
 |