Location: PHPKode > scripts > ApiGen > apigen/ApiGen/Generator.php
<?php

/**
 * ApiGen 2.8.0 - API documentation generator for PHP 5.3+
 *
 * Copyright (c) 2010-2011 David Grudl (http://davidgrudl.com)
 * Copyright (c) 2011-2012 Jaroslav Hanslík (https://github.com/kukulich)
 * Copyright (c) 2011-2012 Ondřej Nešpor (https://github.com/Andrewsville)
 *
 * For the full copyright and license information, please view
 * the file LICENSE.md that was distributed with this source code.
 */

namespace ApiGen;

use TokenReflection\Broker;
use Nette, FSHL;
use InvalidArgumentException, RuntimeException;

/**
 * Generates a HTML API documentation.
 */
class Generator extends Nette\Object
{
	/**
	 * Library name.
	 *
	 * @var string
	 */
	const NAME = 'ApiGen';

	/**
	 * Library version.
	 *
	 * @var string
	 */
	const VERSION = '2.8.0';

	/**
	 * Configuration.
	 *
	 * @var \ApiGen\Config
	 */
	private $config;

	/**
	 * List of parsed classes.
	 *
	 * @var \ArrayObject
	 */
	private $parsedClasses = null;

	/**
	 * List of parsed constants.
	 *
	 * @var \ArrayObject
	 */
	private $parsedConstants = null;

	/**
	 * List of parsed functions.
	 *
	 * @var \ArrayObject
	 */
	private $parsedFunctions = null;

	/**
	 * List of packages.
	 *
	 * @var array
	 */
	private $packages = array();

	/**
	 * List of namespaces.
	 *
	 * @var array
	 */
	private $namespaces = array();

	/**
	 * List of classes.
	 *
	 * @var array
	 */
	private $classes = array();

	/**
	 * List of interfaces.
	 *
	 * @var array
	 */
	private $interfaces = array();

	/**
	 * List of traits.
	 *
	 * @var array
	 */
	private $traits = array();

	/**
	 * List of exceptions.
	 *
	 * @var array
	 */
	private $exceptions = array();

	/**
	 * List of constants.
	 *
	 * @var array
	 */
	private $constants = array();

	/**
	 * List of functions.
	 *
	 * @var array
	 */
	private $functions = array();

	/**
	 * List of symlinks.
	 *
	 * @var array
	 */
	private $symlinks = array();

	/**
	 * List of detected character sets for parsed files.
	 *
	 * @var array
	 */
	private $charsets = array();

	/**
	 * Progressbar settings and status.
	 *
	 * @var array
	 */
	private $progressbar = array(
		'skeleton' => '[%s] %\' 6.2f%% %\' 3dMB',
		'width' => 80,
		'bar' => 64,
		'current' => 0,
		'maximum' => 1
	);

	/**
	 * Sets configuration.
	 *
	 * @param array $config
	 */
	public function __construct(Config $config)
	{
		$this->config = $config;
		$this->parsedClasses = new \ArrayObject();
		$this->parsedConstants = new \ArrayObject();
		$this->parsedFunctions = new \ArrayObject();
	}

	/**
	 * Scans and parses PHP files.
	 *
	 * @return array
	 * @throws \RuntimeException If no PHP files have been found.
	 */
	public function parse()
	{
		$files = array();

		$flags = \RecursiveDirectoryIterator::CURRENT_AS_FILEINFO | \RecursiveDirectoryIterator::SKIP_DOTS;
		if (defined('\\RecursiveDirectoryIterator::FOLLOW_SYMLINKS')) {
			// Available from PHP 5.3.1
			$flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS;
		}

		foreach ($this->config->source as $source) {
			$entries = array();
			if (is_dir($source)) {
				foreach (new \RecursiveIteratorIterator(new SourceFilesFilterIterator(new \RecursiveDirectoryIterator($source, $flags), $this->config->exclude)) as $entry) {
					if (!$entry->isFile()) {
						continue;
					}
					$entries[] = $entry;
				}
			} elseif ($this->isPhar($source)) {
				if (!extension_loaded('phar')) {
					throw new RuntimeException('Phar extension is not loaded');
				}
				foreach (new \RecursiveIteratorIterator(new \Phar($source, $flags)) as $entry) {
					if (!$entry->isFile()) {
						continue;
					}
					$entries[] = $entry;
				}
			} else {
				$entries[] = new \SplFileInfo($source);
			}

			$regexp = '~\\.' . implode('|', $this->config->extensions) . '$~i';
			foreach ($entries as $entry) {
				if (!preg_match($regexp, $entry->getFilename())) {
					continue;
				}

				$pathName = $this->normalizePath($entry->getPathName());
				$files[$pathName] = $entry->getSize();
				if (false !== $entry->getRealPath() && $pathName !== $entry->getRealPath()) {
					$this->symlinks[$entry->getRealPath()] = $pathName;
				}
			}
		}

		if (empty($files)) {
			throw new RuntimeException('No PHP files found');
		}

		if ($this->config->progressbar) {
			$this->prepareProgressBar(array_sum($files));
		}

		$broker = new Broker(new Backend($this, !empty($this->config->report)), Broker::OPTION_DEFAULT & ~(Broker::OPTION_PARSE_FUNCTION_BODY | Broker::OPTION_SAVE_TOKEN_STREAM));

		$errors = array();

		foreach ($files as $fileName => $size) {
			$content = file_get_contents($fileName);
			$charset = $this->detectCharset($content);
			$this->charsets[$fileName] = $charset;
			$content = $this->toUtf($content, $charset);

			try {
				$broker->processString($content, $fileName);
			} catch (\Exception $e) {
				$errors[] = $e;
			}

			$this->incrementProgressBar($size);
			$this->checkMemory();
		}

		// Classes
		$this->parsedClasses->exchangeArray($broker->getClasses(Backend::TOKENIZED_CLASSES | Backend::INTERNAL_CLASSES | Backend::NONEXISTENT_CLASSES));
		$this->parsedClasses->uksort('strcasecmp');

		// Constants
		$this->parsedConstants->exchangeArray($broker->getConstants());
		$this->parsedConstants->uksort('strcasecmp');

		// Functions
		$this->parsedFunctions->exchangeArray($broker->getFunctions());
		$this->parsedFunctions->uksort('strcasecmp');

		$documentedCounter = function($count, $element) {
			return $count += (int) $element->isDocumented();
		};

		return (object) array(
			'classes' => count($broker->getClasses(Backend::TOKENIZED_CLASSES)),
			'constants' => count($this->parsedConstants),
			'functions' => count($this->parsedFunctions),
			'internalClasses' => count($broker->getClasses(Backend::INTERNAL_CLASSES)),
			'documentedClasses' => array_reduce($broker->getClasses(Backend::TOKENIZED_CLASSES), $documentedCounter),
			'documentedConstants' => array_reduce($this->parsedConstants->getArrayCopy(), $documentedCounter),
			'documentedFunctions' => array_reduce($this->parsedFunctions->getArrayCopy(), $documentedCounter),
			'documentedInternalClasses' => array_reduce($broker->getClasses(Backend::INTERNAL_CLASSES), $documentedCounter),
			'errors' => $errors
		);
	}

	/**
	 * Returns configuration.
	 *
	 * @return mixed
	 */
	public function getConfig()
	{
		return $this->config;
	}

	/**
	 * Returns parsed class list.
	 *
	 * @return \ArrayObject
	 */
	public function getParsedClasses()
	{
		return $this->parsedClasses;
	}

	/**
	 * Returns parsed constant list.
	 *
	 * @return \ArrayObject
	 */
	public function getParsedConstants()
	{
		return $this->parsedConstants;
	}

	/**
	 * Returns parsed function list.
	 *
	 * @return \ArrayObject
	 */
	public function getParsedFunctions()
	{
		return $this->parsedFunctions;
	}

	/**
	 * Wipes out the destination directory.
	 *
	 * @return boolean
	 */
	public function wipeOutDestination()
	{
		foreach ($this->getGeneratedFiles() as $path) {
			if (is_file($path) && !@unlink($path)) {
				return false;
			}
		}

		$archive = $this->getArchivePath();
		if (is_file($archive) && !@unlink($archive)) {
			return false;
		}

		return true;
	}

	/**
	 * Generates API documentation.
	 *
	 * @throws \RuntimeException If destination directory is not writable.
	 */
	public function generate()
	{
		@mkdir($this->config->destination, 0755, true);
		if (!is_dir($this->config->destination) || !is_writable($this->config->destination)) {
			throw new RuntimeException(sprintf('Directory "%s" isn\'t writable', $this->config->destination));
		}

		// Copy resources
		foreach ($this->config->template['resources'] as $resourceSource => $resourceDestination) {
			// File
			$resourcePath = $this->getTemplateDir() . DIRECTORY_SEPARATOR . $resourceSource;
			if (is_file($resourcePath)) {
				copy($resourcePath, $this->forceDir($this->config->destination . DIRECTORY_SEPARATOR . $resourceDestination));
				continue;
			}

			// Dir
			$iterator = Nette\Utils\Finder::findFiles('*')->from($resourcePath)->getIterator();
			foreach ($iterator as $item) {
				copy($item->getPathName(), $this->forceDir($this->config->destination . DIRECTORY_SEPARATOR . $resourceDestination . DIRECTORY_SEPARATOR . $iterator->getSubPathName()));
			}
		}

		// Categorize by packages and namespaces
		$this->categorize();

		// Prepare progressbar
		if ($this->config->progressbar) {
			$max = count($this->packages)
				+ count($this->namespaces)
				+ count($this->classes)
				+ count($this->interfaces)
				+ count($this->traits)
				+ count($this->exceptions)
				+ count($this->constants)
				+ count($this->functions)
				+ count($this->config->template['templates']['common'])
				+ (int) !empty($this->config->report)
				+ (int) $this->config->tree
				+ (int) $this->config->deprecated
				+ (int) $this->config->todo
				+ (int) $this->config->download
				+ (int) $this->isSitemapEnabled()
				+ (int) $this->isOpensearchEnabled()
				+ (int) $this->isRobotsEnabled();

			if ($this->config->sourceCode) {
				$tokenizedFilter = function(ReflectionClass $class) {
					return $class->isTokenized();
				};
				$max += count(array_filter($this->classes, $tokenizedFilter))
					+ count(array_filter($this->interfaces, $tokenizedFilter))
					+ count(array_filter($this->traits, $tokenizedFilter))
					+ count(array_filter($this->exceptions, $tokenizedFilter))
					+ count($this->constants)
					+ count($this->functions);
				unset($tokenizedFilter);
			}

			$this->prepareProgressBar($max);
		}

		// Prepare template
		$tmp = $this->config->destination . DIRECTORY_SEPARATOR . 'tmp';
		$this->deleteDir($tmp);
		@mkdir($tmp, 0755, true);
		$template = new Template($this);
		$template->setCacheStorage(new Nette\Caching\Storages\PhpFileStorage($tmp));
		$template->generator = self::NAME;
		$template->version = self::VERSION;
		$template->config = $this->config;

		$this->registerCustomTemplateMacros($template);

		// Common files
		$this->generateCommon($template);

		// Optional files
		$this->generateOptional($template);

		// List of poorly documented elements
		if (!empty($this->config->report)) {
			$this->generateReport();
		}

		// List of deprecated elements
		if ($this->config->deprecated) {
			$this->generateDeprecated($template);
		}

		// List of tasks
		if ($this->config->todo) {
			$this->generateTodo($template);
		}

		// Classes/interfaces/traits/exceptions tree
		if ($this->config->tree) {
			$this->generateTree($template);
		}

		// Generate packages summary
		$this->generatePackages($template);

		// Generate namespaces summary
		$this->generateNamespaces($template);

		// Generate classes, interfaces, traits, exceptions, constants and functions files
		$this->generateElements($template);

		// Generate ZIP archive
		if ($this->config->download) {
			$this->generateArchive();
		}

		// Delete temporary directory
		$this->deleteDir($tmp);
	}

	/**
	 * Loads template-specific macro and helper libraries.
	 *
	 * @param \ApiGen\Template $template Template instance
	 */
	private function registerCustomTemplateMacros(Template $template)
	{
		$latte = new Nette\Latte\Engine();

		if (!empty($this->config->template['options']['extensions'])) {
			$this->output("Loading custom template macro and helper libraries\n");
			$broker = new Broker(new Broker\Backend\Memory(), 0);

			$baseDir = dirname($this->config->template['config']);
			foreach ((array) $this->config->template['options']['extensions'] as $fileName) {
				$pathName = $baseDir . DIRECTORY_SEPARATOR . $fileName;
				if (is_file($pathName)) {
					try {
						$reflectionFile = $broker->processFile($pathName, true);

						foreach ($reflectionFile->getNamespaces() as $namespace) {
							foreach ($namespace->getClasses() as $class) {
								if ($class->isSubclassOf('ApiGen\\MacroSet')) {
									// Macro set

									include $pathName;
									call_user_func(array($class->getName(), 'install'), $latte->compiler);

									$this->output(sprintf("  %s (macro set)\n", $class->getName()));
								} elseif ($class->implementsInterface('ApiGen\\IHelperSet')) {
									// Helpers set

									include $pathName;
									$className = $class->getName();
									$template->registerHelperLoader(callback(new $className($template), 'loader'));

									$this->output(sprintf("  %s (helper set)\n", $class->getName()));
								}
							}
						}
					} catch (\Exception $e) {
						throw new \Exception(sprintf('Could not load macros and helpers from file "%s"', $pathName), 0, $e);
					}
				} else {
					throw new \Exception(sprintf('Helper file "%s" does not exist.', $pathName));
				}
			}
		}

		$template->registerFilter($latte);
	}

	/**
	 * Categorizes by packages and namespaces.
	 *
	 * @return \ApiGen\Generator
	 */
	private function categorize()
	{
		foreach (array('classes', 'constants', 'functions') as $type) {
			foreach ($this->{'parsed' . ucfirst($type)} as $elementName => $element) {
				if (!$element->isDocumented()) {
					continue;
				}

				$packageName = $element->getPseudoPackageName();
				$namespaceName = $element->getPseudoNamespaceName();

				if ($element instanceof ReflectionConstant) {
					$this->constants[$elementName] = $element;
					$this->packages[$packageName]['constants'][$elementName] = $element;
					$this->namespaces[$namespaceName]['constants'][$element->getShortName()] = $element;
				} elseif ($element instanceof ReflectionFunction) {
					$this->functions[$elementName] = $element;
					$this->packages[$packageName]['functions'][$elementName] = $element;
					$this->namespaces[$namespaceName]['functions'][$element->getShortName()] = $element;
				} elseif ($element->isInterface()) {
					$this->interfaces[$elementName] = $element;
					$this->packages[$packageName]['interfaces'][$elementName] = $element;
					$this->namespaces[$namespaceName]['interfaces'][$element->getShortName()] = $element;
				} elseif ($element->isTrait()) {
					$this->traits[$elementName] = $element;
					$this->packages[$packageName]['traits'][$elementName] = $element;
					$this->namespaces[$namespaceName]['traits'][$element->getShortName()] = $element;
				} elseif ($element->isException()) {
					$this->exceptions[$elementName] = $element;
					$this->packages[$packageName]['exceptions'][$elementName] = $element;
					$this->namespaces[$namespaceName]['exceptions'][$element->getShortName()] = $element;
				} else {
					$this->classes[$elementName] = $element;
					$this->packages[$packageName]['classes'][$elementName] = $element;
					$this->namespaces[$namespaceName]['classes'][$element->getShortName()] = $element;
				}
			}
		}

		// Select only packages or namespaces
		$userPackagesCount = count(array_diff(array_keys($this->packages), array('PHP', 'None')));
		$userNamespacesCount = count(array_diff(array_keys($this->namespaces), array('PHP', 'None')));

		$namespacesEnabled = ('auto' === $this->config->groups && ($userNamespacesCount > 0 || 0 === $userPackagesCount)) || 'namespaces' === $this->config->groups;
		$packagesEnabled = ('auto' === $this->config->groups && !$namespacesEnabled) || 'packages' === $this->config->groups;

		if ($namespacesEnabled) {
			$this->packages = array();
			$this->namespaces = $this->sortGroups($this->namespaces);
		} elseif ($packagesEnabled) {
			$this->namespaces = array();
			$this->packages = $this->sortGroups($this->packages);
		} else {
			$this->namespaces = array();
			$this->packages = array();
		}

		return $this;
	}

	/**
	 * Sorts and filters groups.
	 *
	 * @param array $groups
	 * @return array
	 */
	private function sortGroups(array $groups)
	{
		// Don't generate only 'None' groups
		if (1 === count($groups) && isset($groups['None'])) {
			return array();
		}

		$emptyList = array('classes' => array(), 'interfaces' => array(), 'traits' => array(), 'exceptions' => array(), 'constants' => array(), 'functions' => array());

		$groupNames = array_keys($groups);
		$lowerGroupNames = array_flip(array_map(function($y) {
			return strtolower($y);
		}, $groupNames));

		foreach ($groupNames as $groupName) {
			// Add missing parent groups
			$parent = '';
			foreach (explode('\\', $groupName) as $part) {
				$parent = ltrim($parent . '\\' . $part, '\\');
				if (!isset($lowerGroupNames[strtolower($parent)])) {
					$groups[$parent] = $emptyList;
				}
			}

			// Add missing element types
			foreach ($this->getElementTypes() as $type) {
				if (!isset($groups[$groupName][$type])) {
					$groups[$groupName][$type] = array();
				}
			}
		}

		$main = $this->config->main;
		uksort($groups, function($one, $two) use ($main) {
			// \ as separator has to be first
			$one = str_replace('\\', ' ', $one);
			$two = str_replace('\\', ' ', $two);

			if ($main) {
				if (0 === strpos($one, $main) && 0 !== strpos($two, $main)) {
					return -1;
				} elseif (0 !== strpos($one, $main) && 0 === strpos($two, $main)) {
					return 1;
				}
			}

			return strcasecmp($one, $two);
		});

		return $groups;
	}

	/**
	 * Generates common files.
	 *
	 * @param \ApiGen\Template $template Template
	 * @return \ApiGen\Generator
	 */
	private function generateCommon(Template $template)
	{
		$template->namespace = null;
		$template->namespaces = array_keys($this->namespaces);
		$template->package = null;
		$template->packages = array_keys($this->packages);
		$template->class = null;
		$template->classes = array_filter($this->classes, $this->getMainFilter());
		$template->interfaces = array_filter($this->interfaces, $this->getMainFilter());
		$template->traits = array_filter($this->traits, $this->getMainFilter());
		$template->exceptions = array_filter($this->exceptions, $this->getMainFilter());
		$template->constant = null;
		$template->constants = array_filter($this->constants, $this->getMainFilter());
		$template->function = null;
		$template->functions = array_filter($this->functions, $this->getMainFilter());
		$template->archive = basename($this->getArchivePath());

		// Elements for autocomplete
		$elements = array();
		$autocomplete = array_flip($this->config->autocomplete);
		foreach ($this->getElementTypes() as $type) {
			foreach ($this->$type as $element) {
				if ($element instanceof ReflectionClass) {
					if (isset($autocomplete['classes'])) {
						$elements[] = array('c', $element->getPrettyName());
					}
					if (isset($autocomplete['methods'])) {
						foreach ($element->getOwnMethods() as $method) {
							$elements[] = array('m', $method->getPrettyName());
						}
						foreach ($element->getOwnMagicMethods() as $method) {
							$elements[] = array('mm', $method->getPrettyName());
						}
					}
					if (isset($autocomplete['properties'])) {
						foreach ($element->getOwnProperties() as $property) {
							$elements[] = array('p', $property->getPrettyName());
						}
						foreach ($element->getOwnMagicProperties() as $property) {
							$elements[] = array('mp', $property->getPrettyName());
						}
					}
					if (isset($autocomplete['classconstants'])) {
						foreach ($element->getOwnConstants() as $constant) {
							$elements[] = array('cc', $constant->getPrettyName());
						}
					}
				} elseif ($element instanceof ReflectionConstant && isset($autocomplete['constants'])) {
					$elements[] = array('co', $element->getPrettyName());
				} elseif ($element instanceof ReflectionFunction && isset($autocomplete['functions'])) {
					$elements[] = array('f', $element->getPrettyName());
				}
			}
		}
		usort($elements, function($one, $two) {
			return strcasecmp($one[1], $two[1]);
		});
		$template->elements = $elements;

		foreach ($this->config->template['templates']['common'] as $source => $destination) {
			$template
				->setFile($this->getTemplateDir() . DIRECTORY_SEPARATOR . $source)
				->save($this->forceDir($this->config->destination . DIRECTORY_SEPARATOR . $destination));

			$this->incrementProgressBar();
		}

		unset($template->elements);

		$this->checkMemory();

		return $this;
	}

	/**
	 * Generates optional files.
	 *
	 * @param \ApiGen\Template $template Template
	 * @return \ApiGen\Generator
	 */
	private function generateOptional(Template $template)
	{
		if ($this->isSitemapEnabled()) {
			$template
				->setFile($this->getTemplatePath('sitemap', 'optional'))
				->save($this->forceDir($this->getTemplateFileName('sitemap', 'optional')));
			$this->incrementProgressBar();
		}
		if ($this->isOpensearchEnabled()) {
			$template
				->setFile($this->getTemplatePath('opensearch', 'optional'))
				->save($this->forceDir($this->getTemplateFileName('opensearch', 'optional')));
			$this->incrementProgressBar();
		}
		if ($this->isRobotsEnabled()) {
			$template
				->setFile($this->getTemplatePath('robots', 'optional'))
				->save($this->forceDir($this->getTemplateFileName('robots', 'optional')));
			$this->incrementProgressBar();
		}

		$this->checkMemory();

		return $this;
	}

	/**
	 * Generates list of poorly documented elements.
	 *
	 * @return \ApiGen\Generator
	 * @throws \RuntimeException If file isn't writable.
	 */
	private function generateReport()
	{
		// Function for element labels
		$that = $this;
		$labeler = function($element) use ($that) {
			if ($element instanceof ReflectionClass) {
				if ($element->isInterface()) {
					$label = 'interface';
				} elseif ($element->isTrait()) {
					$label = 'trait';
				} elseif ($element->isException()) {
					$label = 'exception';
				} else {
					$label = 'class';
				}
			} elseif ($element instanceof ReflectionMethod) {
				$label = 'method';
			} elseif ($element instanceof ReflectionFunction) {
				$label = 'function';
			} elseif ($element instanceof ReflectionConstant) {
				$label = 'constant';
			} elseif ($element instanceof ReflectionProperty) {
				$label = 'property';
			} elseif ($element instanceof ReflectionParameter) {
				$label = 'parameter';
			}
			return sprintf('%s %s', $label, $element->getPrettyName());
		};

		$list = array();
		foreach ($this->getElementTypes() as $type) {
			foreach ($this->$type as $parentElement) {
				$fileName = $this->unPharPath($parentElement->getFileName());

				if (!$parentElement->isValid()) {
					$list[$fileName][] = array('error', 0, sprintf('Duplicate %s', $labeler($parentElement)));
					continue;
				}

				// Skip elements not from the main project
				if (!$parentElement->isMain()) {
					continue;
				}

				// Internal elements don't have documentation
				if ($parentElement->isInternal()) {
					continue;
				}

				$elements = array($parentElement);
				if ($parentElement instanceof ReflectionClass) {
					$elements = array_merge(
						$elements,
						array_values($parentElement->getOwnMethods()),
						array_values($parentElement->getOwnConstants()),
						array_values($parentElement->getOwnProperties())
					);
				}

				$tokens = $parentElement->getBroker()->getFileTokens($parentElement->getFileName());

				foreach ($elements as $element) {
					$line = $element->getStartLine();
					$label = $labeler($element);

					$annotations = $element->getAnnotations();

					// Documentation
					if (empty($element->shortDescription)) {
						if (empty($annotations)) {
							$list[$fileName][] = array('error', $line, sprintf('Missing documentation of %s', $label));
							continue;
						}
						// Description
						$list[$fileName][] = array('error', $line, sprintf('Missing description of %s', $label));
					}

					// Documentation of method
					if ($element instanceof ReflectionMethod || $element instanceof ReflectionFunction) {
						// Parameters
						$unlimited = false;
						foreach ($element->getParameters() as $no => $parameter) {
							if (!isset($annotations['param'][$no])) {
								$list[$fileName][] = array('error', $line, sprintf('Missing documentation of %s', $labeler($parameter)));
								continue;
							}

							if (!preg_match('~^[\\w\\\\]+(?:\\[\\])?(?:\\|[\\w\\\\]+(?:\\[\\])?)*(?:\\s+\\$' . $parameter->getName() . ($parameter->isUnlimited() ? ',\\.{3}' : '') . ')?(?:\\s+.+)?$~s', $annotations['param'][$no])) {
								$list[$fileName][] = array('warning', $line, sprintf('Invalid documentation "%s" of %s', $annotations['param'][$no], $labeler($parameter)));
							}

							if ($unlimited && $parameter->isUnlimited()) {
								$list[$fileName][] = array('warning', $line, sprintf('More than one unlimited parameters of %s', $labeler($element)));
							} elseif ($parameter->isUnlimited()) {
								$unlimited = true;
							}

							unset($annotations['param'][$no]);
						}
						if (isset($annotations['param'])) {
							foreach ($annotations['param'] as $annotation) {
								$list[$fileName][] = array('warning', $line, sprintf('Existing documentation "%s" of nonexistent parameter of %s', $annotation, $label));
							}
						}

						// Return values
						$return = false;
						$tokens->seek($element->getStartPosition())
							->find(T_FUNCTION);
						while ($tokens->next() && $tokens->key() < $element->getEndPosition()) {
							$type = $tokens->getType();
							if (T_FUNCTION === $type) {
								// Skip annonymous functions
								$tokens->find('{')->findMatchingBracket();
							} elseif (T_RETURN === $type && !$tokens->skipWhitespaces()->is(';')) {
								// Skip return without return value
								$return = true;
								break;
							}
						}
						if ($return && !isset($annotations['return'])) {
							$list[$fileName][] = array('error', $line, sprintf('Missing documentation of return value of %s', $label));
						} elseif (isset($annotations['return'])) {
							if (!$return && 'void' !== $annotations['return'][0] && ($element instanceof ReflectionFunction || (!$parentElement->isInterface() && !$element->isAbstract()))) {
								$list[$fileName][] = array('warning', $line, sprintf('Existing documentation "%s" of nonexistent return value of %s', $annotations['return'][0], $label));
							} elseif (!preg_match('~^[\\w\\\\]+(?:\\[\\])?(?:\\|[\\w\\\\]+(?:\\[\\])?)*(?:\\s+.+)?$~s', $annotations['return'][0])) {
								$list[$fileName][] = array('warning', $line, sprintf('Invalid documentation "%s" of return value of %s', $annotations['return'][0], $label));
							}
						}
						if (isset($annotations['return'][1])) {
							$list[$fileName][] = array('warning', $line, sprintf('Duplicate documentation "%s" of return value of %s', $annotations['return'][1], $label));
						}

						// Throwing exceptions
						$throw = false;
						$tokens->seek($element->getStartPosition())
							->find(T_FUNCTION);
						while ($tokens->next() && $tokens->key() < $element->getEndPosition()) {
							$type = $tokens->getType();
							if (T_TRY === $type) {
								// Skip try
								$tokens->find('{')->findMatchingBracket();
							} elseif (T_THROW === $type) {
								$throw = true;
								break;
							}
						}
						if ($throw && !isset($annotations['throws'])) {
							$list[$fileName][] = array('error', $line, sprintf('Missing documentation of throwing an exception of %s', $label));
						} elseif (isset($annotations['throws'])	&& !preg_match('~^[\\w\\\\]+(?:\\|[\\w\\\\]+)*(?:\\s+.+)?$~s', $annotations['throws'][0])) {
							$list[$fileName][] = array('warning', $line, sprintf('Invalid documentation "%s" of throwing an exception of %s', $annotations['throws'][0], $label));
						}
					}

					// Data type of constants & properties
					if ($element instanceof ReflectionProperty || $element instanceof ReflectionConstant) {
						if (!isset($annotations['var'])) {
							$list[$fileName][] = array('error', $line, sprintf('Missing documentation of the data type of %s', $label));
						} elseif (!preg_match('~^[\\w\\\\]+(?:\\[\\])?(?:\\|[\\w\\\\]+(?:\\[\\])?)*(?:\\s+.+)?$~s', $annotations['var'][0])) {
							$list[$fileName][] = array('warning', $line, sprintf('Invalid documentation "%s" of the data type of %s', $annotations['var'][0], $label));
						}

						if (isset($annotations['var'][1])) {
							$list[$fileName][] = array('warning', $line, sprintf('Duplicate documentation "%s" of the data type of %s', $annotations['var'][1], $label));
						}
					}
				}
				unset($tokens);
			}
		}
		uksort($list, 'strcasecmp');

		$file = @fopen($this->config->report, 'w');
		if (false === $file) {
			throw new RuntimeException(sprintf('File "%s" isn\'t writable', $this->config->report));
		}
		fwrite($file, sprintf('<?xml version="1.0" encoding="UTF-8"?>%s', "\n"));
		fwrite($file, sprintf('<checkstyle version="1.3.0">%s', "\n"));
		foreach ($list as $fileName => $reports) {
			fwrite($file, sprintf('%s<file name="%s">%s', "\t", $fileName, "\n"));

			// Sort by line
			usort($reports, function($one, $two) {
				return strnatcmp($one[1], $two[1]);
			});

			foreach ($reports as $report) {
				list($severity, $line, $message) = $report;
				$message = preg_replace('~\\s+~u', ' ', $message);
				fwrite($file, sprintf('%s<error severity="%s" line="%s" message="%s" source="ApiGen.Documentation.Documentation"/>%s', "\t\t", $severity, $line, htmlspecialchars($message), "\n"));
			}

			fwrite($file, sprintf('%s</file>%s', "\t", "\n"));
		}
		fwrite($file, sprintf('</checkstyle>%s', "\n"));
		fclose($file);

		$this->incrementProgressBar();
		$this->checkMemory();

		return $this;
	}

	/**
	 * Generates list of deprecated elements.
	 *
	 * @param \ApiGen\Template $template Template
	 * @return \ApiGen\Generator
	 * @throws \RuntimeException If template is not set.
	 */
	private function generateDeprecated(Template $template)
	{
		$this->prepareTemplate('deprecated');

		$deprecatedFilter = function($element) {
			return $element->isDeprecated();
		};

		$template->deprecatedMethods = array();
		$template->deprecatedConstants = array();
		$template->deprecatedProperties = array();
		foreach (array_reverse($this->getElementTypes()) as $type) {
			$template->{'deprecated' . ucfirst($type)} = array_filter(array_filter($this->$type, $this->getMainFilter()), $deprecatedFilter);

			if ('constants' === $type || 'functions' === $type) {
				continue;
			}

			foreach ($this->$type as $class) {
				if (!$class->isMain()) {
					continue;
				}

				if ($class->isDeprecated()) {
					continue;
				}

				$template->deprecatedMethods = array_merge($template->deprecatedMethods, array_values(array_filter($class->getOwnMethods(), $deprecatedFilter)));
				$template->deprecatedConstants = array_merge($template->deprecatedConstants, array_values(array_filter($class->getOwnConstants(), $deprecatedFilter)));
				$template->deprecatedProperties = array_merge($template->deprecatedProperties, array_values(array_filter($class->getOwnProperties(), $deprecatedFilter)));
			}
		}
		usort($template->deprecatedMethods, array($this, 'sortMethods'));
		usort($template->deprecatedConstants, array($this, 'sortConstants'));
		usort($template->deprecatedFunctions, array($this, 'sortFunctions'));
		usort($template->deprecatedProperties, array($this, 'sortProperties'));

		$template
			->setFile($this->getTemplatePath('deprecated'))
			->save($this->forceDir($this->getTemplateFileName('deprecated')));

		foreach ($this->getElementTypes() as $type) {
			unset($template->{'deprecated' . ucfirst($type)});
		}
		unset($template->deprecatedMethods);
		unset($template->deprecatedProperties);

		$this->incrementProgressBar();
		$this->checkMemory();

		return $this;
	}

	/**
	 * Generates list of tasks.
	 *
	 * @param \ApiGen\Template $template Template
	 * @return \ApiGen\Generator
	 * @throws \RuntimeException If template is not set.
	 */
	private function generateTodo(Template $template)
	{
		$this->prepareTemplate('todo');

		$todoFilter = function($element) {
			return $element->hasAnnotation('todo');
		};

		$template->todoMethods = array();
		$template->todoConstants = array();
		$template->todoProperties = array();
		foreach (array_reverse($this->getElementTypes()) as $type) {
			$template->{'todo' . ucfirst($type)} = array_filter(array_filter($this->$type, $this->getMainFilter()), $todoFilter);

			if ('constants' === $type || 'functions' === $type) {
				continue;
			}

			foreach ($this->$type as $class) {
				if (!$class->isMain()) {
					continue;
				}

				$template->todoMethods = array_merge($template->todoMethods, array_values(array_filter($class->getOwnMethods(), $todoFilter)));
				$template->todoConstants = array_merge($template->todoConstants, array_values(array_filter($class->getOwnConstants(), $todoFilter)));
				$template->todoProperties = array_merge($template->todoProperties, array_values(array_filter($class->getOwnProperties(), $todoFilter)));
			}
		}
		usort($template->todoMethods, array($this, 'sortMethods'));
		usort($template->todoConstants, array($this, 'sortConstants'));
		usort($template->todoFunctions, array($this, 'sortFunctions'));
		usort($template->todoProperties, array($this, 'sortProperties'));

		$template
			->setFile($this->getTemplatePath('todo'))
			->save($this->forceDir($this->getTemplateFileName('todo')));

		foreach ($this->getElementTypes() as $type) {
			unset($template->{'todo' . ucfirst($type)});
		}
		unset($template->todoMethods);
		unset($template->todoProperties);

		$this->incrementProgressBar();
		$this->checkMemory();

		return $this;
	}

	/**
	 * Generates classes/interfaces/traits/exceptions tree.
	 *
	 * @param \ApiGen\Template $template Template
	 * @return \ApiGen\Generator
	 * @throws \RuntimeException If template is not set.
	 */
	private function generateTree(Template $template)
	{
		$this->prepareTemplate('tree');

		$classTree = array();
		$interfaceTree = array();
		$traitTree = array();
		$exceptionTree = array();

		$processed = array();
		foreach ($this->parsedClasses as $className => $reflection) {
			if (!$reflection->isMain() || !$reflection->isDocumented() || isset($processed[$className])) {
				continue;
			}

			if (null === $reflection->getParentClassName()) {
				// No parent classes
				if ($reflection->isInterface()) {
					$t = &$interfaceTree;
				} elseif ($reflection->isTrait()) {
					$t = &$traitTree;
				} elseif ($reflection->isException()) {
					$t = &$exceptionTree;
				} else {
					$t = &$classTree;
				}
			} else {
				foreach (array_values(array_reverse($reflection->getParentClasses())) as $level => $parent) {
					if (0 === $level) {
						// The topmost parent decides about the reflection type
						if ($parent->isInterface()) {
							$t = &$interfaceTree;
						} elseif ($parent->isTrait()) {
							$t = &$traitTree;
						} elseif ($parent->isException()) {
							$t = &$exceptionTree;
						} else {
							$t = &$classTree;
						}
					}
					$parentName = $parent->getName();

					if (!isset($t[$parentName])) {
						$t[$parentName] = array();
						$processed[$parentName] = true;
						ksort($t, SORT_STRING);
					}

					$t = &$t[$parentName];
				}
			}
			$t[$className] = array();
			ksort($t, SORT_STRING);
			$processed[$className] = true;
			unset($t);
		}

		$template->classTree = new Tree($classTree, $this->parsedClasses);
		$template->interfaceTree = new Tree($interfaceTree, $this->parsedClasses);
		$template->traitTree = new Tree($traitTree, $this->parsedClasses);
		$template->exceptionTree = new Tree($exceptionTree, $this->parsedClasses);

		$template
			->setFile($this->getTemplatePath('tree'))
			->save($this->forceDir($this->getTemplateFileName('tree')));

		unset($template->classTree);
		unset($template->interfaceTree);
		unset($template->traitTree);
		unset($template->exceptionTree);

		$this->incrementProgressBar();
		$this->checkMemory();

		return $this;
	}

	/**
	 * Generates packages summary.
	 *
	 * @param \ApiGen\Template $template Template
	 * @return \ApiGen\Generator
	 * @throws \RuntimeException If template is not set.
	 */
	private function generatePackages(Template $template)
	{
		if (empty($this->packages)) {
			return $this;
		}

		$this->prepareTemplate('package');

		$template->namespace = null;

		foreach ($this->packages as $packageName => $package) {
			$template->package = $packageName;
			$template->subpackages = array_filter($template->packages, function($subpackageName) use ($packageName) {
				return (bool) preg_match('~^' . preg_quote($packageName) . '\\\\[^\\\\]+$~', $subpackageName);
			});
			$template->classes = $package['classes'];
			$template->interfaces = $package['interfaces'];
			$template->traits = $package['traits'];
			$template->exceptions = $package['exceptions'];
			$template->constants = $package['constants'];
			$template->functions = $package['functions'];
			$template
				->setFile($this->getTemplatePath('package'))
				->save($this->config->destination . DIRECTORY_SEPARATOR . $template->getPackageUrl($packageName));

			$this->incrementProgressBar();
		}
		unset($template->subpackages);

		$this->checkMemory();

		return $this;
	}

	/**
	 * Generates namespaces summary.
	 *
	 * @param \ApiGen\Template $template Template
	 * @return \ApiGen\Generator
	 * @throws \RuntimeException If template is not set.
	 */
	private function generateNamespaces(Template $template)
	{
		if (empty($this->namespaces)) {
			return $this;
		}

		$this->prepareTemplate('namespace');

		$template->package = null;

		foreach ($this->namespaces as $namespaceName => $namespace) {
			$template->namespace = $namespaceName;
			$template->subnamespaces = array_filter($template->namespaces, function($subnamespaceName) use ($namespaceName) {
				return (bool) preg_match('~^' . preg_quote($namespaceName) . '\\\\[^\\\\]+$~', $subnamespaceName);
			});
			$template->classes = $namespace['classes'];
			$template->interfaces = $namespace['interfaces'];
			$template->traits = $namespace['traits'];
			$template->exceptions = $namespace['exceptions'];
			$template->constants = $namespace['constants'];
			$template->functions = $namespace['functions'];
			$template
				->setFile($this->getTemplatePath('namespace'))
				->save($this->config->destination . DIRECTORY_SEPARATOR . $template->getNamespaceUrl($namespaceName));

			$this->incrementProgressBar();
		}
		unset($template->subnamespaces);

		$this->checkMemory();

		return $this;
	}

	/**
	 * Generate classes, interfaces, traits, exceptions, constants and functions files.
	 *
	 * @param Template $template Template
	 * @return \ApiGen\Generator
	 * @throws \RuntimeException If template is not set.
	 */
	private function generateElements(Template $template)
	{
		if (!empty($this->classes) || !empty($this->interfaces) || !empty($this->traits) || !empty($this->exceptions)) {
			$this->prepareTemplate('class');
		}
		if (!empty($this->constants)) {
			$this->prepareTemplate('constant');
		}
		if (!empty($this->functions)) {
			$this->prepareTemplate('function');
		}
		if ($this->config->sourceCode) {
			$this->prepareTemplate('source');

			$fshl = new FSHL\Highlighter(new FSHL\Output\Html(), FSHL\Highlighter::OPTION_TAB_INDENT | FSHL\Highlighter::OPTION_LINE_COUNTER);
			$fshl->setLexer(new FSHL\Lexer\Php());
		}

		// Add @usedby annotation
		foreach ($this->getElementTypes() as $type) {
			foreach ($this->$type as $parentElement) {
				$elements = array($parentElement);
				if ($parentElement instanceof ReflectionClass) {
					$elements = array_merge(
						$elements,
						array_values($parentElement->getOwnMethods()),
						array_values($parentElement->getOwnConstants()),
						array_values($parentElement->getOwnProperties())
					);
				}
				foreach ($elements as $element) {
					$uses = $element->getAnnotation('uses');
					if (null === $uses) {
						continue;
					}
					foreach ($uses as $value) {
						list($link, $description) = preg_split('~\s+|$~', $value, 2);
						$resolved = $this->resolveElement($link, $element);
						if (null !== $resolved) {
							$resolved->addAnnotation('usedby', $element->getPrettyName() . ' ' . $description);
						}
					}
				}
			}
		}

		$template->package = null;
		$template->namespace = null;
		$template->classes = $this->classes;
		$template->interfaces = $this->interfaces;
		$template->traits = $this->traits;
		$template->exceptions = $this->exceptions;
		$template->constants = $this->constants;
		$template->functions = $this->functions;
		foreach ($this->getElementTypes() as $type) {
			foreach ($this->$type as $element) {
				if (!empty($this->namespaces)) {
					$template->namespace = $namespaceName = $element->getPseudoNamespaceName();
					$template->classes = $this->namespaces[$namespaceName]['classes'];
					$template->interfaces = $this->namespaces[$namespaceName]['interfaces'];
					$template->traits = $this->namespaces[$namespaceName]['traits'];
					$template->exceptions = $this->namespaces[$namespaceName]['exceptions'];
					$template->constants = $this->namespaces[$namespaceName]['constants'];
					$template->functions = $this->namespaces[$namespaceName]['functions'];
				} elseif (!empty($this->packages)) {
					$template->package = $packageName = $element->getPseudoPackageName();
					$template->classes = $this->packages[$packageName]['classes'];
					$template->interfaces = $this->packages[$packageName]['interfaces'];
					$template->traits = $this->packages[$packageName]['traits'];
					$template->exceptions = $this->packages[$packageName]['exceptions'];
					$template->constants = $this->packages[$packageName]['constants'];
					$template->functions = $this->packages[$packageName]['functions'];
				}

				$template->class = null;
				$template->constant = null;
				$template->function = null;
				if ($element instanceof ReflectionClass) {
					// Class
					$template->tree = array_merge(array_reverse($element->getParentClasses()), array($element));

					$template->directSubClasses = $element->getDirectSubClasses();
					uksort($template->directSubClasses, 'strcasecmp');
					$template->indirectSubClasses = $element->getIndirectSubClasses();
					uksort($template->indirectSubClasses, 'strcasecmp');

					$template->directImplementers = $element->getDirectImplementers();
					uksort($template->directImplementers, 'strcasecmp');
					$template->indirectImplementers = $element->getIndirectImplementers();
					uksort($template->indirectImplementers, 'strcasecmp');

					$template->directUsers = $element->getDirectUsers();
					uksort($template->directUsers, 'strcasecmp');
					$template->indirectUsers = $element->getIndirectUsers();
					uksort($template->indirectUsers, 'strcasecmp');

					$template->class = $element;

					$template
						->setFile($this->getTemplatePath('class'))
						->save($this->config->destination . DIRECTORY_SEPARATOR . $template->getClassUrl($element));
				} elseif ($element instanceof ReflectionConstant) {
					// Constant
					$template->constant = $element;

					$template
						->setFile($this->getTemplatePath('constant'))
						->save($this->config->destination . DIRECTORY_SEPARATOR . $template->getConstantUrl($element));
				} elseif ($element instanceof ReflectionFunction) {
					// Function
					$template->function = $element;

					$template
						->setFile($this->getTemplatePath('function'))
						->save($this->config->destination . DIRECTORY_SEPARATOR . $template->getFunctionUrl($element));
				}

				$this->incrementProgressBar();

				// Generate source codes
				if ($this->config->sourceCode && $element->isTokenized()) {
					$template->fileName = $this->getRelativePath($element->getFileName());
					$template->source = $fshl->highlight($this->toUtf(file_get_contents($element->getFileName()), $this->charsets[$element->getFileName()]));
					$template
						->setFile($this->getTemplatePath('source'))
						->save($this->config->destination . DIRECTORY_SEPARATOR . $template->getSourceUrl($element, false));

					$this->incrementProgressBar();
				}

				$this->checkMemory();
			}
		}

		return $this;
	}

	/**
	 * Creates ZIP archive.
	 *
	 * @return \ApiGen\Generator
	 * @throws \RuntimeException If something went wrong.
	 */
	private function generateArchive()
	{
		if (!extension_loaded('zip')) {
			throw new RuntimeException('Extension zip is not loaded');
		}

		$archive = new \ZipArchive();
		if (true !== $archive->open($this->getArchivePath(), \ZipArchive::CREATE)) {
			throw new RuntimeException('Could not open ZIP archive');
		}

		$archive->setArchiveComment(trim(sprintf('%s API documentation generated by %s %s on %s', $this->config->title, self::NAME, self::VERSION, date('Y-m-d H:i:s'))));

		$directory = Nette\Utils\Strings::webalize(trim(sprintf('%s API documentation', $this->config->title)), null, false);
		$destinationLength = strlen($this->config->destination);
		foreach ($this->getGeneratedFiles() as $file) {
			if (is_file($file)) {
				$archive->addFile($file, $directory . DIRECTORY_SEPARATOR . substr($file, $destinationLength + 1));
			}
		}

		if (false === $archive->close()) {
			throw new RuntimeException('Could not save ZIP archive');
		}

		$this->incrementProgressBar();
		$this->checkMemory();

		return $this;
	}

	/**
	 * Tries to resolve string as class, interface or exception name.
	 *
	 * @param string $className Class name description
	 * @param string $namespace Namespace name
	 * @return \ApiGen\ReflectionClass
	 */
	public function getClass($className, $namespace = '')
	{
		if (isset($this->parsedClasses[$namespace . '\\' . $className])) {
			$class = $this->parsedClasses[$namespace . '\\' . $className];
		} elseif (isset($this->parsedClasses[ltrim($className, '\\')])) {
			$class = $this->parsedClasses[ltrim($className, '\\')];
		} else {
			return null;
		}

		// Class is not "documented"
		if (!$class->isDocumented()) {
			return null;
		}

		return $class;
	}

	/**
	 * Tries to resolve type as constant name.
	 *
	 * @param string $constantName Constant name
	 * @param string $namespace Namespace name
	 * @return \ApiGen\ReflectionConstant
	 */
	public function getConstant($constantName, $namespace = '')
	{
		if (isset($this->parsedConstants[$namespace . '\\' . $constantName])) {
			$constant = $this->parsedConstants[$namespace . '\\' . $constantName];
		} elseif (isset($this->parsedConstants[ltrim($constantName, '\\')])) {
			$constant = $this->parsedConstants[ltrim($constantName, '\\')];
		} else {
			return null;
		}

		// Constant is not "documented"
		if (!$constant->isDocumented()) {
			return null;
		}

		return $constant;
	}

	/**
	 * Tries to resolve type as function name.
	 *
	 * @param string $functionName Function name
	 * @param string $namespace Namespace name
	 * @return \ApiGen\ReflectionFunction
	 */
	public function getFunction($functionName, $namespace = '')
	{
		if (isset($this->parsedFunctions[$namespace . '\\' . $functionName])) {
			$function = $this->parsedFunctions[$namespace . '\\' . $functionName];
		} elseif (isset($this->parsedFunctions[ltrim($functionName, '\\')])) {
			$function = $this->parsedFunctions[ltrim($functionName, '\\')];
		} else {
			return null;
		}

		// Function is not "documented"
		if (!$function->isDocumented()) {
			return null;
		}

		return $function;
	}

	/**
	 * Tries to parse a definition of a class/method/property/constant/function and returns the appropriate instance if successful.
	 *
	 * @param string $definition Definition
	 * @param \ApiGen\ReflectionElement|\ApiGen\ReflectionParameter $context Link context
	 * @param string $expectedName Expected element name
	 * @return \ApiGen\ReflectionElement|null
	 */
	public function resolveElement($definition, $context, &$expectedName = null)
	{
		// No simple type resolving
		static $types = array(
			'boolean' => 1, 'integer' => 1, 'float' => 1, 'string' => 1,
			'array' => 1, 'object' => 1, 'resource' => 1, 'callback' => 1,
			'callable' => 1, 'null' => 1, 'false' => 1, 'true' => 1, 'mixed' => 1
		);

		if (empty($definition) || isset($types[$definition])) {
			return null;
		}

		$originalContext = $context;

		if ($context instanceof ReflectionParameter && null === $context->getDeclaringClassName()) {
			// Parameter of function in namespace or global space
			$context = $this->getFunction($context->getDeclaringFunctionName());
		} elseif ($context instanceof ReflectionMethod || $context instanceof ReflectionParameter
			|| ($context instanceof ReflectionConstant && null !== $context->getDeclaringClassName())
			|| $context instanceof ReflectionProperty
		) {
			// Member of a class
			$context = $this->getClass($context->getDeclaringClassName());
		}

		if (null === $context) {
			return null;
		}

		// self, $this references
		if ('self' === $definition || '$this' === $definition) {
			return $context instanceof ReflectionClass ? $context : null;
		}

		$definitionBase = substr($definition, 0, strcspn($definition, '\\:'));
		$namespaceAliases = $context->getNamespaceAliases();
		if (!empty($definitionBase) && isset($namespaceAliases[$definitionBase]) && $definition !== ($className = \TokenReflection\Resolver::resolveClassFQN($definition, $namespaceAliases, $context->getNamespaceName()))) {
			// Aliased class
			$expectedName = $className;

			if (false === strpos($className, ':')) {
				return $this->getClass($className, $context->getNamespaceName());
			} else {
				$definition = $className;
			}
		} elseif ($class = $this->getClass($definition, $context->getNamespaceName())) {
			// Class
			return $class;
		} elseif ($constant = $this->getConstant($definition, $context->getNamespaceName())) {
			// Constant
			return $constant;
		} elseif (($function = $this->getFunction($definition, $context->getNamespaceName()))
			|| ('()' === substr($definition, -2) && ($function = $this->getFunction(substr($definition, 0, -2), $context->getNamespaceName())))
		) {
			// Function
			return $function;
		}

		if (($pos = strpos($definition, '::')) || ($pos = strpos($definition, '->'))) {
			// Class::something or Class->something
			if (0 === strpos($definition, 'parent::') && ($parentClassName = $context->getParentClassName())) {
				$context = $this->getClass($parentClassName);
			} elseif (0 !== strpos($definition, 'self::')) {
				$class = $this->getClass(substr($definition, 0, $pos), $context->getNamespaceName());

				if (null === $class) {
					$class = $this->getClass(\TokenReflection\Resolver::resolveClassFQN(substr($definition, 0, $pos), $context->getNamespaceAliases(), $context->getNamespaceName()));
				}

				$context = $class;
			}

			$definition = substr($definition, $pos + 2);
		} elseif ($originalContext instanceof ReflectionParameter) {
			return null;
		}

		// No usable context
		if (null === $context || $context instanceof ReflectionConstant || $context instanceof ReflectionFunction) {
			return null;
		}

		if ($context->hasProperty($definition)) {
			// Class property
			return $context->getProperty($definition);
		} elseif ('$' === $definition{0} && $context->hasProperty(substr($definition, 1))) {
			// Class $property
			return $context->getProperty(substr($definition, 1));
		} elseif ($context->hasMethod($definition)) {
			// Class method
			return $context->getMethod($definition);
		} elseif ('()' === substr($definition, -2) && $context->hasMethod(substr($definition, 0, -2))) {
			// Class method()
			return $context->getMethod(substr($definition, 0, -2));
		} elseif ($context->hasConstant($definition)) {
			// Class constant
			return $context->getConstant($definition);
		}

		return null;
	}

	/**
	 * Prints message if printing is enabled.
	 *
	 * @param string $message Output message
	 */
	public function output($message)
	{
		if (!$this->config->quiet) {
			echo $this->colorize($message);
		}
	}

	/**
	 * Colorizes message or removes placeholders if OS doesn't support colors.
	 *
	 * @param string $message
	 * @return string
	 */
	public function colorize($message)
	{
		static $placeholders = array(
			'@header@' => "\x1b[1;34m",
			'@count@' => "\x1b[1;34m",
			'@option@' => "\x1b[0;36m",
			'@value@' => "\x1b[0;32m",
			'@error@' => "\x1b[0;31m",
			'@c' => "\x1b[0m"
		);

		if (!$this->config->colors) {
			$placeholders = array_fill_keys(array_keys($placeholders), '');
		}

		return strtr($message, $placeholders);
	}

	/**
	 * Returns header.
	 *
	 * @return string
	 */
	public function getHeader()
	{
		$name = sprintf('%s %s', self::NAME, self::VERSION);
		return sprintf("@header@%hide@address.com\n%s\n", $name, str_repeat('-', strlen($name)));
	}

	/**
	 * Removes phar:// from the path.
	 *
	 * @param string $path Path
	 * @return string
	 */
	public function unPharPath($path)
	{
		if (0 === strpos($path, 'phar://')) {
			$path = substr($path, 7);
		}
		return $path;
	}

	/**
	 * Adds phar:// to the path.
	 *
	 * @param string $path Path
	 * @return string
	 */
	private function pharPath($path)
	{
		return 'phar://' . $path;
	}

	/**
	 * Checks if given path is a phar.
	 *
	 * @param string $path
	 * @return boolean
	 */
	private function isPhar($path)
	{
		return (bool) preg_match('~\\.phar(?:\\.zip|\\.tar|(?:(?:\\.tar)?(?:\\.gz|\\.bz2))|$)~i', $path);
	}

	/**
	 * Normalizes directory separators in given path.
	 *
	 * @param string $path Path
	 * @return string
	 */
	private function normalizePath($path)
	{
		$path = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $path);
		$path = str_replace('phar:\\\\', 'phar://', $path);
		return $path;
	}

	/**
	 * Prepares the progressbar.
	 *
	 * @param integer $maximum Maximum progressbar value
	 */
	private function prepareProgressBar($maximum = 1)
	{
		if (!$this->config->progressbar) {
			return;
		}

		$this->progressbar['current'] = 0;
		$this->progressbar['maximum'] = $maximum;
	}

	/**
	 * Increments the progressbar by one.
	 *
	 * @param integer $increment Progressbar increment
	 */
	private function incrementProgressBar($increment = 1)
	{
		if (!$this->config->progressbar) {
			return;
		}

		echo str_repeat(chr(0x08), $this->progressbar['width']);

		$this->progressbar['current'] += $increment;

		$percent = $this->progressbar['current'] / $this->progressbar['maximum'];

		$progress = str_pad(str_pad('>', round($percent * $this->progressbar['bar']), '=', STR_PAD_LEFT), $this->progressbar['bar'], ' ', STR_PAD_RIGHT);

		echo sprintf($this->progressbar['skeleton'], $progress, $percent * 100, round(memory_get_usage(true) / 1024 / 1024));

		if ($this->progressbar['current'] === $this->progressbar['maximum']) {
			echo "\n";
		}
	}

	/**
	 * Checks memory usage.
	 *
	 * @return \ApiGen\Generator
	 * @throws \RuntimeException If there is unsufficient reserve of memory.
	 */
	public function checkMemory()
	{
		static $limit = null;
		if (null === $limit) {
			$value = ini_get('memory_limit');
			$unit = substr($value, -1);
			if ('-1' === $value) {
				$limit = 0;
			} elseif ('G' === $unit) {
				$limit = (int) $value * 1024 * 1024 * 1024;
			} elseif ('M' === $unit) {
				$limit = (int) $value * 1024 * 1024;
			} else {
				$limit = (int) $value;
			}
		}

		if ($limit && memory_get_usage(true) / $limit >= 0.9) {
			throw new RuntimeException(sprintf('Used %d%% of the current memory limit, please increase the limit to generate the whole documentation.', round(memory_get_usage(true) / $limit * 100)));
		}

		return $this;
	}

	/**
	 * Detects character set for the given text.
	 *
	 * @param string $text Text
	 * @return string
	 */
	private function detectCharset($text)
	{
		// One character set
		if (1 === count($this->config->charset) && 'AUTO' !== $this->config->charset[0]) {
			return $this->config->charset[0];
		}

		static $charsets = array();
		if (empty($charsets)) {
			if (1 === count($this->config->charset) && 'AUTO' === $this->config->charset[0]) {
				// Autodetection
				$charsets = array(
					'Windows-1251', 'Windows-1252', 'ISO-8859-2', 'ISO-8859-1', 'ISO-8859-3', 'ISO-8859-4', 'ISO-8859-5', 'ISO-8859-6',
					'ISO-8859-7', 'ISO-8859-8', 'ISO-8859-9', 'ISO-8859-10', 'ISO-8859-13', 'ISO-8859-14', 'ISO-8859-15'
				);
			} else {
				// More character sets
				$charsets = $this->config->charset;
				if (false !== ($key = array_search('WINDOWS-1250', $charsets))) {
					// WINDOWS-1250 is not supported
					$charsets[$key] = 'ISO-8859-2';
				}
			}
			// Only supported character sets
			$charsets = array_intersect($charsets, mb_list_encodings());

			// UTF-8 have to be first
			array_unshift($charsets, 'UTF-8');
		}

		$charset = mb_detect_encoding($text, $charsets);
		// The previous function can not handle WINDOWS-1250 and returns ISO-8859-2 instead
		if ('ISO-8859-2' === $charset && preg_match('~[\x7F-\x9F\xBC]~', $text)) {
			$charset = 'WINDOWS-1250';
		}

		return $charset;
	}

	/**
	 * Converts text from given character set to UTF-8.
	 *
	 * @param string $text Text
	 * @param string $charset Character set
	 * @return string
	 */
	private function toUtf($text, $charset)
	{
		if ('UTF-8' === $charset) {
			return $text;
		}

		return @iconv($charset, 'UTF-8//TRANSLIT//IGNORE', $text);
	}

	/**
	 * Checks if sitemap.xml is enabled.
	 *
	 * @return boolean
	 */
	private function isSitemapEnabled()
	{
		return !empty($this->config->baseUrl) && $this->templateExists('sitemap', 'optional');
	}

	/**
	 * Checks if opensearch.xml is enabled.
	 *
	 * @return boolean
	 */
	private function isOpensearchEnabled()
	{
		return !empty($this->config->googleCseId) && !empty($this->config->baseUrl) && $this->templateExists('opensearch', 'optional');
	}

	/**
	 * Checks if robots.txt is enabled.
	 *
	 * @return boolean
	 */
	private function isRobotsEnabled()
	{
		return !empty($this->config->baseUrl) && $this->templateExists('robots', 'optional');
	}

	/**
	 * Sorts methods by FQN.
	 *
	 * @param \ApiGen\ReflectionMethod $one
	 * @param \ApiGen\ReflectionMethod $two
	 * @return integer
	 */
	private function sortMethods(ReflectionMethod $one, ReflectionMethod $two)
	{
		return strcasecmp($one->getDeclaringClassName() . '::' . $one->getName(), $two->getDeclaringClassName() . '::' . $two->getName());
	}

	/**
	 * Sorts constants by FQN.
	 *
	 * @param \ApiGen\ReflectionConstant $one
	 * @param \ApiGen\ReflectionConstant $two
	 * @return integer
	 */
	private function sortConstants(ReflectionConstant $one, ReflectionConstant $two)
	{
		return strcasecmp(($one->getDeclaringClassName() ?: $one->getNamespaceName()) . '\\' .  $one->getName(), ($two->getDeclaringClassName() ?: $two->getNamespaceName()) . '\\' .  $two->getName());
	}

	/**
	 * Sorts functions by FQN.
	 *
	 * @param \ApiGen\ReflectionFunction $one
	 * @param \ApiGen\ReflectionFunction $two
	 * @return integer
	 */
	private function sortFunctions(ReflectionFunction $one, ReflectionFunction $two)
	{
		return strcasecmp($one->getNamespaceName() . '\\' . $one->getName(), $two->getNamespaceName() . '\\' . $two->getName());
	}

	/**
	 * Sorts functions by FQN.
	 *
	 * @param \ApiGen\ReflectionProperty $one
	 * @param \ApiGen\ReflectionProperty $two
	 * @return integer
	 */
	private function sortProperties(ReflectionProperty $one, ReflectionProperty $two)
	{
		return strcasecmp($one->getDeclaringClassName() . '::' . $one->getName(), $two->getDeclaringClassName() . '::' . $two->getName());
	}

	/**
	 * Returns list of element types.
	 *
	 * @return array
	 */
	private function getElementTypes()
	{
		static $types = array('classes', 'interfaces', 'traits', 'exceptions', 'constants', 'functions');
		return $types;
	}

	/**
	 * Returns main filter.
	 *
	 * @return \Closure
	 */
	private function getMainFilter()
	{
		return function($element) {
			return $element->isMain();
		};
	}

	/**
	 * Returns ZIP archive path.
	 *
	 * @return string
	 */
	private function getArchivePath()
	{
		$name = trim(sprintf('%s API documentation', $this->config->title));
		return $this->config->destination . DIRECTORY_SEPARATOR . Nette\Utils\Strings::webalize($name) . '.zip';
	}

	/**
	 * Returns filename relative path to the source directory.
	 *
	 * @param string $fileName
	 * @return string
	 * @throws \InvalidArgumentException If relative path could not be determined.
	 */
	public function getRelativePath($fileName)
	{
		if (isset($this->symlinks[$fileName])) {
			$fileName = $this->symlinks[$fileName];
		}
		foreach ($this->config->source as $source) {
			if ($this->isPhar($source)) {
				$source = $this->pharPath($source);
			}
			if (0 === strpos($fileName, $source)) {
				return is_dir($source) ? str_replace('\\', '/', substr($fileName, strlen($source) + 1)) : basename($fileName);
			}
		}

		throw new InvalidArgumentException(sprintf('Could not determine "%s" relative path', $fileName));
	}

	/**
	 * Returns template directory.
	 *
	 * @return string
	 */
	private function getTemplateDir()
	{
		return dirname($this->config->templateConfig);
	}

	/**
	 * Returns template path.
	 *
	 * @param string $name Template name
	 * @param string $type Template type
	 * @return string
	 */
	private function getTemplatePath($name, $type = 'main')
	{
		return $this->getTemplateDir() . DIRECTORY_SEPARATOR . $this->config->template['templates'][$type][$name]['template'];
	}

	/**
	 * Returns template filename.
	 *
	 * @param string $name Template name
	 * @param string $type Template type
	 * @return string
	 */
	private function getTemplateFileName($name, $type = 'main')
	{
		return $this->config->destination . DIRECTORY_SEPARATOR . $this->config->template['templates'][$type][$name]['filename'];
	}

	/**
	 * Checks if template exists.
	 *
	 * @param string $name Template name
	 * @param string $type Template type
	 * @return string
	 */
	private function templateExists($name, $type = 'main')
	{
		return isset($this->config->template['templates'][$type][$name]);
	}

	/**
	 * Checks if template exists and creates dir.
	 *
	 * @param string $name
	 * @throws \RuntimeException If template is not set.
	 */
	private function prepareTemplate($name)
	{
		if (!$this->templateExists($name)) {
			throw new RuntimeException(sprintf('Template for "%s" is not set', $name));
		}

		$this->forceDir($this->getTemplateFileName($name));
		return $this;
	}

	/**
	 * Returns list of all generated files.
	 *
	 * @return array
	 */
	private function getGeneratedFiles()
	{
		$files = array();

		// Resources
		foreach ($this->config->template['resources'] as $item) {
			$path = $this->getTemplateDir() . DIRECTORY_SEPARATOR . $item;
			if (is_dir($path)) {
				$iterator = Nette\Utils\Finder::findFiles('*')->from($path)->getIterator();
				foreach ($iterator as $innerItem) {
					$files[] = $this->config->destination . DIRECTORY_SEPARATOR . $item . DIRECTORY_SEPARATOR . $iterator->getSubPathName();
				}
			} else {
				$files[] = $this->config->destination . DIRECTORY_SEPARATOR . $item;
			}
		}

		// Common files
		foreach ($this->config->template['templates']['common'] as $item) {
			$files[] = $this->config->destination . DIRECTORY_SEPARATOR . $item;
		}

		// Optional files
		foreach ($this->config->template['templates']['optional'] as $optional) {
			$files[] = $this->config->destination . DIRECTORY_SEPARATOR . $optional['filename'];
		}

		// Main files
		$masks = array_map(function($config) {
			return preg_replace('~%[^%]*?s~', '*', $config['filename']);
		}, $this->config->template['templates']['main']);
		$filter = function($item) use ($masks) {
			foreach ($masks as $mask) {
				if (fnmatch($mask, $item->getFilename())) {
					return true;
				}
			}
			return false;
		};

		foreach (Nette\Utils\Finder::findFiles('*')->filter($filter)->from($this->config->destination) as $item) {
			$files[] = $item->getPathName();
		}

		return $files;
	}

	/**
	 * Ensures a directory is created.
	 *
	 * @param string $path Directory path
	 * @return string
	 */
	private function forceDir($path)
	{
		@mkdir(dirname($path), 0755, true);
		return $path;
	}

	/**
	 * Deletes a directory.
	 *
	 * @param string $path Directory path
	 * @return boolean
	 */
	private function deleteDir($path)
	{
		if (!is_dir($path)) {
			return true;
		}

		foreach (Nette\Utils\Finder::find('*')->from($path)->childFirst() as $item) {
			if ($item->isDir()) {
				if (!@rmdir($item)) {
					return false;
				}
			} elseif ($item->isFile()) {
				if (!@unlink($item)) {
					return false;
				}
			}
		}
		if (!@rmdir($path)) {
			return false;
		}

		return true;
	}
}
Return current item: ApiGen