Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/main/php/lang.base.php
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ function newinstance($spec, $args, $def= null) {
}

if ($generic) {
\lang\XPClass::detailsForClass($name);
\lang\XPClass::detailsForClass(new \ReflectionClass($type.$n));
xp::$meta[$name]['class'][DETAIL_GENERIC]= $generic;
}

Expand Down
120 changes: 82 additions & 38 deletions src/main/php/lang/XPClass.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,12 @@
* @test xp://net.xp_framework.unittest.reflection.ClassCastingTest
*/
class XPClass extends Type {
private static $ATTRIBUTES;
private $_class;
private $_reflect= null;

static function __static() {
self::$ATTRIBUTES= class_exists(\ReflectionAttribute::class, false);

// Workaround for missing detail information about return types in
// builtin classes.
Expand Down Expand Up @@ -545,7 +547,7 @@ public function getDeclaredInterfaces() {
* @return string
*/
public function getComment() {
if (!($details= self::detailsForClass($this->name))) return null;
if (!($details= self::detailsForClass($this->reflect()))) return null;
return $details['class'][DETAIL_COMMENT];
}

Expand Down Expand Up @@ -575,12 +577,16 @@ public function getModifiers(): int {
* @return bool
*/
public function hasAnnotation($name, $key= null): bool {
$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] ?? [])
);
$name= strtr($name, '.', '\\');
$details= self::detailsForClass($this->reflect());
$r= $details['class'][DETAIL_ANNOTATIONS] ?? [];
self::mergeAttributes($r, $this->_reflect);

if ($key) {
return is_array($r[$name] ?? null) && array_key_exists($key, $r[$name]);
} else {
return array_key_exists($name, $r);
}
}

/**
Expand All @@ -592,24 +598,27 @@ public function hasAnnotation($name, $key= null): bool {
* @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] ?? [])
)) {
throw new ElementNotFoundException('Annotation "'.$name.($key ? '.'.$key : '').'" does not exist');
$name= strtr($name, '.', '\\');
$details= self::detailsForClass($this->reflect());
$r= $details['class'][DETAIL_ANNOTATIONS] ?? [];
self::mergeAttributes($r, $this->_reflect);

if ($key) {
if (is_array($r[$name] ?? null) && array_key_exists($key, $r[$name])) return $r[$name][$key];
} else {
if (array_key_exists($name, $r)) return $r[$name];
}

return ($key
? $details['class'][DETAIL_ANNOTATIONS][$name][$key]
: $details['class'][DETAIL_ANNOTATIONS][$name]
);
throw new ElementNotFoundException('Annotation "'.$name.($key ? '.'.$key : '').'" does not exist');
}

/** Retrieve whether a method has annotations */
public function hasAnnotations(): bool {
$details= self::detailsForClass($this->name);
return $details ? !empty($details['class'][DETAIL_ANNOTATIONS]) : false;
$details= self::detailsForClass($this->reflect());
$r= $details['class'][DETAIL_ANNOTATIONS] ?? [];
self::mergeAttributes($r, $this->_reflect);

return !empty($r);
}

/**
Expand All @@ -618,8 +627,11 @@ public function hasAnnotations(): bool {
* @return array annotations
*/
public function getAnnotations() {
$details= self::detailsForClass($this->name);
return $details ? $details['class'][DETAIL_ANNOTATIONS] : [];
$details= self::detailsForClass($this->reflect());
$r= $details['class'][DETAIL_ANNOTATIONS] ?? [];
self::mergeAttributes($r, $this->_reflect);

return $r;
}

/** Retrieve the class loader a class was loaded with */
Expand All @@ -641,25 +653,53 @@ protected static function _classLoaderFor($name) {
}
return null; // Internal class, e.g.
}

/**
* Merge attributes
*
* @param &[:var] $details
* @param php.ReflectionClass|php.ReflectionMethod|php.ReflectionProperty $reflect
*/
public static function mergeAttributes(&$details, $reflect) {
if (!self::$ATTRIBUTES) return;

foreach ($reflect->getAttributes() as $attribute) {
$args= $attribute->getArguments();
if (empty($args)) {
$value= null;
} else if (1 === sizeof($args)) {
$value= $args[0];
} else {
$value= $args;
}

// Only resolve uppercase attributes
$name= $attribute->getName();
$p= strrpos($name, '\\');
$l= false === $p || $name[$p + 1] <= 'Z' ? $name : substr($name, $p + 1);
$details[$l] ?? $details[$l]= $value;
}
}

/**
* Retrieve details for a specified class. Note: Results from this
* method are cached!
*
* @param string class fully qualified class name
* @param php.ReflectionClass $class
* @return array or NULL to indicate no details are available
*/
public static function detailsForClass($class) {
public static function detailsForClass(\ReflectionClass $class) {
static $parser= null;

if (isset(\xp::$meta[$class])) return \xp::$meta[$class];
$name= self::nameOf($class->name);
if (isset(\xp::$meta[$name])) return \xp::$meta[$name];

// Retrieve class' sourcecode
$cl= self::_classLoaderFor($class);
if (!$cl || !($bytes= $cl->loadClassBytes($class))) return null;
$cl= self::_classLoaderFor($name);
if (!$cl || !($bytes= $cl->loadClassBytes($name))) return [];

$parser ?? $parser= new \lang\reflect\ClassParser();
return \xp::$meta[$class]= $parser->parseDetails($bytes, $class);
return \xp::$meta[$name]= $parser->parseDetails($bytes, $name);
}

/**
Expand All @@ -671,13 +711,15 @@ public static function detailsForClass($class) {
* @return array or NULL if not available
*/
public static function detailsForMethod($class, $method) {
$details= self::detailsForClass(self::nameOf($class->name));
$details= self::detailsForClass($class);
if (isset($details[1][$method])) return $details[1][$method];
foreach ($class->getTraitNames() as $trait) {
$details= self::detailsForClass(self::nameOf($trait));

foreach ($class->getTraits() as $trait) {
$details= self::detailsForClass($trait);
if (isset($details[1][$method])) return $details[1][$method];
}
return null;

return [];
}

/**
Expand All @@ -689,13 +731,15 @@ public static function detailsForMethod($class, $method) {
* @return array or NULL if not available
*/
public static function detailsForField($class, $field) {
$details= self::detailsForClass(self::nameOf($class->name));
$details= self::detailsForClass($class);
if (isset($details[0][$field])) return $details[0][$field];
foreach ($class->getTraitNames() as $trait) {
$details= self::detailsForClass(self::nameOf($trait));

foreach ($class->getTraits() as $trait) {
$details= self::detailsForClass($trait);
if (isset($details[0][$field])) return $details[0][$field];
}
return null;

return [];
}

/**
Expand Down Expand Up @@ -748,7 +792,7 @@ public function isGenericDefinition(): bool {
* @throws lang.IllegalStateException if this class is not a generic
*/
public function genericDefinition() {
if (!($details= self::detailsForClass($this->name))) return null;
if (!($details= self::detailsForClass($this->reflect()))) return null;
if (!isset($details['class'][DETAIL_GENERIC])) {
throw new IllegalStateException('Class '.$this->name.' is not generic');
}
Expand All @@ -762,7 +806,7 @@ public function genericDefinition() {
* @throws lang.IllegalStateException if this class is not a generic
*/
public function genericArguments() {
if (!($details= self::detailsForClass($this->name))) return null;
if (!($details= self::detailsForClass($this->reflect()))) return null;
if (!isset($details['class'][DETAIL_GENERIC])) {
throw new IllegalStateException('Class '.$this->name.' is not generic');
}
Expand All @@ -778,7 +822,7 @@ public function genericArguments() {

/** Returns whether this class is generic */
public function isGeneric(): bool {
if (!($details= self::detailsForClass($this->name))) return false;
if (!($details= self::detailsForClass($this->reflect()))) return false;
return isset($details['class'][DETAIL_GENERIC]);
}

Expand Down
31 changes: 30 additions & 1 deletion src/main/php/lang/reflect/ClassParser.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -491,9 +491,38 @@ public function parseDetails($bytes, $context= '') {
$comment= $tokens[$i][1];
break;

case T_SL:
if (T_NS_SEPARATOR === $tokens[$i + 1][0]) {
$attribute= '';
} else {
$attribute= $tokens[++$i][1];
}
while (T_NS_SEPARATOR === $tokens[++$i][0]) {
$attribute.= '\\'.$tokens[++$i][1];
}

if ('\\' === $attribute[0]) {
$attribute= substr($attribute, 1);
} else if (isset($imports[$attribute])) {
$attribute= strtr($imports[$attribute], '.', '\\');
} else if ($namespace) {
$attribute= $namespace.'\\'.$attribute;
}
$parsed= '';
break;

case T_SR:
if ('' !== $parsed) {
$j= 1;
$annotations[0][$attribute]= $this->valueOf(token_get_all('<?php '.$parsed), $j, $context, $imports);
$parsed= '';
}
$attribute= null;
break;

case T_COMMENT:
if ('#' === $tokens[$i][1][0]) { // Annotations, #[@test]
if ('[' === $tokens[$i][1][1]) {
if (!isset($attribute) && '[' === $tokens[$i][1][1]) {
$parsed= substr($tokens[$i][1], 2);
} else {
$parsed.= substr($tokens[$i][1], 1);
Expand Down
26 changes: 18 additions & 8 deletions src/main/php/lang/reflect/Field.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,15 @@ public function getTypeName(): string {
* @return bool
*/
public function hasAnnotation($name, $key= null): bool {
$name= strtr($name, '.', '\\');
$details= XPClass::detailsForField($this->_reflect->getDeclaringClass(), $this->_reflect->getName());
$r= $details[DETAIL_ANNOTATIONS] ?? [];
XPClass::mergeAttributes($r, $this->_reflect);

if ($key) {
$a= $details[DETAIL_ANNOTATIONS][$name] ?? null;
return is_array($a) && array_key_exists($key, $a);
return is_array($r[$name] ?? null) && array_key_exists($key, $r[$name]);
} else {
return array_key_exists($name, $details[DETAIL_ANNOTATIONS] ?? []);
return array_key_exists($name, $r);
}
}

Expand All @@ -108,12 +111,15 @@ public function hasAnnotation($name, $key= null): bool {
* @throws lang.ElementNotFoundException
*/
public function getAnnotation($name, $key= null) {
$name= strtr($name, '.', '\\');
$details= XPClass::detailsForField($this->_reflect->getDeclaringClass(), $this->_reflect->getName());
$r= $details[DETAIL_ANNOTATIONS] ?? [];
XPClass::mergeAttributes($r, $this->_reflect);

if ($key) {
$a= $details[DETAIL_ANNOTATIONS][$name] ?? null;
if (is_array($a) && array_key_exists($key, $a)) return $a[$key];
if (is_array($r[$name] ?? null) && array_key_exists($key, $r[$name])) return $r[$name][$key];
} else {
if (array_key_exists($name, $details[DETAIL_ANNOTATIONS] ?? [])) return $details[DETAIL_ANNOTATIONS][$name];
if (array_key_exists($name, $r)) return $r[$name];
}

throw new ElementNotFoundException('Annotation "'.$name.($key ? '.'.$key : '').'" does not exist');
Expand All @@ -122,7 +128,9 @@ public function getAnnotation($name, $key= null) {
/** Retrieve whether this field has annotations */
public function hasAnnotations(): bool {
$details= XPClass::detailsForField($this->_reflect->getDeclaringClass(), $this->_reflect->getName());
return $details ? !empty($details[DETAIL_ANNOTATIONS]) : false;
$r= $details[DETAIL_ANNOTATIONS] ?? [];
XPClass::mergeAttributes($r, $this->_reflect);
return !empty($r);
}

/**
Expand All @@ -132,7 +140,9 @@ public function hasAnnotations(): bool {
*/
public function getAnnotations() {
$details= XPClass::detailsForField($this->_reflect->getDeclaringClass(), $this->_reflect->getName());
return $details ? $details[DETAIL_ANNOTATIONS] : [];
$r= $details[DETAIL_ANNOTATIONS] ?? [];
XPClass::mergeAttributes($r, $this->_reflect);
return $r;
}

/**
Expand Down
20 changes: 16 additions & 4 deletions src/main/php/lang/reflect/Parameter.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,16 @@ public function getDefaultValue() {
* @return bool
*/
public function hasAnnotation($name, $key= null) {
$name= strtr($name, '.', '\\');
$details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1]);
$r= $details[DETAIL_TARGET_ANNO]['$'.$this->_reflect->getName()] ?? [];
XPClass::mergeAttributes($r, $this->_reflect);

return $key ? array_key_exists($key, $r[$name] ?? []) : array_key_exists($name, $r);
if ($key) {
return is_array($r[$name] ?? null) && array_key_exists($key, $r[$name]);
} else {
return array_key_exists($name, $r);
}
}

/**
Expand All @@ -208,11 +214,13 @@ public function hasAnnotation($name, $key= null) {
* @throws lang.ElementNotFoundException
*/
public function getAnnotation($name, $key= null) {
$name= strtr($name, '.', '\\');
$details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1]);
$r= $details[DETAIL_TARGET_ANNO]['$'.$this->_reflect->getName()] ?? [];
XPClass::mergeAttributes($r, $this->_reflect);

if ($key) {
if (array_key_exists($key, $r[$name] ?? [])) return $r[$name][$key];
if (is_array($r[$name] ?? null) && array_key_exists($key, $r[$name])) return $r[$name][$key];
} else {
if (array_key_exists($name, $r)) return $r[$name];
}
Expand All @@ -227,7 +235,9 @@ public function getAnnotation($name, $key= null) {
*/
public function hasAnnotations() {
$details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1]);
return !empty($details[DETAIL_TARGET_ANNO]['$'.$this->_reflect->getName()] ?? []);
$r= $details[DETAIL_TARGET_ANNO]['$'.$this->_reflect->getName()] ?? [];
XPClass::mergeAttributes($r, $this->_reflect);
return !empty($r);
}

/**
Expand All @@ -237,7 +247,9 @@ public function hasAnnotations() {
*/
public function getAnnotations() {
$details= XPClass::detailsForMethod($this->_reflect->getDeclaringClass(), $this->_details[1]);
return $details[DETAIL_TARGET_ANNO]['$'.$this->_reflect->getName()] ?? [];
$r= $details[DETAIL_TARGET_ANNO]['$'.$this->_reflect->getName()] ?? [];
XPClass::mergeAttributes($r, $this->_reflect);
return $r;
}

/**
Expand Down
Loading