/**
  * 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]);
         }
     }
 }
 /**
  * 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)));
 }
 /**
  * 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);
 }