1<?php
2/**
3 * This file is part of the static reflection component.
4 *
5 * PHP Version 5
6 *
7 * Copyright (c) 2009-2011, Manuel Pichler <mapi@pdepend.org>.
8 * All rights reserved.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 *
14 *   * Redistributions of source code must retain the above copyright
15 *     notice, this list of conditions and the following disclaimer.
16 *
17 *   * Redistributions in binary form must reproduce the above copyright
18 *     notice, this list of conditions and the following disclaimer in
19 *     the documentation and/or other materials provided with the
20 *     distribution.
21 *
22 *   * Neither the name of Manuel Pichler nor the names of his
23 *     contributors may be used to endorse or promote products derived
24 *     from this software without specific prior written permission.
25 *
26 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
29 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
30 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
31 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
32 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
33 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
34 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
36 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37 * POSSIBILITY OF SUCH DAMAGE.
38 *
39 * @category  PHP
40 * @package   pdepend\reflection\api
41 * @author    Manuel Pichler <mapi@pdepend.org>
42 * @copyright 2009-2011 Manuel Pichler. All rights reserved.
43 * @license   http://www.opensource.org/licenses/bsd-license.php  BSD License
44 * @version   SVN: $Id$
45 * @link      http://pdepend.org/
46 */
47
48namespace pdepend\reflection\api;
49
50/**
51 * Static class implementation.
52 *
53 * @category  PHP
54 * @package   pdepend\reflection\api
55 * @author    Manuel Pichler <mapi@pdepend.org>
56 * @copyright 2009-2011 Manuel Pichler. All rights reserved.
57 * @license   http://www.opensource.org/licenses/bsd-license.php  BSD License
58 * @version   Release: @package_version@
59 * @link      http://pdepend.org/
60 */
61class StaticReflectionClass extends StaticReflectionInterface
62{
63    /**
64     * The type of this class.
65     */
66    const TYPE = __CLASS__;
67
68    /**
69     * Bitfield with class modifiers.
70     *
71     * @var integer
72     */
73    private $_modifiers = 0;
74
75    /**
76     * Optional parent class.
77     *
78     * @var \ReflectionClass
79     */
80    private $_parentClass = false;
81
82    /**
83     * Array with properties defined/declared in the context class.
84     *
85     * @var array(string=>\ReflectionProperty)
86     */
87    private $_properties = null;
88
89    /**
90     * Constructs a new reflection class instance.
91     *
92     * @param string  $name       The qualified class name.
93     * @param string  $docComment Doc comment for this class.
94     * @param integer $modifiers  Modifiers for this class.
95     */
96    public function __construct( $name, $docComment, $modifiers )
97    {
98        parent::__construct( $name, $docComment );
99
100        $this->_modifiers = $modifiers;
101    }
102
103    /**
104     * Returns a bitfield with modifiers for the reflected class.
105     *
106     * @return integer
107     */
108    public function getModifiers()
109    {
110        if ( count( $this->getMethods( StaticReflectionMethod::IS_ABSTRACT ) ) > 0 )
111        {
112            return $this->_modifiers | self::IS_IMPLICIT_ABSTRACT;
113        }
114        return $this->_modifiers;
115    }
116
117    /**
118     * Returns <b>true</b> when the class is declared abstract or is an interface.
119     *
120     * @return boolean
121     */
122    public function isAbstract()
123    {
124        return ( $this->_isExplicitAbstract() || $this->_isImplicitAbstract() );
125    }
126
127    /**
128     * Returns <b>true</b> when the explicit abstract modifier is present.
129     *
130     * @return boolean
131     */
132    private function _isExplicitAbstract()
133    {
134        return ( ( $this->getModifiers() & self::IS_EXPLICIT_ABSTRACT ) === self::IS_EXPLICIT_ABSTRACT );
135    }
136
137    /**
138     * Returns <b>true</b> when the implicit abstract modifier is present.
139     *
140     * @return boolean
141     */
142    private function _isImplicitAbstract()
143    {
144        return ( ( $this->getModifiers() & self::IS_IMPLICIT_ABSTRACT ) === self::IS_IMPLICIT_ABSTRACT );
145    }
146
147    /**
148     * Returns <b>true</b> when the class is declared as final.
149     *
150     * @return boolean
151     */
152    public function isFinal()
153    {
154        return ( ( $this->_modifiers & self::IS_FINAL ) === self::IS_FINAL );
155    }
156
157    /**
158     * Returns <b>true</b> when the reflected class/interface is an interface,
159     * which means that this concrete implementation always returns <b>false</b>.
160     *
161     * @return boolean
162     */
163    public function isInterface()
164    {
165        return false;
166    }
167
168    /**
169     * Checks that the reflected class is a child of the given class name.
170     *
171     * @param string $class Name of the searched class.
172     *
173     * @return boolean
174     */
175    public function isSubclassOf( $class )
176    {
177        if ( $this->_parentClass === false )
178        {
179            return parent::isSubclassOf( $class );
180        }
181        else if ( strcasecmp( $this->_parentClass->getName(), ltrim( $class, '\\' ) ) === 0 )
182        {
183            return true;
184        }
185        return $this->_parentClass->isSubclassOf( $class );
186    }
187
188    /**
189     * Checks if the class is instantiable.
190     *
191     * @return boolean
192     */
193    public function isInstantiable()
194    {
195        if ( $this->isAbstract() )
196        {
197            return false;
198        }
199        else if ( is_object( $constructor = $this->getConstructor() ) )
200        {
201            return ( $constructor->isPublic() && !$constructor->isAbstract() );
202        }
203        return true;
204    }
205
206    /**
207     * This method will return <b>true</b> when the reflected object is a class
208     * and implements the interface <b>Traversable</b>.
209     *
210     * @return boolean
211     */
212    public function isIterateable()
213    {
214        return $this->isSubclassOf( 'Traversable' );
215    }
216
217    /**
218     * Returns the parent of the reflected class or <b>false</b> when no parent
219     * exists.
220     *
221     * @return \ReflectionClass|boolean
222     */
223    public function getParentClass()
224    {
225        return $this->_parentClass;
226    }
227
228    /**
229     * Initializes the parent class of the reflected class.
230     *
231     * @param \ReflectionClass $parentClass The parent class instance.
232     *
233     * @return void
234     * @access private
235     * @throws \LogicException When the parentClass property was already set.
236     */
237    public function initParentClass( \ReflectionClass $parentClass )
238    {
239        if ( $this->_parentClass === false )
240        {
241            $this->_parentClass = $parentClass;
242        }
243        else
244        {
245            throw new \LogicException( 'Property parentClass already set' );
246        }
247    }
248
249    /**
250     * Returns an array with all implemented/extended interfaces.
251     *
252     * @return array(\ReflectionClass)
253     */
254    public function getInterfaces()
255    {
256        $result = $this->_mergeInterfaces( parent::getInterfaces() );
257        if ( is_object( $parentClass = $this->getParentClass() ) )
258        {
259            $result = $this->_mergeInterfaces( $parentClass->getInterfaces(), $result );
260        }
261        return array_values( $result );
262    }
263
264    /**
265     * Merges the given interfaces into the given result array.
266     *
267     * @param array(\ReflectionClass)         $interfaces Input interface array.
268     * @param array(string=>\ReflectionClass) $result     Result interface list.
269     *
270     * @return array(\ReflectionClass)
271     */
272    private function _mergeInterfaces( array $interfaces, array &$result = array())
273    {
274        foreach ( $interfaces as $interface )
275        {
276            if ( !isset( $result[$interface->getName()] ) )
277            {
278                $result[$interface->getName()] = $interface;
279            }
280        }
281        return $result;
282    }
283
284    /**
285     * Returns an array with constants defined in this or one of its parent
286     * classes.
287     *
288     * @return array(string=>mixed)
289     */
290    public function getConstants()
291    {
292        if ( $this->_parentClass === false )
293        {
294            return parent::getConstants();
295        }
296        return $this->_collectConstants( $this->_parentClass, parent::getConstants() );
297    }
298
299    /**
300     * Collects all constants from the given class when they are not present in
301     * the given <b>$result</b> array.
302     *
303     * @param \ReflectionClass     $class  The context class.
304     * @param array(string=>mixed) $result Previous collected constants
305     *
306     * @return array(string=>mixed)
307     */
308    private function _collectConstants( \ReflectionClass $class, array $result )
309    {
310        foreach ( $class->getConstants() as $name => $value )
311        {
312            if ( array_key_exists( $name, $result ) === false )
313            {
314                $result[$name] = $value;
315            }
316        }
317        return $result;
318    }
319
320    /**
321     * Returns an <b>array</b> with methods defined in the inheritence hierarchy
322     * of the reflection class. You can pass an optional filter argument that
323     * contains a bitfield of required method modifiers.
324     *
325     * @param integer $filter Optional filter for the returned methods
326     *
327     * @return array(\ReflectionMethod)
328     */
329    public function getMethods( $filter = -1 )
330    {
331        if ( $this->_parentClass === false )
332        {
333            return parent::getMethods( $filter );
334        }
335        return $this->_collectMethodsFromParentClass( $filter );
336    }
337
338    /**
339     * Will collect all methods from a parent class.
340     *
341     * @param integer $filter Optional bitfield with modifiers that a collected
342     *        method must match to.
343     *
344     * @return array(\ReflectionMethod)
345     */
346    private function _collectMethodsFromParentClass( $filter )
347    {
348        $result = parent::collectMethods();
349        foreach ( $this->_parentClass->getMethods() as $method )
350        {
351            $result = $this->_collectMethodFromParentClass( $method, $result );
352        }
353        return $this->prepareCollectedObjects( $filter, $result );
354    }
355
356    /**
357     * Will add the given method to the list of available methods.
358     *
359     * @param \ReflectionMethod        $method The currently reflected method
360     * @param array(\ReflectionMethod) $result List with already found methods.
361     *
362     * @return array(\ReflectionMethod)
363     */
364    private function _collectMethodFromParentClass( \ReflectionMethod $method, array $result )
365    {
366        $name = strtolower( $method->getName() );
367        if ( !isset( $result[$name] ) )
368        {
369            $result[$name] = $method;
370        }
371        else if ( $result[$name]->isAbstract() && !$method->isAbstract() )
372        {
373            $result[$name] = $method;
374        }
375        return $result;
376    }
377
378    /**
379     * Returns the constructor method of the reflected class or <b>null</b> when
380     * no constructor is available.
381     *
382     * @return \ReflectionMethod
383     */
384    public function getConstructor()
385    {
386        foreach ( $this->getMethods() as $method )
387        {
388            if ( $method->isConstructor() )
389            {
390                return $method;
391            }
392        }
393        return null;
394    }
395
396    /**
397     * Checks whether the specified property is defined.
398     *
399     * @param string $name Name of the property being checked for.
400     *
401     * @return boolean
402     */
403    public function hasProperty( $name )
404    {
405        return array_key_exists( $name, $this->_collectProperties() );
406    }
407
408    /**
409     * Returns a property for the given name or throws an exception when no such
410     * property exists.
411     *
412     * @param string $name Name of the searched property.
413     *
414     * @return \ReflectionProperty
415     */
416    public function getProperty( $name )
417    {
418        if ( $this->hasProperty( $name ) )
419        {
420            $properties = $this->_collectProperties();
421            return $properties[$name];
422        }
423        throw new \ReflectionException( sprintf( 'Property %s does not exist', $name ) );
424    }
425
426    /**
427     * Returns all properties of the reflected class or one of its parent classes.
428     *
429     * @param integer $filter Optional bitfield with property modifiers that
430     *        will be used as a filter for the collected properties.
431     *
432     * @return array(\ReflectionProperty)
433     */
434    public function getProperties( $filter = -1 )
435    {
436        return $this->prepareCollectedObjects( $filter, $this->_collectProperties() );
437    }
438
439    /**
440     * Collects all properties defined for the reflected class or one of its
441     * parents.
442     *
443     * @return array(string=>\ReflectionProperty)
444     */
445    private function _collectProperties()
446    {
447        if ( $this->_parentClass === false )
448        {
449            return (array) $this->_properties;
450        }
451        return $this->_collectPropertiesFromParentClass( (array) $this->_properties );
452    }
453
454    /**
455     * Collects all properties of the current an its parent class.
456     *
457     * @param array(string=>\ReflectionProperty) $result Properties defined
458     *        within the scope of the currently reflected class.
459     *
460     * @return array(string=>\ReflectionProperty)
461     */
462    private function _collectPropertiesFromParentClass( array $result )
463    {
464        foreach ( $this->_parentClass->getProperties() as $property )
465        {
466            $result = $this->_collectPropertyFromParentClass( $property, $result );
467        }
468        return $result;
469    }
470
471    /**
472     * Adds the given property to the result array when it does not already
473     * exist and is not declared as private.
474     *
475     * @param ReflectionProperty                 $property The current property
476     *        instance that should be added to the result of available properties.
477     * @param array(string=>\ReflectionProperty) $result   An array with all
478     *        properties that have already been collected for the reflected class.
479     *
480     * @return array(string=>\ReflectionProperty)
481     */
482    private function _collectPropertyFromParentClass( \ReflectionProperty $property, array $result )
483    {
484        if ( !$property->isPrivate() && !isset( $result[$property->getName()] ) )
485        {
486            $result[$property->getName()] = $property;
487        }
488        return $result;
489    }
490
491    /**
492     * Tries to initializes the properties of the reflected class the first time,
493     * it will throw an exception when the properties are already set .
494     *
495     * @param array(\ReflectionProperty) $properties The properties of this class.
496     *
497     * @return void
498     * @access private
499     */
500    public function initProperties( array $properties )
501    {
502        if ( $this->_properties === null )
503        {
504            $this->_initProperties( $properties );
505        }
506        else
507        {
508            throw new \LogicException( 'Property properties already set' );
509        }
510    }
511
512    /**
513     * Initializes the properties of the reflected class.
514     *
515     * @param array(\ReflectionProperty) $properties The properties of this class.
516     *
517     * @return void
518     */
519    private function _initProperties( array $properties )
520    {
521        $this->_properties = array();
522        foreach ( $properties as $property )
523        {
524            $property->initDeclaringClass( $this );
525            $this->_properties[$property->getName()] = $property;
526        }
527    }
528
529    /**
530     * Gets the static property values.
531     *
532     * @param string $name    The property name.
533     * @param mixed  $default Optional default value.
534     *
535     * @return mixed
536     */
537    public function getStaticPropertyValue( $name, $default = null )
538    {
539        $properties = $this->getStaticProperties();
540        if ( array_key_exists( $name, $properties ) )
541        {
542            return $properties[$name];
543        }
544        return parent::getStaticPropertyValue( $name, $default );
545    }
546
547    /**
548     * Get the static properties.
549     *
550     * @return array(string=>mixed)
551     */
552    public function getStaticProperties()
553    {
554        $properties = array();
555        foreach ( $this->_collectProperties() as $name => $property )
556        {
557            if ( $property->isStatic() )
558            {
559                $properties[$name] = $property->getValue();
560            }
561        }
562        return $properties;
563    }
564
565    /**
566     * Returns a string representation of the currently reflected class.
567     *
568     * @return string
569     */
570    public function __toString()
571    {
572        return sprintf(
573            'Class [ <user> %sclass %s%s%s ] %s',
574            $this->isAbstract() ? 'abstract ' : '',
575            $this->getShortName(),
576            $this->parentClassToString(),
577            $this->implementedInterfacesToString(),
578            $this->bodyToString()
579        );
580    }
581
582    /**
583     * Returns a string representation of the parent class or an empty string
584     * when no parent was declared.
585     *
586     * @return string
587     */
588    protected function parentClassToString()
589    {
590        if ( $this->getParentClass() === false )
591        {
592            return '';
593        }
594        return ' extends ' . $this->getParentClass()->getName();
595    }
596
597    /**
598     * This method returns a string representation of the parent interfaces or
599     * an empty string when the reflected class does not declare any parents.
600     *
601     * @return string
602     */
603    protected function implementedInterfacesToString()
604    {
605        if ( count( $this->getInterfaceNames() ) === 0 )
606        {
607            return '';
608        }
609        return ' implements ' . join( ', ', $this->getInterfaceNames() );
610    }
611
612    /**
613     * This method returns a string representation of all properties declared
614     * for the currently reflected interface.
615     *
616     * @return string
617     */
618    protected function propertiesToString()
619    {
620        $string = '';
621        foreach ( $this->getProperties() as $property )
622        {
623            $string .= $property->__toString( '    ' ) . PHP_EOL;
624        }
625        return $string;
626    }
627}
628