<?php
/**
* Analyses a class.
* 
* @version    $Id: PhpdocClassAnalyser.php,v 1.2 2002/04/11 15:06:45 timmyg Exp $
*/
class PhpdocClassAnalyser extends PhpdocAnalyser {

    /**
    * Class data.
    *
    * @var  array
    */
    var $classes = array();
    
    /**
    * Name of the baseclass of the given classes.
    *
    * @var  string    
    */
    var $baseclass = "";
    
    /**
    * Ordered list of all classes.
    *
    * @var  array
    */ 
    var $classlist = array();
    
    /**
    * List of not inherited elements.
    *
    * @var  array
    */
    var $notinherited = array(
                                "class"    => array(
                                                    "name"          => true,
                                                    "extends"       => true,
                                                    "undoc"         => true,
                                                    "variables"     => true,
                                                    "functions"     => true,
                                                    "consts"        => true,
                                                    "uses"          => true,
                                                    "filename"      => true,
                                                    "subclasses"    => true,
                                                    "path"          => true,
                                                    "baseclass"     => true,
                                                    "abstract"      => true
                                                ),
                                                                
                                "functions"    => array(
                                                        "name"      => true,
                                                        "undoc"     => true,
                                                        "inherited" => true,
                                                        "overrides" => true,
                                                        "abstract"  => true
                                                    ),
                                                                        
                                "variables"    => array(
                                                        "name"      => true,
                                                        "undoc"     => true,
                                                        "inherited" => true,
                                                        "overrides" => true,
                                                        "abstract"  => true
                                                    ),    
                                                                        
                                "uses"            => array(
                                                            "name"      => true,
                                                            "undoc"     => true,
                                                            "inherited" => true,
                                                            "overrides" => true
                                                        ),                                                                                                                                                                                                                                
                                
                                "consts"    => array(
                                                    "name"      => true,
                                                    "undoc"     => true,
                                                    "inherited" => true,
                                                    "overrides" => true
                                                )                                                                                                                        
                        );
                                                    

    /**
    * Puuuh - findUndocumented() needs this.
    *
    * @var  array
    * @see  findUndocumented()
    */                                                        
    var $undocumentedFields = array(
                                    "functions"    => "function",
                                    "variables"    => "variable",
                                    "uses"            => "included file",
                                    "consts"        => "constant"
                            );

    /**
    * Sets the class data and the name of the baseclass.
    *
    * @param    array   Raw class data from the parser
    * @param    string  Name of the baseclass of the given classes
    * @access   public
    */                                                        
    function setClasses($classes, $baseclass) {
        
        $this->classes = $classes;
        $this->baseclass = $baseclass;
        
    } // end func setClasses
                                                    
    function analyse() {
        
        $this->flag_get = false;

        $this->updateAccessReturn();
        $this->updateBrothersSisters();
        $this->checkSee();
        
        $this->classlist = array();
        
        $this->buildBottomUpClasslist($this->baseclass);
        
    } // end func analyse

    /**
    * Returns an analysed class or false if there're no classes any more.
    *
    * @return   mixed   False if there no classes anymore, otherwise an array with 
    *                   the data of the class.
    * @accesss  public
    */    
    function getClass() {
    
        if (!$this->flag_get) {
            reset($this->classlist);
            $this->flag_get = true;
        }
        if (list($k, $classname) = each($this->classlist)) {

            if (isset($this->classes[$classname]["path"]))
                $this->inheritClassElements($classname);    
            
            $this->checkFunctionArgs($classname);
            $this->findUndocumented($classname);
            
            $class = $this->classes[$classname];
            unset($this->classes[$classname]);
            return $class;
            
        } else {
        
            return false;
            
        }
    } // end func getClass

    /**
    * Looks for undocumented elements in a certain class
    * 
    * @param    string  Classname
    */
    function findUndocumented($classname) {
        
        $file = $this->classes["filename"];
        if ($this->classes["undoc"])
            $this->warn->addDocWarning($file, "class", $name, "The class is not documented.", "missing");
            
        reset($this->undocumentedFields);
        while (list($index, $eltype) = each($this->undocumentedFields)) {
            if (!isset($this->classes[$index]))
                continue;
                
            reset($this->classes[$index]);
            while (list($elname, $data) = each($this->classes[$index]))
                if (isset($data["undoc"]) && $data["undoc"])
                    $this->warn->addDocWarning($file, $eltype, $elname, "Undocumented element.", "missing");
                    
        }
        
    } // end func findUndocumented
    
    /**
    * Checks the function documentation of a certain class.
    *
    * @param    string  Classname
    */
    function checkFunctionArgs($classname) {

        if (!isset($this->classes[$classname]["functions"]))
            return;
                
        $file = $this->classes[$classname]["filename"];
            
        reset($this->classes[$classname]["functions"]);
        while (list($fname, $function) = each($this->classes[$classname]["functions"])) {

            $inherited = isset($function["paraminherited"]);            
            $this->classes[$classname]["functions"][$fname]["params"] = $this->checkArgDocs($function["args"], $function["params"], $fname, $file, $inherited);
            unset($this->classes[$classname]["functions"][$fname]["args"]);

            if ($inherited)
                unset($this->classes[$classname]["functions"][$fname]["paraminherited"]);
                
        }
    } // end func checkFunctionArgs
    
    /**
    * Builds an internal list of all classes.
    * 
    * The analyser needs an ordered list of all classes
    * to inherit information effective.
    * 
    * @param    string      Name of the class that starts the recursive build process. 
    * @see      $classlist
    */
    function buildBottomUpClasslist($classname) {
        
        if (isset($this->classes[$classname]["subclasses"])) {
            
            reset($this->classes[$classname]["subclasses"]);
            while (list($subclass, $v) = each($this->classes[$classname]["subclasses"]))
                $this->buildBottomUpClasslist($subclass);
            
            $this->classlist[] = $classname;
            
        } else {
         
             $this->classlist[] = $classname;
        
        }
    } // end func buildBottomUpClasslist

    /**
    * Adds inherited elements to a class.
    * 
    * @param    string  Classname
    * @return   boolean $ok
    * @see      $classes, $notinherited, addInheritedElements()
    */    
    function inheritClassElements($classname) {
        
        if (!isset($this->classes[$classname]["path"]))
            return false;

        $undoc = $this->classes[$classname]["undoc"];
                
        $path = $this->classes[$classname]["path"];
        reset($path);
        while (list($k, $parentclass) = each($path)) {

            $this->addInheritedElements($classname, $parentclass, "functions");
            $this->addInheritedElements($classname, $parentclass, "variables");
            $this->addInheritedElements($classname, $parentclass, "consts");
            $this->addInheritedElements($classname, $parentclass, "uses");
            
            reset($this->classes[$parentclass]);
            while (list($field, $value) = each($this->classes[$parentclass])) 
                if (!isset($this->notinherited["class"][$field]) && !isset($this->classes[$classname][$field]))
                    $this->classes[$classname][$field] = $value;
            
            if ($undoc && !$this->classes[$parentclass]["undoc"]) {
                $this->classes[$classname]["docinherited"] = true;
                $this->classes[$classname]["undoc"] = false;
                $undoc = false;
            }
            
        }    
        
        return true;
    } // end func inheritClassElements
    
    /**
    * Adds inherited functions, variables, constants or included files to a class.
    *  
    * @param    string  Name of the class that inherits the informations.
    * @param    string  Name of the parentclass
    * @param    string  Type of elements inherited: "functions", "variables", "uses", "consts"
    * @return   boolean $ok
    * @see      $classes, $notinherited, isUndocumented()
    */
    function addInheritedElements($classname, $parentclass, $type) {
    
        if (!isset($this->classes[$parentclass][$type]))
            return false;
            
        reset($this->classes[$parentclass][$type]);
        while (list($elementname, $data) = each($this->classes[$parentclass][$type])) {
            
            if (!isset($this->classes[$classname][$type][$elementname])) {

                $this->classes[$classname]["inherited"][$type][$parentclass][$elementname] = true;            

            } else {
        
                $this->classes[$classname][$type][$elementname]["overrides"] = $parentclass;
                $this->classes[$classname][$type][$elementname]["undoc"] = $this->isUndocumented($parentclass, $type, $elementname);
                $this->classes[$classname]["overrides"][$type][$parentclass][$elementname] = true;
                
                reset($data);
                while (list($field, $value)=each($data)) {
                
                    if (!isset($this->classes[$classname][$type][$elementname][$field]) && !isset($this->notinherited[$type][$field])) {
                        $this->classes[$classname][$type][$elementname][$field] = $value;
                        if ("params" == $field && "functions" == $type) $this->classes[$classname][$type][$elementname]["paraminherited"] = true;
                    }
                        
                }
            }
            
        }
        
        return true;
    } // end func addInheritedElements

    /**
    * Returns true if the requested element is undocumented and false if it's documented.
    *
    * The function checks if the element might inherit documentation
    * from any parentclass. 
    *
    * @param    string  Name of the class of the element
    * @param    string  Element type: functions, variables, uses, consts.
    * @param    string  Element name
    * @return   boolean $ok
    */    
    function isUndocumented($classname, $type, $elementname) {

        if ( !isset($this->classes[$classname][$type][$elementname]) || $this->classes[$classname][$type][$elementname]["undoc"] || !isset($this->classes[$classname]["path"]) ) 
            return true;
        
        $path = $this->classes[$classname]["path"];
        while (list($k, $parentclass) = each($path))
            if ($this->isUndocumented($parentclass, $type, $elementname))
                return true;
        
        return false;
    } // end func isUndocumented
    
    function updateBrothersSisters() {
        
        reset($this->classes);
        while (list($classname, $data) = each($this->classes)) {
            $this->updateBrotherSisterElements($classname, "functions");
            $this->updateBrotherSisterElements($classname, "variables");
        }    
        
    } // end func updateBrothersSisters
    
    /**
    * @param    string  Name of the class to update
    * @param    string  Elementtype: functions, variables, ...
    * @return   boolean
    */
    function updateBrotherSisterElements($classname, $type) {
        
        if (!isset($this->classes[$classname][$type])) 
            return false;
            
        reset($this->classes[$classname][$type]);
        while (list($elementname, $data) = each($this->classes[$classname][$type])) {
            
            if (isset($data["brother"])) {

                $name = ( "functions" == $type ) ? substr($data["brother"], 0, -2) : substr($data["brother"], 1);
                $name = strtolower($name);

                if (!isset($this->classes[$classname][$type][$name])) {
                
                    $this->warn->addDocWarning($this->classes[$classname]["filename"], $type, $elementname, "Brother '$name' is unknown. Tags gets ignored.", "mismatch");
                    unset($this->classes[$classname][$type][$elementname]["brother"]);
                    
                } else {
                
                    $this->classes[$classname][$type][$elementname]["brother"] = $name;
                    $this->classes[$classname][$type][$elementname] = $this->copyBrotherSisterFields($this->classes[$classname][$type][$elementname], $this->classes[$classname][$type][$name]);

                }

            }
            
        }
        
    } // end func updateBrotherSisterElements
    
    function updateAccessReturn() {
        
        reset($this->classes);
        while (list($classname, $data) = each($this->classes)) {
            
            if (!isset($data["access"]))
                $this->classes[$classname]["access"] = "private";
                
            $this->updateAccessReturnElements($classname, "functions");
            $this->updateAccessElements($classname, "variables");
            $this->updateAccessElements($classname, "consts");
            
        }
                
    } // end func updateAccessReturn
    
    /**
    * Updates access and return for certain elements.
    * 
    * This function should only be used to update functions.
    * Functions that have the same name as the class (constructors)
    * get return void and access public. Functions without 
    * access get access public and functions without return get
    * return void.
    * 
    * @param    string  Classname
    * @param    string  Element type: functions (, variables, consts, uses)
    * @return   boolean $ok
    * @see      updateAccessReturn()
    */
    function updateAccessReturnElements($classname, $type) {
        
        if (!isset($this->classes[$classname][$type]))
            return false;

        reset($this->classes[$classname][$type]);
        while (list($elementname, $data) = each($this->classes[$classname][$type])) {
        
            if (!isset($data["access"])) 
                $this->classes[$classname][$type][$elementname]["access"] = ("functions" == $type && strtolower($elementname) == strtolower($classname)) ? "public" : "private";
                
            if (!isset($data["return"]))
                $this->classes[$classname][$type][$elementname]["return"] = "void";
            else 
                if ("functions" == $type && $elementname == $classname) {
                    $this->warn->addDocWarning($this->classes[$classname]["filename"], "functions", $elementname, "The constructor can't have a return value. @return gets ignored.", "mismatch");
                    $this->classes[$classname]["functions"][$elementname]["return"] = "void";
                }
                
        }
                
    } // end func updateAccessReturnElements
    
    /**
    * Updates access tags.
    *
    * @param    string  Classname
    * @param    string  Element type: functions, variables, consts (, uses)
    * @see      updateAccessReturnElements()
    */
    function updateAccessElements($classname, $type) {
        
        if (!isset($this->classes[$classname][$type]))
            return false;
            
        reset($this->classes[$classname][$type]);
        while (list($elementname, $data) = each($this->classes[$classname][$type])) {
            
            if (!isset($data["access"])) 
                $this->classes[$classname][$type][$elementname]["access"] = ("functions" == $type && $elementname == $classname) ? "public" : "private";
        
        }
        
    } // end func updateAccessElements
    
    function checkSee() {
        
        reset($this->classes);
        while (list($classname, $class) = each($this->classes)) {
        
            $this->buildElementlist($classname);
            
            if (isset($class["functions"])) 
                $this->checkSeeElements($class["functions"], $classname, "functions");
                
            if (isset($class["variables"]))
                $this->checkSeeElements($class["variables"], $classname, "variables");
            
            if (isset($class["consts"])) 
                $this->checkSeeElements($class["consts"], $classname, "consts");
                
            if (isset($class["uses"]))
                $this->checkSeeElements($class["uses"], $classname, "uses");
            
        }
        
    } // end func checkSee

    /**
    * Checks see references in the given element array (functions, variables...)
    *
    * References to variables and functions within the same class get checked.
    * If the references element does not exist, the reference gets deleted and 
    * a doc warning gets generated.
    * 
    * @param    array   List of functions, variables,...
    * @param    string  Name of the class that contains the given elements.
    * @param    string  Elementtype: functions, variables, consts, uses.
    */    
    function checkSeeElements($elements, $classname, $eltype) {
        
        reset($elements);
        while (list($elname, $element) = each($elements)) {
        
            if (isset($element["see"])) {
                
                if (isset($element["see"]["var"])) {
                    
                    reset($element["see"]["var"]);
                    while (list($k, $variable) = each($element["see"]["var"])) 
                        if (!isset($this->elementlist["variables"][strtolower($variable["name"])])) {
                            $this->warn->addDocWarning($this->classes[$classname]["filename"], "variables", $elname, "@see referrs to the variable '" . $variable["name"] . "' which is not defined in the class. Entry gets ignored.", "mismatch");
                            unset($this->classes[$classname][$eltype][$elname]["see"]["var"][$k]);
                        }

                }
                
                if (isset($element["see"]["function"])) {
                    
                    reset($element["see"]["function"]);
                    while (list($k, $function) = each($element["see"]["function"]))
                        if (!isset($this->elementlist["functions"][strtolower(substr($function["name"], 0, -2))])) {
                            $this->warn->addDocWarning($this->classes[$classname]["filename"], "functions", $elname, "@see referrs to the function '" . $function["name"] . "' which is not defined in the class. Entry gets ignored.", "mismatch");
                            unset($this->classes[$classname][$eltype][$elname]["see"]["function"][$k]);
                        }

                }
                
            }
            
        }    
        
    } // end func checkSeeElement
    
    /**
    * Builds an array with all elements of a class and saves it to $this->elementlist.
    * 
    * @param    string  Name of the class to scan.
    */
    function buildElementlist($classname) {
        
        $elements = array();
        $fields = array("functions", "variables", "consts", "uses");
        
        reset($fields);
        while (list($k, $field) = each($fields)) 
            if (isset($this->classes[$classname][$field])) {
                
                reset($this->classes[$classname][$field]);
                while (list($element, ) = each($this->classes[$classname][$field])) 
                    $elements[$field][$element] = true;
                    
            }
        
        $this->elementlist = $elements;
        
    } // end func buildElementlist

} // end class PhpdocClassAnalyser
?>