home *** CD-ROM | disk | FTP | other *** search
/ Cricao de Sites - 650 Layouts Prontos / WebMasters.iso / Servidores / xampp-win32-1.6.7-installer.exe / php / PEAR / phing / IntrospectionHelper.php < prev    next >
Encoding:
PHP Script  |  2007-02-05  |  21.9 KB  |  543 lines

  1. <?php
  2.  
  3. /*
  4.  *  $Id: IntrospectionHelper.php 144 2007-02-05 15:19:00Z hans $
  5.  *
  6.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  7.  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  8.  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  9.  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  10.  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  11.  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  12.  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  13.  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  14.  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  15.  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  16.  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  17.  *
  18.  * This software consists of voluntary contributions made by many individuals
  19.  * and is licensed under the LGPL. For more information please see
  20.  * <http://phing.info>.
  21.  */
  22.  
  23. include_once 'phing/types/Reference.php';
  24. include_once 'phing/types/Path.php';
  25. include_once 'phing/util/StringHelper.php';
  26.  
  27. /**
  28.  * Helper class that collects the methods that a task or nested element
  29.  * holds to set attributes, create nested elements or hold PCDATA
  30.  * elements.
  31.  *
  32.  *<ul>
  33.  * <li><strong>SMART-UP INLINE DOCS</strong></li>
  34.  * <li><strong>POLISH-UP THIS CLASS</strong></li>
  35.  *</ul>
  36.  *
  37.  * @author    Andreas Aderhold <andi@binarycloud.com>
  38.  * @author    Hans Lellelid <hans@xmpl.org>
  39.  * @copyright ⌐ 2001,2002 THYRELL. All rights reserved
  40.  * @version   $Revision: 1.19 $
  41.  * @package   phing
  42.  */
  43. class IntrospectionHelper {
  44.  
  45.  
  46.  
  47.     /** 
  48.      * Holds the attribute setter methods.
  49.      * 
  50.      * @var array string[]
  51.      */
  52.     private $attributeSetters = array();
  53.  
  54.     /**  
  55.      * Holds methods to create nested elements. 
  56.      *
  57.      * @var array string[]
  58.      */
  59.     private $nestedCreators = array();
  60.  
  61.     /**
  62.      * Holds methods to store configured nested elements. 
  63.      *
  64.      * @var array string[]
  65.      */
  66.     private $nestedStorers = array();
  67.     
  68.     /**
  69.      * Map from attribute names to nested types.
  70.      */
  71.     private $nestedTypes = array();
  72.         
  73.     /**
  74.      * New idea in phing: any class can register certain
  75.      * keys -- e.g. "task.current_file" -- which can be used in
  76.      * task attributes, if supported.  In the build XML these
  77.      * are referred to like this:
  78.      *         <regexp pattern="\n" replace="%{task.current_file}"/>
  79.      * In the type/task a listener method must be defined:
  80.      *         function setListeningReplace($slot) {}
  81.      * @var array string[]
  82.       */
  83.     private $slotListeners = array();
  84.     
  85.     /** 
  86.      * The method to add PCDATA stuff. 
  87.      *
  88.      * @var string Method name of the addText (redundant?) method, if class supports it :)
  89.      */
  90.     private $methodAddText = null;
  91.  
  92.     /**
  93.      * The Class that's been introspected.
  94.      *
  95.      * @var     object
  96.      * @access  private
  97.      */
  98.     private $bean;
  99.     
  100.     /**
  101.      * The cache of IntrospectionHelper classes instantiated by getHelper().
  102.      * @var array IntrospectionHelpers[]
  103.      */
  104.     private static $helpers = array();
  105.     
  106.     /** 
  107.      * Factory method for helper objects. 
  108.      *
  109.      * @param string $class The class to create a Helper for
  110.      */
  111.     public static function getHelper($class) {
  112.         if (!isset(self::$helpers[$class])) {
  113.             self::$helpers[$class] = new IntrospectionHelper($class);
  114.         }
  115.         return self::$helpers[$class];
  116.     }
  117.  
  118.     /**
  119.      * This function constructs a new introspection helper for a specific class.
  120.      * 
  121.      * This method loads all methods for the specified class and categorizes them
  122.      * as setters, creators, slot listeners, etc.  This way, the setAttribue() doesn't
  123.      * need to perform any introspection -- either the requested attribute setter/creator
  124.      * exists or it does not & a BuildException is thrown.
  125.      * 
  126.      * @param string $bean The classname for this IH.
  127.      */
  128.     function __construct($class) {
  129.     
  130.         $this->bean = new ReflectionClass($class);
  131.         
  132.         //$methods = get_class_methods($bean);
  133.         foreach($this->bean->getMethods() as $method) {
  134.         
  135.             if ($method->isPublic()) {                
  136.             
  137.                 // We're going to keep case-insensitive method names
  138.                 // for as long as we're allowed :)  It makes it much
  139.                 // easier to map XML attributes to PHP class method names.
  140.                 $name = strtolower($method->getName());
  141.                 
  142.                 // There are a few "reserved" names that might look like attribute setters
  143.                 // but should actually just be skipped.  (Note: this means you can't ever
  144.                 // have an attribute named "location" or "tasktype" or a nested element named "task".)
  145.                 if ($name === "setlocation" || $name === "settasktype" || $name === "addtask") {
  146.                     continue;
  147.                 }
  148.                 
  149.                 if ($name === "addtext") {
  150.                     
  151.                     $this->methodAddText = $method;
  152.                     
  153.                 } elseif (strpos($name, "setlistening") === 0) {
  154.                     
  155.                     // Phing supports something unique called "RegisterSlots"
  156.                     // These are dynamic values that use a basic slot system so that
  157.                     // classes can register to listen to specific slots, and the value
  158.                     // will always be grabbed from the slot (and never set in the project
  159.                     // component).  This is useful for things like tracking the current
  160.                     // file being processed by a filter (e.g. AppendTask sets an append.current_file
  161.                     // slot, which can be ready by the XSLTParam type.)
  162.                     
  163.                     if (count($method->getParameters()) !== 1) {
  164.                         throw new BuildException($method->getDeclaringClass()->getName()."::".$method->getName()."() must take exactly one parameter.");
  165.                     }
  166.                                                 
  167.                     $this->slotListeners[$name] = $method;
  168.                     
  169.                 } elseif (strpos($name, "set") === 0) {
  170.                     
  171.                     // A standard attribute setter.
  172.                     
  173.                     if (count($method->getParameters()) !== 1) {
  174.                         throw new BuildException($method->getDeclaringClass()->getName()."::".$method->getName()."() must take exactly one parameter.");
  175.                     }
  176.                     
  177.                     $this->attributeSetters[$name] = $method;
  178.                     
  179.                 } elseif (strpos($name, "create") === 0) {                            
  180.                     
  181.                     if (count($method->getParameters()) > 0) {
  182.                         throw new BuildException($method->getDeclaringClass()->getName()."::".$method->getName()."() may not take any parameters.");
  183.                     }
  184.                     
  185.                     // Because PHP doesn't support return types, we are going to do
  186.                     // two things here to guess return type:
  187.                     //     1) parse comments for an explicit value
  188.                     //     2) if that fails, assume that the part of the method after "create"
  189.                     //    is the name of the return type (in many cases it is not)
  190.                     
  191.                     // This isn't super important -- i.e. we're not instantaiting classes
  192.                     // based on this information.  It's more just so that IntrospectionHelper
  193.                     // can keep track of all the nested types -- and provide more helpful
  194.                     // exception messages, etc.
  195.                                 
  196.                     preg_match('/@return[\s]+([\w]+)/', $method->getDocComment(), $matches);
  197.                     if (!empty($matches[1]) && class_exists($matches[1], false)) {
  198.                         $this->nestedTypes[$name] = $matches[1];
  199.                     } else {                    
  200.                         // assume that method createEquals() creates object of type "Equals"
  201.                         // (that example would be false, of course)                    
  202.                         $this->nestedTypes[$name] = $this->getPropertyName($name, "create");
  203.                     }
  204.                     
  205.                     $this->nestedCreators[$name] = $method;
  206.                     
  207.                 } elseif (strpos($name, "addconfigured") === 0) {
  208.                     
  209.                     // *must* use class hints if using addConfigured ...
  210.                     
  211.                     // 1 param only
  212.                     $params = $method->getParameters();
  213.                     
  214.                     if (count($params) < 1) {
  215.                         throw new BuildException($method->getDeclaringClass()->getName()."::".$method->getName()."() must take at least one parameter.");
  216.                     }
  217.                     
  218.                     if (count($params) > 1) {
  219.                         $this->warn($method->getDeclaringClass()->getName()."::".$method->getName()."() takes more than one parameter. (IH only uses the first)");
  220.                     }
  221.                     
  222.                     $classname = null;
  223.                     
  224.                     if (($hint = $params[0]->getClass()) !== null) { 
  225.                         $classname = $hint->getName();    
  226.                     }                    
  227.                     
  228.                     if ($classname === null) {
  229.                         throw new BuildException($method->getDeclaringClass()->getName()."::".$method->getName()."() method MUST use a class hint to indicate the class type of parameter.");
  230.                     }
  231.                         
  232.                     $this->nestedTypes[$name] = $classname;
  233.                 
  234.                     $this->nestedStorers[$name] = $method;
  235.                     
  236.                 } elseif (strpos($name, "add") === 0) {
  237.                     
  238.                     // *must* use class hints if using add ...
  239.                     
  240.                     // 1 param only
  241.                     $params = $method->getParameters();
  242.                     if (count($params) < 1) {
  243.                         throw new BuildException($method->getDeclaringClass()->getName()."::".$method->getName()."() must take at least one parameter.");
  244.                     }
  245.                     
  246.                     if (count($params) > 1) {
  247.                         $this->warn($method->getDeclaringClass()->getName()."::".$method->getName()."() takes more than one parameter. (IH only uses the first)");
  248.                     }
  249.  
  250.                     $classname = null;
  251.                     
  252.                     if (($hint = $params[0]->getClass()) !== null) { 
  253.                         $classname = $hint->getName();    
  254.                     }                    
  255.                     
  256.                     // we don't use the classname here, but we need to make sure it exists before
  257.                     // we later try to instantiate a non-existant class
  258.                     if ($classname === null) {
  259.                         throw new BuildException($method->getDeclaringClass()->getName()."::".$method->getName()."() method MUST use a class hint to indicate the class type of parameter.");
  260.                     }
  261.                 
  262.                     $this->nestedCreators[$name] = $method;
  263.                 } 
  264.             } // if $method->isPublic()        
  265.         } // foreach        
  266.     }
  267.  
  268.  
  269.     /** Sets the named attribute. */
  270.     function setAttribute(Project $project, $element, $attributeName, &$value) {
  271.         
  272.         // we want to check whether the value we are setting looks like
  273.         // a slot-listener variable:  %{task.current_file}
  274.         //
  275.         // slot-listener variables are not like properties, in that they cannot be mixed with
  276.         // other text values.  The reason for this disparity is that properties are only
  277.         // set when first constructing objects from XML, whereas slot-listeners are always dynamic.
  278.         //
  279.         // This is made possible by PHP5 (objects automatically passed by reference) and PHP's loose
  280.         // typing.
  281.         
  282.         if (StringHelper::isSlotVar($value)) {
  283.             
  284.             $as = "setlistening" . strtolower($attributeName);
  285.  
  286.             if (!isset($this->slotListeners[$as])) {
  287.                 $msg = $this->getElementName($project, $element) . " doesn't support a slot-listening '$attributeName' attribute.";
  288.                 throw new BuildException($msg);
  289.             }
  290.             
  291.             $method = $this->slotListeners[$as];
  292.             
  293.             $key = StringHelper::slotVar($value);
  294.             $value = Register::getSlot($key); // returns a RegisterSlot object which will hold current value of that register (accessible using getValue())
  295.             
  296.         } else {
  297.             
  298.             // Traditional value options
  299.             
  300.             $as = "set".strtolower($attributeName);
  301.             
  302.             if (!isset($this->attributeSetters[$as])) {
  303.                 $msg = $this->getElementName($project, $element) . " doesn't support the '$attributeName' attribute.";
  304.                 throw new BuildException($msg);
  305.             }
  306.             
  307.             $method = $this->attributeSetters[$as];            
  308.             
  309.             if ($as == "setrefid") {            
  310.                 $value = new Reference($value);
  311.             } else {
  312.             
  313.                 // decode any html entities in string
  314.                 $value = html_entity_decode($value);                
  315.                 
  316.                 // value is a string representation of a boolean type,
  317.                 // convert it to primitive
  318.                 if (StringHelper::isBoolean($value)) {
  319.  
  320.                     $value = StringHelper::booleanValue($value);
  321.                 }
  322.                 
  323.                 // does method expect a PhingFile object? if so, then 
  324.                 // pass a project-relative file.
  325.                 $params = $method->getParameters();
  326.  
  327.                 $classname = null;
  328.                 
  329.                 if (($hint = $params[0]->getClass()) !== null) { 
  330.                     $classname = $hint->getName();    
  331.                 }
  332.                 
  333.                 // there should only be one param; we'll just assume ....
  334.                 if ($classname !== null) {
  335.                     switch(strtolower($classname)) {
  336.                         case "phingfile":
  337.                             $value = $project->resolveFile($value);
  338.                             break;
  339.                         case "path":
  340.                             $value = new Path($project, $value);
  341.                             break;
  342.                         case "reference":
  343.                             $value = new Reference($value);
  344.                             break;            
  345.                         // any other object params we want to support should go here ...
  346.                     }
  347.                     
  348.                 } // if hint !== null
  349.                 
  350.             } // if not setrefid
  351.             
  352.         } // if is slot-listener
  353.         
  354.         try {
  355.             $project->log("    -calling setter ".$method->getDeclaringClass()->getName()."::".$method->getName()."()", Project::MSG_DEBUG);
  356.             $method->invoke($element, $value);
  357.         } catch(Exception $exc) {
  358.             throw new BuildException($exc);
  359.         }
  360.         
  361.     }
  362.  
  363.     /** Adds PCDATA areas.*/
  364.     function addText(Project $project, $element, $text) {
  365.         if ($this->methodAddText === null) {
  366.             $msg = $this->getElementName($project, $element)." doesn't support nested text data.";
  367.             throw new BuildException($msg);
  368.         }        
  369.         try {
  370.             $method = $this->methodAddText;
  371.             $method->invoke($element, $text);
  372.         } catch (Exception $exc) {
  373.             throw new BuildException($exc);
  374.         }
  375.     }
  376.  
  377.     /**
  378.      * Creates a named nested element. 
  379.      * 
  380.      * Valid creators can be in the form createFoo() or addFoo(Bar).
  381.      * @return object Returns the nested element.
  382.      * @throws BuildException
  383.      */
  384.     function createElement(Project $project, $element, $elementName) {
  385.     
  386.         $addMethod = "add".strtolower($elementName);
  387.         $createMethod = "create".strtolower($elementName);
  388.         $nestedElement = null;
  389.         
  390.         if (isset($this->nestedCreators[$createMethod])) {
  391.             
  392.             $method = $this->nestedCreators[$createMethod];
  393.              try { // try to invoke the creator method on object
  394.                 $project->log("    -calling creator ".$method->getDeclaringClass()->getName()."::".$method->getName()."()", Project::MSG_DEBUG);
  395.                 $nestedElement = $method->invoke($element);
  396.             } catch (Exception $exc) {
  397.                 throw new BuildException($exc);
  398.             }            
  399.             
  400.         } elseif (isset($this->nestedCreators[$addMethod])) {            
  401.             
  402.             $method = $this->nestedCreators[$addMethod];
  403.             
  404.             // project components must use class hints to support the add methods
  405.             
  406.             try { // try to invoke the adder method on object
  407.             
  408.                 $project->log("    -calling adder ".$method->getDeclaringClass()->getName()."::".$method->getName()."()", Project::MSG_DEBUG);
  409.                 // we've already assured that correct num of params
  410.                 // exist and that method is using class hints                
  411.                 $params = $method->getParameters();
  412.  
  413.                 $classname = null;
  414.             
  415.                 if (($hint = $params[0]->getClass()) !== null) { 
  416.                     $classname = $hint->getName();    
  417.                 }                
  418.                 
  419.                 // create a new instance of the object and add it via $addMethod                
  420.                 $nestedElement = new $classname();
  421.                 
  422.                 $method->invoke($element, $nestedElement);
  423.                                 
  424.             } catch (Exception $exc) {
  425.                 throw new BuildException($exc);
  426.             }
  427.         } else {
  428.             $msg = $this->getElementName($project, $element) . " doesn't support the '$elementName' creator/adder.";
  429.             throw new BuildException($msg);
  430.         }                                
  431.         
  432.         if ($nestedElement instanceof ProjectComponent) {
  433.             $nestedElement->setProject($project);
  434.         }
  435.         
  436.         return $nestedElement;
  437.     }
  438.  
  439.     /**
  440.      * Creates a named nested element.
  441.      * @return void
  442.      * @throws BuildException
  443.      */
  444.     function storeElement($project, $element, $child, $elementName = null) {
  445.     
  446.         if ($elementName === null) {
  447.             return;
  448.         }
  449.         
  450.         $storer = "addconfigured".strtolower($elementName);
  451.           
  452.         if (isset($this->nestedStorers[$storer])) {
  453.             
  454.             $method = $this->nestedStorers[$storer];
  455.             
  456.             try {                                
  457.                 $project->log("    -calling storer ".$method->getDeclaringClass()->getName()."::".$method->getName()."()", Project::MSG_DEBUG);                    
  458.                 $method->invoke($element, $child);            
  459.             } catch (Exception $exc) {
  460.                 throw new BuildException($exc);
  461.             }
  462.         }
  463.         
  464.     }
  465.  
  466.     /** Does the introspected class support PCDATA? */
  467.     function supportsCharacters() {
  468.         return ($this->methodAddText !== null);
  469.     }
  470.  
  471.     /** Return all attribues supported by the introspected class. */
  472.     function getAttributes() {
  473.         $attribs = array();
  474.         foreach (array_keys($this->attributeSetters) as $setter) {
  475.             $attribs[] =$this->getPropertyName($setter, "set");
  476.         }
  477.         return $attribs;
  478.     }
  479.  
  480.     /** Return all nested elements supported by the introspected class. */
  481.     function getNestedElements() {
  482.         return $this->nestedTypes;
  483.     }
  484.     
  485.     /**
  486.      * Get the the name for an element.
  487.      * When possible the full classnam (phing.tasks.system.PropertyTask) will
  488.      * be returned.  If not available (loaded in taskdefs or typedefs) then the
  489.      * XML element name will be returned.
  490.      *
  491.      * @param Project $project
  492.      * @param object $element The Task or type element.
  493.      * @return string Fully qualified class name of element when possible.
  494.      */
  495.     function getElementName(Project $project, $element) {
  496.        
  497.           $taskdefs = $project->getTaskDefinitions();
  498.         $typedefs = $project->getDataTypeDefinitions();
  499.         
  500.         // check if class of element is registered with project (tasks & types)        
  501.         // most element types don't have a getTag() method
  502.         $elClass = get_class($element);
  503.         
  504.         if (!in_array('getTag', get_class_methods($elClass))) {
  505.                 // loop through taskdefs and typesdefs and see if the class name
  506.                 // matches (case-insensitive) any of the classes in there
  507.                 foreach(array_merge($taskdefs, $typedefs) as $elName => $class) {
  508.                     if (0 === strcasecmp($elClass, StringHelper::unqualify($class))) {
  509.                         return $class;
  510.                     }
  511.                 }
  512.                 return "$elClass (unknown)";
  513.         } else {
  514.             // ->getTag() method does exist, so use it
  515.             $elName = $element->getTag();
  516.             if (isset($taskdefs[$elName])) {
  517.                 return $taskdefs[$elName];
  518.             } elseif (isset($typedefs[$elName])) {
  519.  
  520.                 return $typedefs[$elName];
  521.             } else {
  522.                 return "$elName (unknown)";
  523.             }
  524.         }        
  525.     }
  526.  
  527.     /** extract the name of a property from a method name - subtracting  a given prefix. */
  528.     function getPropertyName($methodName, $prefix) {
  529.         $start = strlen($prefix);
  530.         return strtolower(substr($methodName, $start));
  531.     }
  532.     
  533.     /**
  534.      * Prints warning message to screen if -debug was used.
  535.      */
  536.     function warn($msg) {
  537.         if (Phing::getMsgOutputLevel() === Project::MSG_DEBUG) {
  538.             print("[IntrospectionHelper] " . $msg . "\n");
  539.         }
  540.     }
  541.  
  542. }
  543.