/** * Returns a method for a class by a given name * * @param text.doclet.ClassDoc self * @param string name * @return text.doclet.MethodDoc * @throws lang.ElementNotFoundException */ public static function methodNamed(ClassDoc $self, $name) { foreach ($self->methods as $method) { if ($name === $method->name()) { return $method; } } raise('lang.ElementNotFoundException', 'No such method ' . $name . ' in ' . $self->name()); }
/** * Parses a class file and returns a classdoc element * * @param string classname fully qualified class name * @return text.doclet.ClassDoc * @throws lang.IllegalArgumentException if class could not be found or parsed */ public function classNamed($classname) { static $cache = array(); static $map = array('uses' => self::T_USES, 'define' => self::T_DEFINE); // Check cache if (isset($cache[$classname])) { return $cache[$classname]; } // Check for php namespace - in this case, we have a builtin class. These // classes will not be documented for the moment. if ('php.' == substr($classname, 0, 4)) { return null; } // Find class if (!($loader = $this->findClass($classname))) { throw new \lang\IllegalArgumentException(sprintf('Could not find %s in %s', \xp::stringOf($classname), \xp::stringOf($this->sourcepath))); } // Tokenize contents $tokens = @token_get_all($loader->loadClassBytes($classname)); if (!$tokens || T_OPEN_TAG !== $tokens[0][0]) { throw new \lang\IllegalArgumentException(sprintf('Could not parse "%s" from %s, first token: %s', $classname, \xp::stringOf($loader), \xp::stringOf($tokens[0]))); } with($doc = new ClassDoc(), $doc->setLoader($loader), $doc->setRoot($this)); $annotations = $comment = $package = null; $modifiers = array(); $state = self::ST_INITIAL; for ($i = 0, $s = sizeof($tokens); $i < $s; $i++) { $t = $tokens[$i]; if (is_array($t) && isset($map[$t[1]])) { $t[0] = $map[$t[1]]; } switch ($state . $t[0]) { case self::ST_INITIAL . T_DOC_COMMENT: case self::ST_CLASS_BODY . T_DOC_COMMENT: $comment = $t[1]; break; case self::ST_INITIAL . T_COMMENT: case self::ST_CLASS_BODY . T_COMMENT: if (strncmp('#[@', $t[1], 3) == 0) { $annotations = substr($t[1], 2); } else { if (strncmp('#', $t[1], 1) == 0) { $annotations .= substr($t[1], 1); } } if (']' == substr(rtrim($t[1]), -1)) { $annotations = '[' . trim($annotations); } break; case self::ST_INITIAL . T_VARIABLE: if ('$package' === $t[1]) { // RFC #0037: $package= 'lang.reflect'; while (T_CONSTANT_ENCAPSED_STRING !== $tokens[$i][0] && $i < $s) { $i++; } $package = trim($tokens[$i][1], '\'"'); } break; case self::ST_INITIAL . self::T_USES: $state = self::ST_USES; break; case self::ST_USES . T_CONSTANT_ENCAPSED_STRING: $cn = trim($t[1], '"\''); if (!$this->findClass($cn)) { throw new \lang\IllegalStateException('Could not find used class "' . $cn . '" for class ' . $classname . ' in ' . \xp::stringOf($this->sourcepath)); } $doc->usedClasses->classes[] = $cn; break; case self::ST_USES . ')': $state = self::ST_INITIAL; break; case self::ST_INITIAL . self::T_DEFINE: $state = self::ST_DEFINE; break; case self::ST_INITIAL . T_NAMESPACE: $package = ''; $i += 2; // Eat "namespace" while (';' !== $tokens[$i][0] && $i < $s) { $package .= trim($tokens[$i++][1]); } $package = strtr($package, '\\', '.'); break; case self::ST_DEFINE . T_CONSTANT_ENCAPSED_STRING: $state = self::ST_DEFINE_VALUE; $define = trim($t[1], '"\''); break; case self::ST_DEFINE_VALUE . T_CONSTANT_ENCAPSED_STRING: case self::ST_DEFINE_VALUE . T_LNUMBER: case self::ST_DEFINE_VALUE . T_DNUMBER: case self::ST_DEFINE_VALUE . T_STRING: $doc->constants[$define] = $t[1]; break; case self::ST_DEFINE_VALUE . ')': $state = self::ST_INITIAL; break; case self::ST_INITIAL . T_INTERFACE: $doc->type = INTERFACE_CLASS; // Fall-through intended // Fall-through intended case self::ST_INITIAL . T_CLASS: while (T_STRING !== $tokens[$i][0] && $i < $s) { $i++; } $name = $tokens[$i][1]; if ($package && substr(strtr($name, '·', '.'), 0, strlen($package)) == $package) { $name = substr($name, strlen($package) + 1); } $doc->name = $name; $doc->qualifiedName = $classname; $doc->rawComment = $comment; $doc->annotations = $annotations; $doc->modifiers = $modifiers; $comment = $annotations = null; $modifiers = array(); $state = self::ST_CLASS; break; case self::ST_CLASS . T_EXTENDS: while (T_STRING !== $tokens[$i][0] && $i < $s) { $i++; } $doc->superclass = $this->classNamed($this->qualifyName($doc, $tokens[$i][1])); break; case self::ST_CLASS . T_IMPLEMENTS: $state = self::ST_IMPLEMENTS; break; case self::ST_IMPLEMENTS . T_STRING: $doc->interfaces->classes[] = $this->qualifyName($doc, $t[1]); break; case self::ST_CLASS . '{': case self::ST_IMPLEMENTS . '{': $state = self::ST_CLASS_BODY; break; case self::ST_CLASS_BODY . T_VARIABLE: $state = self::ST_CLASS_VAR; // Fall-through intended // Fall-through intended case self::ST_CLASS_VAR . T_VARIABLE: unset($field); $field = new FieldDoc(); $field->declaring = $doc; $field->name = $t[1]; $field->modifiers = $modifiers; break; case self::ST_CLASS_VAR . '=': $state = self::ST_VARIABLE_VALUE; break; case self::ST_CLASS_VAR . ',': $doc->fields[] = $field; break; case self::ST_CLASS_VAR . ';': $doc->fields[] = $field; $state = self::ST_CLASS_BODY; $modifiers = array(); break; case self::ST_VARIABLE_VALUE . T_CONSTANT_ENCAPSED_STRING: case self::ST_VARIABLE_VALUE . T_LNUMBER: case self::ST_VARIABLE_VALUE . T_DNUMBER: case self::ST_VARIABLE_VALUE . T_STRING: $field->constantValue = $t[1]; $state = self::ST_CLASS_VAR; break; case self::ST_VARIABLE_VALUE . T_ARRAY: $brackets = 0; $src = ''; do { $t = $tokens[$i]; $src .= is_array($t) ? $t[1] : $t; if ('(' == $t[0]) { $brackets++; } else { if (')' == $t[0] and --$brackets <= 0) { break; } } } while (++$i < $s); $field->constantValue = $src; $state = self::ST_CLASS_VAR; break; // Before member declaration (e.g. public static $..., protected function ...) // Before member declaration (e.g. public static $..., protected function ...) case self::ST_CLASS_BODY . T_PUBLIC: case self::ST_CLASS_BODY . T_PRIVATE: case self::ST_CLASS_BODY . T_PROTECTED: case self::ST_CLASS_BODY . T_STATIC: case self::ST_CLASS_BODY . T_FINAL: case self::ST_CLASS_BODY . T_ABSTRACT: // Before class declaration (e.g. abstract class ...) // Before class declaration (e.g. abstract class ...) case self::ST_INITIAL . T_FINAL: case self::ST_INITIAL . T_ABSTRACT: $modifiers[$t[1]] = true; break; case self::ST_CLASS_BODY . T_FUNCTION: while (T_STRING !== $tokens[$i][0] && $i < $s) { $i++; } with($method = new MethodDoc(), $method->setRoot($this)); $method->name = $tokens[$i][1]; $method->rawComment = $comment; $method->annotations = $annotations; $method->modifiers = $modifiers; $method->declaring = $doc; // Omit static initializer, it's not a real function if ('__static' != $method->name) { $doc->methods[] = $method; } $comment = $annotations = null; $modifiers = array(); $state = self::ST_FUNCTION; break; case self::ST_FUNCTION . '(': $state = self::ST_FUNCTION_ARGUMENTS; $argument = null; break; case self::ST_FUNCTION_ARGUMENTS . T_VARIABLE: $argument = $t[1]; break; case self::ST_FUNCTION_ARGUMENTS . ',': $method->arguments[$argument] = null; break; case self::ST_FUNCTION_ARGUMENTS . '=': $state = self::ST_ARGUMENT_VALUE; break; case self::ST_ARGUMENT_VALUE . T_CONSTANT_ENCAPSED_STRING: case self::ST_ARGUMENT_VALUE . T_LNUMBER: case self::ST_ARGUMENT_VALUE . T_DNUMBER: case self::ST_ARGUMENT_VALUE . T_STRING: $method->arguments[$argument] = $t[1]; break; case self::ST_ARGUMENT_VALUE . T_ARRAY: $brackets = 0; $src = ''; do { $t = $tokens[$i]; $src .= is_array($t) ? $t[1] : $t; if ('(' == $t[0]) { $brackets++; } else { if (')' == $t[0] and --$brackets <= 0) { break; } } } while (++$i < $s); $method->arguments[$argument] = $src; break; case self::ST_ARGUMENT_VALUE . ',': $state = self::ST_FUNCTION_ARGUMENTS; break; case self::ST_ARGUMENT_VALUE . ')': $state = self::ST_FUNCTION; break; case self::ST_FUNCTION_ARGUMENTS . ')': $argument && ($method->arguments[$argument] = null); $state = self::ST_FUNCTION; break; case self::ST_FUNCTION . ';': // Interface and abstract methods have no body $state = self::ST_CLASS_BODY; break; case self::ST_FUNCTION . '{': $brackets = 0; do { $c = $tokens[$i][0]; if ('{' == $c) { $brackets++; } else { if ('}' == $c and --$brackets <= 0) { break; } } } while (++$i < $s); $state = self::ST_CLASS_BODY; break; case self::ST_CLASS_BODY . '}': $state = self::ST_INITIAL; break; } } return $cache[$classname] = $doc; }