/** * Resolves a class member, which is either a field, a class constant * or the `ClassName::class` syntax, which returns the class' literal. * * @param lang.XPClass $class * @param var[] $token A token as returned by `token_get_all()` * @param string $context * @return var */ protected function memberOf($class, $token, $context) { if (T_VARIABLE === $token[0]) { $field = $class->getField(substr($token[1], 1)); $m = $field->getModifiers(); if ($m & MODIFIER_PUBLIC) { return $field->get(null); } else { if ($m & MODIFIER_PROTECTED && $class->isAssignableFrom($context)) { return $field->setAccessible(true)->get(null); } else { if ($m & MODIFIER_PRIVATE && $class->getName() === $context) { return $field->setAccessible(true)->get(null); } else { throw new IllegalAccessException(sprintf('Cannot access %s field %s::$%s', implode(' ', Modifiers::namesOf($m)), $class->getName(), $field->getName())); } } } } else { if (T_CLASS === $token[0]) { return $class->literal(); } else { return $class->getConstant($token[1]); } } }
/** * Invokes the underlying method represented by this Method object, * on the specified object with the specified parameters. * * Example: * <code> * $method= XPClass::forName('lang.Object')->getMethod('toString'); * * var_dump($method->invoke(new Object())); * </code> * * Example (passing arguments) * <code> * $method= XPClass::forName('lang.types.String')->getMethod('concat'); * * var_dump($method->invoke(new String('Hello'), array('World'))); * </code> * * Example (static invokation): * <code> * $method= XPClass::forName('util.log.Logger')->getMethod('getInstance'); * * var_dump($method->invoke(NULL)); * </code> * * @param lang.Object obj * @param var[] args default array() * @return var * @throws lang.IllegalArgumentException in case the passed object is not an instance of the declaring class * @throws lang.IllegalAccessException in case the method is not public or if it is abstract * @throws lang.reflect.TargetInvocationException for any exception raised from the invoked method */ public function invoke($obj, $args = array()) { if (NULL !== $obj && !$obj instanceof $this->_class) { throw new IllegalArgumentException(sprintf('Passed argument is not a %s class (%s)', xp::nameOf($this->_class), xp::typeOf($obj))); } // Check modifiers. If caller is an instance of this class, allow // protected method invocation (which the PHP reflection API does // not). $m = $this->_reflect->getModifiers(); if ($m & MODIFIER_ABSTRACT) { throw new IllegalAccessException(sprintf('Cannot invoke abstract %s::%s', $this->_class, $this->_reflect->getName())); } $public = $m & MODIFIER_PUBLIC; if (!$public && !$this->accessible) { $t = debug_backtrace(0); $decl = $this->_reflect->getDeclaringClass()->getName(); if ($m & MODIFIER_PROTECTED) { $allow = $t[1]['class'] === $decl || is_subclass_of($t[1]['class'], $decl); } else { $allow = $t[1]['class'] === $decl && self::$SETACCESSIBLE_AVAILABLE; } if (!$allow) { throw new IllegalAccessException(sprintf('Cannot invoke %s %s::%s from scope %s', Modifiers::stringOf($this->getModifiers()), $this->_class, $this->_reflect->getName(), $t[1]['class'])); } } // For non-public methods: Use setAccessible() / invokeArgs() combination // if possible, resort to __call() workaround. try { if ($public) { return $this->_reflect->invokeArgs($obj, (array) $args); } if (self::$SETACCESSIBLE_AVAILABLE) { $this->_reflect->setAccessible(TRUE); return $this->_reflect->invokeArgs($obj, (array) $args); } else { if ($m & MODIFIER_STATIC) { return call_user_func(array($this->_class, '__callStatic'), "" . $this->_reflect->getName(), $args); } else { return $obj->__call("" . $this->_reflect->getName(), $args); } } } catch (SystemExit $e) { throw $e; } catch (Throwable $e) { throw new TargetInvocationException($this->_class . '::' . $this->_reflect->getName(), $e); } catch (Exception $e) { throw new TargetInvocationException($this->_class . '::' . $this->_reflect->getName(), new XPException($e->getMessage())); } }
/** * Returns a list of all classes inside a given package * * @param lang.reflect.Package * @param bool recursive whether to include subpackages * @return lang.XPClass[] */ protected static function testClassesIn(Package $package, $recursive) { $r = array(); foreach ($package->getClasses() as $class) { if (!$class->isSubclassOf('unittest.TestCase') || Modifiers::isAbstract($class->getModifiers())) { continue; } $r[] = $class; } if ($recursive) { foreach ($package->getPackages() as $package) { $r = array_merge($r, self::testClassesIn($package, $recursive)); } } return $r; }
/** * Uses the constructor represented by this Constructor object to create * and initialize a new instance of the constructor's declaring class, * with the specified initialization parameters. * * Example: * <code> * $constructor= XPClass::forName('util.Binford')->getConstructor(); * * $instance= $constructor->newInstance(); * $instance= $constructor->newInstance(array(6100)); * </code> * * @param var[] args * @return lang.Generic * @throws lang.IllegalAccessException in case the constructor is not public or if it is abstract * @throws lang.reflect.TargetInvocationException in case the constructor throws an exception */ public function newInstance(array $args = array()) { // Check whether class is abstract $class = new ReflectionClass($this->_class); if ($class->isAbstract()) { throw new IllegalAccessException('Cannot instantiate abstract class ' . $this->_class); } // Check modifiers. If caller is an instance of this class, allow private and // protected constructor invocation (which the PHP reflection API does not). $m = $this->_reflect->getModifiers(); $public = $m & MODIFIER_PUBLIC; if (!$public && !$this->accessible) { $t = debug_backtrace(0); $decl = $this->_reflect->getDeclaringClass()->getName(); if ($m & MODIFIER_PROTECTED) { $allow = $t[1]['class'] === $decl || is_subclass_of($t[1]['class'], $decl); } else { $allow = $t[1]['class'] === $decl && self::$SETACCESSIBLE_AVAILABLE; } if (!$allow) { throw new IllegalAccessException(sprintf('Cannot invoke %s constructor of class %s from scope %s', Modifiers::stringOf($this->getModifiers()), $this->_class, $t[1]['class'])); } } // For non-public constructors: Use setAccessible() / invokeArgs() combination // if possible, resort to __call() workaround. try { if ($public) { return $class->newInstanceArgs($args); } $instance = unserialize('O:' . strlen($this->_class) . ':"' . $this->_class . '":0:{}'); if (self::$SETACCESSIBLE_AVAILABLE) { $this->_reflect->setAccessible(TRUE); $this->_reflect->invokeArgs($instance, $args); } else { $instance->__call("__construct", $args); } return $instance; } catch (SystemExit $e) { throw $e; } catch (Throwable $e) { throw new TargetInvocationException($this->_class . '::<init>', $e); } catch (Exception $e) { throw new TargetInvocationException($this->_class . '::<init>', new XPException($e->getMessage())); } }
/** * Get all test cases * * @param var[] arguments * @return unittest.TestCase[] */ public function testCasesWith($arguments) { if (NULL === ($cl = $this->findLoaderFor($this->folder->getURI()))) { throw new IllegalArgumentException($this->folder->toString() . ' is not in class path'); } $l = strlen($cl->path); $e = -strlen(xp::CLASS_FILE_EXT); $it = new FilteredIOCollectionIterator(new FileCollection($this->folder), new ExtensionEqualsFilter(xp::CLASS_FILE_EXT), TRUE); $cases = array(); foreach ($it as $element) { $name = strtr(substr($element->getUri(), $l, $e), DIRECTORY_SEPARATOR, '.'); $class = XPClass::forName($name); if (!$class->isSubclassOf('unittest.TestCase') || Modifiers::isAbstract($class->getModifiers())) { continue; } $cases = array_merge($cases, $this->testCasesInClass($class, $arguments)); } if (empty($cases)) { throw new IllegalArgumentException('Cannot find any test cases in ' . $this->folder->toString()); } return $cases; }
/** * Invokes the underlying method represented by this Method object, * on the specified object with the specified parameters. * * Example: * ```php * $method= XPClass::forName('lang.Object')->getMethod('toString'); * $str= $method->invoke(new Object()); * ``` * * Example (passing arguments) * ```php * $method= XPClass::forName('util.Date')->getMethod('format'); * $str= $method->invoke(Date::now(), ['%d.%m.%Y']); * ``` * * Example (static invokation): * ```php * $method= XPClass::forName('util.log.Logger')->getMethod('getInstance'); * $log= $method->invoke(null); * ``` * * @param object $obj * @param var[] $args default [] * @return var * @throws lang.IllegalArgumentException in case the passed object is not an instance of the declaring class * @throws lang.IllegalAccessException in case the method is not public or if it is abstract * @throws lang.reflect.TargetInvocationException for any exception raised from the invoked method */ public function invoke($obj, $args = []) { if (null !== $obj && !$obj instanceof $this->_class) { throw new IllegalArgumentException(sprintf('Passed argument is not a %s class (%s)', XPClass::nameOf($this->_class), \xp::typeOf($obj))); } // Check modifiers. If caller is an instance of this class, allow // protected method invocation (which the PHP reflection API does // not). $m = $this->_reflect->getModifiers(); if ($m & MODIFIER_ABSTRACT) { throw new IllegalAccessException(sprintf('Cannot invoke abstract %s::%s', XPClass::nameOf($this->_class), $this->_reflect->getName())); } $public = $m & MODIFIER_PUBLIC; if (!$public && !$this->accessible) { $t = debug_backtrace(0, 2); $decl = $this->_reflect->getDeclaringClass()->getName(); if ($m & MODIFIER_PROTECTED) { $allow = $t[1]['class'] === $decl || is_subclass_of($t[1]['class'], $decl); } else { $allow = $t[1]['class'] === $decl; } if (!$allow) { throw new IllegalAccessException(sprintf('Cannot invoke %s %s::%s from scope %s', Modifiers::stringOf($this->getModifiers()), XPClass::nameOf($this->_class), $this->_reflect->getName(), $t[1]['class'])); } } try { if (!$public) { $this->_reflect->setAccessible(true); } return $this->_reflect->invokeArgs($obj, (array) $args); } catch (\lang\SystemExit $e) { throw $e; } catch (\Throwable $e) { throw new TargetInvocationException(XPClass::nameOf($this->_class) . '::' . $this->_reflect->getName(), $e); } }
/** Retrieve string representation */ public function toString() : string { $signature = ''; foreach ($this->getParameters() as $param) { if ($param->isOptional()) { $signature .= ', [' . $param->getTypeName() . ' $' . $param->getName() . '= ' . str_replace("\n", ' ', \xp::stringOf($param->getDefaultValue())) . ']'; } else { $signature .= ', ' . $param->getTypeName() . ' $' . $param->getName(); } } if ($exceptions = $this->getExceptionNames()) { $throws = ' throws ' . implode(', ', $exceptions); } else { $throws = ''; } return sprintf('%s %s %s(%s)%s', Modifiers::stringOf($this->getModifiers()), $this->getReturnTypeName(), $this->getName(), substr($signature, 2), $throws); }
/** * Asserts given modifiers do not contain abstract * * @param int modifiers * @throws unittest.AssertionFailedError */ protected function assertNotAbstract($modifiers) { $this->assertFalse(Modifiers::isAbstract($modifiers), implode(' | ', Modifiers::namesOf($modifiers))); }
public function staticModifier() { $this->assertTrue(Modifiers::isStatic(MODIFIER_STATIC)); $this->assertEquals('public static', Modifiers::stringOf(MODIFIER_STATIC)); }
/** * Prints package * * @param lang.reflect.Package package */ protected static function printPackage($package) { Console::writeLine('package ', $package->getName(), ' {'); // Child packages foreach ($package->getPackages() as $child) { Console::writeLine(' package ', $child->getName()); } // Classes $order = array('interface' => array(), 'enum' => array(), 'class' => array()); foreach ($package->getClasses() as $class) { $mod = $class->getModifiers(); if ($class->isInterface()) { $type = 'interface'; $mod = $mod ^ MODIFIER_ABSTRACT; } else { if ($class->isEnum()) { $type = 'enum'; } else { $type = 'class'; } } $name = self::displayNameOf($class); $order[$type][] = implode(' ', Modifiers::namesOf($mod)) . ' ' . $type . ' ' . self::displayNameOf($class); } foreach ($order as $type => $classes) { if (empty($classes)) { continue; } Console::writeLine(); sort($classes); foreach ($classes as $name) { Console::writeLine(' ', $name); } } Console::writeLine('}'); }
/** * Parses annotation string * * @param string input * @param string context the class name * @return [:string] imports * @param int line * @return [:var] * @throws lang.ClassFormatException */ public static function parseAnnotations($input, $context, $imports = array(), $line = -1) { static $states = array('annotation', 'annotation name', 'annotation value', 'annotation map key', 'annotation map value', 'multi-value'); $tokens = token_get_all('<?php ' . trim($input, "[# \t\n\r")); $annotations = array(0 => array(), 1 => array()); $place = $context . (-1 === $line ? '' : ', line ' . $line); // Resolve classes $resolve = function ($type, $context, $imports) { if ('self' === $type) { return XPClass::forName($context); } else { if ('parent' === $type) { return XPClass::forName($context)->getParentclass(); } else { if (FALSE !== strpos($type, '.')) { return XPClass::forName($type); } else { if (isset($imports[$type])) { return XPClass::forName($imports[$type]); } else { if (isset(xp::$cn[$type])) { return XPClass::forName(xp::$cn[$type]); } else { if (FALSE !== ($p = strrpos($context, '.'))) { return XPClass::forName(substr($context, 0, $p + 1) . $type); } else { return XPClass::forName($type); } } } } } } }; // Class::CONSTANT vs. Class::$MEMBER $memberOf = function ($tokens, &$i, $class) use($context) { if (T_VARIABLE === $tokens[$i][0]) { $field = $class->getField(substr($tokens[$i][1], 1)); $m = $field->getModifiers(); if ($m & MODIFIER_PUBLIC) { return $field->get(NULL); } else { if ($m & MODIFIER_PROTECTED && $class->isAssignableFrom($context)) { return $field->setAccessible(TRUE)->get(NULL); } else { if ($m & MODIFIER_PRIVATE && $class->getName() === $context) { return $field->setAccessible(TRUE)->get(NULL); } else { throw new IllegalAccessException(sprintf('Cannot access %s field %s::$%s', implode(' ', Modifiers::namesOf($m)), $class->getName(), $field->getName())); } } } } else { return $class->getConstant($tokens[$i][1]); } }; // Parse a single value (recursively, if necessary) $valueOf = function ($tokens, &$i) use(&$valueOf, &$memberOf, &$resolve, $context, $imports) { if ('-' === $tokens[$i][0]) { $i++; return -1 * $valueOf($tokens, $i); } else { if ('+' === $tokens[$i][0]) { $i++; return +1 * $valueOf($tokens, $i); } else { if (T_CONSTANT_ENCAPSED_STRING === $tokens[$i][0]) { return eval('return ' . $tokens[$i][1] . ';'); } else { if (T_LNUMBER === $tokens[$i][0]) { return (int) $tokens[$i][1]; } else { if (T_DNUMBER === $tokens[$i][0]) { return (double) $tokens[$i][1]; } else { if ('[' === $tokens[$i] || T_ARRAY === $tokens[$i][0]) { $value = array(); $element = NULL; $key = 0; $end = '[' === $tokens[$i] ? ']' : ')'; for ($i++, $s = sizeof($tokens);; $i++) { if ($i >= $s) { throw new IllegalStateException('Parse error: Unterminated array'); } else { if ($end === $tokens[$i]) { $element && ($value[$key] = $element[0]); break; } else { if ('(' === $tokens[$i]) { // Skip } else { if (',' === $tokens[$i]) { $element || raise('lang.IllegalStateException', 'Parse error: Malformed array - no value before comma'); $value[$key] = $element[0]; $element = NULL; $key = sizeof($value); } else { if (T_DOUBLE_ARROW === $tokens[$i][0]) { $key = $element[0]; $element = NULL; } else { if (T_WHITESPACE === $tokens[$i][0]) { continue; } else { $element && raise('lang.IllegalStateException', 'Parse error: Malformed array - missing comma'); $element = array($valueOf($tokens, $i)); } } } } } } } return $value; } else { if ('"' === $tokens[$i] || T_ENCAPSED_AND_WHITESPACE === $tokens[$i][0]) { throw new IllegalStateException('Parse error: Unterminated string'); } else { if (T_NS_SEPARATOR === $tokens[$i][0]) { $type = ''; while (T_NS_SEPARATOR === $tokens[$i++][0]) { $type .= '.' . $tokens[$i++][1]; } return $memberOf($tokens, $i, XPClass::forName(substr($type, 1))); } else { if (T_STRING === $tokens[$i][0]) { // constant vs. class::constant if (T_DOUBLE_COLON === $tokens[$i + 1][0]) { $i += 2; return $memberOf($tokens, $i, $resolve($tokens[$i - 2][1], $context, $imports)); } else { if (defined($tokens[$i][1])) { return constant($tokens[$i][1]); } else { raise('lang.ElementNotFoundException', 'Undefined constant "' . $tokens[$i][1] . '"'); } } } else { if (T_NEW === $tokens[$i][0]) { $type = ''; while ('(' !== $tokens[$i++]) { if (T_STRING === $tokens[$i][0]) { $type .= '.' . $tokens[$i][1]; } } $class = $resolve(substr($type, 1), $context, $imports); for ($args = array(), $arg = NULL, $s = sizeof($tokens);; $i++) { if (')' === $tokens[$i]) { $arg && ($args[] = $arg[0]); break; } else { if (',' === $tokens[$i]) { $args[] = $arg[0]; $arg = NULL; } else { if (T_WHITESPACE !== $tokens[$i][0]) { $arg = array($valueOf($tokens, $i)); } } } } return $class->hasConstructor() ? $class->getConstructor()->newInstance($args) : $class->newInstance(); } else { throw new IllegalStateException(sprintf('Parse error: Unexpected %s', is_array($tokens[$i]) ? token_name($tokens[$i][0]) : '"' . $tokens[$i] . '"')); } } } } } } } } } } }; // Parse tokens try { for ($state = 0, $i = 1, $s = sizeof($tokens); $i < $s; $i++) { if (T_WHITESPACE === $tokens[$i][0]) { continue; } else { if (0 === $state) { // Initial state, expecting @attr or @$param: attr if ('@' === $tokens[$i]) { $annotation = $tokens[$i + 1][1]; $param = NULL; $value = NULL; $i++; $state = 1; } else { throw new IllegalStateException('Parse error: Expecting "@"'); } } else { if (1 === $state) { // Inside attribute, check for values if ('(' === $tokens[$i]) { $state = 2; } else { if (',' === $tokens[$i]) { if ($param) { $annotations[1][$param][$annotation] = $value; } else { $annotations[0][$annotation] = $value; } $state = 0; } else { if (']' === $tokens[$i]) { if ($param) { $annotations[1][$param][$annotation] = $value; } else { $annotations[0][$annotation] = $value; } return $annotations; } else { if (':' === $tokens[$i]) { $param = $annotation; $annotation = NULL; } else { if (T_STRING === $tokens[$i][0]) { $annotation = $tokens[$i][1]; } else { throw new IllegalStateException('Parse error: Expecting either "(", "," or "]"'); } } } } } } else { if (2 === $state) { // Inside braces of @attr(...) if (')' === $tokens[$i]) { $state = 1; } else { if (',' === $tokens[$i]) { trigger_error('Deprecated usage of multi-value annotations in ' . $place, E_USER_DEPRECATED); $value = (array) $value; $state = 5; } else { if ($i + 2 < $s && ('=' === $tokens[$i + 1] || '=' === $tokens[$i + 2])) { $key = $tokens[$i][1]; $value = array(); $state = 3; } else { $value = $valueOf($tokens, $i); } } } } else { if (3 === $state) { // Parsing key inside @attr(a= b, c= d) if (')' === $tokens[$i]) { $state = 1; } else { if (',' === $tokens[$i]) { $key = null; } else { if ('=' === $tokens[$i]) { $state = 4; } else { if (is_array($tokens[$i])) { $key = $tokens[$i][1]; } } } } } else { if (4 === $state) { // Parsing value inside @attr(a= b, c= d) $value[$key] = $valueOf($tokens, $i); $state = 3; } else { if (5 === $state) { if (')' === $tokens[$i]) { // BC: Deprecated multi-value annotations $value[] = $element; $state = 1; } else { if (',' === $tokens[$i]) { $value[] = $element; } else { $element = $valueOf($tokens, $i); } } } } } } } } } } } catch (XPException $e) { raise('lang.ClassFormatException', $e->getMessage() . ' in ' . $place, $e); } raise('lang.ClassFormatException', 'Parse error: Unterminated ' . $states[$state] . ' in ' . $place); }
/** Creates a string representation of this field */ public function toString() : string { return sprintf('%s %s %s::$%s', Modifiers::stringOf($this->getModifiers()), $this->getTypeName(), $this->getDeclaringClass()->getName(), $this->getName()); }
/** * Convert data based on type * * @param lang.Type type * @param [:var] data * @return var */ public function unmarshal($type, $data) { if (NULL === $type || $type->equals(Type::$VAR)) { // No conversion return $data; } else { if (NULL === $data) { // Valid for any type return NULL; } else { if ($type->equals(XPClass::forName('lang.types.String'))) { return new String($this->valueOf($data)); } else { if ($type->equals(XPClass::forName('util.Date'))) { return $type->newInstance($data); } else { if ($type instanceof XPClass) { foreach ($this->marshallers->keys() as $t) { if ($t->isAssignableFrom($type)) { return $this->marshallers[$t]->unmarshal($type, $data, $this); } } // Check if a public static one-arg valueOf() method exists // E.g.: Assuming the target type has a valueOf(string $id) and the // given payload data is either a map or an array with one element, or // a primitive, then pass that as value. Examples: { "id" : "4711" }, // [ "4711" ] or "4711" - in all cases pass just "4711". if ($type->hasMethod('valueOf')) { $m = $type->getMethod('valueOf'); if (Modifiers::isStatic($m->getModifiers()) && Modifiers::isPublic($m->getModifiers()) && 1 === $m->numParameters()) { if (NULL !== ($arg = $this->keyOf($data))) { return $m->invoke(NULL, array($this->unmarshal($m->getParameter(0)->getType(), $arg[0]))); } } } // Generic approach $return = $type->newInstance(); if (NULL === $data) { $iter = array(); } else { if (is_array($data) || $data instanceof Traversable) { $iter = $data; } else { $iter = array($data); } } foreach ($iter as $name => $value) { foreach ($this->variantsOf($name) as $variant) { if ($type->hasField($variant)) { $field = $type->getField($variant); $m = $field->getModifiers(); if ($m & MODIFIER_STATIC) { continue; } else { if ($m & MODIFIER_PUBLIC) { if (NULL !== ($fType = $field->getType())) { $field->set($return, $this->unmarshal($fType, $value)); } else { $field->set($return, $value); } continue 2; } } } if ($type->hasMethod('set' . $variant)) { $method = $type->getMethod('set' . $variant); if ($method->getModifiers() & MODIFIER_PUBLIC) { if (NULL !== ($param = $method->getParameter(0))) { $method->invoke($return, array($this->unmarshal($param->getType(), $value))); } else { $method->invoke($return, array($value)); } continue 2; } } } } return $return; } else { if ($type instanceof ArrayType) { $return = array(); foreach ($data as $element) { $return[] = $this->unmarshal($type->componentType(), $element); } return $return; } else { if ($type instanceof MapType) { $return = array(); foreach ($data as $key => $element) { $return[$key] = $this->unmarshal($type->componentType(), $element); } return $return; } else { if ($type->equals(Primitive::$STRING)) { return (string) $this->valueOf($data); } else { if ($type->equals(Primitive::$INT)) { return (int) $this->valueOf($data); } else { if ($type->equals(Primitive::$DOUBLE)) { return (double) $this->valueOf($data); } else { if ($type->equals(Primitive::$BOOL)) { return (bool) $this->valueOf($data); } else { throw new FormatException('Cannot convert to ' . xp::stringOf($type)); } } } } } } } } } } } }