<commit_before><?php

/**
 * This file is part of the Arachne EntityLoader extenstion
 *
 * Copyright (c) <NAME> (<EMAIL>)
 *
 * For the full copyright and license information, please view the file license.md that was distributed with this source code.
 */

namespace Arachne\EntityLoader;

use Nette\Application\IPresenterFactory;
use Nette\Application\Request;
use Nette\Application\UI\Presenter;
use Nette\Application\UI\PresenterComponentReflection;
use Nette\Caching\Cache;
use Nette\Caching\IStorage;
use Nette\Object;
use Nette\Reflection\ClassType;
use Nette\Reflection\Method;
use Nette\Reflection\Property;

/**
 * @author <NAME>
 */
class ParameterFinder extends Object
{

	/** @var IPresenterFactory */
	protected $presenterFactory;

	/** @var Cache */
	protected $cache;

	public function __construct(IPresenterFactory $presenterFactory, IStorage $storage)
	{
		$this->presenterFactory = $presenterFactory;
		$this->cache = new Cache($storage, 'Arachne.EntityLoader');
	}

	/**
	 * Returns entity parameters based on the request.
	 * @return string[]
	 */
	public function getEntityParameters(Request $request)
	{
		$parameters = $request->getParameters();
		$presenter = $request->getPresenterName();
		$cacheKey = $this->getCacheKey($request);
		return $this->cache->load($cacheKey, function (& $dependencies) use ($presenter, $parameters) {
			return $this->loadEntityParameters($presenter, $parameters, $dependencies);
		});
	}

	/**
	 * @param string $presenter
	 * @param array $parameters
	 * @param array $dependencies
	 * @return string[]
	 */
	protected function loadEntityParameters($presenter, $parameters, & $dependencies)
	{
		$class = $this->presenterFactory->getPresenterClass($presenter);
		$presenterReflection = new PresenterComponentReflection($class);
		$files = [];

		// Presenter persistent entities
		$entities = $this->getPersistentEntities($presenterReflection);
		$files[] = $presenterReflection->getFileName();

		// Action entities
		$action = $parameters[Presenter::ACTION_KEY];
		$method = 'action' . $action;
		$element = $presenterReflection->hasCallableMethod($method) ? $presenterReflection->getMethod($method) : NULL;
		if (!$element) {
			$method = 'render' . $action;
			$element = $presenterReflection->hasCallableMethod($method) ? $presenterReflection->getMethod($method) : NULL;
		}
		if ($element) {
			$entities += $this->getMethodEntities($element);
			$files[] = $element->getFileName();
		}

		// Persistent component entities
		$components = [];
		foreach ($parameters as $key => $_) {
			$pos = strrpos($key, '-');
			if ($pos !== FALSE) {
				$component = substr($key, 0, $pos);
				if (!isset($components[$component])) {
					$reflection = $this->createReflection($presenterReflection, $component);
					$components[$component] = TRUE;
					if ($reflection) {
						$files[] = $reflection->getFileName();
						$entities += $this->getPersistentEntities($reflection, $component . '-');
					}
				}
			}
		}

		// Signal entities
		if (isset($parameters[Presenter::SIGNAL_KEY])) {
			$signal = $parameters[Presenter::SIGNAL_KEY];
			$pos = strrpos($signal, '-');
			if ($pos !== FALSE) {
				$component = substr($signal, 0, $pos);
				$signal = substr($signal, $pos + 1);
				$reflection = $this->createReflection($presenterReflection, $component);
				$prefix = $component . '-';
			} else {
				$reflection = $presenterReflection;
				$prefix = '';
			}
			$method = 'handle' . ucfirst($signal);
			if ($reflection && $reflection->hasCallableMethod($method)) {
				$element = $reflection->getMethod($method);
				$entities += $this->getMethodEntities($element, $prefix);
				$files[] = $element->getFileName();
			}
		}

		// $dependencies is passed by reference
		$dependencies = [
			Cache::FILES => $files,
		];

		return $entities;
	}

	/**
	 * @param ClassType $reflection
	 * @param string $component
	 * @return PresenterComponentReflection|NULL
	 */
	protected function createReflection(ClassType $reflection, $component)
	{
		$pos = strpos($component, '-');
		if ($pos !== FALSE) {
			$subComponent = substr($component, $pos + 1);
			$component = substr($component, 0, $pos);
		}
		$method = 'createComponent' . ucfirst($component);
		if ($reflection->hasMethod($method)) {
			$element = $reflection->getMethod($method);
			$class = $element->getAnnotation('return');
			if (!is_string($class)) {
				return;
			}
			if ($class[0] != '\\') {
				$class = $reflection->getNamespaceName() . '\\' . $class;
			}
			if (class_exists($class)) {
				if (isset($subComponent)) {
					return $this->createReflection(new ClassType($class), $subComponent);
				} else {
					return new PresenterComponentReflection($class);
				}
			} else {
				throw new InvalidStateException("Class '$class' from $reflection->name::$method @return annotation not found.");
			}
		}
	}

	/**
	 * @param Method $element
	 * @param string $prefix
	 * @param string $default
	 * @return string[]
	 */
	protected function getMethodEntities(Method $reflection, $prefix = NULL)
	{
		$parameters = [];
		foreach ($reflection->getParameters() as $parameter) {
			$parameters[] = $parameter->getName();
		}
		$entities = [];
		foreach ($reflection->getParameters() as $parameter) {
			$type = $parameter->getClassName();
			if ($type) {
				$entities[$prefix . $parameter->getName()] = $type;
			}
		}
		return $entities;
	}

	/**
	 * @param PresenterComponentReflection $reflection
	 * @param string
	 * @return string[]
	 */
	protected function getPersistentEntities(PresenterComponentReflection $reflection, $prefix = NULL)
	{
		$entities = [];
		foreach ($reflection->getPersistentParams() as $persistent => $_) {
			$parameter = new Property($reflection->getName(), $persistent);
			if (!$parameter->isStatic() && $parameter->hasAnnotation('var')) {
				// TODO: Use parser from Doctrine/Annotations to get correct class from use statements
				$entities[$prefix . $persistent] = $parameter->getAnnotation('var');
			}
		}
		return $entities;
	}

	/**
	 * @param Request $request
	 * @return string[]
	 */
	protected function getCacheKey(Request $request)
	{
		$parameters = $request->getParameters();
		$key = [<commit_msg>Improved persistent parameters support<commit_after><?php

/**
 * This file is part of the Arachne EntityLoader extenstion
 *
 * Copyright (c) <NAME> (<EMAIL>)
 *
 * For the full copyright and license information, please view the file license.md that was distributed with this source code.
 */

namespace Arachne\EntityLoader;

use Nette\Application\IPresenterFactory;
use Nette\Application\Request;
use Nette\Application\UI\Presenter;
use Nette\Application\UI\PresenterComponentReflection;
use Nette\Caching\Cache;
use Nette\Caching\IStorage;
use Nette\Object;
use Nette\Reflection\ClassType;
use Nette\Reflection\Method;
use Nette\Reflection\Property;

/**
 * @author <NAME>
 */
class ParameterFinder extends Object
{

	/** @var IPresenterFactory */
	protected $presenterFactory;

	/** @var Cache */
	protected $cache;

	/** @var string[] */
	private static $nonClassTypes = [
		'int',
		'integer',
		'float',
		'double',
		'bool',
		'boolean',
		'string',
		'array',
		'object',
		'resource',
		'null',
		'mixed',
	];

	public function __construct(IPresenterFactory $presenterFactory, IStorage $storage)
	{
		$this->presenterFactory = $presenterFactory;
		$this->cache = new Cache($storage, 'Arachne.EntityLoader');
	}

	/**
	 * Returns entity parameters based on the request.
	 * @return string[]
	 */
	public function getEntityParameters(Request $request)
	{
		$parameters = $request->getParameters();
		$presenter = $request->getPresenterName();
		$cacheKey = $this->getCacheKey($request);
		return $this->cache->load($cacheKey, function (& $dependencies) use ($presenter, $parameters) {
			return $this->loadEntityParameters($presenter, $parameters, $dependencies);
		});
	}

	/**
	 * @param string $presenter
	 * @param array $parameters
	 * @param array $dependencies
	 * @return string[]
	 */
	protected function loadEntityParameters($presenter, $parameters, & $dependencies)
	{
		$class = $this->presenterFactory->getPresenterClass($presenter);
		$presenterReflection = new PresenterComponentReflection($class);
		$files = [];

		// Presenter persistent entities
		$entities = $this->getPersistentEntities($presenterReflection);
		$files[] = $presenterReflection->getFileName();

		// Action entities
		$action = $parameters[Presenter::ACTION_KEY];
		$method = 'action' . $action;
		$element = $presenterReflection->hasCallableMethod($method) ? $presenterReflection->getMethod($method) : NULL;
		if (!$element) {
			$method = 'render' . $action;
			$element = $presenterReflection->hasCallableMethod($method) ? $presenterReflection->getMethod($method) : NULL;
		}
		if ($element) {
			$entities += $this->getMethodEntities($element);
			$files[] = $element->getFileName();
		}

		// Persistent component entities
		$components = [];
		foreach ($parameters as $key => $_) {
			$pos = strrpos($key, '-');
			if ($pos !== FALSE) {
				$component = substr($key, 0, $pos);
				if (!isset($components[$component])) {
					$reflection = $this->createReflection($presenterReflection, $component);
					$components[$component] = TRUE;
					if ($reflection) {
						$files[] = $reflection->getFileName();
						$entities += $this->getPersistentEntities($reflection, $component . '-');
					}
				}
			}
		}

		// Signal entities
		if (isset($parameters[Presenter::SIGNAL_KEY])) {
			$signal = $parameters[Presenter::SIGNAL_KEY];
			$pos = strrpos($signal, '-');
			if ($pos !== FALSE) {
				$component = substr($signal, 0, $pos);
				$signal = substr($signal, $pos + 1);
				$reflection = $this->createReflection($presenterReflection, $component);
				$prefix = $component . '-';
			} else {
				$reflection = $presenterReflection;
				$prefix = '';
			}
			$method = 'handle' . ucfirst($signal);
			if ($reflection && $reflection->hasCallableMethod($method)) {
				$element = $reflection->getMethod($method);
				$entities += $this->getMethodEntities($element, $prefix);
				$files[] = $element->getFileName();
			}
		}

		// $dependencies is passed by reference
		$dependencies = [
			Cache::FILES => $files,
		];

		return $entities;
	}

	/**
	 * @param ClassType $reflection
	 * @param string $component
	 * @return PresenterComponentReflection|NULL
	 */
	protected function createReflection(ClassType $reflection, $component)
	{
		$pos = strpos($component, '-');
		if ($pos !== FALSE) {
			$subComponent = substr($component, $pos + 1);
			$component = substr($component, 0, $pos);
		}
		$method = 'createComponent' . ucfirst($component);
		if ($reflection->hasMethod($method)) {
			$element = $reflection->getMethod($method);
			$class = $element->getAnnotation('return');
			if (!is_string($class)) {
				return;
			}
			if ($class[0] != '\\') {
				$class = $reflection->getNamespaceName() . '\\' . $class;
			}
			if (class_exists($class)) {
				if (isset($subComponent)) {
					return $this->createReflection(new ClassType($class), $subComponent);
				} else {
					return new PresenterComponentReflection($class);
				}
			} else {
				throw new InvalidStateException("Class '$class' from $reflection->name::$method @return annotation not found.");
			}
		}
	}

	/**
	 * @param Method $element
	 * @param string $prefix
	 * @param string $default
	 * @return string[]
	 */
	protected function getMethodEntities(Method $reflection, $prefix = NULL)
	{
		$parameters = [];
		foreach ($reflection->getParameters() as $parameter) {
			$parameters[] = $parameter->getName();
		}
		$entities = [];
		foreach ($reflection->getParameters() as $parameter) {
			$type = $parameter->getClassName();
			if ($type) {
				$entities[$prefix . $parameter->getName()] = $type;
			}
		}
		return $entities;
	}

	/**
	 * @param PresenterComponentReflection $reflection
	 * @param string
	 * @return string[]
	 */
	protected function getPersistentEntities(PresenterComponentReflection $reflection, $prefix = NULL)
	{
		$entities = [];
		foreach ($reflection->getPersistentParams() as $persistent => $_) {
			$parameter = new Property($reflection->getName(), $persistent);
			if (!$parameter->isStatic() && $parameter->hasAnnotation('var')) {
				// TODO: Use parser from Doctrine/Annotations to get correct class from use statements
				$type = (string) $parameter->getAnnotation('var');
				if (!in_array($type, self::$nonClassTypes) && preg_match('/^[[:alnum:]_\\\\]++$/', $type)) {
					$entities[$prefix . $persistent] = $type;
				}
			}
		}
		return $entities;
	}

	/**
	 * @param Request $request
	 * @return string[]
	 */
	protected function getCacheKey(Request $request)
	{
		$parameters = $request->getParameters();
		$key = [
{'fraction_non_alphanumeric': 0.16392280425364317, 'fraction_numerical': 0.0009452540370224497, 'mean_word_length': 4.1968890708145725, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 101, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4115343', 'n_tokens_mistral': 4345, 'n_tokens_neox': 3815, 'n_words': 840}