Created
July 8, 2011 08:19
-
-
Save thekid/1071362 to your computer and use it in GitHub Desktop.
XP Framework: Performance suite for issue #14
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* This class is part of the XP framework | |
* | |
* $Id$ | |
*/ | |
uses('util.profiling.Timer'); | |
/** | |
* Performance test | |
* | |
* @see https://github.com/xp-framework/xp-framework/issues/14 | |
*/ | |
class Issue14PerformanceTest extends Object { | |
protected static $inputs= array( | |
'#[@test]' | |
=> array('test' => NULL), | |
'#[@test(expect = "lang.FormatException")]' | |
=> array('test' => array('expect' => 'lang.FormatException')), | |
"#[@named('sybase')]" | |
=> array('named' => 'sybase'), | |
"#[@permission('rn=login, rt=config')]" | |
=> array('permission' => 'rn=login, rt=config'), | |
'#[@arg(name= "verbose", short="v")]' | |
=> array('arg' => array('name' => 'verbose', 'short' => 'v')), | |
'#[@arg(position= 0)]' | |
=> array('arg' => array('position' => 0)), | |
'#[@fromxml(xpath= "/root/element[position() = 3]/@id")]' | |
=> array('fromxml' => array('xpath' => '/root/element[position() = 3]/@id')), | |
'#[@webmethod, @restricted(role= "admin")]' | |
=> array('webmethod' => NULL, 'restricted' => array('role' => 'admin')), | |
'#[@restricted(roles = array("admin", "root"))]' | |
=> array('restricted' => array('roles' => array('admin', 'root'))), | |
'#[@require(permissions= array("rn=a", "rn=b"))]' | |
=> array('require' => array('permissions' => array('rn=a', 'rn=b'))), | |
'#[@overloaded(signatures= array(array("string"), array("string", "string")))]' | |
=> array('overloaded' => array('signatures' => array(array('string'), array('string', 'string')))), | |
); | |
/** | |
* Entry point | |
* | |
* @param string[] args | |
*/ | |
public static function main(array $args) { | |
$max= isset($args[0]) ? (int)$args[0]: 100000; | |
$total= 0.0; | |
$count= 0; | |
$t= new Timer(); | |
Console::writeLine(XPClass::forName('lang.XPClass')->getClassLoader()); | |
Console::writeLine('=========================================='); | |
foreach (self::$inputs as $input => $expected) { | |
Console::write('>> ', $input); | |
try { | |
$parsed= XPClass::parseAnnotations($input, 'Test'); | |
} catch (Throwable $e) { | |
Console::$err->writeLine('*** Expected '.xp::stringOf($expected).' but was '.$e->compoundMessage()); | |
continue; | |
} | |
if ($expected !== $parsed) { | |
Console::$err->writeLine('*** Expected '.xp::stringOf($expected).' but was '.xp::stringOf($parsed)); | |
continue; | |
} | |
$t->start(); | |
for ($i= 0; $i < $max; $i++) { | |
XPClass::parseAnnotations($input, 'Test'); | |
} | |
$t->stop(); | |
Console::writeLinef(': %d in %.3f seconds', $i, $t->elapsedTime()); | |
$total+= $t->elapsedTime(); | |
$count++; | |
} | |
Console::writeLine('=========================================='); | |
Console::writeLinef('Total: %d tests(s) in %.3f seconds, avg. %.3f seconds', $count, $total, $total / $count); | |
} | |
} | |
?> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
TIMES=100000 | |
IMPL=complete regex scanner | |
all: | |
@echo "- $(MAKE) perf.[$(IMPL)]" | |
@echo " Tests the implementations' performance" | |
@echo | |
@echo "- $(MAKE) test.[$(IMPL)]" | |
@echo " Runs net.xp_framework.unittest.annotations.*" | |
@echo | |
@echo "- $(MAKE) shootout" | |
@echo " Compares different implementations performance-wise" | |
@echo | |
@echo "- $(MAKE) verify" | |
@echo " Compares different implementations functionality" | |
perf.%: | |
@echo '!$*' > class.pth ; echo -n "@$*: " ; xp Issue14PerformanceTest $(TIMES); rm class.pth | |
test.%: | |
@echo '!$*' > class.pth ; echo -n "@$*: " ; unittest net.xp_framework.unittest.annotations.* ; rm class.pth | |
shootout: | |
@for i in $(IMPL) ; do $(MAKE) perf.$$i ; echo "" ; done | |
verify: | |
@for i in $(IMPL) ; do $(MAKE) test.$$i ; echo "" ; done |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* This class is part of the XP framework | |
* | |
* $Id$ | |
*/ | |
uses( | |
'lang.Type', | |
'lang.reflect.Method', | |
'lang.reflect.Field', | |
'lang.reflect.Constructor', | |
'lang.reflect.Modifiers', | |
'lang.reflect.Package' | |
); | |
define('DETAIL_ARGUMENTS', 1); | |
define('DETAIL_RETURNS', 2); | |
define('DETAIL_THROWS', 3); | |
define('DETAIL_COMMENT', 4); | |
define('DETAIL_ANNOTATIONS', 5); | |
define('DETAIL_NAME', 6); | |
define('DETAIL_GENERIC', 7); | |
/** | |
* Represents classes. Every instance of an XP class has an method | |
* called getClass() which returns an instance of this class. | |
* | |
* Warning | |
* ======= | |
* Do not construct this class publicly, instead use either the | |
* $o->getClass() syntax or the static method | |
* $class= XPClass::forName('fully.qualified.Name') | |
* | |
* Examples | |
* ======== | |
* To retrieve the fully qualified name of a class, use this: | |
* <code> | |
* $o= new File('...'); | |
* echo 'The class name for $o is '.$o->getClass()->getName(); | |
* </code> | |
* | |
* Create an instance of a class: | |
* <code> | |
* $instance= XPClass::forName('util.Binford')->newInstance(); | |
* </code> | |
* | |
* Invoke a method by its name: | |
* <code> | |
* try { | |
* $instance->getClass()->getMethod('connect')->invoke($instance); | |
* } catch (TargetInvocationException $e) { | |
* $e->getCause()->printStackTrace(); | |
* } | |
* </code> | |
* | |
* @see xp://lang.Object#getClass | |
* @see xp://lang.XPClass#forName | |
* @test xp://net.xp_framework.unittest.reflection.ReflectionTest | |
* @test xp://net.xp_framework.unittest.reflection.ClassDetailsTest | |
* @test xp://net.xp_framework.unittest.reflection.IsInstanceTest | |
* @test xp://net.xp_framework.unittest.reflection.ClassCastingTest | |
* @purpose Reflection | |
*/ | |
class XPClass extends Type { | |
protected $_class= NULL; | |
public $_reflect= NULL; | |
private static $DECLARING_CLASS_BUG= FALSE; | |
static function __static() { | |
self::$DECLARING_CLASS_BUG= version_compare(PHP_VERSION, '5.2.10', 'lt'); | |
} | |
/** | |
* Constructor | |
* | |
* @param var ref either a class name, a ReflectionClass instance or an object | |
* @throws lang.IllegalStateException | |
*/ | |
public function __construct($ref) { | |
if ($ref instanceof ReflectionClass) { | |
$this->_reflect= $ref; | |
$this->_class= $ref->getName(); | |
} else if (is_object($ref)) { | |
$this->_reflect= new ReflectionClass($ref); | |
$this->_class= get_class($ref); | |
} else { | |
try { | |
$this->_reflect= new ReflectionClass((string)$ref); | |
} catch (ReflectionException $e) { | |
throw new IllegalStateException($e->getMessage()); | |
} | |
$this->_class= $ref; | |
} | |
parent::__construct(xp::nameOf($this->_class)); | |
} | |
/** | |
* Returns simple name | |
* | |
* @return string | |
*/ | |
public function getSimpleName() { | |
return FALSE === ($p= strrpos(substr($this->name, 0, strcspn($this->name, '<')), '.')) | |
? $this->name // Already unqualified | |
: substr($this->name, $p+ 1) // Full name | |
; | |
} | |
/** | |
* Retrieves the package associated with this class | |
* | |
* @return lang.reflect.Package | |
*/ | |
public function getPackage() { | |
return Package::forName(substr($this->name, 0, strrpos($this->name, '.'))); | |
} | |
/** | |
* Creates a new instance of the class represented by this Class object. | |
* The class is instantiated as if by a new expression with an empty argument list. | |
* | |
* Example | |
* ======= | |
* <code> | |
* try { | |
* $o= XPClass::forName($name)->newInstance(); | |
* } catch (ClassNotFoundException $e) { | |
* // handle it! | |
* } | |
* </code> | |
* | |
* Example (passing arguments) | |
* =========================== | |
* <code> | |
* try { | |
* $o= XPClass::forName('peer.Socket')->newInstance('localhost', 6100); | |
* } catch (ClassNotFoundException $e) { | |
* // handle it! | |
* } | |
* </code> | |
* | |
* @param var* args | |
* @return lang.Object | |
* @throws lang.IllegalAccessException in case this class cannot be instantiated | |
*/ | |
public function newInstance() { | |
if ($this->_reflect->isInterface()) { | |
throw new IllegalAccessException('Cannot instantiate interfaces ('.$this->name.')'); | |
} else if ($this->_reflect->isAbstract()) { | |
throw new IllegalAccessException('Cannot instantiate abstract classes ('.$this->name.')'); | |
} | |
try { | |
if (!$this->hasConstructor()) return $this->_reflect->newInstance(); | |
$args= func_get_args(); | |
return $this->_reflect->newInstanceArgs($args); | |
} catch (ReflectionException $e) { | |
throw new IllegalAccessException($e->getMessage()); | |
} | |
} | |
/** | |
* Gets class methods for this class | |
* | |
* @return lang.reflect.Method[] | |
*/ | |
public function getMethods() { | |
$list= array(); | |
foreach ($this->_reflect->getMethods() as $m) { | |
if (0 == strncmp('__', $m->getName(), 2)) continue; | |
$list[]= new Method($this->_class, $m); | |
} | |
return $list; | |
} | |
/** | |
* Gets class methods declared by this class | |
* | |
* @return lang.reflect.Method[] | |
*/ | |
public function getDeclaredMethods() { | |
$list= array(); | |
if (self::$DECLARING_CLASS_BUG) { | |
foreach ($this->_reflect->getMethods() as $m) { | |
if (0 == strncmp('__', $m->getName(), 2) || $m->getDeclaringClass()->getName() !== $this->_reflect->name) continue; | |
$list[]= new Method($this->_class, $m); | |
} | |
} else { | |
foreach ($this->_reflect->getMethods() as $m) { | |
if (0 == strncmp('__', $m->getName(), 2) || $m->class !== $this->_reflect->name) continue; | |
$list[]= new Method($this->_class, $m); | |
} | |
} | |
return $list; | |
} | |
/** | |
* Gets a method by a specified name. | |
* | |
* @param string name | |
* @return lang.reflect.Method | |
* @see xp://lang.reflect.Method | |
* @throws lang.ElementNotFoundException | |
*/ | |
public function getMethod($name) { | |
if ($this->hasMethod($name)) { | |
return new Method($this->_class, $this->_reflect->getMethod($name)); | |
} | |
raise('lang.ElementNotFoundException', 'No such method "'.$name.'" in class '.$this->name); | |
} | |
/** | |
* Checks whether this class has a method named "$method" or not. | |
* | |
* Note | |
* ==== | |
* Since in PHP, methods are case-insensitive, calling hasMethod('toString') | |
* will provide the same result as hasMethod('tostring') | |
* | |
* @param string method the method's name | |
* @return bool TRUE if method exists | |
*/ | |
public function hasMethod($method) { | |
return ((0 === strncmp('__', $method, 2)) | |
? FALSE | |
: $this->_reflect->hasMethod($method) | |
); | |
} | |
/** | |
* Retrieve if a constructor exists | |
* | |
* @return bool | |
*/ | |
public function hasConstructor() { | |
return $this->_reflect->hasMethod('__construct'); | |
} | |
/** | |
* Retrieves this class' constructor. | |
* | |
* @return lang.reflect.Constructor | |
* @see xp://lang.reflect.Constructor | |
* @throws lang.ElementNotFoundException | |
*/ | |
public function getConstructor() { | |
if ($this->hasConstructor()) { | |
return new Constructor($this->_class, $this->_reflect->getMethod('__construct')); | |
} | |
raise('lang.ElementNotFoundException', 'No constructor in class '.$this->name); | |
} | |
/** | |
* Retrieve a list of all member variables | |
* | |
* @return lang.reflect.Field[] array of field objects | |
*/ | |
public function getFields() { | |
$f= array(); | |
foreach ($this->_reflect->getProperties() as $p) { | |
if ('__id' === $p->name) continue; | |
$f[]= new Field($this->_class, $p); | |
} | |
return $f; | |
} | |
/** | |
* Retrieve a list of member variables declared in this class | |
* | |
* @return lang.reflect.Field[] array of field objects | |
*/ | |
public function getDeclaredFields() { | |
$list= array(); | |
if (self::$DECLARING_CLASS_BUG) { | |
foreach ($this->_reflect->getProperties() as $p) { | |
if ('__id' === $p->name || $p->getDeclaringClass()->getName() !== $this->_reflect->name) continue; | |
$list[]= new Field($this->_class, $p); | |
} | |
} else { | |
foreach ($this->_reflect->getProperties() as $p) { | |
if ('__id' === $p->name || $p->class !== $this->_reflect->name) continue; | |
$list[]= new Field($this->_class, $p); | |
} | |
} | |
return $list; | |
} | |
/** | |
* Retrieve a field by a specified name. | |
* | |
* @param string name | |
* @return lang.reflect.Field | |
* @throws lang.ElementNotFoundException | |
*/ | |
public function getField($name) { | |
if ($this->hasField($name)) { | |
return new Field($this->_class, $this->_reflect->getProperty($name)); | |
} | |
raise('lang.ElementNotFoundException', 'No such field "'.$name.'" in class '.$this->name); | |
} | |
/** | |
* Checks whether this class has a field named "$field" or not. | |
* | |
* @param string field the fields's name | |
* @return bool TRUE if field exists | |
*/ | |
public function hasField($field) { | |
return '__id' == $field ? FALSE : $this->_reflect->hasProperty($field); | |
} | |
/** | |
* Retrieve the parent class's class object. Returns NULL if there | |
* is no parent class. | |
* | |
* @return lang.XPClass class object | |
*/ | |
public function getParentclass() { | |
return ($parent= $this->_reflect->getParentClass()) ? new self($parent) : NULL; | |
} | |
/** | |
* Checks whether this class has a constant named "$constant" or not | |
* | |
* @param string constant | |
* @return bool | |
*/ | |
public function hasConstant($constant) { | |
return $this->_reflect->hasConstant($constant); | |
} | |
/** | |
* Retrieve a constant by a specified name. | |
* | |
* @param string constant | |
* @return var | |
* @throws lang.ElementNotFoundException in case constant does not exist | |
*/ | |
public function getConstant($constant) { | |
if ($this->hasConstant($constant)) { | |
return $this->_reflect->getConstant($constant); | |
} | |
raise('lang.ElementNotFoundException', 'No such constants "'.$constant.'" in class '.$this->name); | |
} | |
/** | |
* Cast a given object to the class represented by this object | |
* | |
* @param lang.Generic expression | |
* @return lang.Generic the given expression | |
* @throws lang.ClassCastException | |
*/ | |
public function cast(Generic $expression= NULL) { | |
if (NULL === $expression) { | |
return xp::null(); | |
} else if (is($this->name, $expression)) { | |
return $expression; | |
} | |
raise('lang.ClassCastException', 'Cannot cast '.xp::typeOf($expression).' to '.$this->name); | |
} | |
/** | |
* Tests whether this class is a subclass of a specified class. | |
* | |
* @param var class either a string or an XPClass object | |
* @return bool | |
*/ | |
public function isSubclassOf($class) { | |
if (!($class instanceof self)) $class= XPClass::forName($class); | |
if ($class->name == $this->name) return FALSE; // Catch bordercase (ZE bug?) | |
return $this->_reflect->isSubclassOf($class->_reflect); | |
} | |
/** | |
* Determines whether the specified object is an instance of this | |
* class. This is the equivalent of the is() core functionality. | |
* | |
* Examples | |
* ======== | |
* <code> | |
* uses('io.File', 'io.TempFile'); | |
* $class= XPClass::forName('io.File'); | |
* | |
* var_dump($class->isInstance(new TempFile())); // TRUE | |
* var_dump($class->isInstance(new File())); // TRUE | |
* var_dump($class->isInstance(new Object())); // FALSE | |
* </code> | |
* | |
* @param var obj | |
* @return bool | |
*/ | |
public function isInstance($obj) { | |
return is($this->name, $obj); | |
} | |
/** | |
* Determines if this XPClass object represents an interface type. | |
* | |
* @return bool | |
*/ | |
public function isInterface() { | |
return $this->_reflect->isInterface(); | |
} | |
/** | |
* Determines if this XPClass object represents an interface type. | |
* | |
* @return bool | |
*/ | |
public function isEnum() { | |
$e= xp::reflect('lang.Enum'); | |
return class_exists($e) && $this->_reflect->isSubclassOf($e); | |
} | |
/** | |
* Retrieve interfaces this class implements | |
* | |
* @return lang.XPClass[] | |
*/ | |
public function getInterfaces() { | |
$r= array(); | |
foreach ($this->_reflect->getInterfaces() as $iface) { | |
$r[]= new self($iface->getName()); | |
} | |
return $r; | |
} | |
/** | |
* Retrieve interfaces this class implements in its declaration | |
* | |
* @return lang.XPClass[] | |
*/ | |
public function getDeclaredInterfaces() { | |
$is= $this->_reflect->getInterfaces(); | |
if ($parent= $this->_reflect->getParentclass()) { | |
$ip= $parent->getInterfaces(); | |
} else { | |
$ip= array(); | |
} | |
$filter= array(); | |
foreach ($is as $iname => $i) { | |
// Parent class implements this interface | |
if (isset($ip[$iname])) { | |
$filter[$iname]= TRUE; | |
continue; | |
} | |
// Interface is implemented because it's the parent of another interface | |
foreach ($i->getInterfaces() as $pname => $p) { | |
if (isset($is[$pname])) $filter[$pname]= TRUE; | |
} | |
} | |
$r= array(); | |
foreach ($is as $iname => $i) { | |
if (!isset($filter[$iname])) $r[]= new self($i); | |
} | |
return $r; | |
} | |
/** | |
* Retrieves the api doc comment for this class. Returns NULL if | |
* no documentation is present. | |
* | |
* @return string | |
*/ | |
public function getComment() { | |
if (!($details= self::detailsForClass($this->name))) return NULL; | |
return $details['class'][DETAIL_COMMENT]; | |
} | |
/** | |
* Retrieves this class' modifiers | |
* | |
* @see xp://lang.reflect.Modifiers | |
* @return int | |
*/ | |
public function getModifiers() { | |
$r= MODIFIER_PUBLIC; | |
// Map PHP reflection modifiers to generic form | |
$m= $this->_reflect->getModifiers(); | |
$m & ReflectionClass::IS_EXPLICIT_ABSTRACT && $r |= MODIFIER_ABSTRACT; | |
$m & ReflectionClass::IS_IMPLICIT_ABSTRACT && $r |= MODIFIER_ABSTRACT; | |
$m & ReflectionClass::IS_FINAL && $r |= MODIFIER_FINAL; | |
return $r; | |
} | |
/** | |
* Check whether an annotation exists | |
* | |
* @param string name | |
* @param string key default NULL | |
* @return bool | |
*/ | |
public function hasAnnotation($name, $key= NULL) { | |
$details= self::detailsForClass($this->name); | |
return $details && ($key | |
? @array_key_exists($key, @$details['class'][DETAIL_ANNOTATIONS][$name]) | |
: @array_key_exists($name, @$details['class'][DETAIL_ANNOTATIONS]) | |
); | |
} | |
/** | |
* Retrieve annotation by name | |
* | |
* @param string name | |
* @param string key default NULL | |
* @return var | |
* @throws lang.ElementNotFoundException | |
*/ | |
public function getAnnotation($name, $key= NULL) { | |
$details= self::detailsForClass($this->name); | |
if (!$details || !($key | |
? @array_key_exists($key, @$details['class'][DETAIL_ANNOTATIONS][$name]) | |
: @array_key_exists($name, @$details['class'][DETAIL_ANNOTATIONS]) | |
)) return raise( | |
'lang.ElementNotFoundException', | |
'Annotation "'.$name.($key ? '.'.$key : '').'" does not exist' | |
); | |
return ($key | |
? $details['class'][DETAIL_ANNOTATIONS][$name][$key] | |
: $details['class'][DETAIL_ANNOTATIONS][$name] | |
); | |
} | |
/** | |
* Retrieve whether a method has annotations | |
* | |
* @return bool | |
*/ | |
public function hasAnnotations() { | |
$details= self::detailsForClass($this->name); | |
return $details ? !empty($details['class'][DETAIL_ANNOTATIONS]) : FALSE; | |
} | |
/** | |
* Retrieve all of a method's annotations | |
* | |
* @return array annotations | |
*/ | |
public function getAnnotations() { | |
$details= self::detailsForClass($this->name); | |
return $details ? $details['class'][DETAIL_ANNOTATIONS] : array(); | |
} | |
/** | |
* Retrieve the class loader a class was loaded with. | |
* | |
* @return lang.IClassLoader | |
*/ | |
public function getClassLoader() { | |
return self::_classLoaderFor($this->name); | |
} | |
/** | |
* Fetch a class' classloader by its name | |
* | |
* @param string name fqcn of class | |
* @return lang.IClassLoader | |
*/ | |
protected static function _classLoaderFor($name) { | |
if (isset(xp::$registry[$l= 'classloader.'.$name])) { | |
sscanf(xp::$registry[$l], '%[^:]://%[^$]', $cl, $argument); | |
return call_user_func(array(xp::reflect($cl), 'instanceFor'), $argument); | |
} | |
return NULL; // Internal class, e.g. | |
} | |
/** | |
* Parses annotation string | |
* | |
* @param string input | |
* @param string context the class name, and optionally the line number. | |
* @return [:var] | |
* @throws lang.ClassFormatException | |
*/ | |
public static function parseAnnotations($input, $context) { | |
$input= trim($input, "[]# \t\n\r").']'; | |
$offset= 0; | |
$annotations= array(); | |
$annotation= $value= NULL; | |
$length= strlen($input); | |
while ($offset < $length) { | |
$state= $input{$offset}; | |
if ('@' === $state) { | |
$s= strcspn($input, ',(]', $offset); | |
$annotation= substr($input, $offset+ 1, $s- 1); | |
$offset+= $s; | |
} else if (']' === $state) { | |
$annotations[$annotation]= $value; | |
break; | |
} else if ('(' === $state) { | |
$peek= substr($input, $offset+ 1, strcspn($input, '="\')', $offset)); | |
if ('\'' === $peek{0} || '"' === $peek{0}) { | |
$p= $offset+ 2; | |
$q= $peek{0}; | |
while (($s= strcspn($input, $q, $p)) !== 0) { | |
$p+= $s; | |
if ('\\' !== $input{$p- 1}) break; | |
$p++; | |
} | |
if (!is_string($value= @eval('return '.substr($input, $offset+ 1, $p - $offset).';'))) { | |
raise('lang.ClassFormatException', 'Parse error: Unterminated or malformed string in '.$context); | |
} | |
$offset= $p+ 1; | |
} else if ('array' === substr($peek, 0, 5)) { | |
$b= 1; | |
$p= $offset+ 1+ 6; | |
while ($b > 0) { | |
$p+= strcspn($input, '()"\'', $p); | |
if ($p > $length) break; | |
if ('(' === $input{$p}) $b++; else if (')' === $input{$p}) $b--; else if ('\'' === $input{$p} || '"' === $input{$p}) { | |
$q= $input{$p}; | |
$p++; | |
while (($s= strcspn($input, $q, $p)) !== 0) { | |
$p+= $s; | |
if ('\\' !== $input{$p- 1}) break; | |
$p++; | |
} | |
} | |
$p++; | |
} | |
if (!is_array($value= @eval('return '.substr($input, $offset+ 1, $p- $offset- 1).';'))) { | |
raise('lang.ClassFormatException', 'Parse error: Unterminated or malformed array in '.$context); | |
} | |
$offset= $p; | |
} else if ('=' !== $peek{strlen($peek)- 1}) { | |
$value= eval('return '.substr($peek, 0, -1).';'); | |
$offset+= strlen($peek); | |
} else { | |
$value= array(); | |
do { | |
$key= trim($peek, '= '); | |
$offset+= strlen($peek)+ 1; | |
$offset+= strspn($input, ' ', $offset); | |
if ($offset >= $length) { | |
break; | |
} else if ('array' === substr($input, $offset, 5)) { | |
$b= 1; | |
$p= $offset+ 6; | |
while ($b > 0) { | |
$p+= strcspn($input, '()"\'', $p); | |
if ($p > $length) break; | |
if ('(' === $input{$p}) $b++; else if (')' === $input{$p}) $b--; else if ('\'' === $input{$p} || '"' === $input{$p}) { | |
$q= $input{$p}; | |
$p++; | |
while (($s= strcspn($input, $q, $p)) !== 0) { | |
$p+= $s; | |
if ('\\' !== $input{$p- 1}) break; | |
$p++; | |
} | |
} | |
$p++; | |
} | |
if (!is_array($value[$key]= @eval('return '.substr($input, $offset, $p- $offset).';'))) { | |
raise('lang.ClassFormatException', 'Parse error: Unterminated or malformed array in '.$context); | |
} | |
$offset= $p; | |
} else if ('\'' === $input{$offset} || '"' === $input{$offset}) { | |
$p= $offset+ 1; | |
$q= $input{$offset}; | |
while (($s= strcspn($input, $q, $p)) !== 0) { | |
$p+= $s; | |
if ('\\' !== $input{$p- 1}) break; | |
$p++; | |
} | |
if (!is_string($value[$key]= @eval('return '.substr($input, $offset, $p - $offset + 1).';'))) { | |
raise('lang.ClassFormatException', 'Parse error: Unterminated or malformed string in '.$context); | |
} | |
$offset= $p+ 1; | |
} else { | |
$s= strcspn($input, ',)', $offset); | |
$value[$key]= eval('return '.substr($input, $offset, $s).';'); | |
$offset+= $s; | |
} | |
// Find next key | |
$s= strcspn($input, '="\')', $offset); | |
$peek= substr($input, $offset+ 1, $s); | |
} while ($s); | |
} | |
$offset++; // ")" | |
if ($offset > $length) { | |
raise('lang.ClassFormatException', 'Parse error: Expecting ] in '.$context); | |
} | |
} else if (',' === $state) { | |
$annotations[$annotation]= $value; | |
$annotation= $value= NULL; | |
if (FALSE === ($offset= strpos($input, '@', $offset))) { | |
raise('lang.ClassFormatException', 'Parse error: Expecting @ in '.$context); | |
} | |
} else { | |
raise('lang.ClassFormatException', 'Parse error: Unknown state '.$state.' at position '.$offset.' in '.$context); | |
} | |
} | |
return $annotations; | |
} | |
/** | |
* Retrieve details for a specified class. Note: Results from this | |
* method are cached! | |
* | |
* @param string class fully qualified class name | |
* @return array or NULL to indicate no details are available | |
*/ | |
public static function detailsForClass($class) { | |
if (!$class) return NULL; // Border case | |
if (isset(xp::$registry['details.'.$class])) return xp::$registry['details.'.$class]; | |
// Retrieve class' sourcecode | |
$cl= self::_classLoaderFor($class); | |
if (!$cl || !($bytes= $cl->loadClassBytes($class))) return NULL; | |
$details= array(array(), array()); | |
$annotations= array(); | |
$comment= NULL; | |
$members= TRUE; | |
$parsed= ''; | |
$tokens= token_get_all($bytes); | |
for ($i= 0, $s= sizeof($tokens); $i < $s; $i++) { | |
switch ($tokens[$i][0]) { | |
case T_DOC_COMMENT: | |
$comment= $tokens[$i][1]; | |
break; | |
case T_COMMENT: | |
if ('#' === $tokens[$i][1]{0}) { // Annotations | |
if ('[' === $tokens[$i][1]{1}) { | |
$parsed= substr($tokens[$i][1], 2); | |
} else { | |
$parsed.= substr($tokens[$i][1], 1); | |
} | |
if (']' == substr(rtrim($tokens[$i][1]), -1)) { | |
$annotations= self::parseAnnotations( | |
trim($parsed, " \t\n\r"), | |
$class.(isset($tokens[$i][2]) ? ', line '.$tokens[$i][2] : '') | |
); | |
$parsed= ''; | |
} | |
} | |
break; | |
case T_CLASS: | |
case T_INTERFACE: | |
if ('' !== $parsed) raise( | |
'lang.ClassFormatException', | |
'Unterminated annotation "'.addcslashes($parsed, "\0..\17").'" in '.$class.(isset($tokens[$i][2]) ? ', line '.$tokens[$i][2] : '') | |
); | |
$details['class']= array( | |
DETAIL_COMMENT => trim(preg_replace('/\n \* ?/', "\n", "\n".substr( | |
$comment, | |
4, // "/**\n" | |
strpos($comment, '* @')- 2 // position of first details token | |
))), | |
DETAIL_ANNOTATIONS => $annotations | |
); | |
$annotations= array(); | |
$comment= NULL; | |
break; | |
case T_VARIABLE: | |
if (!$members) break; | |
// Have a member variable | |
'' === $parsed || raise('lang.ClassFormatException', 'Unterminated annotation "'.addcslashes($parsed, "\0..\17").'" in '.$class.', line '.(isset($tokens[$i][2]) ? ', line '.$tokens[$i][2] : '')); | |
$name= substr($tokens[$i][1], 1); | |
$details[0][$name]= array( | |
DETAIL_ANNOTATIONS => $annotations | |
); | |
$annotations= array(); | |
break; | |
case T_FUNCTION: | |
'' === $parsed || raise('lang.ClassFormatException', 'Unterminated annotation "'.addcslashes($parsed, "\0..\17").'" in '.$class.', line '.(isset($tokens[$i][2]) ? ', line '.$tokens[$i][2] : '')); | |
$members= FALSE; | |
while (T_STRING !== $tokens[$i][0]) $i++; | |
$m= $tokens[$i][1]; | |
$details[1][$m]= array( | |
DETAIL_ARGUMENTS => array(), | |
DETAIL_RETURNS => 'void', | |
DETAIL_THROWS => array(), | |
DETAIL_COMMENT => trim(preg_replace('/\n \* ?/', "\n", "\n".substr( | |
$comment, | |
4, // "/**\n" | |
strpos($comment, '* @')- 2 // position of first details token | |
))), | |
DETAIL_ANNOTATIONS => $annotations, | |
DETAIL_NAME => $tokens[$i][1] | |
); | |
$matches= NULL; | |
preg_match_all( | |
'/@([a-z]+)\s*([^<\r\n]+<[^>]+>|[^\r\n ]+) ?([^\r\n ]+)?/', | |
$comment, | |
$matches, | |
PREG_SET_ORDER | |
); | |
$annotations= array(); | |
$comment= NULL; | |
foreach ($matches as $match) { | |
switch ($match[1]) { | |
case 'param': | |
$details[1][$m][DETAIL_ARGUMENTS][]= $match[2]; | |
break; | |
case 'return': | |
$details[1][$m][DETAIL_RETURNS]= $match[2]; | |
break; | |
case 'throws': | |
$details[1][$m][DETAIL_THROWS][]= $match[2]; | |
break; | |
} | |
} | |
break; | |
default: | |
// Empty | |
} | |
} | |
// Return details for specified class | |
xp::$registry['details.'.$class]= $details; | |
return $details; | |
} | |
/** | |
* Retrieve details for a specified class and method. Note: Results | |
* from this method are cached! | |
* | |
* @param string class unqualified class name | |
* @param string method | |
* @return array or NULL if not available | |
*/ | |
public static function detailsForMethod($class, $method) { | |
$details= self::detailsForClass(xp::nameOf($class)); | |
return $details ? (isset($details[1][$method]) ? $details[1][$method] : NULL) : NULL; | |
} | |
/** | |
* Retrieve details for a specified class and field. Note: Results | |
* from this method are cached! | |
* | |
* @param string class unqualified class name | |
* @param string method | |
* @return array or NULL if not available | |
*/ | |
public static function detailsForField($class, $field) { | |
$details= self::detailsForClass(xp::nameOf($class)); | |
return $details ? (isset($details[0][$field]) ? $details[0][$field] : NULL) : NULL; | |
} | |
/** | |
* Creates a generic type | |
* | |
* @param lang.XPClass self | |
* @param lang.Type[] arguments | |
* @return lang.XPClass | |
*/ | |
public static function createGenericType(XPClass $self, array $arguments) { | |
// Verify | |
if (!$self->isGenericDefinition()) { | |
throw new IllegalStateException('Class '.$self->name.' is not a generic definition'); | |
} | |
$components= $self->genericComponents(); | |
$cs= sizeof($components); | |
if ($cs != sizeof($arguments)) { | |
throw new IllegalArgumentException(sprintf( | |
'Class %s expects %d component(s) <%s>, %d argument(s) given', | |
$self->name, | |
$cs, | |
implode(', ', $components), | |
sizeof($arguments) | |
)); | |
} | |
// Compose names | |
$cn= $qc= ''; | |
foreach ($arguments as $typearg) { | |
$cn.= '¸'.$typearg->literal(); | |
$qc.= ','.$typearg->getName(); | |
} | |
$name= xp::reflect($self->name).'··'.substr($cn, 1); | |
$qname= $self->name.'<'.substr($qc, 1).'>'; | |
// Create class if it doesn't exist yet | |
if (!class_exists($name, FALSE) && !interface_exists($name, FALSE)) { | |
$meta= isset(xp::$registry['details.'.$self->name]) ? xp::$registry['details.'.$self->name] : array( | |
'class' => NULL, | |
0 => array(), | |
1 => array() | |
); | |
// Parse placeholders into a lookup map | |
$placeholders= array(); | |
foreach ($components as $i => $component) { | |
$placeholders[$component]= $arguments[$i]->getName(); | |
} | |
// Work on sourcecode | |
$cl= self::_classLoaderFor($self->name); | |
if (!$cl || !($bytes= $cl->loadClassBytes($self->name))) { | |
throw new IllegalStateException($self->name); | |
} | |
// Replace source | |
$src= ''; | |
$comment= NULL; | |
$annotations= array(); | |
$annotation= NULL; | |
$matches= array(); | |
$state= array(0); | |
$counter= 0; | |
$tokens= token_get_all($bytes); | |
for ($i= 0, $s= sizeof($tokens); $i < $s; $i++) { | |
if (T_COMMENT === $tokens[$i][0] && '#' === $tokens[$i][1]{0}) { | |
$annotations= eval('return array('.preg_replace( | |
array('/@([a-z_]+),/i', '/@([a-z_]+)\(\'([^\']+)\'\)/ie', '/@([a-z_]+)\(/i', '/([^a-z_@])([a-z_]+) *= */i'), | |
array('\'$1\' => NULL,', '"\'$1\' => urldecode(\'".urlencode(\'$2\')."\')"', '\'$1\' => array(', '$1\'$2\' => '), | |
trim($tokens[$i][1], "[]# \t\n\r").',' | |
).');'); | |
continue; | |
} else if (T_DOC_COMMENT === $tokens[$i][0]) { | |
$matches= NULL; | |
$comment= trim(preg_replace('/\n\s+\* ?/', "\n", "\n".substr( | |
$tokens[$i][1], | |
4, // "/**\n" | |
strpos($tokens[$i][1], '* @')- 2 // position of first details token | |
))); | |
preg_match_all( | |
'/@([a-z]+)\s*([^<\r\n]+<[^>]+>|[^\r\n ]+) ?([^\r\n ]+)?/', | |
$tokens[$i][1], | |
$matches, | |
PREG_SET_ORDER | |
); | |
} | |
if (0 === $state[0]) { | |
if (T_ABSTRACT === $tokens[$i][0] || T_FINAL === $tokens[$i][0]) { | |
$src.= $tokens[$i][1].' '; | |
} else if (T_CLASS === $tokens[$i][0] || T_INTERFACE === $tokens[$i][0]) { | |
if (NULL === $meta['class']) { | |
$meta['class']= array(DETAIL_COMMENT => $comment, DETAIL_ANNOTATIONS => $annotations); | |
} | |
$meta['class'][DETAIL_GENERIC]= array($self->name, $arguments); | |
$src.= $tokens[$i][1].' '.$name; | |
array_unshift($state, $tokens[$i][0]); | |
} | |
continue; | |
} else if (T_CLASS === $state[0]) { | |
if (T_EXTENDS === $tokens[$i][0]) { | |
if (isset($annotations['generic']['parent'])) { | |
$xargs= array(); | |
foreach (explode(',', $annotations['generic']['parent']) as $j => $placeholder) { | |
$xargs[]= Type::forName(strtr(ltrim($placeholder), $placeholders)); | |
} | |
$src.= ' extends '.self::createGenericType($self->getParentClass(), $xargs)->literal(); | |
} else { | |
$src.= ' extends '.$tokens[$i+ 2][1]; | |
} | |
} else if (T_IMPLEMENTS === $tokens[$i][0]) { | |
$src.= ' implements'; | |
$counter= 0; | |
$annotation= @$annotations['generic']['implements']; | |
array_unshift($state, 5); | |
} else if ('{' === $tokens[$i][0]) { | |
array_shift($state); | |
array_unshift($state, 1); | |
$src.= ' {'; | |
} | |
continue; | |
} else if (T_INTERFACE === $state[0]) { | |
if (T_EXTENDS === $tokens[$i][0]) { | |
$src.= ' extends'; | |
$counter= 0; | |
$annotation= @$annotations['generic']['extends']; | |
array_unshift($state, 5); | |
} else if ('{' === $tokens[$i][0]) { | |
array_shift($state); | |
array_unshift($state, 1); | |
$src.= ' {'; | |
} | |
continue; | |
} else if (1 === $state[0]) { // Class body | |
if (T_FUNCTION === $tokens[$i][0]) { | |
$braces= 0; | |
$parameters= array(); | |
array_unshift($state, 3); | |
array_unshift($state, 2); | |
$m= $tokens[$i+ 2][1]; | |
if (isset($meta[1][$m])) { | |
$annotations= $meta[1][$m][DETAIL_ANNOTATIONS]; | |
} else { | |
$meta[1][$m]= array( | |
DETAIL_ARGUMENTS => array(), | |
DETAIL_RETURNS => 'void', | |
DETAIL_THROWS => array(), | |
DETAIL_COMMENT => $comment, | |
DETAIL_ANNOTATIONS => $annotations, | |
DETAIL_NAME => $m | |
); | |
foreach ($matches as $match) { | |
switch ($match[1]) { | |
case 'param': $meta[1][$m][DETAIL_ARGUMENTS][]= $match[2]; break; | |
case 'return': $meta[1][$m][DETAIL_RETURNS]= $match[2]; break; | |
case 'throws': $meta[1][$m][DETAIL_THROWS][]= $match[2]; break; | |
} | |
} | |
} | |
} else if ('}' === $tokens[$i][0]) { | |
$src.= '}'; | |
break; | |
} else if (T_CLOSE_TAG === $tokens[$i][0]) { | |
break; | |
} | |
} else if (2 === $state[0]) { // Method declaration | |
if ('(' === $tokens[$i][0]) { | |
$braces++; | |
} else if (')' === $tokens[$i][0]) { | |
$braces--; | |
if (0 === $braces) array_shift($state); | |
} else if (T_VARIABLE === $tokens[$i][0]) { | |
$parameters[]= $tokens[$i][1]; | |
} | |
} else if (3 === $state[0]) { // Method body | |
if (';' === $tokens[$i][0]) { | |
// Abstract method | |
array_shift($state); | |
} else if ('{' === $tokens[$i][0]) { | |
$braces= 1; | |
array_shift($state); | |
array_unshift($state, 4); | |
$src.= '{'; | |
if (isset($annotations['generic']['return'])) { | |
$meta[1][$m][DETAIL_RETURNS]= strtr($annotations['generic']['return'], $placeholders); | |
} | |
if (isset($annotations['generic']['params'])) { | |
$generic= array(); | |
foreach (explode(',', $annotations['generic']['params']) as $j => $placeholder) { | |
if ('' === ($replaced= strtr(ltrim($placeholder), $placeholders))) { | |
$generic[$j]= NULL; | |
} else { | |
$meta[1][$m][DETAIL_ARGUMENTS][$j]= $replaced; | |
$generic[$j]= $replaced; | |
} | |
} | |
foreach ($generic as $j => $type) { | |
if (NULL === $type) { | |
continue; | |
} else if ('...' === substr($type, -3)) { | |
$src.= $j ? '$·args= array_slice(func_get_args(), '.$j.');' : '$·args= func_get_args();'; | |
$src.= ( | |
' if (!is(\''.substr($generic[$j], 0, -3).'[]\', $·args)) throw new IllegalArgumentException('. | |
'"Vararg '.($j + 1).' passed to ".__METHOD__."'. | |
' must be of '.$type.', ".xp::stringOf($·args)." given"'. | |
');' | |
); | |
} else { | |
$src.= ( | |
' if (!is(\''.$generic[$j].'\', '.$parameters[$j].')) throw new IllegalArgumentException('. | |
'"Argument '.($j + 1).' passed to ".__METHOD__."'. | |
' must be of '.$type.', ".xp::typeOf('.$parameters[$j].')." given"'. | |
');' | |
); | |
} | |
} | |
$annotations= array(); | |
} | |
unset($meta[1][$m][DETAIL_ANNOTATIONS]['generic']); | |
continue; | |
} | |
} else if (4 === $state[0]) { // Method body | |
if ('{' === $tokens[$i][0]) { | |
$braces++; | |
} else if ('}' === $tokens[$i][0]) { | |
$braces--; | |
if (0 === $braces) array_shift($state); | |
} | |
} else if (5 === $state[0]) { // Implements (class), Extends (interface) | |
if (T_STRING === $tokens[$i][0]) { | |
if (isset($annotation[$counter])) { | |
$iargs= array(); | |
foreach (explode(',', $annotation[$counter]) as $j => $placeholder) { | |
$iargs[]= Type::forName(strtr(ltrim($placeholder), $placeholders)); | |
} | |
$src.= self::createGenericType(new XPClass(new ReflectionClass($tokens[$i][1])), $iargs)->literal(); | |
} else { | |
$src.= $tokens[$i][1]; | |
} | |
$counter++; | |
continue; | |
} else if ('{' === $tokens[$i][0]) { | |
array_shift($state); | |
array_unshift($state, 1); | |
} | |
} | |
$src.= is_array($tokens[$i]) ? $tokens[$i][1] : $tokens[$i]; | |
} | |
// Create class | |
// DEBUG fputs(STDERR, "@* ".substr($src, 0, strpos($src, '{'))." -> $qname\n"); | |
eval($src); | |
method_exists($name, '__static') && call_user_func(array($name, '__static')); | |
unset($meta['class'][DETAIL_ANNOTATIONS]['generic']); | |
xp::$registry['details.'.$qname]= $meta; | |
xp::$registry['class.'.$name]= $qname; | |
} | |
return new XPClass(new ReflectionClass($name)); | |
} | |
/** | |
* Reflectively creates a new type | |
* | |
* @param lang.Type[] arguments | |
* @return lang.XPClass | |
* @throws lang.IllegalStateException if this class is not a generic definition | |
* @throws lang.IllegalArgumentException if number of arguments does not match components | |
*/ | |
public function newGenericType(array $arguments) { | |
return self::createGenericType($this, $arguments); | |
} | |
/** | |
* Returns generic type components | |
* | |
* @return string[] | |
* @throws lang.IllegalStateException if this class is not a generic definition | |
*/ | |
public function genericComponents() { | |
if (!$this->isGenericDefinition()) { | |
throw new IllegalStateException('Class '.$this->name.' is not a generic definition'); | |
} | |
$components= array(); | |
foreach (explode(',', $this->getAnnotation('generic', 'self')) as $name) { | |
$components[]= ltrim($name); | |
} | |
return $components; | |
} | |
/** | |
* Returns whether this class is a generic definition | |
* | |
* @return bool | |
*/ | |
public function isGenericDefinition() { | |
return $this->hasAnnotation('generic', 'self'); | |
} | |
/** | |
* Returns generic type definition | |
* | |
* @return lang.XPClass | |
* @throws lang.IllegalStateException if this class is not a generic | |
*/ | |
public function genericDefinition() { | |
if (!($details= self::detailsForClass($this->name))) return NULL; | |
if (!isset($details['class'][DETAIL_GENERIC])) { | |
throw new IllegalStateException('Class '.$this->name.' is not generic'); | |
} | |
return XPClass::forName($details['class'][DETAIL_GENERIC][0]); | |
} | |
/** | |
* Returns generic type arguments | |
* | |
* @return lang.Type[] | |
* @throws lang.IllegalStateException if this class is not a generic | |
*/ | |
public function genericArguments() { | |
if (!($details= self::detailsForClass($this->name))) return NULL; | |
if (!isset($details['class'][DETAIL_GENERIC])) { | |
throw new IllegalStateException('Class '.$this->name.' is not generic'); | |
} | |
if (!isset($details['class'][DETAIL_GENERIC][1])) { | |
$details['class'][DETAIL_GENERIC][1]= array_map( | |
array(xp::reflect('lang.Type'), 'forName'), | |
$details['class'][DETAIL_GENERIC][2] | |
); | |
unset($details['class'][DETAIL_GENERIC][2]); | |
} | |
return $details['class'][DETAIL_GENERIC][1]; | |
} | |
/** | |
* Returns whether this class is generic | |
* | |
* @return bool | |
*/ | |
public function isGeneric() { | |
if (!($details= self::detailsForClass($this->name))) return FALSE; | |
return isset($details['class'][DETAIL_GENERIC]); | |
} | |
/** | |
* Returns the XPClass object associated with the class with the given | |
* string name. Uses the default classloader if none is specified. | |
* | |
* @param string name - e.g. "io.File", "rdbms.mysql.MySQL" | |
* @param lang.IClassLoader classloader default NULL | |
* @return lang.XPClass class object | |
* @throws lang.ClassNotFoundException when there is no such class | |
*/ | |
public static function forName($name, IClassLoader $classloader= NULL) { | |
if (NULL === $classloader) { | |
$classloader= ClassLoader::getDefault(); | |
} | |
return $classloader->loadClass((string)$name); | |
} | |
/** | |
* Returns type literal | |
* | |
* @return string | |
*/ | |
public function literal() { | |
return xp::reflect($this->name); | |
} | |
/** | |
* Returns an array containing class objects representing all the | |
* public classes | |
* | |
* @return lang.XPClass[] class objects | |
*/ | |
public static function getClasses() { | |
$ret= array(); | |
foreach (get_declared_classes() as $name) { | |
if (isset(xp::$registry['class.'.$name])) $ret[]= new self($name); | |
} | |
return $ret; | |
} | |
} | |
?> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* This class is part of the XP framework | |
* | |
* $Id$ | |
*/ | |
uses( | |
'lang.Type', | |
'lang.reflect.Method', | |
'lang.reflect.Field', | |
'lang.reflect.Constructor', | |
'lang.reflect.Modifiers', | |
'lang.reflect.Package' | |
); | |
define('DETAIL_ARGUMENTS', 1); | |
define('DETAIL_RETURNS', 2); | |
define('DETAIL_THROWS', 3); | |
define('DETAIL_COMMENT', 4); | |
define('DETAIL_ANNOTATIONS', 5); | |
define('DETAIL_NAME', 6); | |
define('DETAIL_GENERIC', 7); | |
/** | |
* Represents classes. Every instance of an XP class has an method | |
* called getClass() which returns an instance of this class. | |
* | |
* Warning | |
* ======= | |
* Do not construct this class publicly, instead use either the | |
* $o->getClass() syntax or the static method | |
* $class= XPClass::forName('fully.qualified.Name') | |
* | |
* Examples | |
* ======== | |
* To retrieve the fully qualified name of a class, use this: | |
* <code> | |
* $o= new File('...'); | |
* echo 'The class name for $o is '.$o->getClass()->getName(); | |
* </code> | |
* | |
* Create an instance of a class: | |
* <code> | |
* $instance= XPClass::forName('util.Binford')->newInstance(); | |
* </code> | |
* | |
* Invoke a method by its name: | |
* <code> | |
* try { | |
* $instance->getClass()->getMethod('connect')->invoke($instance); | |
* } catch (TargetInvocationException $e) { | |
* $e->getCause()->printStackTrace(); | |
* } | |
* </code> | |
* | |
* @see xp://lang.Object#getClass | |
* @see xp://lang.XPClass#forName | |
* @test xp://net.xp_framework.unittest.reflection.ReflectionTest | |
* @test xp://net.xp_framework.unittest.reflection.ClassDetailsTest | |
* @test xp://net.xp_framework.unittest.reflection.IsInstanceTest | |
* @test xp://net.xp_framework.unittest.reflection.ClassCastingTest | |
* @purpose Reflection | |
*/ | |
class XPClass extends Type { | |
protected $_class= NULL; | |
public $_reflect= NULL; | |
private static $DECLARING_CLASS_BUG= FALSE; | |
static function __static() { | |
self::$DECLARING_CLASS_BUG= version_compare(PHP_VERSION, '5.2.10', 'lt'); | |
} | |
/** | |
* Constructor | |
* | |
* @param var ref either a class name, a ReflectionClass instance or an object | |
* @throws lang.IllegalStateException | |
*/ | |
public function __construct($ref) { | |
if ($ref instanceof ReflectionClass) { | |
$this->_reflect= $ref; | |
$this->_class= $ref->getName(); | |
} else if (is_object($ref)) { | |
$this->_reflect= new ReflectionClass($ref); | |
$this->_class= get_class($ref); | |
} else { | |
try { | |
$this->_reflect= new ReflectionClass((string)$ref); | |
} catch (ReflectionException $e) { | |
throw new IllegalStateException($e->getMessage()); | |
} | |
$this->_class= $ref; | |
} | |
parent::__construct(xp::nameOf($this->_class)); | |
} | |
/** | |
* Returns simple name | |
* | |
* @return string | |
*/ | |
public function getSimpleName() { | |
return FALSE === ($p= strrpos(substr($this->name, 0, strcspn($this->name, '<')), '.')) | |
? $this->name // Already unqualified | |
: substr($this->name, $p+ 1) // Full name | |
; | |
} | |
/** | |
* Retrieves the package associated with this class | |
* | |
* @return lang.reflect.Package | |
*/ | |
public function getPackage() { | |
return Package::forName(substr($this->name, 0, strrpos($this->name, '.'))); | |
} | |
/** | |
* Creates a new instance of the class represented by this Class object. | |
* The class is instantiated as if by a new expression with an empty argument list. | |
* | |
* Example | |
* ======= | |
* <code> | |
* try { | |
* $o= XPClass::forName($name)->newInstance(); | |
* } catch (ClassNotFoundException $e) { | |
* // handle it! | |
* } | |
* </code> | |
* | |
* Example (passing arguments) | |
* =========================== | |
* <code> | |
* try { | |
* $o= XPClass::forName('peer.Socket')->newInstance('localhost', 6100); | |
* } catch (ClassNotFoundException $e) { | |
* // handle it! | |
* } | |
* </code> | |
* | |
* @param var* args | |
* @return lang.Object | |
* @throws lang.IllegalAccessException in case this class cannot be instantiated | |
*/ | |
public function newInstance() { | |
if ($this->_reflect->isInterface()) { | |
throw new IllegalAccessException('Cannot instantiate interfaces ('.$this->name.')'); | |
} else if ($this->_reflect->isAbstract()) { | |
throw new IllegalAccessException('Cannot instantiate abstract classes ('.$this->name.')'); | |
} | |
try { | |
if (!$this->hasConstructor()) return $this->_reflect->newInstance(); | |
$args= func_get_args(); | |
return $this->_reflect->newInstanceArgs($args); | |
} catch (ReflectionException $e) { | |
throw new IllegalAccessException($e->getMessage()); | |
} | |
} | |
/** | |
* Gets class methods for this class | |
* | |
* @return lang.reflect.Method[] | |
*/ | |
public function getMethods() { | |
$list= array(); | |
foreach ($this->_reflect->getMethods() as $m) { | |
if (0 == strncmp('__', $m->getName(), 2)) continue; | |
$list[]= new Method($this->_class, $m); | |
} | |
return $list; | |
} | |
/** | |
* Gets class methods declared by this class | |
* | |
* @return lang.reflect.Method[] | |
*/ | |
public function getDeclaredMethods() { | |
$list= array(); | |
if (self::$DECLARING_CLASS_BUG) { | |
foreach ($this->_reflect->getMethods() as $m) { | |
if (0 == strncmp('__', $m->getName(), 2) || $m->getDeclaringClass()->getName() !== $this->_reflect->name) continue; | |
$list[]= new Method($this->_class, $m); | |
} | |
} else { | |
foreach ($this->_reflect->getMethods() as $m) { | |
if (0 == strncmp('__', $m->getName(), 2) || $m->class !== $this->_reflect->name) continue; | |
$list[]= new Method($this->_class, $m); | |
} | |
} | |
return $list; | |
} | |
/** | |
* Gets a method by a specified name. | |
* | |
* @param string name | |
* @return lang.reflect.Method | |
* @see xp://lang.reflect.Method | |
* @throws lang.ElementNotFoundException | |
*/ | |
public function getMethod($name) { | |
if ($this->hasMethod($name)) { | |
return new Method($this->_class, $this->_reflect->getMethod($name)); | |
} | |
raise('lang.ElementNotFoundException', 'No such method "'.$name.'" in class '.$this->name); | |
} | |
/** | |
* Checks whether this class has a method named "$method" or not. | |
* | |
* Note | |
* ==== | |
* Since in PHP, methods are case-insensitive, calling hasMethod('toString') | |
* will provide the same result as hasMethod('tostring') | |
* | |
* @param string method the method's name | |
* @return bool TRUE if method exists | |
*/ | |
public function hasMethod($method) { | |
return ((0 === strncmp('__', $method, 2)) | |
? FALSE | |
: $this->_reflect->hasMethod($method) | |
); | |
} | |
/** | |
* Retrieve if a constructor exists | |
* | |
* @return bool | |
*/ | |
public function hasConstructor() { | |
return $this->_reflect->hasMethod('__construct'); | |
} | |
/** | |
* Retrieves this class' constructor. | |
* | |
* @return lang.reflect.Constructor | |
* @see xp://lang.reflect.Constructor | |
* @throws lang.ElementNotFoundException | |
*/ | |
public function getConstructor() { | |
if ($this->hasConstructor()) { | |
return new Constructor($this->_class, $this->_reflect->getMethod('__construct')); | |
} | |
raise('lang.ElementNotFoundException', 'No constructor in class '.$this->name); | |
} | |
/** | |
* Retrieve a list of all member variables | |
* | |
* @return lang.reflect.Field[] array of field objects | |
*/ | |
public function getFields() { | |
$f= array(); | |
foreach ($this->_reflect->getProperties() as $p) { | |
if ('__id' === $p->name) continue; | |
$f[]= new Field($this->_class, $p); | |
} | |
return $f; | |
} | |
/** | |
* Retrieve a list of member variables declared in this class | |
* | |
* @return lang.reflect.Field[] array of field objects | |
*/ | |
public function getDeclaredFields() { | |
$list= array(); | |
if (self::$DECLARING_CLASS_BUG) { | |
foreach ($this->_reflect->getProperties() as $p) { | |
if ('__id' === $p->name || $p->getDeclaringClass()->getName() !== $this->_reflect->name) continue; | |
$list[]= new Field($this->_class, $p); | |
} | |
} else { | |
foreach ($this->_reflect->getProperties() as $p) { | |
if ('__id' === $p->name || $p->class !== $this->_reflect->name) continue; | |
$list[]= new Field($this->_class, $p); | |
} | |
} | |
return $list; | |
} | |
/** | |
* Retrieve a field by a specified name. | |
* | |
* @param string name | |
* @return lang.reflect.Field | |
* @throws lang.ElementNotFoundException | |
*/ | |
public function getField($name) { | |
if ($this->hasField($name)) { | |
return new Field($this->_class, $this->_reflect->getProperty($name)); | |
} | |
raise('lang.ElementNotFoundException', 'No such field "'.$name.'" in class '.$this->name); | |
} | |
/** | |
* Checks whether this class has a field named "$field" or not. | |
* | |
* @param string field the fields's name | |
* @return bool TRUE if field exists | |
*/ | |
public function hasField($field) { | |
return '__id' == $field ? FALSE : $this->_reflect->hasProperty($field); | |
} | |
/** | |
* Retrieve the parent class's class object. Returns NULL if there | |
* is no parent class. | |
* | |
* @return lang.XPClass class object | |
*/ | |
public function getParentclass() { | |
return ($parent= $this->_reflect->getParentClass()) ? new self($parent) : NULL; | |
} | |
/** | |
* Checks whether this class has a constant named "$constant" or not | |
* | |
* @param string constant | |
* @return bool | |
*/ | |
public function hasConstant($constant) { | |
return $this->_reflect->hasConstant($constant); | |
} | |
/** | |
* Retrieve a constant by a specified name. | |
* | |
* @param string constant | |
* @return var | |
* @throws lang.ElementNotFoundException in case constant does not exist | |
*/ | |
public function getConstant($constant) { | |
if ($this->hasConstant($constant)) { | |
return $this->_reflect->getConstant($constant); | |
} | |
raise('lang.ElementNotFoundException', 'No such constants "'.$constant.'" in class '.$this->name); | |
} | |
/** | |
* Cast a given object to the class represented by this object | |
* | |
* @param lang.Generic expression | |
* @return lang.Generic the given expression | |
* @throws lang.ClassCastException | |
*/ | |
public function cast(Generic $expression= NULL) { | |
if (NULL === $expression) { | |
return xp::null(); | |
} else if (is($this->name, $expression)) { | |
return $expression; | |
} | |
raise('lang.ClassCastException', 'Cannot cast '.xp::typeOf($expression).' to '.$this->name); | |
} | |
/** | |
* Tests whether this class is a subclass of a specified class. | |
* | |
* @param var class either a string or an XPClass object | |
* @return bool | |
*/ | |
public function isSubclassOf($class) { | |
if (!($class instanceof self)) $class= XPClass::forName($class); | |
if ($class->name == $this->name) return FALSE; // Catch bordercase (ZE bug?) | |
return $this->_reflect->isSubclassOf($class->_reflect); | |
} | |
/** | |
* Determines whether the specified object is an instance of this | |
* class. This is the equivalent of the is() core functionality. | |
* | |
* Examples | |
* ======== | |
* <code> | |
* uses('io.File', 'io.TempFile'); | |
* $class= XPClass::forName('io.File'); | |
* | |
* var_dump($class->isInstance(new TempFile())); // TRUE | |
* var_dump($class->isInstance(new File())); // TRUE | |
* var_dump($class->isInstance(new Object())); // FALSE | |
* </code> | |
* | |
* @param var obj | |
* @return bool | |
*/ | |
public function isInstance($obj) { | |
return is($this->name, $obj); | |
} | |
/** | |
* Determines if this XPClass object represents an interface type. | |
* | |
* @return bool | |
*/ | |
public function isInterface() { | |
return $this->_reflect->isInterface(); | |
} | |
/** | |
* Determines if this XPClass object represents an interface type. | |
* | |
* @return bool | |
*/ | |
public function isEnum() { | |
$e= xp::reflect('lang.Enum'); | |
return class_exists($e) && $this->_reflect->isSubclassOf($e); | |
} | |
/** | |
* Retrieve interfaces this class implements | |
* | |
* @return lang.XPClass[] | |
*/ | |
public function getInterfaces() { | |
$r= array(); | |
foreach ($this->_reflect->getInterfaces() as $iface) { | |
$r[]= new self($iface->getName()); | |
} | |
return $r; | |
} | |
/** | |
* Retrieve interfaces this class implements in its declaration | |
* | |
* @return lang.XPClass[] | |
*/ | |
public function getDeclaredInterfaces() { | |
$is= $this->_reflect->getInterfaces(); | |
if ($parent= $this->_reflect->getParentclass()) { | |
$ip= $parent->getInterfaces(); | |
} else { | |
$ip= array(); | |
} | |
$filter= array(); | |
foreach ($is as $iname => $i) { | |
// Parent class implements this interface | |
if (isset($ip[$iname])) { | |
$filter[$iname]= TRUE; | |
continue; | |
} | |
// Interface is implemented because it's the parent of another interface | |
foreach ($i->getInterfaces() as $pname => $p) { | |
if (isset($is[$pname])) $filter[$pname]= TRUE; | |
} | |
} | |
$r= array(); | |
foreach ($is as $iname => $i) { | |
if (!isset($filter[$iname])) $r[]= new self($i); | |
} | |
return $r; | |
} | |
/** | |
* Retrieves the api doc comment for this class. Returns NULL if | |
* no documentation is present. | |
* | |
* @return string | |
*/ | |
public function getComment() { | |
if (!($details= self::detailsForClass($this->name))) return NULL; | |
return $details['class'][DETAIL_COMMENT]; | |
} | |
/** | |
* Retrieves this class' modifiers | |
* | |
* @see xp://lang.reflect.Modifiers | |
* @return int | |
*/ | |
public function getModifiers() { | |
$r= MODIFIER_PUBLIC; | |
// Map PHP reflection modifiers to generic form | |
$m= $this->_reflect->getModifiers(); | |
$m & ReflectionClass::IS_EXPLICIT_ABSTRACT && $r |= MODIFIER_ABSTRACT; | |
$m & ReflectionClass::IS_IMPLICIT_ABSTRACT && $r |= MODIFIER_ABSTRACT; | |
$m & ReflectionClass::IS_FINAL && $r |= MODIFIER_FINAL; | |
return $r; | |
} | |
/** | |
* Check whether an annotation exists | |
* | |
* @param string name | |
* @param string key default NULL | |
* @return bool | |
*/ | |
public function hasAnnotation($name, $key= NULL) { | |
$details= self::detailsForClass($this->name); | |
return $details && ($key | |
? @array_key_exists($key, @$details['class'][DETAIL_ANNOTATIONS][$name]) | |
: @array_key_exists($name, @$details['class'][DETAIL_ANNOTATIONS]) | |
); | |
} | |
/** | |
* Retrieve annotation by name | |
* | |
* @param string name | |
* @param string key default NULL | |
* @return var | |
* @throws lang.ElementNotFoundException | |
*/ | |
public function getAnnotation($name, $key= NULL) { | |
$details= self::detailsForClass($this->name); | |
if (!$details || !($key | |
? @array_key_exists($key, @$details['class'][DETAIL_ANNOTATIONS][$name]) | |
: @array_key_exists($name, @$details['class'][DETAIL_ANNOTATIONS]) | |
)) return raise( | |
'lang.ElementNotFoundException', | |
'Annotation "'.$name.($key ? '.'.$key : '').'" does not exist' | |
); | |
return ($key | |
? $details['class'][DETAIL_ANNOTATIONS][$name][$key] | |
: $details['class'][DETAIL_ANNOTATIONS][$name] | |
); | |
} | |
/** | |
* Retrieve whether a method has annotations | |
* | |
* @return bool | |
*/ | |
public function hasAnnotations() { | |
$details= self::detailsForClass($this->name); | |
return $details ? !empty($details['class'][DETAIL_ANNOTATIONS]) : FALSE; | |
} | |
/** | |
* Retrieve all of a method's annotations | |
* | |
* @return array annotations | |
*/ | |
public function getAnnotations() { | |
$details= self::detailsForClass($this->name); | |
return $details ? $details['class'][DETAIL_ANNOTATIONS] : array(); | |
} | |
/** | |
* Retrieve the class loader a class was loaded with. | |
* | |
* @return lang.IClassLoader | |
*/ | |
public function getClassLoader() { | |
return self::_classLoaderFor($this->name); | |
} | |
/** | |
* Fetch a class' classloader by its name | |
* | |
* @param string name fqcn of class | |
* @return lang.IClassLoader | |
*/ | |
protected static function _classLoaderFor($name) { | |
if (isset(xp::$registry[$l= 'classloader.'.$name])) { | |
sscanf(xp::$registry[$l], '%[^:]://%[^$]', $cl, $argument); | |
return call_user_func(array(xp::reflect($cl), 'instanceFor'), $argument); | |
} | |
return NULL; // Internal class, e.g. | |
} | |
/** | |
* Parses annotation string | |
* | |
* @param string input | |
* @param string context the class name, and optionally the line number. | |
* @return [:var] | |
* @throws lang.ClassFormatException | |
*/ | |
public static function parseAnnotations($input, $context) { | |
ob_start(); | |
$annotations= eval('return array('.($eval= preg_replace( | |
array('/@([a-z_]+),/i', '/@([a-z_]+)\(\'([^\']+)\'\)/ie', '/@([a-z_]+)\(/i', '/(\(|, *)([a-z_]+) *= */i'), | |
array('\'$1\' => NULL,', '"\'$1\' => urldecode(\'".urlencode(\'$2\')."\')"', '\'$1\' => array(', '$1\'$2\' => '), | |
trim($input, "[]# \t\n\r").',' | |
)).');'); | |
$msg= ltrim(ob_get_contents(), ini_get('error_prepend_string')."\r\n\t "); | |
if (FALSE === $annotations || $msg) { | |
ob_end_clean(); | |
xp::gc(); | |
raise('lang.ClassFormatException', 'Parse error: '.$msg.' of "'.addcslashes($eval, "\0..\17").'"'); | |
} | |
ob_end_clean(); | |
return $annotations; | |
} | |
/** | |
* Retrieve details for a specified class. Note: Results from this | |
* method are cached! | |
* | |
* @param string class fully qualified class name | |
* @return array or NULL to indicate no details are available | |
*/ | |
public static function detailsForClass($class) { | |
if (!$class) return NULL; // Border case | |
if (isset(xp::$registry['details.'.$class])) return xp::$registry['details.'.$class]; | |
// Retrieve class' sourcecode | |
$cl= self::_classLoaderFor($class); | |
if (!$cl || !($bytes= $cl->loadClassBytes($class))) return NULL; | |
$details= array(array(), array()); | |
$annotations= array(); | |
$comment= NULL; | |
$members= TRUE; | |
$parsed= ''; | |
$tokens= token_get_all($bytes); | |
for ($i= 0, $s= sizeof($tokens); $i < $s; $i++) { | |
switch ($tokens[$i][0]) { | |
case T_DOC_COMMENT: | |
$comment= $tokens[$i][1]; | |
break; | |
case T_COMMENT: | |
if ('#' === $tokens[$i][1]{0}) { // Annotations | |
if ('[' === $tokens[$i][1]{1}) { | |
$parsed= substr($tokens[$i][1], 2); | |
} else { | |
$parsed.= substr($tokens[$i][1], 1); | |
} | |
if (']' == substr(rtrim($tokens[$i][1]), -1)) { | |
$annotations= self::parseAnnotations( | |
trim($parsed, " \t\n\r"), | |
$class.(isset($tokens[$i][2]) ? ', line '.$tokens[$i][2] : '') | |
); | |
$parsed= ''; | |
} | |
} | |
break; | |
case T_CLASS: | |
case T_INTERFACE: | |
if ('' !== $parsed) raise( | |
'lang.ClassFormatException', | |
'Unterminated annotation "'.addcslashes($parsed, "\0..\17").'" in '.$class.(isset($tokens[$i][2]) ? ', line '.$tokens[$i][2] : '') | |
); | |
$details['class']= array( | |
DETAIL_COMMENT => trim(preg_replace('/\n \* ?/', "\n", "\n".substr( | |
$comment, | |
4, // "/**\n" | |
strpos($comment, '* @')- 2 // position of first details token | |
))), | |
DETAIL_ANNOTATIONS => $annotations | |
); | |
$annotations= array(); | |
$comment= NULL; | |
break; | |
case T_VARIABLE: | |
if (!$members) break; | |
// Have a member variable | |
'' === $parsed || raise('lang.ClassFormatException', 'Unterminated annotation "'.addcslashes($parsed, "\0..\17").'" in '.$class.', line '.(isset($tokens[$i][2]) ? ', line '.$tokens[$i][2] : '')); | |
$name= substr($tokens[$i][1], 1); | |
$details[0][$name]= array( | |
DETAIL_ANNOTATIONS => $annotations | |
); | |
$annotations= array(); | |
break; | |
case T_FUNCTION: | |
'' === $parsed || raise('lang.ClassFormatException', 'Unterminated annotation "'.addcslashes($parsed, "\0..\17").'" in '.$class.', line '.(isset($tokens[$i][2]) ? ', line '.$tokens[$i][2] : '')); | |
$members= FALSE; | |
while (T_STRING !== $tokens[$i][0]) $i++; | |
$m= $tokens[$i][1]; | |
$details[1][$m]= array( | |
DETAIL_ARGUMENTS => array(), | |
DETAIL_RETURNS => 'void', | |
DETAIL_THROWS => array(), | |
DETAIL_COMMENT => trim(preg_replace('/\n \* ?/', "\n", "\n".substr( | |
$comment, | |
4, // "/**\n" | |
strpos($comment, '* @')- 2 // position of first details token | |
))), | |
DETAIL_ANNOTATIONS => $annotations, | |
DETAIL_NAME => $tokens[$i][1] | |
); | |
$matches= NULL; | |
preg_match_all( | |
'/@([a-z]+)\s*([^<\r\n]+<[^>]+>|[^\r\n ]+) ?([^\r\n ]+)?/', | |
$comment, | |
$matches, | |
PREG_SET_ORDER | |
); | |
$annotations= array(); | |
$comment= NULL; | |
foreach ($matches as $match) { | |
switch ($match[1]) { | |
case 'param': | |
$details[1][$m][DETAIL_ARGUMENTS][]= $match[2]; | |
break; | |
case 'return': | |
$details[1][$m][DETAIL_RETURNS]= $match[2]; | |
break; | |
case 'throws': | |
$details[1][$m][DETAIL_THROWS][]= $match[2]; | |
break; | |
} | |
} | |
break; | |
default: | |
// Empty | |
} | |
} | |
// Return details for specified class | |
xp::$registry['details.'.$class]= $details; | |
return $details; | |
} | |
/** | |
* Retrieve details for a specified class and method. Note: Results | |
* from this method are cached! | |
* | |
* @param string class unqualified class name | |
* @param string method | |
* @return array or NULL if not available | |
*/ | |
public static function detailsForMethod($class, $method) { | |
$details= self::detailsForClass(xp::nameOf($class)); | |
return $details ? (isset($details[1][$method]) ? $details[1][$method] : NULL) : NULL; | |
} | |
/** | |
* Retrieve details for a specified class and field. Note: Results | |
* from this method are cached! | |
* | |
* @param string class unqualified class name | |
* @param string method | |
* @return array or NULL if not available | |
*/ | |
public static function detailsForField($class, $field) { | |
$details= self::detailsForClass(xp::nameOf($class)); | |
return $details ? (isset($details[0][$field]) ? $details[0][$field] : NULL) : NULL; | |
} | |
/** | |
* Creates a generic type | |
* | |
* @param lang.XPClass self | |
* @param lang.Type[] arguments | |
* @return lang.XPClass | |
*/ | |
public static function createGenericType(XPClass $self, array $arguments) { | |
// Verify | |
if (!$self->isGenericDefinition()) { | |
throw new IllegalStateException('Class '.$self->name.' is not a generic definition'); | |
} | |
$components= $self->genericComponents(); | |
$cs= sizeof($components); | |
if ($cs != sizeof($arguments)) { | |
throw new IllegalArgumentException(sprintf( | |
'Class %s expects %d component(s) <%s>, %d argument(s) given', | |
$self->name, | |
$cs, | |
implode(', ', $components), | |
sizeof($arguments) | |
)); | |
} | |
// Compose names | |
$cn= $qc= ''; | |
foreach ($arguments as $typearg) { | |
$cn.= '¸'.$typearg->literal(); | |
$qc.= ','.$typearg->getName(); | |
} | |
$name= xp::reflect($self->name).'··'.substr($cn, 1); | |
$qname= $self->name.'<'.substr($qc, 1).'>'; | |
// Create class if it doesn't exist yet | |
if (!class_exists($name, FALSE) && !interface_exists($name, FALSE)) { | |
$meta= isset(xp::$registry['details.'.$self->name]) ? xp::$registry['details.'.$self->name] : array( | |
'class' => NULL, | |
0 => array(), | |
1 => array() | |
); | |
// Parse placeholders into a lookup map | |
$placeholders= array(); | |
foreach ($components as $i => $component) { | |
$placeholders[$component]= $arguments[$i]->getName(); | |
} | |
// Work on sourcecode | |
$cl= self::_classLoaderFor($self->name); | |
if (!$cl || !($bytes= $cl->loadClassBytes($self->name))) { | |
throw new IllegalStateException($self->name); | |
} | |
// Replace source | |
$src= ''; | |
$comment= NULL; | |
$annotations= array(); | |
$annotation= NULL; | |
$matches= array(); | |
$state= array(0); | |
$counter= 0; | |
$tokens= token_get_all($bytes); | |
for ($i= 0, $s= sizeof($tokens); $i < $s; $i++) { | |
if (T_COMMENT === $tokens[$i][0] && '#' === $tokens[$i][1]{0}) { | |
$annotations= eval('return array('.preg_replace( | |
array('/@([a-z_]+),/i', '/@([a-z_]+)\(\'([^\']+)\'\)/ie', '/@([a-z_]+)\(/i', '/([^a-z_@])([a-z_]+) *= */i'), | |
array('\'$1\' => NULL,', '"\'$1\' => urldecode(\'".urlencode(\'$2\')."\')"', '\'$1\' => array(', '$1\'$2\' => '), | |
trim($tokens[$i][1], "[]# \t\n\r").',' | |
).');'); | |
continue; | |
} else if (T_DOC_COMMENT === $tokens[$i][0]) { | |
$matches= NULL; | |
$comment= trim(preg_replace('/\n\s+\* ?/', "\n", "\n".substr( | |
$tokens[$i][1], | |
4, // "/**\n" | |
strpos($tokens[$i][1], '* @')- 2 // position of first details token | |
))); | |
preg_match_all( | |
'/@([a-z]+)\s*([^<\r\n]+<[^>]+>|[^\r\n ]+) ?([^\r\n ]+)?/', | |
$tokens[$i][1], | |
$matches, | |
PREG_SET_ORDER | |
); | |
} | |
if (0 === $state[0]) { | |
if (T_ABSTRACT === $tokens[$i][0] || T_FINAL === $tokens[$i][0]) { | |
$src.= $tokens[$i][1].' '; | |
} else if (T_CLASS === $tokens[$i][0] || T_INTERFACE === $tokens[$i][0]) { | |
if (NULL === $meta['class']) { | |
$meta['class']= array(DETAIL_COMMENT => $comment, DETAIL_ANNOTATIONS => $annotations); | |
} | |
$meta['class'][DETAIL_GENERIC]= array($self->name, $arguments); | |
$src.= $tokens[$i][1].' '.$name; | |
array_unshift($state, $tokens[$i][0]); | |
} | |
continue; | |
} else if (T_CLASS === $state[0]) { | |
if (T_EXTENDS === $tokens[$i][0]) { | |
if (isset($annotations['generic']['parent'])) { | |
$xargs= array(); | |
foreach (explode(',', $annotations['generic']['parent']) as $j => $placeholder) { | |
$xargs[]= Type::forName(strtr(ltrim($placeholder), $placeholders)); | |
} | |
$src.= ' extends '.self::createGenericType($self->getParentClass(), $xargs)->literal(); | |
} else { | |
$src.= ' extends '.$tokens[$i+ 2][1]; | |
} | |
} else if (T_IMPLEMENTS === $tokens[$i][0]) { | |
$src.= ' implements'; | |
$counter= 0; | |
$annotation= @$annotations['generic']['implements']; | |
array_unshift($state, 5); | |
} else if ('{' === $tokens[$i][0]) { | |
array_shift($state); | |
array_unshift($state, 1); | |
$src.= ' {'; | |
} | |
continue; | |
} else if (T_INTERFACE === $state[0]) { | |
if (T_EXTENDS === $tokens[$i][0]) { | |
$src.= ' extends'; | |
$counter= 0; | |
$annotation= @$annotations['generic']['extends']; | |
array_unshift($state, 5); | |
} else if ('{' === $tokens[$i][0]) { | |
array_shift($state); | |
array_unshift($state, 1); | |
$src.= ' {'; | |
} | |
continue; | |
} else if (1 === $state[0]) { // Class body | |
if (T_FUNCTION === $tokens[$i][0]) { | |
$braces= 0; | |
$parameters= array(); | |
array_unshift($state, 3); | |
array_unshift($state, 2); | |
$m= $tokens[$i+ 2][1]; | |
if (isset($meta[1][$m])) { | |
$annotations= $meta[1][$m][DETAIL_ANNOTATIONS]; | |
} else { | |
$meta[1][$m]= array( | |
DETAIL_ARGUMENTS => array(), | |
DETAIL_RETURNS => 'void', | |
DETAIL_THROWS => array(), | |
DETAIL_COMMENT => $comment, | |
DETAIL_ANNOTATIONS => $annotations, | |
DETAIL_NAME => $m | |
); | |
foreach ($matches as $match) { | |
switch ($match[1]) { | |
case 'param': $meta[1][$m][DETAIL_ARGUMENTS][]= $match[2]; break; | |
case 'return': $meta[1][$m][DETAIL_RETURNS]= $match[2]; break; | |
case 'throws': $meta[1][$m][DETAIL_THROWS][]= $match[2]; break; | |
} | |
} | |
} | |
} else if ('}' === $tokens[$i][0]) { | |
$src.= '}'; | |
break; | |
} else if (T_CLOSE_TAG === $tokens[$i][0]) { | |
break; | |
} | |
} else if (2 === $state[0]) { // Method declaration | |
if ('(' === $tokens[$i][0]) { | |
$braces++; | |
} else if (')' === $tokens[$i][0]) { | |
$braces--; | |
if (0 === $braces) array_shift($state); | |
} else if (T_VARIABLE === $tokens[$i][0]) { | |
$parameters[]= $tokens[$i][1]; | |
} | |
} else if (3 === $state[0]) { // Method body | |
if (';' === $tokens[$i][0]) { | |
// Abstract method | |
array_shift($state); | |
} else if ('{' === $tokens[$i][0]) { | |
$braces= 1; | |
array_shift($state); | |
array_unshift($state, 4); | |
$src.= '{'; | |
if (isset($annotations['generic']['return'])) { | |
$meta[1][$m][DETAIL_RETURNS]= strtr($annotations['generic']['return'], $placeholders); | |
} | |
if (isset($annotations['generic']['params'])) { | |
$generic= array(); | |
foreach (explode(',', $annotations['generic']['params']) as $j => $placeholder) { | |
if ('' === ($replaced= strtr(ltrim($placeholder), $placeholders))) { | |
$generic[$j]= NULL; | |
} else { | |
$meta[1][$m][DETAIL_ARGUMENTS][$j]= $replaced; | |
$generic[$j]= $replaced; | |
} | |
} | |
foreach ($generic as $j => $type) { | |
if (NULL === $type) { | |
continue; | |
} else if ('...' === substr($type, -3)) { | |
$src.= $j ? '$·args= array_slice(func_get_args(), '.$j.');' : '$·args= func_get_args();'; | |
$src.= ( | |
' if (!is(\''.substr($generic[$j], 0, -3).'[]\', $·args)) throw new IllegalArgumentException('. | |
'"Vararg '.($j + 1).' passed to ".__METHOD__."'. | |
' must be of '.$type.', ".xp::stringOf($·args)." given"'. | |
');' | |
); | |
} else { | |
$src.= ( | |
' if (!is(\''.$generic[$j].'\', '.$parameters[$j].')) throw new IllegalArgumentException('. | |
'"Argument '.($j + 1).' passed to ".__METHOD__."'. | |
' must be of '.$type.', ".xp::typeOf('.$parameters[$j].')." given"'. | |
');' | |
); | |
} | |
} | |
$annotations= array(); | |
} | |
unset($meta[1][$m][DETAIL_ANNOTATIONS]['generic']); | |
continue; | |
} | |
} else if (4 === $state[0]) { // Method body | |
if ('{' === $tokens[$i][0]) { | |
$braces++; | |
} else if ('}' === $tokens[$i][0]) { | |
$braces--; | |
if (0 === $braces) array_shift($state); | |
} | |
} else if (5 === $state[0]) { // Implements (class), Extends (interface) | |
if (T_STRING === $tokens[$i][0]) { | |
if (isset($annotation[$counter])) { | |
$iargs= array(); | |
foreach (explode(',', $annotation[$counter]) as $j => $placeholder) { | |
$iargs[]= Type::forName(strtr(ltrim($placeholder), $placeholders)); | |
} | |
$src.= self::createGenericType(new XPClass(new ReflectionClass($tokens[$i][1])), $iargs)->literal(); | |
} else { | |
$src.= $tokens[$i][1]; | |
} | |
$counter++; | |
continue; | |
} else if ('{' === $tokens[$i][0]) { | |
array_shift($state); | |
array_unshift($state, 1); | |
} | |
} | |
$src.= is_array($tokens[$i]) ? $tokens[$i][1] : $tokens[$i]; | |
} | |
// Create class | |
// DEBUG fputs(STDERR, "@* ".substr($src, 0, strpos($src, '{'))." -> $qname\n"); | |
eval($src); | |
method_exists($name, '__static') && call_user_func(array($name, '__static')); | |
unset($meta['class'][DETAIL_ANNOTATIONS]['generic']); | |
xp::$registry['details.'.$qname]= $meta; | |
xp::$registry['class.'.$name]= $qname; | |
} | |
return new XPClass(new ReflectionClass($name)); | |
} | |
/** | |
* Reflectively creates a new type | |
* | |
* @param lang.Type[] arguments | |
* @return lang.XPClass | |
* @throws lang.IllegalStateException if this class is not a generic definition | |
* @throws lang.IllegalArgumentException if number of arguments does not match components | |
*/ | |
public function newGenericType(array $arguments) { | |
return self::createGenericType($this, $arguments); | |
} | |
/** | |
* Returns generic type components | |
* | |
* @return string[] | |
* @throws lang.IllegalStateException if this class is not a generic definition | |
*/ | |
public function genericComponents() { | |
if (!$this->isGenericDefinition()) { | |
throw new IllegalStateException('Class '.$this->name.' is not a generic definition'); | |
} | |
$components= array(); | |
foreach (explode(',', $this->getAnnotation('generic', 'self')) as $name) { | |
$components[]= ltrim($name); | |
} | |
return $components; | |
} | |
/** | |
* Returns whether this class is a generic definition | |
* | |
* @return bool | |
*/ | |
public function isGenericDefinition() { | |
return $this->hasAnnotation('generic', 'self'); | |
} | |
/** | |
* Returns generic type definition | |
* | |
* @return lang.XPClass | |
* @throws lang.IllegalStateException if this class is not a generic | |
*/ | |
public function genericDefinition() { | |
if (!($details= self::detailsForClass($this->name))) return NULL; | |
if (!isset($details['class'][DETAIL_GENERIC])) { | |
throw new IllegalStateException('Class '.$this->name.' is not generic'); | |
} | |
return XPClass::forName($details['class'][DETAIL_GENERIC][0]); | |
} | |
/** | |
* Returns generic type arguments | |
* | |
* @return lang.Type[] | |
* @throws lang.IllegalStateException if this class is not a generic | |
*/ | |
public function genericArguments() { | |
if (!($details= self::detailsForClass($this->name))) return NULL; | |
if (!isset($details['class'][DETAIL_GENERIC])) { | |
throw new IllegalStateException('Class '.$this->name.' is not generic'); | |
} | |
if (!isset($details['class'][DETAIL_GENERIC][1])) { | |
$details['class'][DETAIL_GENERIC][1]= array_map( | |
array(xp::reflect('lang.Type'), 'forName'), | |
$details['class'][DETAIL_GENERIC][2] | |
); | |
unset($details['class'][DETAIL_GENERIC][2]); | |
} | |
return $details['class'][DETAIL_GENERIC][1]; | |
} | |
/** | |
* Returns whether this class is generic | |
* | |
* @return bool | |
*/ | |
public function isGeneric() { | |
if (!($details= self::detailsForClass($this->name))) return FALSE; | |
return isset($details['class'][DETAIL_GENERIC]); | |
} | |
/** | |
* Returns the XPClass object associated with the class with the given | |
* string name. Uses the default classloader if none is specified. | |
* | |
* @param string name - e.g. "io.File", "rdbms.mysql.MySQL" | |
* @param lang.IClassLoader classloader default NULL | |
* @return lang.XPClass class object | |
* @throws lang.ClassNotFoundException when there is no such class | |
*/ | |
public static function forName($name, IClassLoader $classloader= NULL) { | |
if (NULL === $classloader) { | |
$classloader= ClassLoader::getDefault(); | |
} | |
return $classloader->loadClass((string)$name); | |
} | |
/** | |
* Returns type literal | |
* | |
* @return string | |
*/ | |
public function literal() { | |
return xp::reflect($this->name); | |
} | |
/** | |
* Returns an array containing class objects representing all the | |
* public classes | |
* | |
* @return lang.XPClass[] class objects | |
*/ | |
public static function getClasses() { | |
$ret= array(); | |
foreach (get_declared_classes() as $name) { | |
if (isset(xp::$registry['class.'.$name])) $ret[]= new self($name); | |
} | |
return $ret; | |
} | |
} | |
?> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* This class is part of the XP framework | |
* | |
* $Id$ | |
*/ | |
uses( | |
'lang.Type', | |
'lang.reflect.Method', | |
'lang.reflect.Field', | |
'lang.reflect.Constructor', | |
'lang.reflect.Modifiers', | |
'lang.reflect.Package' | |
); | |
define('DETAIL_ARGUMENTS', 1); | |
define('DETAIL_RETURNS', 2); | |
define('DETAIL_THROWS', 3); | |
define('DETAIL_COMMENT', 4); | |
define('DETAIL_ANNOTATIONS', 5); | |
define('DETAIL_NAME', 6); | |
define('DETAIL_GENERIC', 7); | |
/** | |
* Represents classes. Every instance of an XP class has an method | |
* called getClass() which returns an instance of this class. | |
* | |
* Warning | |
* ======= | |
* Do not construct this class publicly, instead use either the | |
* $o->getClass() syntax or the static method | |
* $class= XPClass::forName('fully.qualified.Name') | |
* | |
* Examples | |
* ======== | |
* To retrieve the fully qualified name of a class, use this: | |
* <code> | |
* $o= new File('...'); | |
* echo 'The class name for $o is '.$o->getClass()->getName(); | |
* </code> | |
* | |
* Create an instance of a class: | |
* <code> | |
* $instance= XPClass::forName('util.Binford')->newInstance(); | |
* </code> | |
* | |
* Invoke a method by its name: | |
* <code> | |
* try { | |
* $instance->getClass()->getMethod('connect')->invoke($instance); | |
* } catch (TargetInvocationException $e) { | |
* $e->getCause()->printStackTrace(); | |
* } | |
* </code> | |
* | |
* @see xp://lang.Object#getClass | |
* @see xp://lang.XPClass#forName | |
* @test xp://net.xp_framework.unittest.reflection.ReflectionTest | |
* @test xp://net.xp_framework.unittest.reflection.ClassDetailsTest | |
* @test xp://net.xp_framework.unittest.reflection.IsInstanceTest | |
* @test xp://net.xp_framework.unittest.reflection.ClassCastingTest | |
* @purpose Reflection | |
*/ | |
class XPClass extends Type { | |
protected $_class= NULL; | |
public $_reflect= NULL; | |
private static $DECLARING_CLASS_BUG= FALSE; | |
static function __static() { | |
self::$DECLARING_CLASS_BUG= version_compare(PHP_VERSION, '5.2.10', 'lt'); | |
} | |
/** | |
* Constructor | |
* | |
* @param var ref either a class name, a ReflectionClass instance or an object | |
* @throws lang.IllegalStateException | |
*/ | |
public function __construct($ref) { | |
if ($ref instanceof ReflectionClass) { | |
$this->_reflect= $ref; | |
$this->_class= $ref->getName(); | |
} else if (is_object($ref)) { | |
$this->_reflect= new ReflectionClass($ref); | |
$this->_class= get_class($ref); | |
} else { | |
try { | |
$this->_reflect= new ReflectionClass((string)$ref); | |
} catch (ReflectionException $e) { | |
throw new IllegalStateException($e->getMessage()); | |
} | |
$this->_class= $ref; | |
} | |
parent::__construct(xp::nameOf($this->_class)); | |
} | |
/** | |
* Returns simple name | |
* | |
* @return string | |
*/ | |
public function getSimpleName() { | |
return FALSE === ($p= strrpos(substr($this->name, 0, strcspn($this->name, '<')), '.')) | |
? $this->name // Already unqualified | |
: substr($this->name, $p+ 1) // Full name | |
; | |
} | |
/** | |
* Retrieves the package associated with this class | |
* | |
* @return lang.reflect.Package | |
*/ | |
public function getPackage() { | |
return Package::forName(substr($this->name, 0, strrpos($this->name, '.'))); | |
} | |
/** | |
* Creates a new instance of the class represented by this Class object. | |
* The class is instantiated as if by a new expression with an empty argument list. | |
* | |
* Example | |
* ======= | |
* <code> | |
* try { | |
* $o= XPClass::forName($name)->newInstance(); | |
* } catch (ClassNotFoundException $e) { | |
* // handle it! | |
* } | |
* </code> | |
* | |
* Example (passing arguments) | |
* =========================== | |
* <code> | |
* try { | |
* $o= XPClass::forName('peer.Socket')->newInstance('localhost', 6100); | |
* } catch (ClassNotFoundException $e) { | |
* // handle it! | |
* } | |
* </code> | |
* | |
* @param var* args | |
* @return lang.Object | |
* @throws lang.IllegalAccessException in case this class cannot be instantiated | |
*/ | |
public function newInstance() { | |
if ($this->_reflect->isInterface()) { | |
throw new IllegalAccessException('Cannot instantiate interfaces ('.$this->name.')'); | |
} else if ($this->_reflect->isAbstract()) { | |
throw new IllegalAccessException('Cannot instantiate abstract classes ('.$this->name.')'); | |
} | |
try { | |
if (!$this->hasConstructor()) return $this->_reflect->newInstance(); | |
$args= func_get_args(); | |
return $this->_reflect->newInstanceArgs($args); | |
} catch (ReflectionException $e) { | |
throw new IllegalAccessException($e->getMessage()); | |
} | |
} | |
/** | |
* Gets class methods for this class | |
* | |
* @return lang.reflect.Method[] | |
*/ | |
public function getMethods() { | |
$list= array(); | |
foreach ($this->_reflect->getMethods() as $m) { | |
if (0 == strncmp('__', $m->getName(), 2)) continue; | |
$list[]= new Method($this->_class, $m); | |
} | |
return $list; | |
} | |
/** | |
* Gets class methods declared by this class | |
* | |
* @return lang.reflect.Method[] | |
*/ | |
public function getDeclaredMethods() { | |
$list= array(); | |
if (self::$DECLARING_CLASS_BUG) { | |
foreach ($this->_reflect->getMethods() as $m) { | |
if (0 == strncmp('__', $m->getName(), 2) || $m->getDeclaringClass()->getName() !== $this->_reflect->name) continue; | |
$list[]= new Method($this->_class, $m); | |
} | |
} else { | |
foreach ($this->_reflect->getMethods() as $m) { | |
if (0 == strncmp('__', $m->getName(), 2) || $m->class !== $this->_reflect->name) continue; | |
$list[]= new Method($this->_class, $m); | |
} | |
} | |
return $list; | |
} | |
/** | |
* Gets a method by a specified name. | |
* | |
* @param string name | |
* @return lang.reflect.Method | |
* @see xp://lang.reflect.Method | |
* @throws lang.ElementNotFoundException | |
*/ | |
public function getMethod($name) { | |
if ($this->hasMethod($name)) { | |
return new Method($this->_class, $this->_reflect->getMethod($name)); | |
} | |
raise('lang.ElementNotFoundException', 'No such method "'.$name.'" in class '.$this->name); | |
} | |
/** | |
* Checks whether this class has a method named "$method" or not. | |
* | |
* Note | |
* ==== | |
* Since in PHP, methods are case-insensitive, calling hasMethod('toString') | |
* will provide the same result as hasMethod('tostring') | |
* | |
* @param string method the method's name | |
* @return bool TRUE if method exists | |
*/ | |
public function hasMethod($method) { | |
return ((0 === strncmp('__', $method, 2)) | |
? FALSE | |
: $this->_reflect->hasMethod($method) | |
); | |
} | |
/** | |
* Retrieve if a constructor exists | |
* | |
* @return bool | |
*/ | |
public function hasConstructor() { | |
return $this->_reflect->hasMethod('__construct'); | |
} | |
/** | |
* Retrieves this class' constructor. | |
* | |
* @return lang.reflect.Constructor | |
* @see xp://lang.reflect.Constructor | |
* @throws lang.ElementNotFoundException | |
*/ | |
public function getConstructor() { | |
if ($this->hasConstructor()) { | |
return new Constructor($this->_class, $this->_reflect->getMethod('__construct')); | |
} | |
raise('lang.ElementNotFoundException', 'No constructor in class '.$this->name); | |
} | |
/** | |
* Retrieve a list of all member variables | |
* | |
* @return lang.reflect.Field[] array of field objects | |
*/ | |
public function getFields() { | |
$f= array(); | |
foreach ($this->_reflect->getProperties() as $p) { | |
if ('__id' === $p->name) continue; | |
$f[]= new Field($this->_class, $p); | |
} | |
return $f; | |
} | |
/** | |
* Retrieve a list of member variables declared in this class | |
* | |
* @return lang.reflect.Field[] array of field objects | |
*/ | |
public function getDeclaredFields() { | |
$list= array(); | |
if (self::$DECLARING_CLASS_BUG) { | |
foreach ($this->_reflect->getProperties() as $p) { | |
if ('__id' === $p->name || $p->getDeclaringClass()->getName() !== $this->_reflect->name) continue; | |
$list[]= new Field($this->_class, $p); | |
} | |
} else { | |
foreach ($this->_reflect->getProperties() as $p) { | |
if ('__id' === $p->name || $p->class !== $this->_reflect->name) continue; | |
$list[]= new Field($this->_class, $p); | |
} | |
} | |
return $list; | |
} | |
/** | |
* Retrieve a field by a specified name. | |
* | |
* @param string name | |
* @return lang.reflect.Field | |
* @throws lang.ElementNotFoundException | |
*/ | |
public function getField($name) { | |
if ($this->hasField($name)) { | |
return new Field($this->_class, $this->_reflect->getProperty($name)); | |
} | |
raise('lang.ElementNotFoundException', 'No such field "'.$name.'" in class '.$this->name); | |
} | |
/** | |
* Checks whether this class has a field named "$field" or not. | |
* | |
* @param string field the fields's name | |
* @return bool TRUE if field exists | |
*/ | |
public function hasField($field) { | |
return '__id' == $field ? FALSE : $this->_reflect->hasProperty($field); | |
} | |
/** | |
* Retrieve the parent class's class object. Returns NULL if there | |
* is no parent class. | |
* | |
* @return lang.XPClass class object | |
*/ | |
public function getParentclass() { | |
return ($parent= $this->_reflect->getParentClass()) ? new self($parent) : NULL; | |
} | |
/** | |
* Checks whether this class has a constant named "$constant" or not | |
* | |
* @param string constant | |
* @return bool | |
*/ | |
public function hasConstant($constant) { | |
return $this->_reflect->hasConstant($constant); | |
} | |
/** | |
* Retrieve a constant by a specified name. | |
* | |
* @param string constant | |
* @return var | |
* @throws lang.ElementNotFoundException in case constant does not exist | |
*/ | |
public function getConstant($constant) { | |
if ($this->hasConstant($constant)) { | |
return $this->_reflect->getConstant($constant); | |
} | |
raise('lang.ElementNotFoundException', 'No such constants "'.$constant.'" in class '.$this->name); | |
} | |
/** | |
* Cast a given object to the class represented by this object | |
* | |
* @param lang.Generic expression | |
* @return lang.Generic the given expression | |
* @throws lang.ClassCastException | |
*/ | |
public function cast(Generic $expression= NULL) { | |
if (NULL === $expression) { | |
return xp::null(); | |
} else if (is($this->name, $expression)) { | |
return $expression; | |
} | |
raise('lang.ClassCastException', 'Cannot cast '.xp::typeOf($expression).' to '.$this->name); | |
} | |
/** | |
* Tests whether this class is a subclass of a specified class. | |
* | |
* @param var class either a string or an XPClass object | |
* @return bool | |
*/ | |
public function isSubclassOf($class) { | |
if (!($class instanceof self)) $class= XPClass::forName($class); | |
if ($class->name == $this->name) return FALSE; // Catch bordercase (ZE bug?) | |
return $this->_reflect->isSubclassOf($class->_reflect); | |
} | |
/** | |
* Determines whether the specified object is an instance of this | |
* class. This is the equivalent of the is() core functionality. | |
* | |
* Examples | |
* ======== | |
* <code> | |
* uses('io.File', 'io.TempFile'); | |
* $class= XPClass::forName('io.File'); | |
* | |
* var_dump($class->isInstance(new TempFile())); // TRUE | |
* var_dump($class->isInstance(new File())); // TRUE | |
* var_dump($class->isInstance(new Object())); // FALSE | |
* </code> | |
* | |
* @param var obj | |
* @return bool | |
*/ | |
public function isInstance($obj) { | |
return is($this->name, $obj); | |
} | |
/** | |
* Determines if this XPClass object represents an interface type. | |
* | |
* @return bool | |
*/ | |
public function isInterface() { | |
return $this->_reflect->isInterface(); | |
} | |
/** | |
* Determines if this XPClass object represents an interface type. | |
* | |
* @return bool | |
*/ | |
public function isEnum() { | |
$e= xp::reflect('lang.Enum'); | |
return class_exists($e) && $this->_reflect->isSubclassOf($e); | |
} | |
/** | |
* Retrieve interfaces this class implements | |
* | |
* @return lang.XPClass[] | |
*/ | |
public function getInterfaces() { | |
$r= array(); | |
foreach ($this->_reflect->getInterfaces() as $iface) { | |
$r[]= new self($iface->getName()); | |
} | |
return $r; | |
} | |
/** | |
* Retrieve interfaces this class implements in its declaration | |
* | |
* @return lang.XPClass[] | |
*/ | |
public function getDeclaredInterfaces() { | |
$is= $this->_reflect->getInterfaces(); | |
if ($parent= $this->_reflect->getParentclass()) { | |
$ip= $parent->getInterfaces(); | |
} else { | |
$ip= array(); | |
} | |
$filter= array(); | |
foreach ($is as $iname => $i) { | |
// Parent class implements this interface | |
if (isset($ip[$iname])) { | |
$filter[$iname]= TRUE; | |
continue; | |
} | |
// Interface is implemented because it's the parent of another interface | |
foreach ($i->getInterfaces() as $pname => $p) { | |
if (isset($is[$pname])) $filter[$pname]= TRUE; | |
} | |
} | |
$r= array(); | |
foreach ($is as $iname => $i) { | |
if (!isset($filter[$iname])) $r[]= new self($i); | |
} | |
return $r; | |
} | |
/** | |
* Retrieves the api doc comment for this class. Returns NULL if | |
* no documentation is present. | |
* | |
* @return string | |
*/ | |
public function getComment() { | |
if (!($details= self::detailsForClass($this->name))) return NULL; | |
return $details['class'][DETAIL_COMMENT]; | |
} | |
/** | |
* Retrieves this class' modifiers | |
* | |
* @see xp://lang.reflect.Modifiers | |
* @return int | |
*/ | |
public function getModifiers() { | |
$r= MODIFIER_PUBLIC; | |
// Map PHP reflection modifiers to generic form | |
$m= $this->_reflect->getModifiers(); | |
$m & ReflectionClass::IS_EXPLICIT_ABSTRACT && $r |= MODIFIER_ABSTRACT; | |
$m & ReflectionClass::IS_IMPLICIT_ABSTRACT && $r |= MODIFIER_ABSTRACT; | |
$m & ReflectionClass::IS_FINAL && $r |= MODIFIER_FINAL; | |
return $r; | |
} | |
/** | |
* Check whether an annotation exists | |
* | |
* @param string name | |
* @param string key default NULL | |
* @return bool | |
*/ | |
public function hasAnnotation($name, $key= NULL) { | |
$details= self::detailsForClass($this->name); | |
return $details && ($key | |
? @array_key_exists($key, @$details['class'][DETAIL_ANNOTATIONS][$name]) | |
: @array_key_exists($name, @$details['class'][DETAIL_ANNOTATIONS]) | |
); | |
} | |
/** | |
* Retrieve annotation by name | |
* | |
* @param string name | |
* @param string key default NULL | |
* @return var | |
* @throws lang.ElementNotFoundException | |
*/ | |
public function getAnnotation($name, $key= NULL) { | |
$details= self::detailsForClass($this->name); | |
if (!$details || !($key | |
? @array_key_exists($key, @$details['class'][DETAIL_ANNOTATIONS][$name]) | |
: @array_key_exists($name, @$details['class'][DETAIL_ANNOTATIONS]) | |
)) return raise( | |
'lang.ElementNotFoundException', | |
'Annotation "'.$name.($key ? '.'.$key : '').'" does not exist' | |
); | |
return ($key | |
? $details['class'][DETAIL_ANNOTATIONS][$name][$key] | |
: $details['class'][DETAIL_ANNOTATIONS][$name] | |
); | |
} | |
/** | |
* Retrieve whether a method has annotations | |
* | |
* @return bool | |
*/ | |
public function hasAnnotations() { | |
$details= self::detailsForClass($this->name); | |
return $details ? !empty($details['class'][DETAIL_ANNOTATIONS]) : FALSE; | |
} | |
/** | |
* Retrieve all of a method's annotations | |
* | |
* @return array annotations | |
*/ | |
public function getAnnotations() { | |
$details= self::detailsForClass($this->name); | |
return $details ? $details['class'][DETAIL_ANNOTATIONS] : array(); | |
} | |
/** | |
* Retrieve the class loader a class was loaded with. | |
* | |
* @return lang.IClassLoader | |
*/ | |
public function getClassLoader() { | |
return self::_classLoaderFor($this->name); | |
} | |
/** | |
* Fetch a class' classloader by its name | |
* | |
* @param string name fqcn of class | |
* @return lang.IClassLoader | |
*/ | |
protected static function _classLoaderFor($name) { | |
if (isset(xp::$registry[$l= 'classloader.'.$name])) { | |
sscanf(xp::$registry[$l], '%[^:]://%[^$]', $cl, $argument); | |
return call_user_func(array(xp::reflect($cl), 'instanceFor'), $argument); | |
} | |
return NULL; // Internal class, e.g. | |
} | |
/** | |
* Parses annotation string | |
* | |
* @param string input | |
* @param string context the class name, and optionally the line number. | |
* @return [:var] | |
* @throws lang.ClassFormatException | |
*/ | |
public static function parseAnnotations($input, $context) { | |
$offset= 2; // "#[" | |
$annotations= array(); | |
$annotation= $value= NULL; | |
while (1) { | |
$state= $input{$offset}; | |
if ('@' === $state) { | |
$span= strcspn($input, ',(]', $offset); | |
$annotation= substr($input, $offset+ 1, $span- 1); | |
$offset+= $span; | |
} else if (']' === $state) { | |
$annotations[$annotation]= $value; | |
break; | |
} else if ('(' === $state) { | |
$brackets= 1; | |
$voffset= $offset; | |
do { | |
$span= strcspn($input, '()', $offset+ 1); | |
$offset+= $span + 1; | |
$state= $input{$offset}; | |
if (')' === $state) { | |
$brackets--; | |
} else if ('(' === $state) { | |
$brackets++; | |
} else { | |
raise('lang.ClassFormatException', 'Unclosed ('); | |
} | |
} while ($brackets); | |
$value= substr($input, $voffset+ 1, $offset- $voffset- 1); | |
if ("'" === $value{0} || "'" === $value{0}) { | |
$value= substr($value, 1, -1); | |
} else { | |
$eval= preg_replace('/(^|, *)([a-z]+) *= */i', '$1\'$2\' => ', $value); | |
$value= eval('return array('.$eval.');'); | |
if (FALSE === $value) { | |
raise('lang.ClassFormatException', $eval); | |
} | |
} | |
$offset++; // ")" | |
} else if (',' === $state) { | |
$annotations[$annotation]= $value; | |
$annotation= $value= NULL; | |
if (FALSE === ($offset= strpos($input, '@', $offset))) { | |
raise('lang.ClassFormatException', 'Expecting @'); | |
} | |
} else { | |
raise('lang.ClassFormatException', 'Unknown state '.$state); | |
} | |
} | |
return $annotations; | |
} | |
/** | |
* Retrieve details for a specified class. Note: Results from this | |
* method are cached! | |
* | |
* @param string class fully qualified class name | |
* @return array or NULL to indicate no details are available | |
*/ | |
public static function detailsForClass($class) { | |
if (!$class) return NULL; // Border case | |
if (isset(xp::$registry['details.'.$class])) return xp::$registry['details.'.$class]; | |
// Retrieve class' sourcecode | |
$cl= self::_classLoaderFor($class); | |
if (!$cl || !($bytes= $cl->loadClassBytes($class))) return NULL; | |
$details= array(array(), array()); | |
$annotations= array(); | |
$comment= NULL; | |
$members= TRUE; | |
$parsed= ''; | |
$tokens= token_get_all($bytes); | |
for ($i= 0, $s= sizeof($tokens); $i < $s; $i++) { | |
switch ($tokens[$i][0]) { | |
case T_DOC_COMMENT: | |
$comment= $tokens[$i][1]; | |
break; | |
case T_COMMENT: | |
if ('#' === $tokens[$i][1]{0}) { // Annotations | |
if ('[' === $tokens[$i][1]{1}) { | |
$parsed= substr($tokens[$i][1], 2); | |
} else { | |
$parsed.= substr($tokens[$i][1], 1); | |
} | |
if (']' == substr(rtrim($tokens[$i][1]), -1)) { | |
$annotations= self::parseAnnotations( | |
trim($parsed, " \t\n\r"), | |
$class.(isset($tokens[$i][2]) ? ', line '.$tokens[$i][2] : '') | |
); | |
$parsed= ''; | |
} | |
} | |
break; | |
case T_CLASS: | |
case T_INTERFACE: | |
if ('' !== $parsed) raise( | |
'lang.ClassFormatException', | |
'Unterminated annotation "'.addcslashes($parsed, "\0..\17").'" in '.$class.(isset($tokens[$i][2]) ? ', line '.$tokens[$i][2] : '') | |
); | |
$details['class']= array( | |
DETAIL_COMMENT => trim(preg_replace('/\n \* ?/', "\n", "\n".substr( | |
$comment, | |
4, // "/**\n" | |
strpos($comment, '* @')- 2 // position of first details token | |
))), | |
DETAIL_ANNOTATIONS => $annotations | |
); | |
$annotations= array(); | |
$comment= NULL; | |
break; | |
case T_VARIABLE: | |
if (!$members) break; | |
// Have a member variable | |
'' === $parsed || raise('lang.ClassFormatException', 'Unterminated annotation "'.addcslashes($parsed, "\0..\17").'" in '.$class.', line '.(isset($tokens[$i][2]) ? ', line '.$tokens[$i][2] : '')); | |
$name= substr($tokens[$i][1], 1); | |
$details[0][$name]= array( | |
DETAIL_ANNOTATIONS => $annotations | |
); | |
$annotations= array(); | |
break; | |
case T_FUNCTION: | |
'' === $parsed || raise('lang.ClassFormatException', 'Unterminated annotation "'.addcslashes($parsed, "\0..\17").'" in '.$class.', line '.(isset($tokens[$i][2]) ? ', line '.$tokens[$i][2] : '')); | |
$members= FALSE; | |
while (T_STRING !== $tokens[$i][0]) $i++; | |
$m= $tokens[$i][1]; | |
$details[1][$m]= array( | |
DETAIL_ARGUMENTS => array(), | |
DETAIL_RETURNS => 'void', | |
DETAIL_THROWS => array(), | |
DETAIL_COMMENT => trim(preg_replace('/\n \* ?/', "\n", "\n".substr( | |
$comment, | |
4, // "/**\n" | |
strpos($comment, '* @')- 2 // position of first details token | |
))), | |
DETAIL_ANNOTATIONS => $annotations, | |
DETAIL_NAME => $tokens[$i][1] | |
); | |
$matches= NULL; | |
preg_match_all( | |
'/@([a-z]+)\s*([^<\r\n]+<[^>]+>|[^\r\n ]+) ?([^\r\n ]+)?/', | |
$comment, | |
$matches, | |
PREG_SET_ORDER | |
); | |
$annotations= array(); | |
$comment= NULL; | |
foreach ($matches as $match) { | |
switch ($match[1]) { | |
case 'param': | |
$details[1][$m][DETAIL_ARGUMENTS][]= $match[2]; | |
break; | |
case 'return': | |
$details[1][$m][DETAIL_RETURNS]= $match[2]; | |
break; | |
case 'throws': | |
$details[1][$m][DETAIL_THROWS][]= $match[2]; | |
break; | |
} | |
} | |
break; | |
default: | |
// Empty | |
} | |
} | |
// Return details for specified class | |
xp::$registry['details.'.$class]= $details; | |
return $details; | |
} | |
/** | |
* Retrieve details for a specified class and method. Note: Results | |
* from this method are cached! | |
* | |
* @param string class unqualified class name | |
* @param string method | |
* @return array or NULL if not available | |
*/ | |
public static function detailsForMethod($class, $method) { | |
$details= self::detailsForClass(xp::nameOf($class)); | |
return $details ? (isset($details[1][$method]) ? $details[1][$method] : NULL) : NULL; | |
} | |
/** | |
* Retrieve details for a specified class and field. Note: Results | |
* from this method are cached! | |
* | |
* @param string class unqualified class name | |
* @param string method | |
* @return array or NULL if not available | |
*/ | |
public static function detailsForField($class, $field) { | |
$details= self::detailsForClass(xp::nameOf($class)); | |
return $details ? (isset($details[0][$field]) ? $details[0][$field] : NULL) : NULL; | |
} | |
/** | |
* Creates a generic type | |
* | |
* @param lang.XPClass self | |
* @param lang.Type[] arguments | |
* @return lang.XPClass | |
*/ | |
public static function createGenericType(XPClass $self, array $arguments) { | |
// Verify | |
if (!$self->isGenericDefinition()) { | |
throw new IllegalStateException('Class '.$self->name.' is not a generic definition'); | |
} | |
$components= $self->genericComponents(); | |
$cs= sizeof($components); | |
if ($cs != sizeof($arguments)) { | |
throw new IllegalArgumentException(sprintf( | |
'Class %s expects %d component(s) <%s>, %d argument(s) given', | |
$self->name, | |
$cs, | |
implode(', ', $components), | |
sizeof($arguments) | |
)); | |
} | |
// Compose names | |
$cn= $qc= ''; | |
foreach ($arguments as $typearg) { | |
$cn.= '¸'.$typearg->literal(); | |
$qc.= ','.$typearg->getName(); | |
} | |
$name= xp::reflect($self->name).'··'.substr($cn, 1); | |
$qname= $self->name.'<'.substr($qc, 1).'>'; | |
// Create class if it doesn't exist yet | |
if (!class_exists($name, FALSE) && !interface_exists($name, FALSE)) { | |
$meta= isset(xp::$registry['details.'.$self->name]) ? xp::$registry['details.'.$self->name] : array( | |
'class' => NULL, | |
0 => array(), | |
1 => array() | |
); | |
// Parse placeholders into a lookup map | |
$placeholders= array(); | |
foreach ($components as $i => $component) { | |
$placeholders[$component]= $arguments[$i]->getName(); | |
} | |
// Work on sourcecode | |
$cl= self::_classLoaderFor($self->name); | |
if (!$cl || !($bytes= $cl->loadClassBytes($self->name))) { | |
throw new IllegalStateException($self->name); | |
} | |
// Replace source | |
$src= ''; | |
$comment= NULL; | |
$annotations= array(); | |
$annotation= NULL; | |
$matches= array(); | |
$state= array(0); | |
$counter= 0; | |
$tokens= token_get_all($bytes); | |
for ($i= 0, $s= sizeof($tokens); $i < $s; $i++) { | |
if (T_COMMENT === $tokens[$i][0] && '#' === $tokens[$i][1]{0}) { | |
$annotations= eval('return array('.preg_replace( | |
array('/@([a-z_]+),/i', '/@([a-z_]+)\(\'([^\']+)\'\)/ie', '/@([a-z_]+)\(/i', '/([^a-z_@])([a-z_]+) *= */i'), | |
array('\'$1\' => NULL,', '"\'$1\' => urldecode(\'".urlencode(\'$2\')."\')"', '\'$1\' => array(', '$1\'$2\' => '), | |
trim($tokens[$i][1], "[]# \t\n\r").',' | |
).');'); | |
continue; | |
} else if (T_DOC_COMMENT === $tokens[$i][0]) { | |
$matches= NULL; | |
$comment= trim(preg_replace('/\n\s+\* ?/', "\n", "\n".substr( | |
$tokens[$i][1], | |
4, // "/**\n" | |
strpos($tokens[$i][1], '* @')- 2 // position of first details token | |
))); | |
preg_match_all( | |
'/@([a-z]+)\s*([^<\r\n]+<[^>]+>|[^\r\n ]+) ?([^\r\n ]+)?/', | |
$tokens[$i][1], | |
$matches, | |
PREG_SET_ORDER | |
); | |
} | |
if (0 === $state[0]) { | |
if (T_ABSTRACT === $tokens[$i][0] || T_FINAL === $tokens[$i][0]) { | |
$src.= $tokens[$i][1].' '; | |
} else if (T_CLASS === $tokens[$i][0] || T_INTERFACE === $tokens[$i][0]) { | |
if (NULL === $meta['class']) { | |
$meta['class']= array(DETAIL_COMMENT => $comment, DETAIL_ANNOTATIONS => $annotations); | |
} | |
$meta['class'][DETAIL_GENERIC]= array($self->name, $arguments); | |
$src.= $tokens[$i][1].' '.$name; | |
array_unshift($state, $tokens[$i][0]); | |
} | |
continue; | |
} else if (T_CLASS === $state[0]) { | |
if (T_EXTENDS === $tokens[$i][0]) { | |
if (isset($annotations['generic']['parent'])) { | |
$xargs= array(); | |
foreach (explode(',', $annotations['generic']['parent']) as $j => $placeholder) { | |
$xargs[]= Type::forName(strtr(ltrim($placeholder), $placeholders)); | |
} | |
$src.= ' extends '.self::createGenericType($self->getParentClass(), $xargs)->literal(); | |
} else { | |
$src.= ' extends '.$tokens[$i+ 2][1]; | |
} | |
} else if (T_IMPLEMENTS === $tokens[$i][0]) { | |
$src.= ' implements'; | |
$counter= 0; | |
$annotation= @$annotations['generic']['implements']; | |
array_unshift($state, 5); | |
} else if ('{' === $tokens[$i][0]) { | |
array_shift($state); | |
array_unshift($state, 1); | |
$src.= ' {'; | |
} | |
continue; | |
} else if (T_INTERFACE === $state[0]) { | |
if (T_EXTENDS === $tokens[$i][0]) { | |
$src.= ' extends'; | |
$counter= 0; | |
$annotation= @$annotations['generic']['extends']; | |
array_unshift($state, 5); | |
} else if ('{' === $tokens[$i][0]) { | |
array_shift($state); | |
array_unshift($state, 1); | |
$src.= ' {'; | |
} | |
continue; | |
} else if (1 === $state[0]) { // Class body | |
if (T_FUNCTION === $tokens[$i][0]) { | |
$braces= 0; | |
$parameters= array(); | |
array_unshift($state, 3); | |
array_unshift($state, 2); | |
$m= $tokens[$i+ 2][1]; | |
if (isset($meta[1][$m])) { | |
$annotations= $meta[1][$m][DETAIL_ANNOTATIONS]; | |
} else { | |
$meta[1][$m]= array( | |
DETAIL_ARGUMENTS => array(), | |
DETAIL_RETURNS => 'void', | |
DETAIL_THROWS => array(), | |
DETAIL_COMMENT => $comment, | |
DETAIL_ANNOTATIONS => $annotations, | |
DETAIL_NAME => $m | |
); | |
foreach ($matches as $match) { | |
switch ($match[1]) { | |
case 'param': $meta[1][$m][DETAIL_ARGUMENTS][]= $match[2]; break; | |
case 'return': $meta[1][$m][DETAIL_RETURNS]= $match[2]; break; | |
case 'throws': $meta[1][$m][DETAIL_THROWS][]= $match[2]; break; | |
} | |
} | |
} | |
} else if ('}' === $tokens[$i][0]) { | |
$src.= '}'; | |
break; | |
} else if (T_CLOSE_TAG === $tokens[$i][0]) { | |
break; | |
} | |
} else if (2 === $state[0]) { // Method declaration | |
if ('(' === $tokens[$i][0]) { | |
$braces++; | |
} else if (')' === $tokens[$i][0]) { | |
$braces--; | |
if (0 === $braces) array_shift($state); | |
} else if (T_VARIABLE === $tokens[$i][0]) { | |
$parameters[]= $tokens[$i][1]; | |
} | |
} else if (3 === $state[0]) { // Method body | |
if (';' === $tokens[$i][0]) { | |
// Abstract method | |
array_shift($state); | |
} else if ('{' === $tokens[$i][0]) { | |
$braces= 1; | |
array_shift($state); | |
array_unshift($state, 4); | |
$src.= '{'; | |
if (isset($annotations['generic']['return'])) { | |
$meta[1][$m][DETAIL_RETURNS]= strtr($annotations['generic']['return'], $placeholders); | |
} | |
if (isset($annotations['generic']['params'])) { | |
$generic= array(); | |
foreach (explode(',', $annotations['generic']['params']) as $j => $placeholder) { | |
if ('' === ($replaced= strtr(ltrim($placeholder), $placeholders))) { | |
$generic[$j]= NULL; | |
} else { | |
$meta[1][$m][DETAIL_ARGUMENTS][$j]= $replaced; | |
$generic[$j]= $replaced; | |
} | |
} | |
foreach ($generic as $j => $type) { | |
if (NULL === $type) { | |
continue; | |
} else if ('...' === substr($type, -3)) { | |
$src.= $j ? '$·args= array_slice(func_get_args(), '.$j.');' : '$·args= func_get_args();'; | |
$src.= ( | |
' if (!is(\''.substr($generic[$j], 0, -3).'[]\', $·args)) throw new IllegalArgumentException('. | |
'"Vararg '.($j + 1).' passed to ".__METHOD__."'. | |
' must be of '.$type.', ".xp::stringOf($·args)." given"'. | |
');' | |
); | |
} else { | |
$src.= ( | |
' if (!is(\''.$generic[$j].'\', '.$parameters[$j].')) throw new IllegalArgumentException('. | |
'"Argument '.($j + 1).' passed to ".__METHOD__."'. | |
' must be of '.$type.', ".xp::typeOf('.$parameters[$j].')." given"'. | |
');' | |
); | |
} | |
} | |
$annotations= array(); | |
} | |
unset($meta[1][$m][DETAIL_ANNOTATIONS]['generic']); | |
continue; | |
} | |
} else if (4 === $state[0]) { // Method body | |
if ('{' === $tokens[$i][0]) { | |
$braces++; | |
} else if ('}' === $tokens[$i][0]) { | |
$braces--; | |
if (0 === $braces) array_shift($state); | |
} | |
} else if (5 === $state[0]) { // Implements (class), Extends (interface) | |
if (T_STRING === $tokens[$i][0]) { | |
if (isset($annotation[$counter])) { | |
$iargs= array(); | |
foreach (explode(',', $annotation[$counter]) as $j => $placeholder) { | |
$iargs[]= Type::forName(strtr(ltrim($placeholder), $placeholders)); | |
} | |
$src.= self::createGenericType(new XPClass(new ReflectionClass($tokens[$i][1])), $iargs)->literal(); | |
} else { | |
$src.= $tokens[$i][1]; | |
} | |
$counter++; | |
continue; | |
} else if ('{' === $tokens[$i][0]) { | |
array_shift($state); | |
array_unshift($state, 1); | |
} | |
} | |
$src.= is_array($tokens[$i]) ? $tokens[$i][1] : $tokens[$i]; | |
} | |
// Create class | |
// DEBUG fputs(STDERR, "@* ".substr($src, 0, strpos($src, '{'))." -> $qname\n"); | |
eval($src); | |
method_exists($name, '__static') && call_user_func(array($name, '__static')); | |
unset($meta['class'][DETAIL_ANNOTATIONS]['generic']); | |
xp::$registry['details.'.$qname]= $meta; | |
xp::$registry['class.'.$name]= $qname; | |
} | |
return new XPClass(new ReflectionClass($name)); | |
} | |
/** | |
* Reflectively creates a new type | |
* | |
* @param lang.Type[] arguments | |
* @return lang.XPClass | |
* @throws lang.IllegalStateException if this class is not a generic definition | |
* @throws lang.IllegalArgumentException if number of arguments does not match components | |
*/ | |
public function newGenericType(array $arguments) { | |
return self::createGenericType($this, $arguments); | |
} | |
/** | |
* Returns generic type components | |
* | |
* @return string[] | |
* @throws lang.IllegalStateException if this class is not a generic definition | |
*/ | |
public function genericComponents() { | |
if (!$this->isGenericDefinition()) { | |
throw new IllegalStateException('Class '.$this->name.' is not a generic definition'); | |
} | |
$components= array(); | |
foreach (explode(',', $this->getAnnotation('generic', 'self')) as $name) { | |
$components[]= ltrim($name); | |
} | |
return $components; | |
} | |
/** | |
* Returns whether this class is a generic definition | |
* | |
* @return bool | |
*/ | |
public function isGenericDefinition() { | |
return $this->hasAnnotation('generic', 'self'); | |
} | |
/** | |
* Returns generic type definition | |
* | |
* @return lang.XPClass | |
* @throws lang.IllegalStateException if this class is not a generic | |
*/ | |
public function genericDefinition() { | |
if (!($details= self::detailsForClass($this->name))) return NULL; | |
if (!isset($details['class'][DETAIL_GENERIC])) { | |
throw new IllegalStateException('Class '.$this->name.' is not generic'); | |
} | |
return XPClass::forName($details['class'][DETAIL_GENERIC][0]); | |
} | |
/** | |
* Returns generic type arguments | |
* | |
* @return lang.Type[] | |
* @throws lang.IllegalStateException if this class is not a generic | |
*/ | |
public function genericArguments() { | |
if (!($details= self::detailsForClass($this->name))) return NULL; | |
if (!isset($details['class'][DETAIL_GENERIC])) { | |
throw new IllegalStateException('Class '.$this->name.' is not generic'); | |
} | |
if (!isset($details['class'][DETAIL_GENERIC][1])) { | |
$details['class'][DETAIL_GENERIC][1]= array_map( | |
array(xp::reflect('lang.Type'), 'forName'), | |
$details['class'][DETAIL_GENERIC][2] | |
); | |
unset($details['class'][DETAIL_GENERIC][2]); | |
} | |
return $details['class'][DETAIL_GENERIC][1]; | |
} | |
/** | |
* Returns whether this class is generic | |
* | |
* @return bool | |
*/ | |
public function isGeneric() { | |
if (!($details= self::detailsForClass($this->name))) return FALSE; | |
return isset($details['class'][DETAIL_GENERIC]); | |
} | |
/** | |
* Returns the XPClass object associated with the class with the given | |
* string name. Uses the default classloader if none is specified. | |
* | |
* @param string name - e.g. "io.File", "rdbms.mysql.MySQL" | |
* @param lang.IClassLoader classloader default NULL | |
* @return lang.XPClass class object | |
* @throws lang.ClassNotFoundException when there is no such class | |
*/ | |
public static function forName($name, IClassLoader $classloader= NULL) { | |
if (NULL === $classloader) { | |
$classloader= ClassLoader::getDefault(); | |
} | |
return $classloader->loadClass((string)$name); | |
} | |
/** | |
* Returns type literal | |
* | |
* @return string | |
*/ | |
public function literal() { | |
return xp::reflect($this->name); | |
} | |
/** | |
* Returns an array containing class objects representing all the | |
* public classes | |
* | |
* @return lang.XPClass[] class objects | |
*/ | |
public static function getClasses() { | |
$ret= array(); | |
foreach (get_declared_classes() as $name) { | |
if (isset(xp::$registry['class.'.$name])) $ret[]= new self($name); | |
} | |
return $ret; | |
} | |
} | |
?> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment