diff --git a/composer.json b/composer.json index 20ade09dbb97..7319385463db 100644 --- a/composer.json +++ b/composer.json @@ -83,6 +83,7 @@ "symfony/ldap": "self.version", "symfony/lock": "self.version", "symfony/mailer": "self.version", + "symfony/marshaller": "self.version", "symfony/messenger": "self.version", "symfony/mime": "self.version", "symfony/monolog-bridge": "self.version", @@ -180,7 +181,8 @@ "Symfony\\Component\\": "src/Symfony/Component/" }, "files": [ - "src/Symfony/Component/String/Resources/functions.php" + "src/Symfony/Component/String/Resources/functions.php", + "src/Symfony/Component/Marshaller/Resources/functions.php" ], "classmap": [ "src/Symfony/Component/Cache/Traits/ValueWrapper.php" diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/MarshallerCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/MarshallerCacheWarmer.php new file mode 100644 index 000000000000..1b5b56c510cd --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/MarshallerCacheWarmer.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\CacheWarmer; + +use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; +use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; +use Symfony\Component\Marshaller\Attribute\Marshallable; +use Symfony\Component\Marshaller\Context\ContextBuilderInterface; +use Symfony\Component\Marshaller\Exception\ExceptionInterface; +use Symfony\Component\Marshaller\MarshallableResolverInterface; +use Symfony\Component\VarExporter\ProxyHelper; + +use function Symfony\Component\Marshaller\marshal_generate; + +/** + * @author Mathias Arlaud + */ +final class MarshallerCacheWarmer implements CacheWarmerInterface +{ + /** + * @param iterable $contextBuilders + * @param list $formats + */ + public function __construct( + private readonly MarshallableResolverInterface $marshallableResolver, + private readonly iterable $contextBuilders, + private readonly string $templateCacheDir, + private readonly string $lazyObjectCacheDir, + private readonly array $formats, + private readonly bool $nullableData, + private readonly LoggerInterface $logger = new NullLogger(), + ) { + } + + public function warmUp(string $cacheDir): array + { + if (!file_exists($this->templateCacheDir)) { + mkdir($this->templateCacheDir, recursive: true); + } + + if (!file_exists($this->lazyObjectCacheDir)) { + mkdir($this->lazyObjectCacheDir, recursive: true); + } + + foreach ($this->marshallableResolver->resolve() as $class => $attribute) { + foreach ($this->formats as $format) { + $this->warmClassTemplate($class, $attribute, $format); + } + + $this->warmClassLazyObject($class); + } + + return []; + } + + public function isOptional(): bool + { + return true; + } + + /** + * @param class-string $class + */ + private function warmClassTemplate(string $class, Marshallable $attribute, string $format): void + { + if ($attribute->nullable ?? $this->nullableData) { + $class = '?'.$class; + } + + if (file_exists($path = sprintf('%s%s%s.%s.php', $this->templateCacheDir, \DIRECTORY_SEPARATOR, md5($class), $format))) { + return; + } + + try { + $context = ['cache_dir' => $this->templateCacheDir]; + + foreach ($this->contextBuilders as $contextBuilder) { + $context = $contextBuilder->buildMarshalContext($context, true); + } + + file_put_contents($path, marshal_generate($class, $format, $context)); + } catch (ExceptionInterface $e) { + $this->logger->debug('Cannot generate template for "{class}": {exception}', ['class' => $class, 'exception' => $e]); + } + } + + /** + * @param class-string $class + */ + private function warmClassLazyObject(string $class): void + { + if (file_exists($path = sprintf('%s%s%s.php', $this->lazyObjectCacheDir, \DIRECTORY_SEPARATOR, md5($class)))) { + return; + } + + file_put_contents($path, sprintf( + 'class %s%s', + sprintf('%sGhost', preg_replace('/\\\\/', '', $class)), + ProxyHelper::generateLazyGhost(new \ReflectionClass($class)), + )); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php index 7fa0fb289005..2db2178c5357 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php @@ -62,6 +62,9 @@ class UnusedTagsPass implements CompilerPassInterface 'kernel.reset', 'ldap', 'mailer.transport_factory', + 'marshaller.context_builder', + 'marshaller.hook.marshal', + 'marshaller.hook.unmarshal', 'messenger.bus', 'messenger.message_handler', 'messenger.receiver', diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index e11d610b42da..89235f467329 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -31,6 +31,7 @@ use Symfony\Component\Lock\Lock; use Symfony\Component\Lock\Store\SemaphoreStore; use Symfony\Component\Mailer\Mailer; +use Symfony\Component\Marshaller\Marshaller; use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Notifier\Notifier; use Symfony\Component\PropertyAccess\PropertyAccessor; @@ -185,6 +186,7 @@ public function getConfigTreeBuilder(): TreeBuilder $this->addHtmlSanitizerSection($rootNode, $enableIfStandalone); $this->addWebhookSection($rootNode, $enableIfStandalone); $this->addRemoteEventSection($rootNode, $enableIfStandalone); + $this->addMarshallerSection($rootNode, $enableIfStandalone); return $treeBuilder; } @@ -2388,4 +2390,39 @@ private function addHtmlSanitizerSection(ArrayNodeDefinition $rootNode, callable ->end() ; } + + private function addMarshallerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void + { + $rootNode + ->children() + ->arrayNode('marshaller') + ->info('Marshaller configuration') + ->{$enableIfStandalone('symfony/marshaller', Marshaller::class)}() + ->fixXmlConfig('marshallable_path') + ->children() + ->arrayNode('marshallable_paths') + ->info('Defines where the marshaller should look to find marshallable classes.') + ->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end() + ->prototype('scalar')->end() + ->end() + ->arrayNode('template_warm_up') + ->addDefaultsIfNotSet() + ->fixXmlConfig('format') + ->children() + ->arrayNode('formats') + ->info('Defines the formats that will be handled.') + ->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end() + ->prototype('scalar')->end() + ->end() + ->booleanNode('nullable_data') + ->info('Defines if nullable data should be handled by default.') + ->defaultFalse() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ; + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 6d1401463fcf..0596d823a06a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -15,6 +15,8 @@ use Doctrine\Common\Annotations\Reader; use Http\Client\HttpAsyncClient; use Http\Client\HttpClient; +use Symfony\Component\Marshaller\Context\ContextBuilderInterface; +use Symfony\Component\Marshaller\MarshallerInterface; use phpDocumentor\Reflection\DocBlockFactoryInterface; use phpDocumentor\Reflection\Types\ContextFactory; use PhpParser\Parser; @@ -37,8 +39,8 @@ use Symfony\Component\Cache\Adapter\ChainAdapter; use Symfony\Component\Cache\Adapter\TagAwareAdapter; use Symfony\Component\Cache\DependencyInjection\CachePoolPass; -use Symfony\Component\Cache\Marshaller\DefaultMarshaller; -use Symfony\Component\Cache\Marshaller\MarshallerInterface; +use Symfony\Component\Cache\Marshaller\DefaultMarshaller as CacheDefaultMarshaller; +use Symfony\Component\Cache\Marshaller\MarshallerInterface as CacheMarshallerInterface; use Symfony\Component\Cache\ResettableInterface; use Symfony\Component\Clock\ClockInterface; use Symfony\Component\Config\Definition\ConfigurationInterface; @@ -597,6 +599,10 @@ public function load(array $configs, ContainerBuilder $container) $this->registerHtmlSanitizerConfiguration($config['html_sanitizer'], $container, $loader); } + if ($this->readConfigEnabled('marshaller', $container, $config['marshaller'])) { + $this->registerMarshallerConfiguration($config['marshaller'], $container, $loader); + } + $this->addAnnotatedClassesToCompile([ '**\\Controller\\', '**\\Entity\\', @@ -652,7 +658,7 @@ public function load(array $configs, ContainerBuilder $container) $container->registerForAutoconfiguration(ResetInterface::class) ->addTag('kernel.reset', ['method' => 'reset']); - if (!interface_exists(MarshallerInterface::class)) { + if (!interface_exists(CacheMarshallerInterface::class)) { $container->registerForAutoconfiguration(ResettableInterface::class) ->addTag('kernel.reset', ['method' => 'reset']); } @@ -689,6 +695,8 @@ public function load(array $configs, ContainerBuilder $container) ->addTag('mime.mime_type_guesser'); $container->registerForAutoconfiguration(LoggerAwareInterface::class) ->addMethodCall('setLogger', [new Reference('logger')]); + $container->registerForAutoconfiguration(ContextBuilderInterface::class) + ->addTag('marshaller.context_builder'); $container->registerAttributeForAutoconfiguration(AsEventListener::class, static function (ChildDefinition $definition, AsEventListener $attribute, \ReflectionClass|\ReflectionMethod $reflector) { $tagAttributes = get_object_vars($attribute); @@ -2265,7 +2273,7 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder private function registerCacheConfiguration(array $config, ContainerBuilder $container): void { - if (!class_exists(DefaultMarshaller::class)) { + if (!class_exists(CacheDefaultMarshaller::class)) { $container->removeDefinition('cache.default_marshaller'); } @@ -2992,6 +3000,19 @@ private function registerHtmlSanitizerConfiguration(array $config, ContainerBuil } } + private function registerMarshallerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void + { + if (!interface_exists(MarshallerInterface::class)) { + throw new LogicException('Marshaller support cannot be enabled as the Marshaller component is not installed. Try running "composer require symfony/marshaller'); + } + + $container->setParameter('marshaller.marshallable_paths', $config['marshallable_paths']); + $container->setParameter('marshaller.template_warm_up.formats', $config['template_warm_up']['formats']); + $container->setParameter('marshaller.template_warm_up.nullable_data', $config['template_warm_up']['nullable_data']); + + $loader->load('marshaller.php'); + } + private function resolveTrustedHeaders(array $headers): int { $trustedHeaders = 0; diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index ffb96a23e5f5..75cdf575c9dd 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -56,6 +56,7 @@ use Symfony\Component\HttpKernel\DependencyInjection\RemoveEmptyControllerArgumentLocatorsPass; use Symfony\Component\HttpKernel\DependencyInjection\ResettableServicePass; use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\Marshaller\DependencyInjection\MarshallerPass; use Symfony\Component\Messenger\DependencyInjection\MessengerPass; use Symfony\Component\Mime\DependencyInjection\AddMimeTypeGuesserPass; use Symfony\Component\PropertyInfo\DependencyInjection\PropertyInfoPass; @@ -173,6 +174,7 @@ public function build(ContainerBuilder $container) $container->addCompilerPass(new RegisterReverseContainerPass(true)); $container->addCompilerPass(new RegisterReverseContainerPass(false), PassConfig::TYPE_AFTER_REMOVING); $container->addCompilerPass(new RemoveUnusedSessionMarshallingHandlerPass()); + $this->addCompilerPassIfExists($container, MarshallerPass::class); if ($container->getParameter('kernel.debug')) { $container->addCompilerPass(new EnableLoggerDebugModePass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -33); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.php index 2ea62a0b7188..a2d2f82e58b8 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.php @@ -76,6 +76,11 @@ ->private() ->tag('cache.pool') + ->set('cache.marshaller') + ->parent('cache.system') + ->private() + ->tag('cache.pool') + ->set('cache.adapter.system', AdapterInterface::class) ->abstract() ->factory([AbstractAdapter::class, 'createSystemCache']) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/marshaller.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/marshaller.php new file mode 100644 index 000000000000..5e9aa876230f --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/marshaller.php @@ -0,0 +1,165 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader\Configurator; + +use Symfony\Bundle\FrameworkBundle\CacheWarmer\MarshallerCacheWarmer; +use Symfony\Component\Marshaller\CachedMarshallableResolver; +use Symfony\Component\Marshaller\Context\ContextBuilder\CachedContextBuilder; +use Symfony\Component\Marshaller\Context\ContextBuilder\FormatterAttributeContextBuilder; +use Symfony\Component\Marshaller\Context\ContextBuilder\HookContextBuilder; +use Symfony\Component\Marshaller\Context\ContextBuilder\InstantiatorContextBuilder; +use Symfony\Component\Marshaller\Context\ContextBuilder\NameAttributeContextBuilder; +use Symfony\Component\Marshaller\Instantiator\LazyInstantiator; +use Symfony\Component\Marshaller\MarshallableResolver; +use Symfony\Component\Marshaller\MarshallableResolverInterface; +use Symfony\Component\Marshaller\Marshaller; +use Symfony\Component\Marshaller\MarshallerInterface; +use Symfony\Component\Marshaller\Type\PhpstanTypeExtractor; +use Symfony\Component\Marshaller\Type\ReflectionTypeExtractor; +use Symfony\Component\Marshaller\Type\TypeExtractorInterface; +use Symfony\Component\Marshaller\Hook\Marshal as MarshalHook; +use Symfony\Component\Marshaller\Hook\Unmarshal as UnmarshalHook; + +return static function (ContainerConfigurator $container) { + $container->parameters() + ->set('marshaller.cache_dir.template', '%kernel.cache_dir%/marshaller/template') + ->set('marshaller.cache_dir.lazy_object', '%kernel.cache_dir%/marshaller/lazy_object') + ; + + $container->services() + // Marshaller + ->set('marshaller', Marshaller::class) + ->args([ + tagged_iterator('marshaller.context_builder'), + param('marshaller.cache_dir.template') + ]) + ->alias(MarshallerInterface::class, 'marshaller') + + // Type extractors + ->set('marshaller.type_extractor.reflection', ReflectionTypeExtractor::class) + ->lazy() + ->tag('proxy', ['interface' => TypeExtractorInterface::class]) + + ->set('marshaller.type_extractor.phpstan', PhpstanTypeExtractor::class) + ->decorate('marshaller.type_extractor.reflection') + ->args([ + service('marshaller.type_extractor.phpstan.inner'), + ]) + ->lazy() + ->tag('proxy', ['interface' => TypeExtractorInterface::class]) + + ->alias('marshaller.type_extractor', 'marshaller.type_extractor.reflection') + + // Context builders + ->set('marshaller.context_builder.hook', HookContextBuilder::class) + ->args([ + tagged_iterator('marshaller.hook.marshal', 'name'), + tagged_iterator('marshaller.hook.unmarshal', 'name'), + ]) + ->tag('marshaller.context_builder', ['priority' => -1024]) + + ->set('marshaller.context_builder.instantiator', InstantiatorContextBuilder::class) + ->args([ + service('marshaller.instantiator.lazy'), + ]) + ->tag('marshaller.context_builder', ['priority' => -1024]) + + ->set('marshaller.context_builder.name_attribute', NameAttributeContextBuilder::class) + ->args([ + service('marshaller.marshallable_resolver'), + ]) + ->tag('marshaller.context_builder', ['priority' => -1024]) + + ->set('marshaller.context_builder.name_attribute.cached', CachedContextBuilder::class) + ->decorate('marshaller.context_builder.name_attribute') + ->args([ + service('marshaller.context_builder.name_attribute.cached.inner'), + 'marshaller.context.name_attribute', + 'property_name', + service('cache.marshaller')->ignoreOnInvalid(), + ]) + + ->set('marshaller.context_builder.formatter_attribute', FormatterAttributeContextBuilder::class) + ->args([ + service('marshaller.marshallable_resolver'), + ]) + ->tag('marshaller.context_builder', ['priority' => -1024]) + + ->set('marshaller.context_builder.formatter_attribute.cached', CachedContextBuilder::class) + ->decorate('marshaller.context_builder.formatter_attribute') + ->args([ + service('marshaller.context_builder.formatter_attribute.cached.inner'), + 'marshaller.context.formatter_attribute', + 'property_formatter', + service('cache.marshaller')->ignoreOnInvalid(), + ]) + + // Hooks + ->set('marshaller.hook.marshal.object', MarshalHook\ObjectHook::class) + ->args([ + service('marshaller.type_extractor'), + ]) + ->tag('marshaller.hook.marshal', ['name' => 'object']) + + ->set('marshaller.hook.marshal.property', MarshalHook\PropertyHook::class) + ->args([ + service('marshaller.type_extractor'), + ]) + ->tag('marshaller.hook.marshal', ['name' => 'property']) + + ->set('marshaller.hook.unmarshal.object', UnmarshalHook\ObjectHook::class) + ->args([ + service('marshaller.type_extractor'), + ]) + ->tag('marshaller.hook.unmarshal', ['name' => 'object']) + + ->set('marshaller.hook.unmarshal.property', UnmarshalHook\PropertyHook::class) + ->args([ + service('marshaller.type_extractor'), + ]) + ->tag('marshaller.hook.unmarshal', ['name' => 'property']) + + // Marshallable resolvers + ->set('marshaller.marshallable_resolver', MarshallableResolver::class) + ->args([ + param('marshaller.marshallable_paths'), + ]) + + ->set('marshaller.marshallable_resolver.cached', CachedMarshallableResolver::class) + ->decorate('marshaller.marshallable_resolver') + ->args([ + service('marshaller.marshallable_resolver.cached.inner'), + service('cache.marshaller')->ignoreOnInvalid(), + ]) + + ->alias(MarshallableResolverInterface::class, 'marshaller.marshallable_resolver') + + // Object instantiators + ->set('marshaller.instantiator.lazy', LazyInstantiator::class) + ->args([ + param('marshaller.cache_dir.lazy_object'), + ]) + + // Cache + ->set('marshaller.cache_warmer', MarshallerCacheWarmer::class) + ->args([ + service('marshaller.marshallable_resolver'), + tagged_iterator('marshaller.context_builder'), + param('marshaller.cache_dir.template'), + param('marshaller.cache_dir.lazy_object'), + param('marshaller.template_warm_up.formats'), + param('marshaller.template_warm_up.nullable_data'), + service('logger')->ignoreOnInvalid(), + ]) + ->tag('kernel.cache_warmer') + ; +}; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/CacheWarmer/MarshallerCacheWarmerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/CacheWarmer/MarshallerCacheWarmerTest.php new file mode 100644 index 000000000000..a77840124979 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/CacheWarmer/MarshallerCacheWarmerTest.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\Tests\CacheWarmer; + +use Symfony\Bundle\FrameworkBundle\CacheWarmer\MarshallerCacheWarmer; +use Symfony\Bundle\FrameworkBundle\Tests\TestCase; +use Symfony\Component\Marshaller\Attribute\Marshallable; +use Symfony\Component\Marshaller\MarshallableResolverInterface; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\ClassicDummy; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\DummyWithMethods; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\DummyWithQuotes; + +class MarshallerCacheWarmerTest extends TestCase +{ + private string $templateCacheDir; + private string $lazyObjectCacheDir; + + protected function setUp(): void + { + parent::setUp(); + + $this->templateCacheDir = sprintf('%s/symfony_marshaller_template', sys_get_temp_dir()); + + if (is_dir($this->templateCacheDir)) { + array_map('unlink', glob($this->templateCacheDir.'/*')); + rmdir($this->templateCacheDir); + } + + $this->lazyObjectCacheDir = sprintf('%s/symfony_marshaller_lazy_object', sys_get_temp_dir()); + + if (is_dir($this->lazyObjectCacheDir)) { + array_map('unlink', glob($this->lazyObjectCacheDir.'/*')); + rmdir($this->lazyObjectCacheDir); + } + } + + /** + * @dataProvider warmUpTemplateDataProvider + * + * @param list $expectedClasses + */ + public function testWarmUpTemplate(array $expectedClasses, bool $nullableData) + { + $marshallableResolver = $this->createStub(MarshallableResolverInterface::class); + $marshallableResolver->method('resolve')->willReturn(new \ArrayIterator([ + ClassicDummy::class => new Marshallable(), + DummyWithQuotes::class => new Marshallable(true), + DummyWithMethods::class => new Marshallable(false), + ])); + + (new MarshallerCacheWarmer($marshallableResolver, [], $this->templateCacheDir, $this->lazyObjectCacheDir, ['json'], $nullableData))->warmUp('useless'); + + $expectedTemplates = array_map(fn (string $c): string => sprintf('%s/%s.json.php', $this->templateCacheDir, md5($c)), $expectedClasses); + + $this->assertSame($expectedTemplates, glob($this->templateCacheDir.'/*')); + } + + /** + * @return iterable, 1: bool}> + */ + public function warmUpTemplateDataProvider(): iterable + { + yield [[ClassicDummy::class, DummyWithMethods::class, '?'.DummyWithQuotes::class], false]; + yield [['?'.ClassicDummy::class, DummyWithMethods::class, '?'.DummyWithQuotes::class], true]; + } + + public function testWarmUpLazyObject() + { + $marshallableResolver = $this->createStub(MarshallableResolverInterface::class); + $marshallableResolver->method('resolve')->willReturn(new \ArrayIterator([ + ClassicDummy::class => new Marshallable(), + DummyWithQuotes::class => new Marshallable(), + DummyWithMethods::class => new Marshallable(), + ])); + + (new MarshallerCacheWarmer($marshallableResolver, [], $this->templateCacheDir, $this->lazyObjectCacheDir, ['json'], false))->warmUp('useless'); + + $expectedTemplates = array_map(fn (string $c): string => sprintf('%s/%s.php', $this->lazyObjectCacheDir, md5($c)), [ + ClassicDummy::class, + DummyWithQuotes::class, + DummyWithMethods::class, + ]); + + $this->assertSame($expectedTemplates, glob($this->lazyObjectCacheDir.'/*')); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index e11042d66e94..45d2d34267e2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -23,6 +23,7 @@ use Symfony\Component\HttpClient\HttpClient; use Symfony\Component\Lock\Store\SemaphoreStore; use Symfony\Component\Mailer\Mailer; +use Symfony\Component\Marshaller\Marshaller; use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Notifier\Notifier; use Symfony\Component\RateLimiter\Policy\TokenBucketLimiter; @@ -701,6 +702,14 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor 'remote-event' => [ 'enabled' => false, ], + 'marshaller' => [ + 'enabled' => !class_exists(FullStack::class) && class_exists(Marshaller::class), + 'marshallable_paths' => [], + 'template_warm_up' => [ + 'nullable_data' => false, + 'formats' => [], + ], + ], ]; } } diff --git a/src/Symfony/Component/Marshaller/.gitattributes b/src/Symfony/Component/Marshaller/.gitattributes new file mode 100644 index 000000000000..84c7add058fb --- /dev/null +++ b/src/Symfony/Component/Marshaller/.gitattributes @@ -0,0 +1,4 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore diff --git a/src/Symfony/Component/Marshaller/.gitignore b/src/Symfony/Component/Marshaller/.gitignore new file mode 100644 index 000000000000..c49a5d8df5c6 --- /dev/null +++ b/src/Symfony/Component/Marshaller/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/src/Symfony/Component/Marshaller/Attribute/Formatter.php b/src/Symfony/Component/Marshaller/Attribute/Formatter.php new file mode 100644 index 000000000000..a815630e021b --- /dev/null +++ b/src/Symfony/Component/Marshaller/Attribute/Formatter.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Attribute; + +use Symfony\Component\Marshaller\Exception\InvalidArgumentException; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +#[\Attribute(\Attribute::TARGET_PROPERTY)] +final class Formatter +{ + /** + * @param string|array{0: string, 1: string}|null $marshal + * @param string|array{0: string, 1: string}|null $unmarshal + */ + public function __construct( + public readonly string|array|null $marshal = null, + public readonly string|array|null $unmarshal = null, + ) { + if (null !== $marshal && !\is_callable($marshal)) { + throw new InvalidArgumentException(sprintf('Parameter "$marshal" of attribute "%s" must be a valid callable.', self::class)); + } + + if (null !== $unmarshal && !\is_callable($unmarshal)) { + throw new InvalidArgumentException(sprintf('Parameter "$unmarshal" of attribute "%s" must be a valid callable.', self::class)); + } + } +} diff --git a/src/Symfony/Component/Marshaller/Attribute/Marshallable.php b/src/Symfony/Component/Marshaller/Attribute/Marshallable.php new file mode 100644 index 000000000000..3ca583f615f3 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Attribute/Marshallable.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Attribute; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +#[\Attribute(\Attribute::TARGET_CLASS)] +final class Marshallable +{ + public function __construct( + public readonly ?bool $nullable = null, + ) { + } +} diff --git a/src/Symfony/Component/Marshaller/Attribute/Name.php b/src/Symfony/Component/Marshaller/Attribute/Name.php new file mode 100644 index 000000000000..5959dcf42741 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Attribute/Name.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Attribute; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +#[\Attribute(\Attribute::TARGET_PROPERTY)] +final class Name +{ + public function __construct( + public readonly string $name, + ) { + } +} diff --git a/src/Symfony/Component/Marshaller/CHANGELOG.md b/src/Symfony/Component/Marshaller/CHANGELOG.md new file mode 100644 index 000000000000..67d966ac573e --- /dev/null +++ b/src/Symfony/Component/Marshaller/CHANGELOG.md @@ -0,0 +1,8 @@ +CHANGELOG +========= + +6.4 +--- + + * added the component + diff --git a/src/Symfony/Component/Marshaller/CachedMarshallableResolver.php b/src/Symfony/Component/Marshaller/CachedMarshallableResolver.php new file mode 100644 index 000000000000..2de7b371bcb3 --- /dev/null +++ b/src/Symfony/Component/Marshaller/CachedMarshallableResolver.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller; + +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Marshaller\Util\CachedTrait; + +/** + * @author Mathias Arlaud + */ +final class CachedMarshallableResolver implements MarshallableResolverInterface +{ + use CachedTrait; + + public function __construct( + private readonly MarshallableResolverInterface $resolver, + private readonly CacheItemPoolInterface|null $cacheItemPool = null, + ) { + } + + public function resolve(): iterable + { + yield from $this->getCached('marshaller.marshallable', function (): array { + $marshallables = []; + foreach ($this->resolver->resolve() as $class => $marshallable) { + $marshallables[$class] = $marshallable; + } + + return $marshallables; + }); + } +} diff --git a/src/Symfony/Component/Marshaller/Context/ContextBuilder/CachedContextBuilder.php b/src/Symfony/Component/Marshaller/Context/ContextBuilder/CachedContextBuilder.php new file mode 100644 index 000000000000..d39d63618109 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Context/ContextBuilder/CachedContextBuilder.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Context\ContextBuilder; + +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Marshaller\Context\ContextBuilderInterface; +use Symfony\Component\Marshaller\Util\CachedTrait; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +final class CachedContextBuilder implements ContextBuilderInterface +{ + use CachedTrait; + + public function __construct( + private readonly ContextBuilderInterface $contextBuilder, + private readonly string $contextKey, + private readonly string $cacheKey, + private readonly CacheItemPoolInterface|null $cacheItemPool = null, + ) { + } + + public function buildMarshalContext(array $context, bool $willGenerateTemplate): array + { + $cachedContextPart = $this->getCached($this->cacheKey.'_marshal', fn () => $this->contextBuilder->buildMarshalContext($context, $willGenerateTemplate)['_symfony'][$this->contextKey] ?? null); + + if (null !== $cachedContextPart) { + $context['_symfony'][$this->contextKey] = $cachedContextPart; + } + + return $context; + } + + public function buildUnmarshalContext(array $context): array + { + $cachedContextPart = $this->getCached($this->cacheKey.'_unmarshal', fn () => $this->contextBuilder->buildUnmarshalContext($context)['_symfony'][$this->contextKey] ?? null); + + if (null !== $cachedContextPart) { + $context['_symfony'][$this->contextKey] = $cachedContextPart; + } + + return $context; + } +} diff --git a/src/Symfony/Component/Marshaller/Context/ContextBuilder/FormatterAttributeContextBuilder.php b/src/Symfony/Component/Marshaller/Context/ContextBuilder/FormatterAttributeContextBuilder.php new file mode 100644 index 000000000000..191380eab132 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Context/ContextBuilder/FormatterAttributeContextBuilder.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Context\ContextBuilder; + +use Symfony\Component\Marshaller\Attribute\Formatter; +use Symfony\Component\Marshaller\Context\ContextBuilderInterface; +use Symfony\Component\Marshaller\MarshallableResolverInterface; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +final class FormatterAttributeContextBuilder implements ContextBuilderInterface +{ + public function __construct( + private readonly MarshallableResolverInterface $marshallableResolver, + ) { + } + + public function buildMarshalContext(array $context, bool $willGenerateTemplate): array + { + if (!$willGenerateTemplate) { + return $context; + } + + return $this->addPropertyFormattersToContext($context); + } + + public function buildUnmarshalContext(array $context): array + { + return $this->addPropertyFormattersToContext($context); + } + + /** + * @param array $context + * + * @return array + */ + private function addPropertyFormattersToContext(array $context): array + { + foreach ($this->marshallableResolver->resolve() as $className => $_) { + $context = $this->addPropertyFormatters($className, $context); + } + + return $context; + } + + /** + * @param class-string $className + * @param array $context + * + * @return array + */ + private function addPropertyFormatters(string $className, array $context): array + { + foreach ((new \ReflectionClass($className))->getProperties() as $property) { + foreach ($property->getAttributes() as $attribute) { + if (Formatter::class !== $attribute->getName()) { + continue; + } + + $propertyIdentifier = sprintf('%s::$%s', $property->getDeclaringClass()->getName(), $property->getName()); + + /** @var Formatter $attributeInstance */ + $attributeInstance = $attribute->newInstance(); + + if (null !== $attributeInstance->marshal) { + $context['_symfony']['property_formatter'][$propertyIdentifier]['marshal'] = $attributeInstance->marshal; + } + + if (null !== $attributeInstance->unmarshal) { + $context['_symfony']['property_formatter'][$propertyIdentifier]['unmarshal'] = $attributeInstance->unmarshal; + } + + break; + } + } + + return $context; + } +} diff --git a/src/Symfony/Component/Marshaller/Context/ContextBuilder/HookContextBuilder.php b/src/Symfony/Component/Marshaller/Context/ContextBuilder/HookContextBuilder.php new file mode 100644 index 000000000000..81472d8bfcfa --- /dev/null +++ b/src/Symfony/Component/Marshaller/Context/ContextBuilder/HookContextBuilder.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Context\ContextBuilder; + +use Symfony\Component\Marshaller\Context\ContextBuilderInterface; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +final class HookContextBuilder implements ContextBuilderInterface +{ + /** + * @param iterable $marshalHooks + * @param iterable $unmarshalHooks + */ + public function __construct( + private readonly iterable $marshalHooks, + private readonly iterable $unmarshalHooks, + ) { + } + + public function buildMarshalContext(array $context, bool $willGenerateTemplate): array + { + if (!$willGenerateTemplate) { + return $context; + } + + foreach ($this->marshalHooks as $hookName => $hook) { + if (!isset($context['hooks']['marshal'][$hookName])) { + $context['hooks']['marshal'][$hookName] = $hook; + } + } + + return $context; + } + + public function buildUnmarshalContext(array $context): array + { + foreach ($this->unmarshalHooks as $hookName => $hook) { + if (!isset($context['hooks']['unmarshal'][$hookName])) { + $context['hooks']['unmarshal'][$hookName] = $hook; + } + } + + return $context; + } +} diff --git a/src/Symfony/Component/Marshaller/Context/ContextBuilder/InstantiatorContextBuilder.php b/src/Symfony/Component/Marshaller/Context/ContextBuilder/InstantiatorContextBuilder.php new file mode 100644 index 000000000000..fb815f1bca19 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Context/ContextBuilder/InstantiatorContextBuilder.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Context\ContextBuilder; + +use Symfony\Component\Marshaller\Context\ContextBuilderInterface; +use Symfony\Component\Marshaller\Exception\InvalidArgumentException; +use Symfony\Component\Marshaller\Instantiator\InstantiatorInterface; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +final class InstantiatorContextBuilder implements ContextBuilderInterface +{ + public function __construct( + private readonly InstantiatorInterface $lazyObjectInstantiator, + ) { + } + + public function buildMarshalContext(array $context, bool $willGenerateTemplate): array + { + return $context; + } + + public function buildUnmarshalContext(array $context): array + { + if (\is_callable($instantiator = $context['instantiator'] ?? null)) { + return $context; + } + + $context['instantiator'] = match ($instantiator) { + 'lazy', null => ($this->lazyObjectInstantiator)(...), + 'eager' => null, + default => throw new InvalidArgumentException('Context value "instantiator" must be "lazy", "eager", or a valid callable.'), + }; + + return $context; + } +} diff --git a/src/Symfony/Component/Marshaller/Context/ContextBuilder/NameAttributeContextBuilder.php b/src/Symfony/Component/Marshaller/Context/ContextBuilder/NameAttributeContextBuilder.php new file mode 100644 index 000000000000..c400672963fe --- /dev/null +++ b/src/Symfony/Component/Marshaller/Context/ContextBuilder/NameAttributeContextBuilder.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Context\ContextBuilder; + +use Symfony\Component\Marshaller\Attribute\Name; +use Symfony\Component\Marshaller\Context\ContextBuilderInterface; +use Symfony\Component\Marshaller\MarshallableResolverInterface; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +final class NameAttributeContextBuilder implements ContextBuilderInterface +{ + public function __construct( + private readonly MarshallableResolverInterface $marshallableResolver, + ) { + } + + public function buildMarshalContext(array $context, bool $willGenerateTemplate): array + { + if (!$willGenerateTemplate) { + return $context; + } + + return $this->addPropertyNamesToContext($context); + } + + public function buildUnmarshalContext(array $context): array + { + return $this->addPropertyNamesToContext($context); + } + + /** + * @param array $context + * + * @return array + */ + private function addPropertyNamesToContext(array $context): array + { + foreach ($this->marshallableResolver->resolve() as $className => $_) { + $context = $this->addPropertyNames($className, $context); + } + + return $context; + } + + /** + * @param class-string $className + * @param array $context + * + * @return array + */ + private function addPropertyNames(string $className, array $context): array + { + foreach ((new \ReflectionClass($className))->getProperties() as $property) { + foreach ($property->getAttributes() as $attribute) { + if (Name::class !== $attribute->getName()) { + continue; + } + + /** @var Name $attributeInstance */ + $attributeInstance = $attribute->newInstance(); + + $propertyIdentifier = sprintf('%s::$%s', $property->getDeclaringClass()->getName(), $property->getName()); + $context['_symfony']['property_name'][$propertyIdentifier] = $attributeInstance->name; + + $keyIdentifier = sprintf('%s[%s]', $property->getDeclaringClass()->getName(), $attributeInstance->name); + $context['_symfony']['property_name'][$keyIdentifier] = $property->getName(); + + break; + } + } + + return $context; + } +} diff --git a/src/Symfony/Component/Marshaller/Context/ContextBuilderInterface.php b/src/Symfony/Component/Marshaller/Context/ContextBuilderInterface.php new file mode 100644 index 000000000000..47af0ec4cb8a --- /dev/null +++ b/src/Symfony/Component/Marshaller/Context/ContextBuilderInterface.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Context; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +interface ContextBuilderInterface +{ + /** + * @param array $context + * + * @return array + */ + public function buildMarshalContext(array $context, bool $willGenerateTemplate): array; + + /** + * @param array $context + * + * @return array + */ + public function buildUnmarshalContext(array $context): array; +} diff --git a/src/Symfony/Component/Marshaller/Context/ContextInterface.php b/src/Symfony/Component/Marshaller/Context/ContextInterface.php new file mode 100644 index 000000000000..8559729bae87 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Context/ContextInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Context; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +interface ContextInterface +{ + /** + * @return array + */ + public function toArray(): array; +} diff --git a/src/Symfony/Component/Marshaller/Context/MarshalContext.php b/src/Symfony/Component/Marshaller/Context/MarshalContext.php new file mode 100644 index 000000000000..6f7850b9d94f --- /dev/null +++ b/src/Symfony/Component/Marshaller/Context/MarshalContext.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Context; + +use Symfony\Component\Marshaller\Hook\Marshal\ObjectHookInterface; +use Symfony\Component\Marshaller\Hook\Marshal\PropertyHookInterface; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +class MarshalContext implements ContextInterface +{ + /** + * @param array $options + */ + final public function __construct( + protected readonly array $options = [], + ) { + } + + public function toArray(): array + { + return $this->options; + } + + public function withType(string $type): self + { + return new self(['type' => $type] + $this->options); + } + + public function withJsonEncodeFlags(int $flags): self + { + return new self(['json_encode_flags' => $flags] + $this->options); + } + + /** + * @param array $unionSelector + */ + public function withUnionSelector(array $unionSelector): self + { + return new self(['union_selector' => $unionSelector] + $this->options); + } + + /** + * @param ObjectHookInterface|callable(string, string, array): array{type: string, accessor: string, context: array} $hook + * @param class-string|null $className + */ + public function withObjectHook(ObjectHookInterface|callable $hook, string $className = null): self + { + $hookName = $className ?? 'object'; + + return $this->withHook($hookName, $hook); + } + + /** + * @param PropertyHookInterface|callable(\ReflectionProperty, string, array): array{name: string, type: string, accessor: string, context: array} $hook + * @param class-string|null $className + */ + public function withPropertyHook(PropertyHookInterface|callable $hook, string $className = null, string $propertyName = null): self + { + $hookName = null !== $className && null !== $propertyName ? sprintf('%s::$%s', $className, $propertyName) : 'property'; + + return $this->withHook($hookName, $hook); + } + + private function withHook(string $hookName, callable $hook): self + { + $hooks = $this->options['hooks'] ?? []; + $hooks['marshal'][$hookName] = $hook; + + return new self(['hooks' => $hooks] + $this->options); + } +} diff --git a/src/Symfony/Component/Marshaller/Context/UnmarshalContext.php b/src/Symfony/Component/Marshaller/Context/UnmarshalContext.php new file mode 100644 index 000000000000..2f3ba15db4f7 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Context/UnmarshalContext.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Context; + +use Symfony\Component\Marshaller\Hook\Unmarshal\ObjectHookInterface; +use Symfony\Component\Marshaller\Hook\Unmarshal\PropertyHookInterface; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +class UnmarshalContext implements ContextInterface +{ + /** + * @param array $options + */ + final public function __construct( + protected readonly array $options = [], + ) { + } + + public function toArray(): array + { + return $this->options; + } + + public function withCollectErrors(bool $collectErrors = true): self + { + return new self(['collect_errors' => $collectErrors] + $this->options); + } + + public function withJsonDecodeFlags(int $flags): self + { + return new self(['json_decode_flags' => $flags] + $this->options); + } + + /** + * @param array $unionSelector + */ + public function withUnionSelector(array $unionSelector): self + { + return new self(['union_selector' => $unionSelector] + $this->options); + } + + /** + * @param ObjectHookInterface|callable(string, array): array{type: string, context: array} $hook + * @param class-string|null $className + */ + public function withObjectHook(ObjectHookInterface|callable $hook, string $className = null): self + { + $hookName = $className ?? 'object'; + + return $this->withHook($hookName, $hook); + } + + /** + * @param PropertyHookInterface|callable(\ReflectionClass, string, callable(): mixed, array): array{name?: string, value?: callable(): mixed, context?: array} $hook + * @param class-string|null $className + */ + public function withPropertyHook(PropertyHookInterface|callable $hook, string $className = null, string $propertyName = null): self + { + $hookName = null !== $className && null !== $propertyName ? sprintf('%s::$%s', $className, $propertyName) : 'property'; + + return $this->withHook($hookName, $hook); + } + + public function withEagerReading(): self + { + return new self(['lazy_reading' => false] + $this->options); + } + + public function withLazyReading(): self + { + return new self(['lazy_reading' => true] + $this->options); + } + + public function withEagerInstantiation(): self + { + return new self(['instantiator' => 'eager'] + $this->options); + } + + public function withLazyInstantiation(): self + { + return new self(['instantiator' => 'lazy'] + $this->options); + } + + /** + * @template T of object + * + * @param callable(\ReflectionClass, array, array): T $instantiator + */ + public function withCustomInstantiator(callable $instantiator): self + { + return new self(['instantiator' => $instantiator] + $this->options); + } + + private function withHook(string $hookName, callable $hook): self + { + $hooks = $this->options['hooks'] ?? []; + $hooks['unmarshal'][$hookName] = $hook; + + return new self(['hooks' => $hooks] + $this->options); + } +} diff --git a/src/Symfony/Component/Marshaller/DependencyInjection/MarshallerPass.php b/src/Symfony/Component/Marshaller/DependencyInjection/MarshallerPass.php new file mode 100644 index 000000000000..b99b7e655efc --- /dev/null +++ b/src/Symfony/Component/Marshaller/DependencyInjection/MarshallerPass.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +final class MarshallerPass implements CompilerPassInterface +{ + use PriorityTaggedServiceTrait; + + public function process(ContainerBuilder $container): void + { + if (!$container->hasDefinition('marshaller')) { + return; + } + + $container->getDefinition('marshaller') + ->replaceArgument(0, $this->findAndSortTaggedServices('marshaller.context_builder', $container)); + } +} diff --git a/src/Symfony/Component/Marshaller/Exception/CircularReferenceException.php b/src/Symfony/Component/Marshaller/Exception/CircularReferenceException.php new file mode 100644 index 000000000000..bd69d414ee72 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Exception/CircularReferenceException.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Exception; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +final class CircularReferenceException extends UnexpectedValueException +{ + /** + * @param class-string $className + */ + public function __construct(string $className) + { + parent::__construct(sprintf('A circular reference has been detected on class "%s".', $className)); + } +} diff --git a/src/Symfony/Component/Marshaller/Exception/ExceptionInterface.php b/src/Symfony/Component/Marshaller/Exception/ExceptionInterface.php new file mode 100644 index 000000000000..066c3f5abacc --- /dev/null +++ b/src/Symfony/Component/Marshaller/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Exception; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +interface ExceptionInterface extends \Throwable +{ +} diff --git a/src/Symfony/Component/Marshaller/Exception/InvalidArgumentException.php b/src/Symfony/Component/Marshaller/Exception/InvalidArgumentException.php new file mode 100644 index 000000000000..f5785f81d6c7 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Exception/InvalidArgumentException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Exception; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/src/Symfony/Component/Marshaller/Exception/InvalidConstructorArgumentException.php b/src/Symfony/Component/Marshaller/Exception/InvalidConstructorArgumentException.php new file mode 100644 index 000000000000..83d9e5aa367c --- /dev/null +++ b/src/Symfony/Component/Marshaller/Exception/InvalidConstructorArgumentException.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Exception; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +final class InvalidConstructorArgumentException extends InvalidArgumentException +{ + /** + * @param class-string $className + */ + public function __construct(string $parameter, string $className) + { + parent::__construct(sprintf('Parameter "%s" of "%s" constructor must either have a default value or be nullable.', $parameter, $className)); + } +} diff --git a/src/Symfony/Component/Marshaller/Exception/InvalidResourceException.php b/src/Symfony/Component/Marshaller/Exception/InvalidResourceException.php new file mode 100644 index 000000000000..07e92a90ff0b --- /dev/null +++ b/src/Symfony/Component/Marshaller/Exception/InvalidResourceException.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Exception; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +final class InvalidResourceException extends UnexpectedValueException +{ + /** + * @param resource $resource + */ + public function __construct(mixed $resource) + { + parent::__construct(sprintf('Resource "%s" is not valid.', stream_get_meta_data($resource)['uri'])); + } +} diff --git a/src/Symfony/Component/Marshaller/Exception/InvalidTypeException.php b/src/Symfony/Component/Marshaller/Exception/InvalidTypeException.php new file mode 100644 index 000000000000..8f7ce8cbf242 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Exception/InvalidTypeException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Exception; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +final class InvalidTypeException extends InvalidArgumentException +{ + public function __construct(string $type) + { + parent::__construct(sprintf('Invalid "%s" type.', $type)); + } +} diff --git a/src/Symfony/Component/Marshaller/Exception/LogicException.php b/src/Symfony/Component/Marshaller/Exception/LogicException.php new file mode 100644 index 000000000000..97d33e2e6000 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Exception/LogicException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Exception; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/src/Symfony/Component/Marshaller/Exception/MissingTypeException.php b/src/Symfony/Component/Marshaller/Exception/MissingTypeException.php new file mode 100644 index 000000000000..186078cf0075 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Exception/MissingTypeException.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Exception; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +final class MissingTypeException extends InvalidArgumentException +{ + public function __construct(string $path, string $type) + { + parent::__construct(sprintf('Type of "%s" %s has not been defined.', $path, $type)); + } + + public static function forProperty(\ReflectionProperty $property): self + { + return new self(sprintf('%s::$%s', $property->getDeclaringClass()->getName(), $property->getName()), 'property'); + } + + public static function forFunctionReturn(\ReflectionFunctionAbstract $function): self + { + /** @var \ReflectionClass|null $declaringClass */ + $declaringClass = $function instanceof \ReflectionMethod ? $function->getDeclaringClass() : $function->getClosureScopeClass(); + + $path = null !== $declaringClass + ? sprintf('%s::%s()', $declaringClass->getName(), $function->getName()) + : sprintf('%s()', $function->getName()); + + return new self($path, 'return'); + } + + public static function forFunctionParameter(\ReflectionParameter $parameter): self + { + $function = $parameter->getDeclaringFunction(); + + /** @var \ReflectionClass|null $declaringClass */ + $declaringClass = $function instanceof \ReflectionMethod ? $function->getDeclaringClass() : $function->getClosureScopeClass(); + + $path = null !== $declaringClass + ? sprintf('%s::%s($%s)', $declaringClass->getName(), $function->getName(), $parameter->getName()) + : sprintf('%s($%s)', $function->getName(), $parameter->getName()); + + return new self($path, 'property'); + } +} diff --git a/src/Symfony/Component/Marshaller/Exception/PartialUnmarshalException.php b/src/Symfony/Component/Marshaller/Exception/PartialUnmarshalException.php new file mode 100644 index 000000000000..8ab6c8c6ee9f --- /dev/null +++ b/src/Symfony/Component/Marshaller/Exception/PartialUnmarshalException.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Exception; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +final class PartialUnmarshalException extends UnexpectedValueException +{ + /** + * @param resource $resource + * @param list $errors + */ + public function __construct( + mixed $resource, + public readonly mixed $unmarshalled, + public readonly array $errors, + ) { + parent::__construct(sprintf('The "%s" resource has been partially unmarshalled.', stream_get_meta_data($resource)['uri'])); + } +} diff --git a/src/Symfony/Component/Marshaller/Exception/RuntimeException.php b/src/Symfony/Component/Marshaller/Exception/RuntimeException.php new file mode 100644 index 000000000000..d7f6c2960ec7 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Exception/RuntimeException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Exception; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/src/Symfony/Component/Marshaller/Exception/UnexpectedTokenException.php b/src/Symfony/Component/Marshaller/Exception/UnexpectedTokenException.php new file mode 100644 index 000000000000..d92ec26a8e54 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Exception/UnexpectedTokenException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Exception; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +final class UnexpectedTokenException extends UnexpectedValueException +{ + public function __construct(string $expected, string $actual) + { + parent::__construct(sprintf('Expected "%s" token, got "%s".', $expected, $actual)); + } +} diff --git a/src/Symfony/Component/Marshaller/Exception/UnexpectedTypeException.php b/src/Symfony/Component/Marshaller/Exception/UnexpectedTypeException.php new file mode 100644 index 000000000000..b66a6864eadc --- /dev/null +++ b/src/Symfony/Component/Marshaller/Exception/UnexpectedTypeException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Exception; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +final class UnexpectedTypeException extends UnexpectedValueException +{ +} diff --git a/src/Symfony/Component/Marshaller/Exception/UnexpectedValueException.php b/src/Symfony/Component/Marshaller/Exception/UnexpectedValueException.php new file mode 100644 index 000000000000..29c96b1aeada --- /dev/null +++ b/src/Symfony/Component/Marshaller/Exception/UnexpectedValueException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Exception; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +class UnexpectedValueException extends \UnexpectedValueException implements ExceptionInterface +{ +} diff --git a/src/Symfony/Component/Marshaller/Exception/UnsupportedFormatException.php b/src/Symfony/Component/Marshaller/Exception/UnsupportedFormatException.php new file mode 100644 index 000000000000..3c6d467cc6d6 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Exception/UnsupportedFormatException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Exception; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +final class UnsupportedFormatException extends InvalidArgumentException +{ + public function __construct(string $format) + { + parent::__construct(sprintf('"%s" format is not supported.', $format)); + } +} diff --git a/src/Symfony/Component/Marshaller/Exception/UnsupportedTypeException.php b/src/Symfony/Component/Marshaller/Exception/UnsupportedTypeException.php new file mode 100644 index 000000000000..5d9e18cd9a1a --- /dev/null +++ b/src/Symfony/Component/Marshaller/Exception/UnsupportedTypeException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Exception; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +final class UnsupportedTypeException extends InvalidArgumentException +{ + public function __construct(string $type) + { + parent::__construct(sprintf('"%s" type is not supported.', $type)); + } +} diff --git a/src/Symfony/Component/Marshaller/Hook/Marshal/ObjectHook.php b/src/Symfony/Component/Marshaller/Hook/Marshal/ObjectHook.php new file mode 100644 index 000000000000..fb8e827bea51 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Hook/Marshal/ObjectHook.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Hook\Marshal; + +use Symfony\Component\Marshaller\Exception\InvalidArgumentException; +use Symfony\Component\Marshaller\Type\TypeExtractorInterface; +use Symfony\Component\Marshaller\Type\TypeGenericsHelper; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +final class ObjectHook implements ObjectHookInterface +{ + private readonly TypeGenericsHelper $typeGenericsHelper; + + public function __construct( + private readonly TypeExtractorInterface $typeExtractor, + ) { + $this->typeGenericsHelper = new TypeGenericsHelper(); + } + + public function __invoke(string $type, string $accessor, array $context): array + { + return [ + 'type' => $type, + 'accessor' => $accessor, + 'context' => $this->addGenericParameterTypes($type, $context), + ]; + } + + /** + * @param array $context + * + * @return array + */ + private function addGenericParameterTypes(string $type, array $context): array + { + $generics = $this->typeGenericsHelper->extractGenerics($type); + + $genericType = $generics['genericType']; + $genericParameters = $generics['genericParameters']; + + if (!class_exists($genericType)) { + return $context; + } + + $templates = $this->typeExtractor->extractTemplateFromClass(new \ReflectionClass($genericType)); + + if (\count($templates) !== \count($genericParameters)) { + throw new InvalidArgumentException(sprintf('Given %d generic parameters in "%s", but %d templates are defined in "%s".', \count($genericParameters), $type, \count($templates), $genericType)); + } + + foreach ($genericParameters as $i => $genericParameter) { + $context['_symfony']['generic_parameter_types'][$genericType][$templates[$i]] = $genericParameter; + } + + return $context; + } +} diff --git a/src/Symfony/Component/Marshaller/Hook/Marshal/ObjectHookInterface.php b/src/Symfony/Component/Marshaller/Hook/Marshal/ObjectHookInterface.php new file mode 100644 index 000000000000..0bee4bcd5f83 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Hook/Marshal/ObjectHookInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Hook\Marshal; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +interface ObjectHookInterface +{ + /** + * @param array $context + * + * @return array{type?: string, accessor?: string, context?: array} + */ + public function __invoke(string $type, string $accessor, array $context): array; +} diff --git a/src/Symfony/Component/Marshaller/Hook/Marshal/PropertyHook.php b/src/Symfony/Component/Marshaller/Hook/Marshal/PropertyHook.php new file mode 100644 index 000000000000..46a4890a18be --- /dev/null +++ b/src/Symfony/Component/Marshaller/Hook/Marshal/PropertyHook.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Hook\Marshal; + +use Symfony\Component\Marshaller\Exception\InvalidArgumentException; +use Symfony\Component\Marshaller\Type\TypeExtractorInterface; +use Symfony\Component\Marshaller\Type\TypeGenericsHelper; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +final class PropertyHook implements PropertyHookInterface +{ + private TypeGenericsHelper|null $typeGenericsHelper = null; + + public function __construct( + private readonly TypeExtractorInterface $typeExtractor, + ) { + } + + public function __invoke(\ReflectionProperty $property, string $accessor, array $context): array + { + $propertyIdentifier = sprintf('%s::$%s', $property->getDeclaringClass()->getName(), $property->getName()); + + $propertyFormatter = isset($context['_symfony']['property_formatter'][$propertyIdentifier]['marshal']) + ? new \ReflectionFunction(\Closure::fromCallable($context['_symfony']['property_formatter'][$propertyIdentifier]['marshal'])) + : null; + + return [ + 'name' => $this->name($property, $propertyIdentifier, $context), + 'type' => $this->type($property, $propertyFormatter, $context), + 'accessor' => $this->accessor($propertyIdentifier, $propertyFormatter, $accessor, $context), + 'context' => $context, + ]; + } + + /** + * @param array $context + */ + private function name(\ReflectionProperty $property, string $propertyIdentifier, array $context): string + { + $name = $property->getName(); + + if (isset($context['_symfony']['property_name'][$propertyIdentifier])) { + $name = $context['_symfony']['property_name'][$propertyIdentifier]; + } + + return $name; + } + + /** + * @param array $context + */ + private function type(\ReflectionProperty $property, ?\ReflectionFunction $propertyFormatter, array $context): string + { + $propertyClass = $property->getDeclaringClass()->getName(); + + $type = null !== $propertyFormatter + ? $this->typeExtractor->extractFromFunctionReturn($propertyFormatter) + : $this->typeExtractor->extractFromProperty($property); + + // if method doesn't belong to the property class, ignore generic search + if (null !== $propertyFormatter && $propertyFormatter->getClosureScopeClass()?->getName() !== $propertyClass) { + $propertyClass = null; + } + + if ([] !== ($genericTypes = $context['_symfony']['generic_parameter_types'][$propertyClass] ?? [])) { + $this->typeGenericsHelper = $this->typeGenericsHelper ?? new TypeGenericsHelper(); + $type = $this->typeGenericsHelper->replaceGenericTypes($type, $genericTypes); + } + + return $type; + } + + /** + * @param array $context + */ + private function accessor(string $propertyIdentifier, ?\ReflectionFunction $propertyFormatter, string $accessor, array $context): string + { + if (null === $propertyFormatter) { + return $accessor; + } + + if (!$propertyFormatter->getClosureScopeClass()?->hasMethod($propertyFormatter->getName()) || !$propertyFormatter->isStatic()) { + throw new InvalidArgumentException(sprintf('Property formatter "%s" must be a static method.', $propertyIdentifier)); + } + + if (($returnType = $propertyFormatter->getReturnType()) instanceof \ReflectionNamedType && ('void' === $returnType->getName() || 'never' === $returnType->getName())) { + throw new InvalidArgumentException(sprintf('Return type of property formatter "%s" must not be "void" nor "never".', $propertyIdentifier)); + } + + if (null !== ($contextParameter = $propertyFormatter->getParameters()[1] ?? null)) { + $contextParameterType = $contextParameter->getType(); + + if (!$contextParameterType instanceof \ReflectionNamedType || 'array' !== $contextParameterType->getName()) { + throw new InvalidArgumentException(sprintf('Second argument of property formatter "%s" must be an array.', $propertyIdentifier)); + } + } + + return sprintf('%s::%s(%s, $context)', $propertyFormatter->getClosureScopeClass()->getName(), $propertyFormatter->getName(), $accessor); + } +} diff --git a/src/Symfony/Component/Marshaller/Hook/Marshal/PropertyHookInterface.php b/src/Symfony/Component/Marshaller/Hook/Marshal/PropertyHookInterface.php new file mode 100644 index 000000000000..08872b023e9e --- /dev/null +++ b/src/Symfony/Component/Marshaller/Hook/Marshal/PropertyHookInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Hook\Marshal; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +interface PropertyHookInterface +{ + /** + * @param array $context + * + * @return array{name?: string, type?: string, accessor?: string, context?: array} + */ + public function __invoke(\ReflectionProperty $property, string $accessor, array $context): array; +} diff --git a/src/Symfony/Component/Marshaller/Hook/Unmarshal/ObjectHook.php b/src/Symfony/Component/Marshaller/Hook/Unmarshal/ObjectHook.php new file mode 100644 index 000000000000..acce3ba6a2ad --- /dev/null +++ b/src/Symfony/Component/Marshaller/Hook/Unmarshal/ObjectHook.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Hook\Unmarshal; + +use Symfony\Component\Marshaller\Exception\InvalidArgumentException; +use Symfony\Component\Marshaller\Type\TypeExtractorInterface; +use Symfony\Component\Marshaller\Type\TypeGenericsHelper; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +final class ObjectHook implements ObjectHookInterface +{ + /** + * @var array{class_reflection: array>} + */ + private static array $cache = [ + 'class_reflection' => [], + ]; + + private readonly TypeGenericsHelper $typeGenericsHelper; + + public function __construct( + private readonly TypeExtractorInterface $typeExtractor, + ) { + $this->typeGenericsHelper = new TypeGenericsHelper(); + } + + public function __invoke(string $type, array $context): array + { + return [ + 'type' => $type, + 'context' => $this->addGenericParameterTypes($type, $context), + ]; + } + + /** + * @param array $context + * + * @return array + */ + private function addGenericParameterTypes(string $type, array $context): array + { + $generics = $this->typeGenericsHelper->extractGenerics($type); + + $genericType = $generics['genericType']; + $genericParameters = $generics['genericParameters']; + + if (!class_exists($genericType)) { + return $context; + } + + if (!isset(self::$cache['class_reflection'][$type])) { + self::$cache['class_reflection'][$type] = new \ReflectionClass($genericType); + } + + $templates = $this->typeExtractor->extractTemplateFromClass(self::$cache['class_reflection'][$type]); + + if (\count($templates) !== \count($genericParameters)) { + throw new InvalidArgumentException(sprintf('Given %d generic parameters in "%s", but %d templates are defined in "%s".', \count($genericParameters), $type, \count($templates), $genericType)); + } + + foreach ($genericParameters as $i => $genericParameter) { + $context['_symfony']['generic_parameter_types'][$genericType][$templates[$i]] = $genericParameter; + } + + return $context; + } +} diff --git a/src/Symfony/Component/Marshaller/Hook/Unmarshal/ObjectHookInterface.php b/src/Symfony/Component/Marshaller/Hook/Unmarshal/ObjectHookInterface.php new file mode 100644 index 000000000000..29de7a54f29b --- /dev/null +++ b/src/Symfony/Component/Marshaller/Hook/Unmarshal/ObjectHookInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Hook\Unmarshal; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +interface ObjectHookInterface +{ + /** + * @param array $context + * + * @return array{type?: string, context?: array} + */ + public function __invoke(string $type, array $context): array; +} diff --git a/src/Symfony/Component/Marshaller/Hook/Unmarshal/PropertyHook.php b/src/Symfony/Component/Marshaller/Hook/Unmarshal/PropertyHook.php new file mode 100644 index 000000000000..d1386155cc0d --- /dev/null +++ b/src/Symfony/Component/Marshaller/Hook/Unmarshal/PropertyHook.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Hook\Unmarshal; + +use Symfony\Component\Marshaller\Type\TypeExtractorInterface; +use Symfony\Component\Marshaller\Type\TypeGenericsHelper; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +final class PropertyHook implements PropertyHookInterface +{ + private readonly TypeGenericsHelper $typeGenericsHelper; + + /** + * @var array{value_type: array, class_has_property: array} + */ + private static array $cache = [ + 'value_type' => [], + 'class_has_property' => [], + ]; + + public function __construct( + private readonly TypeExtractorInterface $typeExtractor, + ) { + $this->typeGenericsHelper = new TypeGenericsHelper(); + } + + public function __invoke(\ReflectionClass $class, string $key, callable $value, array $context): array + { + $propertyClass = $class->getName(); + + /** @var string $propertyName */ + $propertyName = $context['_symfony']['property_name'][sprintf('%s[%s]', $propertyClass, $key)] ?? $key; + $cacheKey = $propertyIdentifier = $propertyClass.'::$'.$propertyName; + + if (!self::$cache['class_has_property'][$cacheKey] = self::$cache['class_has_property'][$cacheKey] ?? $class->hasProperty($propertyName)) { + return []; + } + + if (!isset($context['_symfony']['property_formatter'][$propertyIdentifier]['unmarshal'])) { + $valueType = self::$cache['value_type'][$cacheKey] = self::$cache['value_type'][$cacheKey] ?? $this->typeExtractor->extractFromProperty(new \ReflectionProperty($propertyClass, $propertyName)); + + if (isset($context['_symfony']['generic_parameter_types'][$propertyClass])) { + $valueType = $this->typeGenericsHelper->replaceGenericTypes($valueType, $context['_symfony']['generic_parameter_types'][$propertyClass]); + } + + return [ + 'name' => $propertyName, + 'value_provider' => fn () => $value($valueType, $context), + ]; + } + + $cacheKey .= ($propertyFormatterHash = json_encode($context['_symfony']['property_formatter'][$propertyIdentifier]['unmarshal'])); + + $propertyFormatter = \Closure::fromCallable($context['_symfony']['property_formatter'][$propertyIdentifier]['unmarshal']); + $propertyFormatterReflection = new \ReflectionFunction($propertyFormatter); + + $valueType = self::$cache['value_type'][$cacheKey] = self::$cache['value_type'][$cacheKey] ?? $this->typeExtractor->extractFromFunctionParameter($propertyFormatterReflection->getParameters()[0]); + + if ( + isset($context['_symfony']['generic_parameter_types'][$propertyClass]) + && $propertyFormatterReflection->getClosureScopeClass()?->getName() === $propertyClass + ) { + $valueType = $this->typeGenericsHelper->replaceGenericTypes($valueType, $context['_symfony']['generic_parameter_types'][$propertyClass]); + } + + return [ + 'name' => $propertyName, + 'value_provider' => fn () => $propertyFormatter($value($valueType, $context), $context), + ]; + } +} diff --git a/src/Symfony/Component/Marshaller/Hook/Unmarshal/PropertyHookInterface.php b/src/Symfony/Component/Marshaller/Hook/Unmarshal/PropertyHookInterface.php new file mode 100644 index 000000000000..436e8928e977 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Hook/Unmarshal/PropertyHookInterface.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Hook\Unmarshal; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +interface PropertyHookInterface +{ + /** + * @param \ReflectionClass $class + * @param callable(string, array): mixed $value + * @param array $context + * + * @return array{name?: string, value_provider?: callable(): mixed} + */ + public function __invoke(\ReflectionClass $class, string $key, callable $value, array $context): array; +} diff --git a/src/Symfony/Component/Marshaller/Instantiator/InstantiatorInterface.php b/src/Symfony/Component/Marshaller/Instantiator/InstantiatorInterface.php new file mode 100644 index 000000000000..57eca828ea7c --- /dev/null +++ b/src/Symfony/Component/Marshaller/Instantiator/InstantiatorInterface.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Instantiator; + +use Symfony\Component\Marshaller\Exception\InvalidConstructorArgumentException; +use Symfony\Component\Marshaller\Exception\UnexpectedTypeException; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +interface InstantiatorInterface +{ + /** + * @template T of object + * + * @param \ReflectionClass $class + * @param array $propertiesValues + * @param array $context + * + * @return T + * + * @throws InvalidConstructorArgumentException + * @throws UnexpectedTypeException + */ + public function __invoke(\ReflectionClass $class, array $propertiesValues, array $context): object; +} diff --git a/src/Symfony/Component/Marshaller/Instantiator/LazyInstantiator.php b/src/Symfony/Component/Marshaller/Instantiator/LazyInstantiator.php new file mode 100644 index 000000000000..5f47e41a7df5 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Instantiator/LazyInstantiator.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Instantiator; + +use Symfony\Component\VarExporter\ProxyHelper; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +final class LazyInstantiator implements InstantiatorInterface +{ + /** + * @var array{lazy_class_name: array} + */ + private static array $cache = [ + 'lazy_class_name' => [], + ]; + + /** + * @var array + */ + private static array $lazyClassesLoaded = []; + + public function __construct( + private readonly string $cacheDir, + ) { + } + + public function __invoke(\ReflectionClass $class, array $propertiesValues, array $context): object + { + if (!isset(self::$cache['lazy_class_name'][$className = $class->getName()])) { + /** @var class-string $lazyClassName */ + $lazyClassName = sprintf('%sGhost', preg_replace('/\\\\/', '', $className)); + self::$cache['lazy_class_name'][$className] = $lazyClassName; + } + + if (!isset(self::$lazyClassesLoaded[$className]) && !class_exists(self::$cache['lazy_class_name'][$className])) { + if (!file_exists($path = sprintf('%s%s%s.php', $this->cacheDir, \DIRECTORY_SEPARATOR, md5($className)))) { + if (!file_exists($this->cacheDir)) { + mkdir($this->cacheDir, recursive: true); + } + + $lazyClassName = sprintf('%sGhost', preg_replace('/\\\\/', '', $className)); + file_put_contents($path, sprintf('class %s%s', $lazyClassName, ProxyHelper::generateLazyGhost($class))); + } + + eval(file_get_contents(sprintf('%s%s%s.php', $this->cacheDir, \DIRECTORY_SEPARATOR, md5($className)))); + + self::$lazyClassesLoaded[$className] = true; + } + + $lazyGhost = self::$cache['lazy_class_name'][$className]::createLazyGhost($propertiesValues); + + return $lazyGhost; + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/Marshal/Compiler.php b/src/Symfony/Component/Marshaller/Internal/Marshal/Compiler.php new file mode 100644 index 000000000000..4e27785984a0 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Marshal/Compiler.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Marshal; + +/** + * @author Mathias Arlaud + * + * @internal + */ +final class Compiler +{ + private readonly Optimizer $optimizer; + private string $source = ''; + private int $indentationLevel = 0; + + public function __construct() + { + $this->optimizer = new Optimizer(); + } + + public function reset(): static + { + $this->source = ''; + $this->indentationLevel = 0; + + return $this; + } + + public function source(): string + { + return $this->source; + } + + public function compile(NodeInterface $node): static + { + $this->optimizer->optimize($node)->compile($this); + + return $this; + } + + public function subcompile(NodeInterface $node): string + { + $mainSource = $this->source; + $this->source = ''; + + $node->compile($this); + $subCompiledSource = $this->source; + + $this->source = $mainSource; + + return $subCompiledSource; + } + + public function raw(string $string): static + { + $this->source .= $string; + + return $this; + } + + public function line(string $line): static + { + $this->source .= str_repeat(' ', 4 * $this->indentationLevel).$line.\PHP_EOL; + + return $this; + } + + public function indent(): static + { + ++$this->indentationLevel; + + return $this; + } + + public function outdent(): static + { + $this->indentationLevel = max(0, $this->indentationLevel - 1); + + return $this; + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/Marshal/Json/JsonSyntax.php b/src/Symfony/Component/Marshaller/Internal/Marshal/Json/JsonSyntax.php new file mode 100644 index 000000000000..bbf9159b7d3e --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Marshal/Json/JsonSyntax.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Marshal\Json; + +use Symfony\Component\Marshaller\Exception\RuntimeException; +use Symfony\Component\Marshaller\Internal\Marshal\Node\ArrayAccessNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\BinaryNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\FunctionNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\ScalarNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\VariableNode; +use Symfony\Component\Marshaller\Internal\Marshal\NodeInterface; +use Symfony\Component\Marshaller\Internal\Marshal\SyntaxInterface; + +/** + * @author Mathias Arlaud + * + * @internal + */ +final class JsonSyntax implements SyntaxInterface +{ + public function startDictString(): string + { + return '{'; + } + + public function endDictString(): string + { + return '}'; + } + + public function startDictKeyString(): string + { + return '"'; + } + + public function endDictKeyString(): string + { + return '":'; + } + + public function startListString(): string + { + return '['; + } + + public function endListString(): string + { + return ']'; + } + + public function collectionItemSeparatorString(): string + { + return ','; + } + + public function escapeString(string $string): string + { + $encoded = json_encode($string); + if (false === $encoded) { + throw new RuntimeException(sprintf('Cannot encode "%s"', $string)); + } + + return substr($encoded, 1, -1); + } + + public function escapeStringNode(NodeInterface $node): NodeInterface + { + return new FunctionNode('\substr', [ + new FunctionNode('\json_encode', [ + $node, + new BinaryNode('??', new ArrayAccessNode(new VariableNode('context'), new ScalarNode('json_encode_flags')), new ScalarNode(0)), + ]), + new ScalarNode(1), + new ScalarNode(-1), + ]); + } + + public function encodeValueNode(NodeInterface $node): NodeInterface + { + return new FunctionNode('\json_encode', [ + $node, + new BinaryNode('??', new ArrayAccessNode(new VariableNode('context'), new ScalarNode('json_encode_flags')), new ScalarNode(0)), + ]); + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/Marshal/Node/ArgumentsNode.php b/src/Symfony/Component/Marshaller/Internal/Marshal/Node/ArgumentsNode.php new file mode 100644 index 000000000000..8d656c8cb8c0 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Marshal/Node/ArgumentsNode.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Marshal\Node; + +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\NodeInterface; +use Symfony\Component\Marshaller\Internal\Marshal\Optimizer; + +/** + * @author Mathias Arlaud + * + * @internal + */ +final class ArgumentsNode implements NodeInterface +{ + /** + * @param array $arguments + */ + public function __construct( + public readonly array $arguments, + ) { + } + + public function compile(Compiler $compiler): void + { + $argumentSources = []; + foreach ($this->arguments as $name => $type) { + $type = $type ? $type.' ' : ''; + $name = $compiler->subcompile(new VariableNode($name)); + + $argumentSources[] = $type.$name; + } + + $compiler->raw(implode(', ', $argumentSources)); + } + + public function optimize(Optimizer $optimizer): static + { + return $this; + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/Marshal/Node/ArrayAccessNode.php b/src/Symfony/Component/Marshaller/Internal/Marshal/Node/ArrayAccessNode.php new file mode 100644 index 000000000000..a2859447325b --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Marshal/Node/ArrayAccessNode.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Marshal\Node; + +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\NodeInterface; +use Symfony\Component\Marshaller\Internal\Marshal\Optimizer; + +/** + * @author Mathias Arlaud + * + * @internal + */ +final class ArrayAccessNode implements NodeInterface +{ + public function __construct( + public readonly NodeInterface $array, + public readonly NodeInterface $key, + ) { + } + + public function compile(Compiler $compiler): void + { + $compiler->raw(sprintf('%s[%s]', $compiler->subcompile($this->array), $compiler->subcompile($this->key))); + } + + public function optimize(Optimizer $optimizer): static + { + return $this; + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/Marshal/Node/AssignNode.php b/src/Symfony/Component/Marshaller/Internal/Marshal/Node/AssignNode.php new file mode 100644 index 000000000000..32729d11ed3b --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Marshal/Node/AssignNode.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Marshal\Node; + +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\NodeInterface; +use Symfony\Component\Marshaller\Internal\Marshal\Optimizer; + +/** + * @author Mathias Arlaud + * + * @internal + */ +final class AssignNode implements NodeInterface +{ + public function __construct( + public readonly NodeInterface $left, + public readonly NodeInterface $right, + ) { + } + + public function compile(Compiler $compiler): void + { + $compiler + ->compile($this->left) + ->raw(' = ') + ->compile($this->right); + } + + public function optimize(Optimizer $optimizer): static + { + return new self($optimizer->optimize($this->left), $optimizer->optimize($this->right)); + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/Marshal/Node/BinaryNode.php b/src/Symfony/Component/Marshaller/Internal/Marshal/Node/BinaryNode.php new file mode 100644 index 000000000000..ff4925a43929 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Marshal/Node/BinaryNode.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Marshal\Node; + +use Symfony\Component\Marshaller\Exception\InvalidArgumentException; +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\NodeInterface; +use Symfony\Component\Marshaller\Internal\Marshal\Optimizer; + +/** + * @author Mathias Arlaud + * + * @internal + */ +final class BinaryNode implements NodeInterface +{ + private const OPERATORS = [ + '&&', + '||', + '===', + 'instanceof', + '??', + ]; + + public function __construct( + public readonly string $operator, + public readonly NodeInterface $left, + public readonly NodeInterface $right, + ) { + if (!\in_array($this->operator, self::OPERATORS)) { + throw new InvalidArgumentException(sprintf('Invalid "%s" operator.', $this->operator)); + } + } + + public function compile(Compiler $compiler): void + { + $compiler + ->compile($this->left) + ->raw(' '.$this->operator.' ') + ->compile($this->right); + } + + public function optimize(Optimizer $optimizer): static + { + return new self($this->operator, $optimizer->optimize($this->left), $optimizer->optimize($this->right)); + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/Marshal/Node/ClosureNode.php b/src/Symfony/Component/Marshaller/Internal/Marshal/Node/ClosureNode.php new file mode 100644 index 000000000000..02148860a687 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Marshal/Node/ClosureNode.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Marshal\Node; + +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\NodeInterface; +use Symfony\Component\Marshaller\Internal\Marshal\Optimizer; + +/** + * @author Mathias Arlaud + * + * @internal + */ +final class ClosureNode implements NodeInterface +{ + /** + * @param list $body + */ + public function __construct( + public readonly ArgumentsNode $arguments, + public readonly ?string $returnType, + public readonly bool $static, + public readonly array $body, + ) { + } + + public function compile(Compiler $compiler): void + { + $staticSource = $this->static ? 'static ' : ''; + $argumentsSource = $compiler->subcompile($this->arguments); + $returnTypeSource = $this->returnType ? ': '.$this->returnType : ''; + + $compiler + ->raw(sprintf('%sfunction (%s)%s {', $staticSource, $argumentsSource, $returnTypeSource).\PHP_EOL) + ->indent(); + + foreach ($this->body as $bodyNode) { + $compiler->compile($bodyNode); + } + + $compiler + ->outdent() + ->raw('}'); + } + + public function optimize(Optimizer $optimizer): static + { + return new self( + $optimizer->optimize($this->arguments), + $this->returnType, + $this->static, + $optimizer->optimize($this->body), + ); + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/Marshal/Node/ExpressionNode.php b/src/Symfony/Component/Marshaller/Internal/Marshal/Node/ExpressionNode.php new file mode 100644 index 000000000000..18115fd28ae4 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Marshal/Node/ExpressionNode.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Marshal\Node; + +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\NodeInterface; +use Symfony\Component\Marshaller\Internal\Marshal\Optimizer; + +/** + * @author Mathias Arlaud + * + * @internal + * + * @template T of NodeInterface + */ +final class ExpressionNode implements NodeInterface +{ + /** + * @param T $node + */ + public function __construct( + public readonly NodeInterface $node, + ) { + } + + public function compile(Compiler $compiler): void + { + $compiler->line($compiler->subcompile($this->node).';'); + } + + public function optimize(Optimizer $optimizer): static + { + return new self($optimizer->optimize($this->node)); + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/Marshal/Node/ForEachNode.php b/src/Symfony/Component/Marshaller/Internal/Marshal/Node/ForEachNode.php new file mode 100644 index 000000000000..e2e68314465e --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Marshal/Node/ForEachNode.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Marshal\Node; + +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\NodeInterface; +use Symfony\Component\Marshaller\Internal\Marshal\Optimizer; + +/** + * @author Mathias Arlaud + * + * @internal + */ +final class ForEachNode implements NodeInterface +{ + /** + * @param list $body + */ + public function __construct( + public readonly NodeInterface $collection, + public readonly ?string $keyName, + public readonly string $valueName, + public readonly array $body, + ) { + } + + public function compile(Compiler $compiler): void + { + if (null === $this->keyName) { + $compiler->line(sprintf( + 'foreach (%s as %s) {', + $compiler->subcompile($this->collection), + $compiler->subcompile(new VariableNode($this->valueName)), + )); + } else { + $compiler->line(sprintf( + 'foreach (%s as %s => %s) {', + $compiler->subcompile($this->collection), + $compiler->subcompile(new VariableNode($this->keyName)), + $compiler->subcompile(new VariableNode($this->valueName)), + )); + } + + $compiler->indent(); + + foreach ($this->body as $bodyNode) { + $compiler->compile($bodyNode); + } + + $compiler + ->outdent() + ->line('}'); + } + + public function optimize(Optimizer $optimizer): static + { + return new self($optimizer->optimize($this->collection), $this->keyName, $this->valueName, $optimizer->optimize($this->body)); + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/Marshal/Node/FunctionNode.php b/src/Symfony/Component/Marshaller/Internal/Marshal/Node/FunctionNode.php new file mode 100644 index 000000000000..551a40fae06f --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Marshal/Node/FunctionNode.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Marshal\Node; + +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\NodeInterface; +use Symfony\Component\Marshaller\Internal\Marshal\Optimizer; + +/** + * @author Mathias Arlaud + * + * @internal + */ +final class FunctionNode implements NodeInterface +{ + /** + * @param list $parameters + */ + public function __construct( + public readonly string $name, + public readonly array $parameters, + ) { + } + + public function compile(Compiler $compiler): void + { + $parameters = implode(', ', array_map(fn (NodeInterface $v): string => $compiler->subcompile($v), $this->parameters)); + + $compiler->raw(sprintf('%s(%s)', $this->name, $parameters)); + } + + public function optimize(Optimizer $optimizer): static + { + return new self($this->name, $optimizer->optimize($this->parameters)); + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/Marshal/Node/IfNode.php b/src/Symfony/Component/Marshaller/Internal/Marshal/Node/IfNode.php new file mode 100644 index 000000000000..dfcae2b21ee0 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Marshal/Node/IfNode.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Marshal\Node; + +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\NodeInterface; +use Symfony\Component\Marshaller\Internal\Marshal\Optimizer; + +/** + * @author Mathias Arlaud + * + * @internal + */ +final class IfNode implements NodeInterface +{ + /** + * @param list $onIf + * @param list $onElse + * @param list}> $elseIfs + */ + public function __construct( + public readonly NodeInterface $condition, + public readonly array $onIf, + public readonly array $onElse = [], + public readonly array $elseIfs = [], + ) { + } + + public function compile(Compiler $compiler): void + { + $compiler + ->line(sprintf('if (%s) {', $compiler->subcompile($this->condition))) + ->indent(); + + foreach ($this->onIf as $ifBodyNode) { + $compiler->compile($ifBodyNode); + } + + $compiler->outdent(); + + foreach ($this->elseIfs as $elseIf) { + $compiler + ->line(sprintf('} elseif (%s) {', $compiler->subcompile($elseIf['condition']))) + ->indent(); + + foreach ($elseIf['body'] as $elseIfBodyNode) { + $compiler->compile($elseIfBodyNode); + } + + $compiler->outdent(); + } + + if ([] !== $this->onElse) { + $compiler + ->line('} else {') + ->indent(); + + foreach ($this->onElse as $elseBodyNode) { + $compiler->compile($elseBodyNode); + } + + $compiler->outdent(); + } + + $compiler->line('}'); + } + + public function optimize(Optimizer $optimizer): static + { + return new self( + $optimizer->optimize($this->condition), + $optimizer->optimize($this->onIf), + $optimizer->optimize($this->onElse), + array_map(fn (array $e): array => [ + 'condition' => $optimizer->optimize($e['condition']), + 'body' => $optimizer->optimize($e['body']), + ], $this->elseIfs), + ); + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/Marshal/Node/PhpDocNode.php b/src/Symfony/Component/Marshaller/Internal/Marshal/Node/PhpDocNode.php new file mode 100644 index 000000000000..7ab8d57f8f90 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Marshal/Node/PhpDocNode.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Marshal\Node; + +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\NodeInterface; +use Symfony\Component\Marshaller\Internal\Marshal\Optimizer; + +/** + * @author Mathias Arlaud + * + * @internal + */ +final class PhpDocNode implements NodeInterface +{ + /** + * @param list $lines + */ + public function __construct( + public readonly array $lines, + ) { + } + + public function compile(Compiler $compiler): void + { + if ([] === $this->lines) { + return; + } + + $compiler->line('/**'); + foreach ($this->lines as $line) { + if ('' === $line) { + $compiler->line(' *'); + + continue; + } + + $compiler->line(' * '.$line); + } + $compiler->line(' */'); + } + + public function optimize(Optimizer $optimizer): static + { + return $this; + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/Marshal/Node/PropertyNode.php b/src/Symfony/Component/Marshaller/Internal/Marshal/Node/PropertyNode.php new file mode 100644 index 000000000000..d57166553440 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Marshal/Node/PropertyNode.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Marshal\Node; + +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\NodeInterface; +use Symfony\Component\Marshaller\Internal\Marshal\Optimizer; + +/** + * @author Mathias Arlaud + * + * @internal + */ +final class PropertyNode implements NodeInterface +{ + public function __construct( + public readonly NodeInterface $object, + public readonly string $property, + ) { + } + + public function compile(Compiler $compiler): void + { + $compiler + ->compile($this->object) + ->raw(sprintf('->%s', $this->property)); + } + + public function optimize(Optimizer $optimizer): static + { + return new self($optimizer->optimize($this->object), $this->property); + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/Marshal/Node/RawNode.php b/src/Symfony/Component/Marshaller/Internal/Marshal/Node/RawNode.php new file mode 100644 index 000000000000..262bc2b8cac4 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Marshal/Node/RawNode.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Marshal\Node; + +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\NodeInterface; +use Symfony\Component\Marshaller\Internal\Marshal\Optimizer; + +/** + * @author Mathias Arlaud + * + * @internal + */ +final class RawNode implements NodeInterface +{ + public function __construct( + public readonly string $source, + ) { + } + + public function compile(Compiler $compiler): void + { + $compiler->raw($this->source); + } + + public function optimize(Optimizer $optimizer): static + { + return $this; + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/Marshal/Node/ReturnNode.php b/src/Symfony/Component/Marshaller/Internal/Marshal/Node/ReturnNode.php new file mode 100644 index 000000000000..5791d1f8e977 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Marshal/Node/ReturnNode.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Marshal\Node; + +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\NodeInterface; +use Symfony\Component\Marshaller\Internal\Marshal\Optimizer; + +/** + * @author Mathias Arlaud + * + * @internal + */ +final class ReturnNode implements NodeInterface +{ + public function __construct( + public readonly NodeInterface $node, + ) { + } + + public function compile(Compiler $compiler): void + { + $compiler->raw(sprintf('return %s', $compiler->subcompile($this->node))); + } + + public function optimize(Optimizer $optimizer): static + { + return new self($optimizer->optimize($this->node)); + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/Marshal/Node/ScalarNode.php b/src/Symfony/Component/Marshaller/Internal/Marshal/Node/ScalarNode.php new file mode 100644 index 000000000000..ef664049b7ea --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Marshal/Node/ScalarNode.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Marshal\Node; + +use Symfony\Component\Marshaller\Exception\InvalidArgumentException; +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\NodeInterface; +use Symfony\Component\Marshaller\Internal\Marshal\Optimizer; + +/** + * @author Mathias Arlaud + * + * @internal + */ +final class ScalarNode implements NodeInterface +{ + public function __construct( + public readonly mixed $value, + ) { + } + + public function compile(Compiler $compiler): void + { + if (null === $this->value) { + $compiler->raw('null'); + + return; + } + + if (\is_int($this->value) || \is_float($this->value)) { + $compiler->raw((string) $this->value); + + return; + } + + if (\is_bool($this->value)) { + $compiler->raw($this->value ? 'true' : 'false'); + + return; + } + + if (\is_string($this->value)) { + $compiler->raw(sprintf('"%s"', addcslashes($this->value, '"\\'))); + + return; + } + + throw new InvalidArgumentException(sprintf('Given value is not a scalar. Got "%s".', get_debug_type($this->value))); + } + + public function optimize(Optimizer $optimizer): static + { + return $this; + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/Marshal/Node/TemplateStringNode.php b/src/Symfony/Component/Marshaller/Internal/Marshal/Node/TemplateStringNode.php new file mode 100644 index 000000000000..7029e25bc178 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Marshal/Node/TemplateStringNode.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Marshal\Node; + +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\NodeInterface; +use Symfony\Component\Marshaller\Internal\Marshal\Optimizer; + +/** + * @author Mathias Arlaud + * + * @internal + */ +final class TemplateStringNode implements NodeInterface +{ + /** + * @var list + */ + public readonly array $parts; + + public function __construct(string|VariableNode ...$parts) + { + $this->parts = array_values($parts); + } + + public function compile(Compiler $compiler): void + { + $templateString = ''; + foreach ($this->parts as $part) { + if (\is_string($part)) { + $templateString .= sprintf('%s', addcslashes($part, '"\\')); + + continue; + } + + $templateString .= sprintf('{%s}', $compiler->subcompile($part)); + } + + $compiler->raw(sprintf('"%s"', $templateString)); + } + + public function optimize(Optimizer $optimizer): static + { + return new self(...array_map(fn (VariableNode|string $p): VariableNode|string => \is_string($p) ? $p : $optimizer->optimize($p), $this->parts)); + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/Marshal/Node/TernaryConditionNode.php b/src/Symfony/Component/Marshaller/Internal/Marshal/Node/TernaryConditionNode.php new file mode 100644 index 000000000000..fec86d634c88 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Marshal/Node/TernaryConditionNode.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Marshal\Node; + +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\NodeInterface; +use Symfony\Component\Marshaller\Internal\Marshal\Optimizer; + +/** + * @author Mathias Arlaud + * + * @internal + */ +final class TernaryConditionNode implements NodeInterface +{ + public function __construct( + public readonly NodeInterface $condition, + public readonly NodeInterface $onTrue, + public readonly NodeInterface $onFalse, + ) { + } + + public function compile(Compiler $compiler): void + { + $compiler + ->compile($this->condition) + ->raw(' ? ') + ->compile($this->onTrue) + ->raw(' : ') + ->compile($this->onFalse); + } + + public function optimize(Optimizer $optimizer): static + { + return new self($optimizer->optimize($this->condition), $optimizer->optimize($this->onTrue), $optimizer->optimize($this->onFalse)); + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/Marshal/Node/UnaryNode.php b/src/Symfony/Component/Marshaller/Internal/Marshal/Node/UnaryNode.php new file mode 100644 index 000000000000..b097e9de022f --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Marshal/Node/UnaryNode.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Marshal\Node; + +use Symfony\Component\Marshaller\Exception\InvalidArgumentException; +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\NodeInterface; +use Symfony\Component\Marshaller\Internal\Marshal\Optimizer; + +/** + * @author Mathias Arlaud + * + * @internal + */ +final class UnaryNode implements NodeInterface +{ + private const OPERATORS = [ + '!', + ]; + + public function __construct( + public readonly string $operator, + public readonly NodeInterface $node, + ) { + if (!\in_array($this->operator, self::OPERATORS)) { + throw new InvalidArgumentException(sprintf('Invalid "%s" operator.', $this->operator)); + } + } + + public function compile(Compiler $compiler): void + { + $compiler + ->raw($this->operator) + ->compile($this->node); + } + + public function optimize(Optimizer $optimizer): static + { + return new self($this->operator, $optimizer->optimize($this->node)); + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/Marshal/Node/VariableNode.php b/src/Symfony/Component/Marshaller/Internal/Marshal/Node/VariableNode.php new file mode 100644 index 000000000000..a8ec69635cf0 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Marshal/Node/VariableNode.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Marshal\Node; + +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\NodeInterface; +use Symfony\Component\Marshaller\Internal\Marshal\Optimizer; + +/** + * @author Mathias Arlaud + * + * @internal + */ +final class VariableNode implements NodeInterface +{ + public function __construct( + public readonly string $name, + ) { + } + + public function compile(Compiler $compiler): void + { + $compiler->raw('$'.$this->name); + } + + public function optimize(Optimizer $optimizer): static + { + return $this; + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/Marshal/NodeInterface.php b/src/Symfony/Component/Marshaller/Internal/Marshal/NodeInterface.php new file mode 100644 index 000000000000..74518dd61799 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Marshal/NodeInterface.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Marshal; + +/** + * @author Mathias Arlaud + * + * @internal + */ +interface NodeInterface +{ + public function compile(Compiler $compiler): void; + + public function optimize(Optimizer $optimizer): static; +} diff --git a/src/Symfony/Component/Marshaller/Internal/Marshal/Optimizer.php b/src/Symfony/Component/Marshaller/Internal/Marshal/Optimizer.php new file mode 100644 index 000000000000..ab61fcc19450 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Marshal/Optimizer.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Marshal; + +use Symfony\Component\Marshaller\Internal\Marshal\Node\ExpressionNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\FunctionNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\ScalarNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\VariableNode; + +/** + * @author Mathias Arlaud + * + * @internal + */ +final class Optimizer +{ + /** + * @template T of NodeInterface|list + * + * @param T $subject + * + * @return T + */ + public function optimize(NodeInterface|array $subject): NodeInterface|array + { + /** @var T $optimized */ + $optimized = $subject instanceof NodeInterface ? $subject->optimize($this) : $this->optimizeNodeCollection($subject); + + return $optimized; + } + + /** + * @param list $nodes + * + * @return list + */ + private function optimizeNodeCollection(array $nodes): array + { + return $this->mergeResourceStringFwrites($nodes); + } + + /** + * @param list $nodes + * + * @return list + */ + private function mergeResourceStringFwrites(array $nodes): array + { + $createFwriteExpression = fn (string $content) => new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode($content)])); + + $stringContent = ''; + $mergedNodes = []; + + foreach ($nodes as $node) { + if (!$this->isStringResourceFwrite($node)) { + if ('' !== $stringContent) { + $mergedNodes[] = $createFwriteExpression($stringContent); + $stringContent = ''; + } + + $mergedNodes[] = $node; + + continue; + } + + /** @var ExpressionNode $node */ + $stringContent = $stringContent.$node->node->parameters[1]->value; + } + + if ('' !== $stringContent) { + $mergedNodes[] = $createFwriteExpression($stringContent); + } + + /** @var list $optimizedNodes */ + $optimizedNodes = array_map($this->optimize(...), $mergedNodes); + + return $optimizedNodes; + } + + private function isStringResourceFwrite(NodeInterface $node): bool + { + if (!$node instanceof ExpressionNode) { + return false; + } + + $currentNode = $node->node; + + if (!$currentNode instanceof FunctionNode || '\\fwrite' !== $currentNode->name) { + return false; + } + + $resourceParameter = $currentNode->parameters[0] ?? null; + $dataParameter = $currentNode->parameters[1] ?? null; + + return $resourceParameter instanceof VariableNode && 'resource' === $resourceParameter->name + && $dataParameter instanceof ScalarNode && \is_string($dataParameter->value); + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/Marshal/SyntaxInterface.php b/src/Symfony/Component/Marshaller/Internal/Marshal/SyntaxInterface.php new file mode 100644 index 000000000000..5cd28a2ed60e --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Marshal/SyntaxInterface.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Marshal; + +/** + * @author Mathias Arlaud + * + * @internal + */ +interface SyntaxInterface +{ + public function startDictString(): string; + + public function endDictString(): string; + + public function startDictKeyString(): string; + + public function endDictKeyString(): string; + + public function startListString(): string; + + public function endListString(): string; + + public function collectionItemSeparatorString(): string; + + public function escapeString(string $string): string; + + public function escapeStringNode(NodeInterface $node): NodeInterface; + + public function encodeValueNode(NodeInterface $node): NodeInterface; +} diff --git a/src/Symfony/Component/Marshaller/Internal/Marshal/TemplateGenerator.php b/src/Symfony/Component/Marshaller/Internal/Marshal/TemplateGenerator.php new file mode 100644 index 000000000000..91f2725df9f4 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Marshal/TemplateGenerator.php @@ -0,0 +1,298 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Marshal; + +use Symfony\Component\Marshaller\Exception\CircularReferenceException; +use Symfony\Component\Marshaller\Exception\LogicException; +use Symfony\Component\Marshaller\Exception\UnsupportedTypeException; +use Symfony\Component\Marshaller\Internal\Marshal\Node\AssignNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\BinaryNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\ExpressionNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\ForEachNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\FunctionNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\IfNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\PropertyNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\RawNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\ScalarNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\TemplateStringNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\UnaryNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\VariableNode; +use Symfony\Component\Marshaller\Internal\Type; +use Symfony\Component\Marshaller\Internal\TypeFactory; +use Symfony\Component\Marshaller\Internal\UnionType; +use Symfony\Component\Marshaller\Type\ReflectionTypeExtractor; + +/** + * @author Mathias Arlaud + * + * @internal + */ +final class TemplateGenerator +{ + use VariableNameScoperTrait; + + public function __construct( + private readonly ReflectionTypeExtractor $reflectionTypeExtractor, + private readonly TypeSorter $typeSorter, + private readonly SyntaxInterface $syntax, + ) { + } + + /** + * @param array $context + * + * @return list + */ + public function generate(Type|UnionType $type, NodeInterface $accessor, array $context): array + { + $nodes = $this->nodes($type, $accessor, $context); + + if (!$type->isNullable()) { + return $nodes; + } + + return [ + new IfNode( + new BinaryNode('===', new ScalarNode(null), $accessor), + $this->scalarNodes($accessor), + $nodes, + ), + ]; + } + + /** + * @param array $context + * + * @return list + */ + private function nodes(Type|UnionType $type, NodeInterface $accessor, array $context): array + { + return match (true) { + $type instanceof UnionType => $this->unionNodes($type, $accessor, $context), + $type->isScalar(), $type->isNull() => $this->scalarNodes($accessor), + $type->isObject() => $this->objectNodes($type, $accessor, $context), + $type->isList() => $this->listNodes($type, $accessor, $context), + $type->isDict() => $this->dictNodes($type, $accessor, $context), + default => throw new UnsupportedTypeException((string) $type), + }; + } + + /** + * @param array $context + * + * @return list + */ + public function unionNodes(UnionType $type, NodeInterface $accessor, array $context): array + { + if (\count($type->types) <= 0) { + return []; + } + + $types = $this->typeSorter->sortByPrecision($type->types); + + if (1 === \count($types)) { + return $this->generate($types[0], $accessor, $context); + } + + /** @var Type $ifType */ + $ifType = array_shift($types); + + /** @var Type $elseType */ + $elseType = array_pop($types); + + /** @var list}> $elseIfTypes */ + $elseIfTypes = array_map(fn (Type $t): array => ['condition' => $this->typeValidatorNode($t, $accessor), 'body' => $this->generate($t, $accessor, $context)], $types); + + return [new IfNode( + $this->typeValidatorNode($ifType, $accessor), + $this->generate($ifType, $accessor, $context), + $this->generate($elseType, $accessor, $context), + $elseIfTypes, + )]; + } + + /** + * @return list + */ + public function scalarNodes(NodeInterface $accessor): array + { + return [ + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), $this->syntax->encodeValueNode($accessor)])), + ]; + } + + /** + * @param array $context + * + * @return list + */ + public function listNodes(Type $type, NodeInterface $accessor, array $context): array + { + $prefixName = $this->scopeVariableName('prefix', $context); + $valueName = $this->scopeVariableName('value', $context); + + return [ + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode($this->syntax->startListString())])), + new ExpressionNode(new AssignNode(new VariableNode($prefixName), new ScalarNode(''))), + + new ForEachNode($accessor, null, $valueName, [ + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new VariableNode($prefixName)])), + ...$this->generate($type->collectionValueType(), new VariableNode($valueName), $context), + new ExpressionNode(new AssignNode(new VariableNode($prefixName), new ScalarNode($this->syntax->collectionItemSeparatorString()))), + ]), + + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode($this->syntax->endListString())])), + ]; + } + + /** + * @param array $context + * + * @return list + */ + private function dictNodes(Type $type, NodeInterface $accessor, array $context): array + { + $prefixName = $this->scopeVariableName('prefix', $context); + $keyName = $this->scopeVariableName('key', $context); + $valueName = $this->scopeVariableName('value', $context); + + return [ + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode($this->syntax->startDictString())])), + new ExpressionNode(new AssignNode(new VariableNode($prefixName), new ScalarNode(''))), + + new ForEachNode($accessor, $keyName, $valueName, [ + new ExpressionNode(new AssignNode(new VariableNode($keyName), $this->syntax->escapeStringNode(new VariableNode($keyName)))), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new TemplateStringNode( + new VariableNode($prefixName), + $this->syntax->startDictKeyString(), + new VariableNode($keyName), + $this->syntax->endDictKeyString(), + )])), + ...$this->generate($type->collectionValueType(), new VariableNode($valueName), $context), + new ExpressionNode(new AssignNode(new VariableNode($prefixName), new ScalarNode($this->syntax->collectionItemSeparatorString()))), + ]), + + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode($this->syntax->endDictString())])), + ]; + } + + /** + * @param array $context + * + * @return list + */ + private function objectNodes(Type $type, NodeInterface $accessor, array $context): array + { + $hook = null; + + if (isset($context['hooks']['marshal'][$className = $type->className()])) { + $hook = $context['hooks']['marshal'][$className]; + } elseif (isset($context['hooks']['marshal']['object'])) { + $hook = $context['hooks']['marshal']['object']; + } + + if (null !== $hook) { + $hookResult = $hook((string) $type, (new Compiler())->compile($accessor)->source(), $context); + + /** @var Type $type */ + $type = isset($hookResult['type']) ? TypeFactory::createFromString($hookResult['type']) : $type; + $accessor = isset($hookResult['accessor']) ? new RawNode($hookResult['accessor']) : $accessor; + $context = $hookResult['context'] ?? $context; + } + + $className = $type->className(); + + if (isset($context['generated_classes'][$className])) { + throw new CircularReferenceException($className); + } + + $context['generated_classes'][$className] = true; + + $class = new \ReflectionClass($type->className()); + $objectName = $this->scopeVariableName('object', $context); + + $nodes = [ + new ExpressionNode(new AssignNode(new VariableNode($objectName), $accessor)), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode($this->syntax->startDictString())])), + ]; + + $propertySeparator = ''; + + foreach ($class->getProperties() as $property) { + if (!$property->isPublic()) { + throw new LogicException(sprintf('"%s::$%s" must be public.', $class->getName(), $property->getName())); + } + + $propertyName = $property->getName(); + $propertyType = $this->reflectionTypeExtractor->extractFromProperty($property); + $propertyAccessor = new PropertyNode(new VariableNode($objectName), $property->getName()); + $propertyContext = $context; + + $hook = null; + + if (isset($context['hooks']['marshal'][$className.'::$'.$propertyName])) { + $hook = $context['hooks']['marshal'][$className.'::$'.$propertyName]; + } elseif (isset($context['hooks']['marshal']['property'])) { + $hook = $context['hooks']['marshal']['property']; + } + + if (null !== $hook) { + $hookResult = $hook($property, (new Compiler())->compile($propertyAccessor)->source(), $context); + + $propertyName = $hookResult['name'] ?? $propertyName; + $propertyType = $hookResult['type'] ?? $propertyType; + $propertyAccessor = isset($hookResult['accessor']) ? new RawNode($hookResult['accessor']) : $propertyAccessor; + $propertyContext = $hookResult['context'] ?? $propertyContext; + } + + array_push( + $nodes, + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode($propertySeparator)])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode($this->syntax->startDictKeyString())])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode($this->syntax->escapeString($propertyName))])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode($this->syntax->endDictKeyString())])), + ...$this->generate(TypeFactory::createFromString($propertyType), $propertyAccessor, $propertyContext), + ); + + $propertySeparator = $this->syntax->collectionItemSeparatorString(); + } + + $nodes[] = new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode($this->syntax->endDictString())])); + + return $nodes; + } + + public function typeValidatorNode(Type $type, NodeInterface $accessor): NodeInterface + { + if ($type->isNull()) { + return new BinaryNode('===', new ScalarNode(null), $accessor); + } + + if ($type->isScalar()) { + return new FunctionNode(sprintf('\is_%s', $type->name()), [$accessor]); + } + + if ($type->isList()) { + return new BinaryNode('&&', new FunctionNode('\is_array', [$accessor]), new FunctionNode('\array_is_list', [$accessor])); + } + + if ($type->isDict()) { + return new BinaryNode('&&', new FunctionNode('\is_array', [$accessor]), new UnaryNode('!', new FunctionNode('\array_is_list', [$accessor]))); + } + + if ($type->isObject()) { + return new BinaryNode('instanceof', $accessor, new ScalarNode($type->className())); + } + + throw new LogicException(sprintf('Cannot find validator for "%s".', (string) $type)); + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/Marshal/TemplateGeneratorFactory.php b/src/Symfony/Component/Marshaller/Internal/Marshal/TemplateGeneratorFactory.php new file mode 100644 index 000000000000..3c09dad39a20 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Marshal/TemplateGeneratorFactory.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Marshal; + +use Symfony\Component\Marshaller\Exception\UnsupportedFormatException; +use Symfony\Component\Marshaller\Internal\Marshal\Json\JsonSyntax; +use Symfony\Component\Marshaller\Type\ReflectionTypeExtractor; + +/** + * @author Mathias Arlaud + * + * @internal + */ +final class TemplateGeneratorFactory +{ + private function __construct() + { + } + + public static function create(string $format): TemplateGenerator + { + return match ($format) { + 'json' => self::json(), + default => throw new UnsupportedFormatException($format), + }; + } + + private static function json(): TemplateGenerator + { + return new TemplateGenerator( + reflectionTypeExtractor: new ReflectionTypeExtractor(), + typeSorter: new TypeSorter(), + syntax: new JsonSyntax(), + ); + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/Marshal/TypeSorter.php b/src/Symfony/Component/Marshaller/Internal/Marshal/TypeSorter.php new file mode 100644 index 000000000000..d32f4b4095ef --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Marshal/TypeSorter.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Marshal; + +use Symfony\Component\Marshaller\Exception\LogicException; +use Symfony\Component\Marshaller\Internal\Type; + +/** + * @author Mathias Arlaud + * + * @internal + * + * @psalm-type PhoneType = array{phone: string} + */ +final class TypeSorter +{ + /** + * @param list $types + * + * @return list + */ + public function sortByPrecision(array $types): array + { + $regularTypes = array_values(array_filter($types, fn (Type $t): bool => !$t->isObject())); + + $objectTypes = []; + foreach (array_diff($types, $regularTypes) as $objectType) { + $objectTypes[$objectType->className()] = $objectType; + } + + /** @var array $classes */ + $classes = []; + $previousClass = null; + foreach (array_keys($objectTypes) as $class) { + $classOrParent = $class; + + while ($classOrParent) { + $classes[$classOrParent] = ['class' => $classOrParent]; + if ($previousClass && $previousClass !== $classOrParent) { + $classes[$previousClass]['parent'] = $classOrParent; + } + + $previousClass = $classOrParent; + $classOrParent = get_parent_class($classOrParent); + } + } + + $classTree = $this->buildClassTree($classes, false); + + $classDepths = []; + foreach ($classes as $class) { + $classDepths[$class['class']] = $this->depth($class['class'], $classTree); + } + + if (\count(array_unique($classDepths)) !== \count($classDepths)) { + throw new LogicException('Found several classes at the same hierarchy level.'); + } + + arsort($classDepths); + + $sortedObjectTypes = []; + foreach (array_keys($classDepths) as $class) { + if (isset($objectTypes[$class])) { + $sortedObjectTypes[] = $objectTypes[$class]; + } + } + + return array_unique(array_merge($regularTypes, $sortedObjectTypes)); + } + + /** + * @param array $classes + * @param class-string|false $parentClass + * + * @return array}> + */ + private function buildClassTree(array $classes, string|false $parentClass): array + { + $branch = []; + + foreach ($classes as $class) { + if (($class['parent'] ?? false) === $parentClass) { + if ([] !== $children = $this->buildClassTree($classes, $class['class'])) { + $class['children'] = $children; + } + + $branch[$class['class']] = $class; + } + } + + return $branch; + } + + /** + * @param class-string $class + * @param array}}> $classTree + */ + private function depth(string $class, array $classTree, int $depth = 0): int + { + foreach ($classTree as $leaf) { + if ($leaf['class'] === $class) { + return $depth; + } + } + + $maxDepth = $depth; + foreach ($classTree as $branch) { + $maxDepth = max($this->depth($class, $branch['children'] ?? [], $depth + 1), $maxDepth); + } + + return $maxDepth; + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/Marshal/VariableNameScoperTrait.php b/src/Symfony/Component/Marshaller/Internal/Marshal/VariableNameScoperTrait.php new file mode 100644 index 000000000000..9c16f3a9b3bc --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Marshal/VariableNameScoperTrait.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Marshal; + +/** + * @author Mathias Arlaud + * + * @internal + */ +trait VariableNameScoperTrait +{ + /** + * @param array $context + */ + protected function scopeVariableName(string $prefix, array &$context): string + { + if (!isset($context['variable_counters'][$prefix])) { + $context['variable_counters'][$prefix] = 0; + } + + $name = sprintf('%s_%d', $prefix, $context['variable_counters'][$prefix]); + + ++$context['variable_counters'][$prefix]; + + return $name; + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/Type.php b/src/Symfony/Component/Marshaller/Internal/Type.php new file mode 100644 index 000000000000..a990cf9540eb --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Type.php @@ -0,0 +1,176 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal; + +use Symfony\Component\Marshaller\Exception\InvalidArgumentException; +use Symfony\Component\Marshaller\Exception\LogicException; + +/** + * @author Mathias Arlaud + * + * @internal + */ +final class Type implements \Stringable +{ + private readonly string $stringValue; + + /** + * @param class-string|null $className + * @param list $genericParameterTypes + */ + public function __construct( + private readonly string $name, + private readonly bool $isNullable = false, + private readonly ?string $className = null, + private readonly bool $isGeneric = false, + private readonly array $genericParameterTypes = [], + ) { + if ($this->isObject() && null === $this->className) { + throw new InvalidArgumentException('Missing className of "object" type.'); + } + + if ($this->isGeneric && !$this->genericParameterTypes) { + throw new InvalidArgumentException(sprintf('Missing generic parameter types of "%s" type.', $this->name)); + } + + if ($this->isCollection() && 2 !== \count($this->genericParameterTypes)) { + throw new InvalidArgumentException(sprintf('Invalid generic parameter types of "%s" type.', $this->name)); + } + + $this->stringValue = $this->computeStringValue(); + } + + public function name(): string + { + return $this->name; + } + + public function isNullable(): bool + { + return $this->isNullable; + } + + /** + * @return class-string + */ + public function className(): string + { + if (!$this->isObject()) { + throw new LogicException(sprintf('Cannot get class on "%s" type as it\'s not an object.', $this->name)); + } + + /** @var class-string $className */ + $className = $this->className; + + return $className; + } + + /** + * @return list + */ + public function genericParameterTypes(): array + { + return $this->genericParameterTypes; + } + + public function isScalar(): bool + { + return \in_array($this->name, ['int', 'float', 'string', 'bool'], true); + } + + public function isNull(): bool + { + return 'null' === $this->name; + } + + public function isObject(): bool + { + return 'object' === $this->name; + } + + public function isGeneric(): bool + { + return $this->isGeneric; + } + + public function isCollection(): bool + { + return \in_array($this->name, ['array', 'iterable'], true); + } + + public function isIterable(): bool + { + return 'iterable' === $this->name; + } + + public function isList(): bool + { + if (!$this->isCollection()) { + return false; + } + + $collectionKeyType = $this->collectionKeyType(); + if (!$collectionKeyType instanceof self) { + return false; + } + + return 'int' === $collectionKeyType->name(); + } + + public function isDict(): bool + { + return $this->isCollection() && !$this->isList(); + } + + public function collectionKeyType(): self|UnionType + { + if (!$this->isCollection()) { + throw new LogicException(sprintf('Cannot get collection key type on "%s" type as it\'s not a collection.', $this->name)); + } + + return $this->genericParameterTypes[0]; + } + + public function collectionValueType(): self|UnionType + { + if (!$this->isCollection()) { + throw new LogicException(sprintf('Cannot get collection value type on "%s" type as it\'s not a collection.', $this->name)); + } + + return $this->genericParameterTypes[1]; + } + + private function computeStringValue(): string + { + if ($this->isNull()) { + return 'null'; + } + + $nullablePrefix = $this->isNullable() ? '?' : ''; + + $name = $this->name(); + if ($this->isObject()) { + $name = $this->className(); + } + + if ($this->isGeneric()) { + $name .= sprintf('<%s>', implode(', ', $this->genericParameterTypes)); + } + + return $nullablePrefix.$name; + } + + public function __toString(): string + { + return $this->stringValue; + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/TypeFactory.php b/src/Symfony/Component/Marshaller/Internal/TypeFactory.php new file mode 100644 index 000000000000..c7f93724a261 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/TypeFactory.php @@ -0,0 +1,172 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal; + +use Symfony\Component\Marshaller\Exception\InvalidTypeException; +use Symfony\Component\Marshaller\Exception\UnsupportedTypeException; + +/** + * @author Mathias Arlaud + * + * @internal + */ +abstract class TypeFactory +{ + /** + * @var array + */ + private static array $cache = []; + + private function __construct() + { + } + + public static function createFromString(string $string): Type|UnionType + { + if (isset(self::$cache[$cacheKey = $string])) { + return self::$cache[$cacheKey]; + } + + $currentTypeString = ''; + $typeStrings = []; + $nestedLevel = 0; + + foreach (str_split(str_replace(' ', '', $string)) as $char) { + if ('<' === $char) { + ++$nestedLevel; + } + + if ('>' === $char) { + --$nestedLevel; + } + + if ('|' === $char && 0 === $nestedLevel) { + $typeStrings[] = $currentTypeString; + $currentTypeString = ''; + + continue; + } + + $currentTypeString .= $char; + } + + $typeStrings[] = $currentTypeString; + + if (0 !== $nestedLevel) { + throw new InvalidTypeException($string); + } + + if (\count($typeStrings) > 1) { + $nullable = false; + + $types = []; + + foreach ($typeStrings as $typeString) { + if (str_starts_with($typeString, '?')) { + $nullable = true; + $typeString = substr($typeString, 1); + } + + if ('null' === $typeString) { + $nullable = true; + + continue; + } + + /** @var Type $type */ + $type = self::createFromString($typeString); + $types[] = $type; + } + + if ($nullable) { + $types[] = new Type('null'); + } + + return self::$cache[$cacheKey] = new UnionType($types); + } + + if ('null' === $string) { + return self::$cache[$cacheKey] = new Type('null'); + } + + if ($isNullable = str_starts_with($string, '?')) { + $string = substr($string, 1); + } + + if (\count(explode('&', $string)) > 1) { + throw new UnsupportedTypeException($string); + } + + if (\in_array($string, ['int', 'string', 'float', 'bool'])) { + return self::$cache[$cacheKey] = new Type($string, $isNullable); + } + + if (class_exists($string) || interface_exists($string)) { + return self::$cache[$cacheKey] = new Type('object', $isNullable, $string); + } + + $results = []; + if (preg_match('/^(?P[^<]+)<(?P.+)>$/', $string, $results)) { + $genericType = $results['type']; + $genericParameters = []; + $currentGenericParameter = ''; + $nestedLevel = 0; + + foreach (str_split(str_replace(' ', '', $results['diamond'])) as $char) { + if (',' === $char && 0 === $nestedLevel) { + $genericParameters[] = $currentGenericParameter; + $currentGenericParameter = ''; + + continue; + } + + if ('<' === $char) { + ++$nestedLevel; + } + + if ('>' === $char) { + --$nestedLevel; + } + + $currentGenericParameter .= $char; + } + + $genericParameters[] = $currentGenericParameter; + + if (0 !== $nestedLevel) { + throw new InvalidTypeException($string); + } + + if (\in_array($genericType, ['array', 'iterable'], true) && 1 === \count($genericParameters)) { + array_unshift($genericParameters, 'int'); + } + + $type = $genericType; + $className = null; + + if (class_exists($genericType)) { + $type = 'object'; + $className = $genericType; + } + + return self::$cache[$cacheKey] = new Type( + name: $type, + isNullable: $isNullable, + isGeneric: true, + className: $className, + genericParameterTypes: array_map(fn (string $t): Type|UnionType => self::createFromString($t), $genericParameters), + ); + } + + return self::$cache[$cacheKey] = new Type($string, $isNullable); + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/UnionType.php b/src/Symfony/Component/Marshaller/Internal/UnionType.php new file mode 100644 index 000000000000..30f821443ba0 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/UnionType.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal; + +/** + * @author Mathias Arlaud + * + * @internal + */ +final class UnionType implements \Stringable +{ + /** + * @param list $types + */ + public function __construct( + public readonly array $types, + ) { + } + + public function isNullable(): bool + { + return $this->atLeastOneTypeIs(fn (Type $t): bool => $t->isNull()); + } + + /** + * @param callable(Type): bool $callable + */ + public function atLeastOneTypeIs(callable $callable): bool + { + foreach ($this->types as $type) { + if ($callable($type)) { + return true; + } + } + + return false; + } + + public function __toString(): string + { + return implode('|', array_map(fn (Type $t): string => (string) $t, $this->types)); + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/Unmarshal/DecoderFactory.php b/src/Symfony/Component/Marshaller/Internal/Unmarshal/DecoderFactory.php new file mode 100644 index 000000000000..74fe553ad97c --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Unmarshal/DecoderFactory.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Unmarshal; + +use Symfony\Component\Marshaller\Exception\UnsupportedFormatException; +use Symfony\Component\Marshaller\Internal\Unmarshal\Json\JsonDecoder; + +/** + * @author Mathias Arlaud + * + * @internal + */ +abstract class DecoderFactory +{ + private function __construct() + { + } + + public static function create(string $format): DecoderInterface + { + return match ($format) { + 'json' => new JsonDecoder(), + default => throw new UnsupportedFormatException($format), + }; + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/Unmarshal/DecoderInterface.php b/src/Symfony/Component/Marshaller/Internal/Unmarshal/DecoderInterface.php new file mode 100644 index 000000000000..f2de23d517bd --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Unmarshal/DecoderInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Unmarshal; + +use Symfony\Component\Marshaller\Exception\InvalidResourceException; +use Symfony\Component\Marshaller\Exception\RuntimeException; + +/** + * @author Mathias Arlaud + * + * @internal + */ +interface DecoderInterface +{ + /** + * @param resource $resource + * @param array $context + * + * @throws RuntimeException + * @throws InvalidResourceException + */ + public function decode(mixed $resource, int $offset, int $length, array $context): mixed; +} diff --git a/src/Symfony/Component/Marshaller/Internal/Unmarshal/DictSplitterInterface.php b/src/Symfony/Component/Marshaller/Internal/Unmarshal/DictSplitterInterface.php new file mode 100644 index 000000000000..e2751b7c15f5 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Unmarshal/DictSplitterInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Unmarshal; + +use Symfony\Component\Marshaller\Internal\Type; + +/** + * @author Mathias Arlaud + * + * @internal + */ +interface DictSplitterInterface +{ + /** + * @param resource $resource + * @param array $context + * + * @return \Iterator|null + */ + public function split(mixed $resource, Type $type, array $context): ?\Iterator; +} diff --git a/src/Symfony/Component/Marshaller/Internal/Unmarshal/Instantiator.php b/src/Symfony/Component/Marshaller/Internal/Unmarshal/Instantiator.php new file mode 100644 index 000000000000..bc4b98c44903 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Unmarshal/Instantiator.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Unmarshal; + +use Symfony\Component\Marshaller\Exception\InvalidConstructorArgumentException; +use Symfony\Component\Marshaller\Exception\UnexpectedTypeException; +use Symfony\Component\Marshaller\Instantiator\InstantiatorInterface; + +/** + * @author Mathias Arlaud + * + * @internal + */ +final class Instantiator implements InstantiatorInterface +{ + /** + * @var array + */ + private static array $cache = []; + + /** + * @template T of object + * + * @param \ReflectionClass $class + * + * @return T + */ + public function __invoke(\ReflectionClass $class, array $propertiesValues, array $context): object + { + if (isset(self::$cache[$className = $class->getName()])) { + /** @var T $object */ + $object = clone self::$cache[$className]; + } else { + if (null === $constructor = $class->getConstructor()) { + $object = new ($class->getName())(); + } elseif (!$constructor->isPublic()) { + $object = $class->newInstanceWithoutConstructor(); + } else { + $parameters = []; + $validContructor = true; + + foreach ($constructor->getParameters() as $parameter) { + if ($parameter->isDefaultValueAvailable()) { + $parameters[] = $parameter->getDefaultValue(); + + continue; + } + + if ($parameter->getType()?->allowsNull()) { + $parameters[] = null; + + continue; + } + + $exception = new InvalidConstructorArgumentException($parameter->getName(), $class->getName()); + if (!($context['collect_errors'] ?? false)) { + throw $exception; + } + + $context['collected_errors'][] = $exception; + $validContructor = false; + } + + $object = ($validContructor ? $class->newInstanceArgs($parameters) : $class->newInstanceWithoutConstructor()); + } + + self::$cache[$className] = clone $object; + } + + foreach ($propertiesValues as $property => $value) { + try { + $object->{$property} = $value(); + } catch (\TypeError $e) { + $exception = new UnexpectedTypeException($e->getMessage()); + + if (!($context['collect_errors'] ?? false)) { + throw $exception; + } + + $context['collected_errors'][] = $exception; + } + } + + return $object; + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/Unmarshal/Json/JsonDecoder.php b/src/Symfony/Component/Marshaller/Internal/Unmarshal/Json/JsonDecoder.php new file mode 100644 index 000000000000..2ce6cc55751b --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Unmarshal/Json/JsonDecoder.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Unmarshal\Json; + +use Symfony\Component\Marshaller\Exception\InvalidResourceException; +use Symfony\Component\Marshaller\Exception\RuntimeException; +use Symfony\Component\Marshaller\Internal\Unmarshal\DecoderInterface; + +/** + * @author Mathias Arlaud + * + * @internal + */ +final class JsonDecoder implements DecoderInterface +{ + public function decode(mixed $resource, int $offset, int $length, array $context): mixed + { + try { + if (false === $content = stream_get_contents($resource, $length, $offset)) { + throw new \RuntimeException(); + } + } catch (\Throwable) { + throw new RuntimeException('Cannot read JSON resource.'); + } + + try { + return json_decode($content, associative: true, flags: ($context['json_decode_flags'] ?? 0) | \JSON_THROW_ON_ERROR); + } catch (\JsonException) { + throw new InvalidResourceException($resource); + } + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/Unmarshal/Json/JsonDictSplitter.php b/src/Symfony/Component/Marshaller/Internal/Unmarshal/Json/JsonDictSplitter.php new file mode 100644 index 000000000000..2a8942d3ae4a --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Unmarshal/Json/JsonDictSplitter.php @@ -0,0 +1,125 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Unmarshal\Json; + +use Symfony\Component\Marshaller\Exception\InvalidResourceException; +use Symfony\Component\Marshaller\Internal\Type; +use Symfony\Component\Marshaller\Internal\Unmarshal\DictSplitterInterface; +use Symfony\Component\Marshaller\Internal\Unmarshal\LexerInterface; + +/** + * @author Mathias Arlaud + * + * @internal + */ +final class JsonDictSplitter implements DictSplitterInterface +{ + private const NESTING_CHARS = ['{' => true, '[' => true]; + private const UNNESTING_CHARS = ['}' => true, ']' => true]; + + /** + * @var array{key: array} + */ + private static array $cache = [ + 'key' => [], + ]; + + public function __construct( + private readonly LexerInterface $lexer, + ) { + } + + public function split(mixed $resource, Type $type, array $context): ?\Iterator + { + $tokens = $this->lexer->tokens($resource, $context['boundary'][0], $context['boundary'][1], $context); + $currentToken = $tokens->current(); + + if ('null' === $tokens->current()[0] && 1 === iterator_count($tokens)) { + return null; + } + + return $this->createBoundaries($tokens, $resource, $context); + } + + /** + * @param \Iterator $tokens + * @param resource $resource + * @param array $context + * + * @return \Iterator + */ + public function createBoundaries(\Iterator $tokens, mixed $resource, array $context): \Iterator + { + $level = 0; + $offset = 0; + $firstValueToken = false; + $key = null; + + foreach ($tokens as $i => $token) { + if (0 === $i) { + continue; + } + + $value = $token[0]; + $position = $token[1]; + + if ($firstValueToken) { + $firstValueToken = false; + $offset = $position; + } + + if (isset(self::NESTING_CHARS[$value])) { + ++$level; + + continue; + } + + if (isset(self::UNNESTING_CHARS[$value])) { + --$level; + + continue; + } + + if (0 !== $level) { + continue; + } + + if (':' === $value) { + $firstValueToken = true; + + continue; + } + + if (',' === $value) { + if (null !== $key && ($length = $position - $offset) > 0) { + yield $key => [$offset, $length]; + } + + $key = null; + + continue; + } + + if (null === $key) { + $key = self::$cache['key'][$value] = self::$cache['key'][$value] ?? json_decode($value, flags: $context['json_decode_flags'] ?? 0); + } + } + + if (-1 !== $level || !isset($value, $position) || '}' !== $value) { + throw new InvalidResourceException($resource); + } + + if (null !== $key && ($length = $position - $offset) > 0) { + yield $key => [$offset, $length]; + } + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/Unmarshal/Json/JsonLexer.php b/src/Symfony/Component/Marshaller/Internal/Unmarshal/Json/JsonLexer.php new file mode 100644 index 000000000000..e0f10b10f60f --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Unmarshal/Json/JsonLexer.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Unmarshal\Json; + +use Symfony\Component\Marshaller\Exception\RuntimeException; +use Symfony\Component\Marshaller\Internal\Unmarshal\LexerInterface; + +/** + * @author Mathias Arlaud + * + * @internal + */ +final class JsonLexer implements LexerInterface +{ + private const MAX_CHUNK_LENGTH = 8192; + + private const WHITESPACE_CHARS = [' ' => true, "\r" => true, "\t" => true, "\n" => true]; + private const STRUCTURE_CHARS = [',' => true, ':' => true, '{' => true, '}' => true, '[' => true, ']' => true]; + private const EMPTY_TOKENS = ['' => true, "\xEF\xBB\xBF" => true]; + + public function tokens(mixed $resource, int $offset, int $length, array $context): \Iterator + { + $currentTokenPosition = $offset; + + $token = ''; + + $inString = $escaping = false; + $infiniteLength = -1 === $length; + $chunkLength = $infiniteLength ? self::MAX_CHUNK_LENGTH : min($length, self::MAX_CHUNK_LENGTH); + + rewind($resource); + + $toReadLength = $length; + + while (!feof($resource) && ($infiniteLength || $toReadLength > 0)) { + try { + if (false === $buffer = stream_get_contents($resource, $infiniteLength ? -1 : min($chunkLength, $toReadLength), $offset)) { + throw new \RuntimeException(); + } + } catch (\Throwable) { + throw new RuntimeException('Cannot read JSON resource.'); + } + + $toReadLength -= $bufferLength = \strlen($buffer); + + for ($i = 0; $i < $bufferLength; ++$i) { + $byte = $buffer[$i]; + + if ($escaping) { + $escaping = false; + $token .= $byte; + + continue; + } + + if ($inString) { + $token .= $byte; + + if ('"' === $byte) { + $inString = false; + } elseif ('\\' === $byte) { + $escaping = true; + } + + continue; + } + + if ('"' === $byte) { + $token .= $byte; + $inString = true; + + continue; + } + + if (isset(self::STRUCTURE_CHARS[$byte]) || isset(self::WHITESPACE_CHARS[$byte])) { + if ('' !== $token) { + if (!isset(self::EMPTY_TOKENS[$token])) { + yield [$token, $currentTokenPosition]; + } + + $currentTokenPosition += \strlen($token); + $token = ''; + } + + if (!isset(self::WHITESPACE_CHARS[$byte])) { + yield [$byte, $currentTokenPosition]; + } + + if ('' !== $byte) { + ++$currentTokenPosition; + } + + continue; + } + + $token .= $byte; + } + + $offset += $bufferLength; + } + + if (!isset(self::EMPTY_TOKENS[$token])) { + yield [$token, $currentTokenPosition]; + } + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/Unmarshal/Json/JsonListSplitter.php b/src/Symfony/Component/Marshaller/Internal/Unmarshal/Json/JsonListSplitter.php new file mode 100644 index 000000000000..3f996b8eda91 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Unmarshal/Json/JsonListSplitter.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Unmarshal\Json; + +use Symfony\Component\Marshaller\Exception\InvalidResourceException; +use Symfony\Component\Marshaller\Internal\Type; +use Symfony\Component\Marshaller\Internal\Unmarshal\LexerInterface; +use Symfony\Component\Marshaller\Internal\Unmarshal\ListSplitterInterface; + +/** + * @author Mathias Arlaud + * + * @internal + */ +final class JsonListSplitter implements ListSplitterInterface +{ + private const NESTING_CHARS = ['{' => true, '[' => true]; + private const UNNESTING_CHARS = ['}' => true, ']' => true]; + + public function __construct( + private readonly LexerInterface $lexer, + ) { + } + + public function split(mixed $resource, Type $type, array $context): ?\Iterator + { + $tokens = $this->lexer->tokens($resource, $context['boundary'][0], $context['boundary'][1], $context); + + if ('null' === $tokens->current()[0] && 1 === iterator_count($tokens)) { + return null; + } + + return $this->createBoundaries($tokens, $resource, $context); + } + + /** + * @param \Iterator $tokens + * @param resource $resource + * @param array $context + * + * @return \Iterator + */ + private function createBoundaries(\Iterator $tokens, mixed $resource, array $context): \Iterator + { + $level = 0; + + foreach ($tokens as $i => $token) { + if (0 === $i) { + continue; + } + + $value = $token[0]; + $position = $token[1]; + $offset = $offset ?? $position; + + if (isset(self::NESTING_CHARS[$value])) { + ++$level; + + continue; + } + + if (isset(self::UNNESTING_CHARS[$value])) { + --$level; + + continue; + } + + if (0 !== $level) { + continue; + } + + if (',' === $value) { + if (($length = $position - $offset) > 0) { + yield [$offset, $length]; + } + + $offset = null; + } + } + + if (-1 !== $level || !isset($value, $offset, $position) || ']' !== $value) { + throw new InvalidResourceException($resource); + } + + if (($length = $position - $offset) > 0) { + yield [$offset, $length]; + } + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/Unmarshal/Json/ValidatingJsonLexer.php b/src/Symfony/Component/Marshaller/Internal/Unmarshal/Json/ValidatingJsonLexer.php new file mode 100644 index 000000000000..720b0212edfb --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Unmarshal/Json/ValidatingJsonLexer.php @@ -0,0 +1,229 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Unmarshal\Json; + +use Symfony\Component\Marshaller\Exception\InvalidResourceException; +use Symfony\Component\Marshaller\Internal\Unmarshal\LexerInterface; + +/** + * @author Mathias Arlaud + * + * @internal + */ +final class ValidatingJsonLexer implements LexerInterface +{ + /** + * @var array{valid_scalar: array, valid_key: array} + */ + private static array $cache = [ + 'valid_scalar' => [], + 'valid_key' => [], + ]; + + private const DICT_START = 1; + private const DICT_END = 2; + + private const LIST_START = 4; + private const LIST_END = 8; + + private const KEY = 16; + private const COLUMN = 32; + private const COMMA = 64; + + private const SCALAR = 128; + + private const END = 256; + + private const VALUE = self::DICT_START | self::LIST_START | self::SCALAR; + + public function __construct( + private readonly LexerInterface $lexer, + ) { + } + + /** + * @throws InvalidResourceException + */ + public function tokens(mixed $resource, int $offset, int $length, array $context): \Iterator + { + $expectedType = self::VALUE; + + $structureStack = $shouldBeDictKeyStack = []; + $structurePointer = $shouldBeDictKeyPointer = -1; + + foreach ($this->lexer->tokens($resource, $offset, $length, $context) as $i => [$token, $offset]) { + if ('{' === $token) { + if (!(self::DICT_START & $expectedType)) { + throw new InvalidResourceException($resource); + } + + $structureStack[++$structurePointer] = 'dict'; + $shouldBeDictKeyStack[++$shouldBeDictKeyPointer] = true; + + $expectedType = self::DICT_END | self::KEY; + + yield [$token, $offset]; + + continue; + } + + if ('}' === $token) { + if (!(self::DICT_END & $expectedType) || -1 === $structurePointer || -1 === $shouldBeDictKeyPointer) { + throw new InvalidResourceException($resource); + } + + --$structurePointer; + --$shouldBeDictKeyPointer; + + if (-1 === $structurePointer) { + $expectedType = self::END; + } elseif ('dict' === $structureStack[$structurePointer]) { + $expectedType = ($shouldBeDictKey = $shouldBeDictKeyStack[$shouldBeDictKeyPointer]) ? self::COLUMN : self::DICT_END | self::COMMA; + $shouldBeDictKeyStack[$shouldBeDictKeyPointer] = !$shouldBeDictKey; + } else { + $expectedType = self::LIST_END | self::COMMA; + } + + yield [$token, $offset]; + + continue; + } + + if ('[' === $token) { + if (!(self::LIST_START & $expectedType)) { + throw new InvalidResourceException($resource); + } + + $expectedType = self::LIST_END | self::VALUE; + + $structureStack[++$structurePointer] = 'list'; + + yield [$token, $offset]; + + continue; + } + + if (']' === $token) { + if (!(self::LIST_END & $expectedType) || -1 === $structurePointer) { + throw new InvalidResourceException($resource); + } + + --$structurePointer; + + if (-1 === $structurePointer) { + $expectedType = self::END; + } elseif ('dict' === $structureStack[$structurePointer]) { + $expectedType = ($shouldBeDictKey = $shouldBeDictKeyStack[$shouldBeDictKeyPointer]) ? self::COLUMN : self::DICT_END | self::COMMA; + $shouldBeDictKeyStack[$shouldBeDictKeyPointer] = !$shouldBeDictKey; + } else { + $expectedType = self::LIST_END | self::COMMA; + } + + yield [$token, $offset]; + + continue; + } + + if (',' === $token) { + if (!(self::COMMA & $expectedType) || -1 === $structurePointer) { + throw new InvalidResourceException($resource); + } + + $expectedType = 'dict' === $structureStack[$structurePointer] ? self::KEY : self::VALUE; + + yield [$token, $offset]; + + continue; + } + + if (':' === $token) { + if (!(self::COLUMN & $expectedType) || 'dict' !== ($structureStack[$structurePointer] ?? null)) { + throw new InvalidResourceException($resource); + } + + $expectedType = self::VALUE; + + yield [$token, $offset]; + + continue; + } + + if (!isset(self::$cache['valid_scalar'][$token])) { + try { + json_decode($token, associative: true, flags: ($context['json_decode_flags'] ?? 0) | \JSON_THROW_ON_ERROR); + + self::$cache['valid_scalar'][$token] = true; + } catch (\JsonException) { + self::$cache['valid_scalar'][$token] = false; + } + } + + if (!self::$cache['valid_scalar'][$token]) { + throw new InvalidResourceException($resource); + } + + if (-1 === $structurePointer) { + if (!(self::VALUE & $expectedType)) { + throw new InvalidResourceException($resource); + } + + $expectedType = self::END; + + yield [$token, $offset]; + + continue; + } + + if ('dict' === $structureStack[$structurePointer]) { + if ($shouldBeDictKey = $shouldBeDictKeyStack[$shouldBeDictKeyPointer]) { + if (!(self::KEY & $expectedType)) { + throw new InvalidResourceException($resource); + } + + if (!isset(self::$cache['valid_key'][$token])) { + self::$cache['valid_key'][$token] = str_starts_with($token, '"') && str_ends_with($token, '"'); + } + + if (!self::$cache['valid_key'][$token]) { + throw new InvalidResourceException($resource); + } + + $expectedType = self::COLUMN; + } else { + if (!(self::VALUE & $expectedType)) { + throw new InvalidResourceException($resource); + } + + $expectedType = self::DICT_END | self::COMMA; + } + + $shouldBeDictKeyStack[$shouldBeDictKeyPointer] = !$shouldBeDictKey; + + yield [$token, $offset]; + + continue; + } + + if (!(self::VALUE & $expectedType)) { + throw new InvalidResourceException($resource); + } + + $expectedType = self::LIST_END | self::COMMA; + + yield [$token, $offset]; + } + + if (self::END !== $expectedType) { + throw new InvalidResourceException($resource); + } + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/Unmarshal/LexerInterface.php b/src/Symfony/Component/Marshaller/Internal/Unmarshal/LexerInterface.php new file mode 100644 index 000000000000..32099f684ccc --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Unmarshal/LexerInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Unmarshal; + +use Symfony\Component\Marshaller\Exception\RuntimeException; + +/** + * @author Mathias Arlaud + * + * @internal + */ +interface LexerInterface +{ + /** + * @param resource $resource + * @param array $context + * + * @return \Iterator + * + * @throws RuntimeException + */ + public function tokens(mixed $resource, int $offset, int $length, array $context): \Iterator; +} diff --git a/src/Symfony/Component/Marshaller/Internal/Unmarshal/ListSplitterInterface.php b/src/Symfony/Component/Marshaller/Internal/Unmarshal/ListSplitterInterface.php new file mode 100644 index 000000000000..b4cc1b42a490 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Unmarshal/ListSplitterInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Unmarshal; + +use Symfony\Component\Marshaller\Internal\Type; + +/** + * @author Mathias Arlaud + * + * @internal + */ +interface ListSplitterInterface +{ + /** + * @param resource $resource + * @param array $context + * + * @return \Iterator|null + */ + public function split(mixed $resource, Type $type, array $context): ?\Iterator; +} diff --git a/src/Symfony/Component/Marshaller/Internal/Unmarshal/Unmarshaller.php b/src/Symfony/Component/Marshaller/Internal/Unmarshal/Unmarshaller.php new file mode 100644 index 000000000000..a2177d980188 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Unmarshal/Unmarshaller.php @@ -0,0 +1,265 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Unmarshal; + +use Symfony\Component\Marshaller\Exception\LogicException; +use Symfony\Component\Marshaller\Exception\UnexpectedValueException; +use Symfony\Component\Marshaller\Exception\UnsupportedTypeException; +use Symfony\Component\Marshaller\Instantiator\InstantiatorInterface; +use Symfony\Component\Marshaller\Internal\Type; +use Symfony\Component\Marshaller\Internal\TypeFactory; +use Symfony\Component\Marshaller\Internal\UnionType; +use Symfony\Component\Marshaller\Type\ReflectionTypeExtractor; + +/** + * @author Mathias Arlaud + * + * @internal + */ +final class Unmarshaller +{ + /** + * @var array> + */ + private static array $cache = [ + 'type' => [], + 'property_type' => [], + 'class_reflection' => [], + 'class_has_property' => [], + ]; + + public function __construct( + private readonly ReflectionTypeExtractor $reflectionTypeExtractor, + private readonly DecoderInterface $decoder, + private readonly ListSplitterInterface $listSplitter, + private readonly DictSplitterInterface $dictSplitter, + private readonly InstantiatorInterface $instantiator, + ) { + } + + /** + * @param array $context + */ + public function unmarshal(mixed $resourceOrData, Type|UnionType $type, array $context): mixed + { + if ($type instanceof UnionType) { + if (!isset($context['union_selector'][$typeString = (string) $type])) { + throw new UnexpectedValueException(sprintf('Cannot guess type to use for "%s", you may specify a type in "$context[\'union_selector\'][\'%1$s\']".', (string) $type)); + } + + if (!isset(self::$cache['type'][$typeString])) { + self::$cache['type'][$typeString] = TypeFactory::createFromString($context['union_selector'][$typeString]); + } + + /** @var Type $type */ + $type = self::$cache['type'][$typeString]; + } + + $result = match (true) { + $type->isScalar() => $this->unmarshalScalar($context['lazy_reading'], $resourceOrData, $type, $context), + $type->isCollection() => $this->unmarshalCollection($context['lazy_reading'], $resourceOrData, $type, $context), + $type->isObject() => $this->unmarshalObject($context['lazy_reading'], $resourceOrData, $type, $context), + + default => throw new UnsupportedTypeException($type), + }; + + if (null === $result && !$type->isNullable()) { + throw new UnexpectedValueException(sprintf('Unexpected "null" value for "%s" type.', (string) $type)); + } + + return $result; + } + + /** + * @param array $context + */ + private function unmarshalScalar(bool $lazy, mixed $resourceOrData, Type $type, array $context): int|string|bool|float|null + { + $data = $lazy ? $this->decoder->decode($resourceOrData, $context['boundary'][0], $context['boundary'][1], $context) : $resourceOrData; + + if (null === $data) { + return null; + } + + return match ($type->name()) { + 'int' => (int) $data, + 'float' => (float) $data, + 'string' => (string) $data, + 'bool' => (bool) $data, + default => throw new LogicException(sprintf('Cannot cast scalar to "%s".', $type->name())), + }; + } + + /** + * @param array $context + * + * @return \Iterator|\Iterator|list|array|null + */ + private function unmarshalCollection(bool $lazy, mixed $resourceOrData, Type $type, array $context): \Iterator|array|null + { + if ($lazy) { + $collectionSplitter = $type->isDict() ? $this->dictSplitter : $this->listSplitter; + + if (null === $boundaries = $collectionSplitter->split($resourceOrData, $type, $context)) { + return null; + } + + $data = $this->lazyUnmarshalCollectionItems($boundaries, $resourceOrData, $type->collectionValueType(), $context); + } else { + if (null === $resourceOrData) { + return null; + } + + $data = $this->unmarshalCollectionItems($resourceOrData, $type->collectionValueType(), $context); + } + + return $type->isIterable() ? $data : iterator_to_array($data); + } + + /** + * @param array|list $collection + * @param array $context + * + * @return \Iterator|\Iterator + */ + private function unmarshalCollectionItems(array $collection, Type|UnionType $type, array $context): \Iterator + { + foreach ($collection as $key => $value) { + yield $key => $this->unmarshal($value, $type, $context); + } + } + + /** + * @param \Iterator $boundaries + * @param resource $resource + * @param array $context + * @param \Iterator $boundaries + * + * @return \Iterator|\Iterator + */ + private function lazyUnmarshalCollectionItems(\Iterator $boundaries, mixed $resource, Type|UnionType $type, array $context): \Iterator + { + foreach ($boundaries as $key => $boundary) { + yield $key => $this->unmarshal($resource, $type, ['boundary' => $boundary] + $context); + } + } + + /** + * @param array $context + */ + private function unmarshalObject(bool $lazy, mixed $resourceOrData, Type $type, array $context): ?object + { + /** @var array|null $data */ + $data = $lazy ? $this->dictSplitter->split($resourceOrData, $type, $context) : $resourceOrData; + + if (null === $data) { + return null; + } + + $hook = null; + + if (isset($context['hooks']['unmarshal'][$className = $type->className()])) { + $hook = $context['hooks']['unmarshal'][$className]; + } elseif (isset($context['hooks']['unmarshal']['object'])) { + $hook = $context['hooks']['unmarshal']['object']; + } + + if (null !== $hook) { + /** @var array{type?: string, context?: array} $hookResult */ + $hookResult = $hook((string) $type, $context); + + if (isset($hookResult['type'])) { + if (!isset(self::$cache['type'][$hookResult['type']])) { + self::$cache['type'][$hookResult['type']] = TypeFactory::createFromString($hookResult['type']); + } + + /** @var Type $type */ + $type = self::$cache['type'][$hookResult['type']]; + } + + $context = $hookResult['context'] ?? $context; + } + + if (!isset(self::$cache['class_reflection'][$typeString = (string) $type])) { + self::$cache['class_reflection'][$typeString] = new \ReflectionClass($type->className()); + } + + /** @var \ReflectionClass $reflection */ + $reflection = self::$cache['class_reflection'][$typeString]; + + /** @var array $propertiesValues */ + $propertiesValues = []; + + foreach ($data as $k => $v) { + $hook = null; + + if (isset($context['hooks']['unmarshal'][($className = $reflection->getName()).'['.$k.']'])) { + $hook = $context['hooks']['unmarshal'][$className.'['.$k.']']; + } elseif (isset($context['hooks']['unmarshal']['property'])) { + $hook = $context['hooks']['unmarshal']['property']; + } + + $propertyName = $k; + + if (null !== $hook) { + /** @var array{name?: string, value_provider?: callable(): mixed, context?: array} $hookResult */ + $hookResult = $hook( + $reflection, + $k, + function (string $type, array $context) use ($v, $resourceOrData, $lazy): mixed { + if (!isset(self::$cache['type'][$type])) { + self::$cache['type'][$type] = TypeFactory::createFromString($type); + } + + if ($lazy) { + return $this->unmarshal($resourceOrData, self::$cache['type'][$type], ['boundary' => $v] + $context); + } + + return $this->unmarshal($v, self::$cache['type'][$type], $context); + }, + $context, + ); + + $propertyName = $hookResult['name'] ?? $propertyName; + $context = $hookResult['context'] ?? $context; + } + + if (!isset(self::$cache['class_has_property'][$propertyIdentifier = $typeString.$propertyName])) { + self::$cache['class_has_property'][$propertyIdentifier] = $reflection->hasProperty($propertyName); + } + + if (!self::$cache['class_has_property'][$propertyIdentifier]) { + continue; + } + + if (isset($hookResult['value_provider'])) { + $propertiesValues[$propertyName] = $hookResult['value_provider']; + + continue; + } + + if (!isset(self::$cache['property_type'][$propertyIdentifier])) { + self::$cache['property_type'][$propertyIdentifier] = TypeFactory::createFromString($this->reflectionTypeExtractor->extractFromProperty($reflection->getProperty($propertyName))); + } + + $propertiesValues[$propertyName] = $lazy + ? fn () => $this->unmarshal($resourceOrData, self::$cache['property_type'][$propertyIdentifier], ['boundary' => $v] + $context) + : fn () => $this->unmarshal($v, self::$cache['property_type'][$propertyIdentifier], $context); + } + + if (isset($context['instantiator'])) { + return $context['instantiator']($reflection, $propertiesValues, $context); + } + + return ($this->instantiator)($reflection, $propertiesValues, $context); + } +} diff --git a/src/Symfony/Component/Marshaller/Internal/Unmarshal/UnmarshallerFactory.php b/src/Symfony/Component/Marshaller/Internal/Unmarshal/UnmarshallerFactory.php new file mode 100644 index 000000000000..09cb59f87940 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Internal/Unmarshal/UnmarshallerFactory.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Internal\Unmarshal; + +use Symfony\Component\Marshaller\Exception\UnsupportedFormatException; +use Symfony\Component\Marshaller\Internal\Unmarshal\Json\JsonDecoder; +use Symfony\Component\Marshaller\Internal\Unmarshal\Json\JsonDictSplitter; +use Symfony\Component\Marshaller\Internal\Unmarshal\Json\JsonLexer; +use Symfony\Component\Marshaller\Internal\Unmarshal\Json\JsonListSplitter; +use Symfony\Component\Marshaller\Internal\Unmarshal\Json\ValidatingJsonLexer; +use Symfony\Component\Marshaller\Type\ReflectionTypeExtractor; + +/** + * @author Mathias Arlaud + * + * @internal + */ +abstract class UnmarshallerFactory +{ + private function __construct() + { + } + + /** + * @param array $context + */ + public static function create(string $format, array $context): Unmarshaller + { + return match ($format) { + 'json' => self::json($context['validate_stream'] ?? false), + default => throw new UnsupportedFormatException($format), + }; + } + + private static function json(bool $validate): Unmarshaller + { + $lexer = new JsonLexer(); + if ($validate) { + $lexer = new ValidatingJsonLexer($lexer); + } + + return new Unmarshaller( + reflectionTypeExtractor: new ReflectionTypeExtractor(), + decoder: new JsonDecoder(), + listSplitter: new JsonListSplitter($lexer), + dictSplitter: new JsonDictSplitter($lexer), + instantiator: new Instantiator(), + ); + } +} diff --git a/src/Symfony/Component/Marshaller/LICENSE b/src/Symfony/Component/Marshaller/LICENSE new file mode 100644 index 000000000000..3ed9f412ce53 --- /dev/null +++ b/src/Symfony/Component/Marshaller/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2023-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/Symfony/Component/Marshaller/MarshallableResolver.php b/src/Symfony/Component/Marshaller/MarshallableResolver.php new file mode 100644 index 000000000000..77dfd17136dc --- /dev/null +++ b/src/Symfony/Component/Marshaller/MarshallableResolver.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller; + +use Symfony\Component\Marshaller\Attribute\Marshallable; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +final class MarshallableResolver implements MarshallableResolverInterface +{ + /** + * @param list $paths + */ + public function __construct( + private readonly array $paths, + ) { + } + + /** + * @return iterable + */ + public function resolve(): iterable + { + foreach ($this->fromPaths($this->paths) as $class) { + if ($class->isAbstract() || $class->isInterface() || $class->isTrait()) { + continue; + } + + $attributeInstance = null; + + foreach ($class->getAttributes() as $attribute) { + if (Marshallable::class === $attribute->getName()) { + $attributeInstance = $attribute->newInstance(); + } + } + + /** @var Marshallable|null $attributeInstance */ + if (null === $attributeInstance) { + continue; + } + + yield $class->getName() => $attributeInstance; + } + } + + /** + * @param list $paths + * + * @return iterable<\ReflectionClass> + */ + private function fromPaths(array $paths): iterable + { + $includedFiles = []; + + foreach ($paths as $path) { + $phpFiles = new \RegexIterator( + new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS), + \RecursiveIteratorIterator::LEAVES_ONLY + ), + '/^.+\.php$/i', + \RecursiveRegexIterator::GET_MATCH + ); + + foreach ($phpFiles as $file) { + $sourceFile = $file[0]; + if (!preg_match('(^phar:)i', (string) $sourceFile)) { + $sourceFile = realpath($sourceFile); + } + + try { + require_once $sourceFile; + } catch (\Throwable) { + continue; + } + + $includedFiles[$sourceFile] = true; + } + } + + foreach (get_declared_classes() as $class) { + $reflectionClass = new \ReflectionClass($class); + $sourceFile = $reflectionClass->getFileName(); + + if (!isset($includedFiles[$sourceFile])) { + continue; + } + + yield $reflectionClass; + } + } +} diff --git a/src/Symfony/Component/Marshaller/MarshallableResolverInterface.php b/src/Symfony/Component/Marshaller/MarshallableResolverInterface.php new file mode 100644 index 000000000000..c5a8b35b94ea --- /dev/null +++ b/src/Symfony/Component/Marshaller/MarshallableResolverInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller; + +use Symfony\Component\Marshaller\Attribute\Marshallable; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +interface MarshallableResolverInterface +{ + /** + * @return iterable + */ + public function resolve(): iterable; +} diff --git a/src/Symfony/Component/Marshaller/Marshaller.php b/src/Symfony/Component/Marshaller/Marshaller.php new file mode 100644 index 000000000000..ecf7bd9b5d66 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Marshaller.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller; + +use Symfony\Component\Marshaller\Context\ContextBuilderInterface; +use Symfony\Component\Marshaller\Context\ContextInterface; +use Symfony\Component\Marshaller\Stream\StreamInterface; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +final class Marshaller implements MarshallerInterface +{ + /** + * @param iterable $contextBuilders + */ + public function __construct( + private readonly iterable $contextBuilders, + private readonly string $templateCacheDir, + ) { + } + + public function marshal(mixed $data, string $format, mixed $output, ContextInterface|array $context = []): void + { + if ($context instanceof ContextInterface) { + $context = $context->toArray(); + } + + if ($output instanceof StreamInterface) { + $output = $output->resource(); + } + + $context['type'] = $context['type'] ?? get_debug_type($data); + $context['cache_dir'] = $context['cache_dir'] ?? $this->templateCacheDir; + + $templateExists = file_exists(sprintf('%s%s%s.%s.php', $context['cache_dir'], \DIRECTORY_SEPARATOR, md5($context['type']), $format)); + + foreach ($this->contextBuilders as $contextBuilder) { + $context = $contextBuilder->buildMarshalContext($context, !$templateExists); + } + + marshal($data, $output, $format, $context); + } + + public function unmarshal(mixed $input, string $type, string $format, ContextInterface|array $context = []): mixed + { + if ($context instanceof ContextInterface) { + $context = $context->toArray(); + } + + if ($input instanceof StreamInterface) { + $input = $input->resource(); + } + + foreach ($this->contextBuilders as $contextBuilder) { + $context = $contextBuilder->buildUnmarshalContext($context); + } + + return unmarshal($input, $type, $format, $context); + } +} diff --git a/src/Symfony/Component/Marshaller/MarshallerInterface.php b/src/Symfony/Component/Marshaller/MarshallerInterface.php new file mode 100644 index 000000000000..2fe5c0ad2b7a --- /dev/null +++ b/src/Symfony/Component/Marshaller/MarshallerInterface.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller; + +use Symfony\Component\Marshaller\Context\ContextInterface; +use Symfony\Component\Marshaller\Exception\PartialUnmarshalException; +use Symfony\Component\Marshaller\Stream\StreamInterface; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +interface MarshallerInterface +{ + /** + * @param StreamInterface|resource $output + * @param ContextInterface|array $context + */ + public function marshal(mixed $data, string $format, mixed $output, ContextInterface|array $context = []): void; + + /** + * @param StreamInterface|resource $input + * @param ContextInterface|array $context + * + * @throws PartialUnmarshalException + */ + public function unmarshal(mixed $input, string $type, string $format, ContextInterface|array $context = []): mixed; +} diff --git a/src/Symfony/Component/Marshaller/README.md b/src/Symfony/Component/Marshaller/README.md new file mode 100644 index 000000000000..9106ce916da6 --- /dev/null +++ b/src/Symfony/Component/Marshaller/README.md @@ -0,0 +1,13 @@ +Marshaller component +==================== + +The Marshaller component provides powerful methods to marshal/unmarshal data structures into/from structured stream like JSON. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/marshaller.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/src/Symfony/Component/Marshaller/Resources/functions.php b/src/Symfony/Component/Marshaller/Resources/functions.php new file mode 100644 index 000000000000..2f5df7209fb0 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Resources/functions.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller; + +use Symfony\Component\Marshaller\Exception\InvalidArgumentException; +use Symfony\Component\Marshaller\Exception\PartialUnmarshalException; +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\Node\ArgumentsNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\ClosureNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\ExpressionNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\PhpDocNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\ReturnNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\VariableNode; +use Symfony\Component\Marshaller\Internal\Marshal\TemplateGeneratorFactory; +use Symfony\Component\Marshaller\Internal\TypeFactory; +use Symfony\Component\Marshaller\Internal\Unmarshal\DecoderFactory; +use Symfony\Component\Marshaller\Internal\Unmarshal\UnmarshallerFactory; + +if (!\function_exists(marshal::class)) { + /** + * @experimental in 6.4 + * + * @param array $context + * @param resource $resource + */ + function marshal(mixed $data, $resource, string $format, array $context = []): void + { + $type = $context['type'] ?? get_debug_type($data); + + $cacheDir = $context['cache_dir'] ?? sys_get_temp_dir().\DIRECTORY_SEPARATOR.'symfony_marshaller'; + $cacheFilename = sprintf('%s%s%s.%s.php', $cacheDir, \DIRECTORY_SEPARATOR, md5($type), $format); + + if (!file_exists($cacheFilename)) { + if (!file_exists($cacheDir)) { + mkdir($cacheDir, recursive: true); + } + + file_put_contents($cacheFilename, marshal_generate($type, $format, $context)); + } + + (require $cacheFilename)($data, $resource, $context); + } +} + +if (!\function_exists(marshal_generate::class)) { + /** + * @experimental in 6.4 + * + * @param array $context + */ + function marshal_generate(string $type, string $format, array $context = []): string + { + $compiler = new Compiler(); + $accessor = new VariableNode('data'); + + $context = $context + [ + 'generated_classes' => [], + 'variable_counters' => [], + ]; + + $compiler->compile(new PhpDocNode([sprintf('@param %s $data', $type), '@param resource $resource'])); + $phpDoc = $compiler->source(); + $compiler->reset(); + + $argumentsNode = new ArgumentsNode(['data' => 'mixed', 'resource' => 'mixed', 'context' => 'array']); + + $compiler->indent(); + $bodyNodes = TemplateGeneratorFactory::create($format)->generate(TypeFactory::createFromString($type), $accessor, $context); + $compiler->outdent(); + + $compiler->compile(new ExpressionNode(new ReturnNode(new ClosureNode($argumentsNode, 'void', true, $bodyNodes)))); + $php = $compiler->source(); + + return ' $context + * + * @throws PartialUnmarshalException + */ + function unmarshal($resource, string $type, string $format, array $context = []): mixed + { + $errors = []; + + if ($context['collect_errors'] ?? false) { + $errors = &$context['collected_errors']; + } + + $context['boundary'] = $context['boundary'] ?? [0, -1]; + $context['lazy_reading'] = $context['lazy_reading'] ?? true; + + $unmarshaller = UnmarshallerFactory::create($format, $context); + $type = TypeFactory::createFromString($type); + + $result = match ($context['lazy_reading']) { + true => $unmarshaller->unmarshal($resource, $type, $context), + false => $unmarshaller->unmarshal( + DecoderFactory::create($format)->decode($resource, $context['boundary'][0], $context['boundary'][1], $context), + $type, + $context, + ), + default => throw new InvalidArgumentException('Context value "lazy_reading" must be a boolean'), + }; + + if ([] !== $errors) { + throw new PartialUnmarshalException($resource, $result, $errors); + } + + return $result; + } +} diff --git a/src/Symfony/Component/Marshaller/Stream/InputStream.php b/src/Symfony/Component/Marshaller/Stream/InputStream.php new file mode 100644 index 000000000000..21a4f7173cf1 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Stream/InputStream.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Stream; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +final class InputStream extends Stream +{ + public function __construct() + { + parent::__construct('php://input', 'placeholder'); + } +} diff --git a/src/Symfony/Component/Marshaller/Stream/MemoryStream.php b/src/Symfony/Component/Marshaller/Stream/MemoryStream.php new file mode 100644 index 000000000000..a06da4fc2eda --- /dev/null +++ b/src/Symfony/Component/Marshaller/Stream/MemoryStream.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Stream; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +final class MemoryStream extends Stream +{ + public function __construct(string $mode = 'w+b') + { + parent::__construct('php://memory', $mode); + } +} diff --git a/src/Symfony/Component/Marshaller/Stream/OutputStream.php b/src/Symfony/Component/Marshaller/Stream/OutputStream.php new file mode 100644 index 000000000000..41b06260c550 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Stream/OutputStream.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Stream; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +final class OutputStream extends Stream +{ + public function __construct() + { + parent::__construct('php://output', 'placeholder'); + } +} diff --git a/src/Symfony/Component/Marshaller/Stream/StdinStream.php b/src/Symfony/Component/Marshaller/Stream/StdinStream.php new file mode 100644 index 000000000000..b8e14cf7931e --- /dev/null +++ b/src/Symfony/Component/Marshaller/Stream/StdinStream.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Stream; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +final class StdinStream extends Stream +{ + public function __construct() + { + parent::__construct('php://stdin', 'placeholder'); + } +} diff --git a/src/Symfony/Component/Marshaller/Stream/StdoutStream.php b/src/Symfony/Component/Marshaller/Stream/StdoutStream.php new file mode 100644 index 000000000000..96b257de82ef --- /dev/null +++ b/src/Symfony/Component/Marshaller/Stream/StdoutStream.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Stream; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +final class StdoutStream extends Stream +{ + public function __construct() + { + parent::__construct('php://stdout', 'placeholder'); + } +} diff --git a/src/Symfony/Component/Marshaller/Stream/Stream.php b/src/Symfony/Component/Marshaller/Stream/Stream.php new file mode 100644 index 000000000000..8bc2769718c2 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Stream/Stream.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Stream; + +use Symfony\Component\Marshaller\Exception\RuntimeException; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +class Stream implements StreamInterface +{ + /** + * @var resource + */ + protected $resource; + + public function __construct( + protected readonly string $filename, + protected readonly string $mode, + ) { + } + + final public function resource() + { + if (null !== $this->resource) { + return $this->resource; + } + + if (false === $resource = @fopen($this->filename, $this->mode)) { + throw new RuntimeException(sprintf('Cannot open "%s" resource', $this->filename)); + } + + return $this->resource = $resource; + } + + final public function __toString(): string + { + rewind($this->resource()); + + if (false === $content = stream_get_contents($this->resource())) { + throw new RuntimeException(sprintf('Cannot read "%s" resource', $this->filename)); + } + + return $content; + } +} diff --git a/src/Symfony/Component/Marshaller/Stream/StreamInterface.php b/src/Symfony/Component/Marshaller/Stream/StreamInterface.php new file mode 100644 index 000000000000..d7b9bffee3f1 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Stream/StreamInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Stream; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +interface StreamInterface extends \Stringable +{ + /** + * @return resource + */ + public function resource(); +} diff --git a/src/Symfony/Component/Marshaller/Stream/TempStream.php b/src/Symfony/Component/Marshaller/Stream/TempStream.php new file mode 100644 index 000000000000..638a1d18086e --- /dev/null +++ b/src/Symfony/Component/Marshaller/Stream/TempStream.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Stream; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +final class TempStream extends Stream +{ + public function __construct(int $memoryThreshold = 2048, string $mode = 'w+b') + { + parent::__construct(sprintf('php://temp/maxmemory:%d', $memoryThreshold), $mode); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Attribute/FormatterTest.php b/src/Symfony/Component/Marshaller/Tests/Attribute/FormatterTest.php new file mode 100644 index 000000000000..6ae503210ddf --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Attribute/FormatterTest.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Attribute; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Attribute\Formatter; +use Symfony\Component\Marshaller\Exception\InvalidArgumentException; + +class FormatterTest extends TestCase +{ + public function testCanCreateWithValidFunction() + { + new Formatter(marshal: 'strtoupper'); + new Formatter(unmarshal: 'strtoupper'); + + $this->addToAssertionCount(2); + } + + public function testCanCreateWithValidMethod() + { + $objectWithoutContext = new class() { + public static function toUpper(string $value): string + { + return strtoupper($value); + } + }; + + $objectWithContext = new class() { + public static function toUpper(string $value, array $context): string + { + return strtoupper($value); + } + }; + + new Formatter(marshal: [$objectWithoutContext::class, 'toUpper']); + new Formatter(marshal: [$objectWithContext::class, 'toUpper']); + + new Formatter(unmarshal: [$objectWithoutContext::class, 'toUpper']); + new Formatter(unmarshal: [$objectWithContext::class, 'toUpper']); + + $this->addToAssertionCount(4); + } + + public function testCannotCreateWithInvalidMarshalCallable() + { + $this->expectException(InvalidArgumentException::class); + + new Formatter(marshal: []); + } + + public function testCannotCreateWithInvalidUnmarshalCallable() + { + $this->expectException(InvalidArgumentException::class); + + new Formatter(unmarshal: []); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/CachedMarshallableResolverTest.php b/src/Symfony/Component/Marshaller/Tests/CachedMarshallableResolverTest.php new file mode 100644 index 000000000000..285e638a9140 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/CachedMarshallableResolverTest.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests; + +use PHPUnit\Framework\TestCase; +use Psr\Cache\CacheException; +use Psr\Cache\CacheItemInterface; +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Marshaller\CachedMarshallableResolver; +use Symfony\Component\Marshaller\MarshallableResolverInterface; + +class CachedMarshallableResolverTest extends TestCase +{ + public function testHitLocalCache() + { + $resolver = $this->createMock(MarshallableResolverInterface::class); + $resolver->method('resolve')->willReturn(new \ArrayIterator(['Foo' => null, 'Bar' => null])); + + $cachePool = $this->createMock(CacheItemPoolInterface::class); + $cachePool->expects($this->once())->method('getItem'); + $cachePool->expects($this->once())->method('save'); + + $cachedResolver = new CachedMarshallableResolver($resolver, $cachePool); + + $this->assertSame(['Foo' => null, 'Bar' => null], iterator_to_array($cachedResolver->resolve())); + iterator_to_array($cachedResolver->resolve()); + } + + public function testHitCachePool() + { + $resolver = $this->createMock(MarshallableResolverInterface::class); + $resolver->expects($this->never())->method('resolve'); + + $cacheItem = $this->createStub(CacheItemInterface::class); + $cacheItem->method('isHit')->willReturn(true); + $cacheItem->method('get')->willReturn(['Foo' => null]); + + $cachePool = $this->createMock(CacheItemPoolInterface::class); + $cachePool->expects($this->once())->method('getItem')->willReturn($cacheItem); + + $cachedResolver = new CachedMarshallableResolver($resolver, $cachePool); + + $this->assertSame(['Foo' => null], iterator_to_array($cachedResolver->resolve())); + iterator_to_array($cachedResolver->resolve()); + } + + public function testCacheException() + { + $resolver = $this->createMock(MarshallableResolverInterface::class); + $resolver->method('resolve')->willReturn(new \ArrayIterator(['Foo' => null, 'Bar' => null])); + + $cachePool = $this->createMock(CacheItemPoolInterface::class); + $cachePool->expects($this->once())->method('getItem')->willThrowException($this->createStub(CacheException::class)); + $cachePool->expects($this->never())->method('save'); + + $cachedResolver = new CachedMarshallableResolver($resolver, $cachePool); + + $this->assertSame(['Foo' => null, 'Bar' => null], iterator_to_array($cachedResolver->resolve())); + iterator_to_array($cachedResolver->resolve()); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/CodeArchitectureTest.php b/src/Symfony/Component/Marshaller/Tests/CodeArchitectureTest.php new file mode 100644 index 000000000000..ebc4dcaf67b4 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/CodeArchitectureTest.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests; + +use phpDocumentor\Reflection\Types\ContextFactory; +use PHPUnit\Framework\TestCase; + +class CodeArchitectureTest extends TestCase +{ + public function testNeverRelyOnInternal() + { + $includedFiles = []; + $path = realpath(__DIR__.'/..'); + $contextFactory = new ContextFactory(); + + $phpFiles = new \RegexIterator( + new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS), + \RecursiveIteratorIterator::LEAVES_ONLY + ), + '/^.+\.php$/i', + \RecursiveRegexIterator::GET_MATCH + ); + + foreach ($phpFiles as $file) { + $sourceFile = realpath($file[0]); + $subPath = str_replace($path.'/', '', $sourceFile); + + if (preg_match('#^(vendor|Tests|Resources|Internal)#', $subPath)) { + continue; + } + + try { + require_once $sourceFile; + } catch (\Throwable) { + continue; + } + + $includedFiles[$sourceFile] = true; + } + + foreach (get_declared_classes() as $class) { + $reflection = new \ReflectionClass($class); + if (!isset($includedFiles[$reflection->getFileName()])) { + continue; + } + + $context = $contextFactory->createFromReflector($reflection); + foreach ($context->getNamespaceAliases() as $use) { + if (str_starts_with($use, 'Symfony\\Component\\Marshaller\\Internal')) { + $this->fail(sprintf('Class "%s" is relying on an internal class: "%s".', $class, $use)); + } + } + + $this->addToAssertionCount(1); + } + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Context/ContextBuilder/CachedContextBuilderTest.php b/src/Symfony/Component/Marshaller/Tests/Context/ContextBuilder/CachedContextBuilderTest.php new file mode 100644 index 000000000000..f23fd8599bc9 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Context/ContextBuilder/CachedContextBuilderTest.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Context\ContextBuilder; + +use PHPUnit\Framework\TestCase; +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\Marshaller\Context\ContextBuilder\CachedContextBuilder; +use Symfony\Component\Marshaller\Context\ContextBuilderInterface; + +class CachedContextBuilderTest extends TestCase +{ + public function testCacheContextValue() + { + $contextBuilder = $this->createMock(ContextBuilderInterface::class); + $contextBuilder + ->expects($this->once()) + ->method('buildMarshalContext') + ->willReturn(['_symfony' => ['context_key' => 'value']]); + + $contextBuilder + ->expects($this->once()) + ->method('buildUnmarshalContext') + ->willReturn(['_symfony' => ['context_key' => 'value']]); + + $cachePool = $this->createMock(CacheItemPoolInterface::class); + $cachePool->expects($this->exactly(2))->method('getItem')->withConsecutive(['cache_key_marshal'], ['cache_key_unmarshal']); + $cachePool->expects($this->exactly(2))->method('save'); + + $contextBuilder = new CachedContextBuilder($contextBuilder, 'context_key', 'cache_key', $cachePool); + + $this->assertSame(['not_cached' => 1, '_symfony' => ['context_key' => 'value']], $contextBuilder->buildMarshalContext(['not_cached' => 1], true)); + $this->assertSame(['not_cached' => 2, '_symfony' => ['context_key' => 'value']], $contextBuilder->buildMarshalContext(['not_cached' => 2], true)); + + $this->assertSame(['not_cached' => 1, '_symfony' => ['context_key' => 'value']], $contextBuilder->buildUnmarshalContext(['not_cached' => 1])); + $this->assertSame(['not_cached' => 2, '_symfony' => ['context_key' => 'value']], $contextBuilder->buildUnmarshalContext(['not_cached' => 2])); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Context/ContextBuilder/FormatterAttributeContextBuilderTest.php b/src/Symfony/Component/Marshaller/Tests/Context/ContextBuilder/FormatterAttributeContextBuilderTest.php new file mode 100644 index 000000000000..495f8c8b3f36 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Context/ContextBuilder/FormatterAttributeContextBuilderTest.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Context\ContextBuilder; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Context\ContextBuilder\FormatterAttributeContextBuilder; +use Symfony\Component\Marshaller\MarshallableResolverInterface; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\AnotherDummyWithFormatterAttributes; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\DummyWithFormatterAttributes; + +class FormatterAttributeContextBuilderTest extends TestCase +{ + public function testAddPropertyFormattersToContext() + { + $marshallableResolver = $this->createStub(MarshallableResolverInterface::class); + $marshallableResolver->method('resolve')->willReturn(new \ArrayIterator([ + DummyWithFormatterAttributes::class => null, + AnotherDummyWithFormatterAttributes::class => null, + ])); + + $contextBuilder = new FormatterAttributeContextBuilder($marshallableResolver); + + $expectedContext = [ + '_symfony' => [ + 'property_formatter' => [ + sprintf('%s::$id', DummyWithFormatterAttributes::class) => [ + 'marshal' => [DummyWithFormatterAttributes::class, 'doubleAndCastToString'], + 'unmarshal' => [DummyWithFormatterAttributes::class, 'divideAndCastToInt'], + ], + sprintf('%s::$name', AnotherDummyWithFormatterAttributes::class) => [ + 'marshal' => [AnotherDummyWithFormatterAttributes::class, 'uppercase'], + 'unmarshal' => [AnotherDummyWithFormatterAttributes::class, 'lowercase'], + ], + ], + ], + ]; + + $this->assertSame($expectedContext, $contextBuilder->buildMarshalContext([], true)); + $this->assertSame($expectedContext, $contextBuilder->buildUnmarshalContext([])); + } + + public function testSkipWhenWontGenerateTemplate() + { + $marshallableResolver = $this->createStub(MarshallableResolverInterface::class); + + $this->assertSame([], (new FormatterAttributeContextBuilder($marshallableResolver))->buildMarshalContext([], false)); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Context/ContextBuilder/HookContextBuilderTest.php b/src/Symfony/Component/Marshaller/Tests/Context/ContextBuilder/HookContextBuilderTest.php new file mode 100644 index 000000000000..d22f0068242f --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Context/ContextBuilder/HookContextBuilderTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Context\ContextBuilder; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Context\ContextBuilder\HookContextBuilder; + +class HookContextBuilderTest extends TestCase +{ + public function testAddHooksToContext() + { + $hook = static function () {}; + $contextBuilder = new HookContextBuilder(['object' => $hook], ['object' => $hook]); + + $this->assertSame(['hooks' => ['marshal' => ['object' => $hook]]], $contextBuilder->buildMarshalContext([], true)); + $this->assertSame(['hooks' => ['unmarshal' => ['object' => $hook]]], $contextBuilder->buildUnmarshalContext([])); + } + + public function testSkipWhenWontGenerateTemplate() + { + $hook = static function () {}; + $contextBuilder = new HookContextBuilder(['object' => $hook], ['object' => $hook]); + + $this->assertSame([], $contextBuilder->buildMarshalContext([], false)); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Context/ContextBuilder/InstantiatorContextBuilderTest.php b/src/Symfony/Component/Marshaller/Tests/Context/ContextBuilder/InstantiatorContextBuilderTest.php new file mode 100644 index 000000000000..bb54058b5051 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Context/ContextBuilder/InstantiatorContextBuilderTest.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Context\ContextBuilder; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Context\ContextBuilder\InstantiatorContextBuilder; +use Symfony\Component\Marshaller\Exception\InvalidArgumentException; +use Symfony\Component\Marshaller\Instantiator\InstantiatorInterface; + +class InstantiatorContextBuilderTest extends TestCase +{ + public function testAddLazyInstantiatorToContextByDefault() + { + $instantiator = $this->createStub(InstantiatorInterface::class); + $context = []; + + $this->assertEquals(['instantiator' => $instantiator(...)], (new InstantiatorContextBuilder($instantiator))->buildUnmarshalContext($context)); + } + + public function testAddLazyInstantiatorToContext() + { + $instantiator = $this->createStub(InstantiatorInterface::class); + $context = ['instantiator' => 'lazy']; + + $this->assertEquals(['instantiator' => $instantiator(...)], (new InstantiatorContextBuilder($instantiator))->buildUnmarshalContext($context)); + } + + public function testAddEagerInstantiatorToContext() + { + $instantiator = $this->createStub(InstantiatorInterface::class); + $context = ['instantiator' => 'eager']; + + $this->assertSame(['instantiator' => null], (new InstantiatorContextBuilder($instantiator))->buildUnmarshalContext($context)); + } + + public function testAddCustomInstantiatorToContext() + { + $instantiator = $this->createStub(InstantiatorInterface::class); + $customInstantiator = static function () {}; + + $context = ['instantiator' => $customInstantiator]; + + $this->assertSame(['instantiator' => $customInstantiator], (new InstantiatorContextBuilder($instantiator))->buildUnmarshalContext($context)); + } + + public function testThrowIfInvalidInstantiator() + { + $instantiator = $this->createStub(InstantiatorInterface::class); + $context = ['instantiator' => 'foo']; + + $this->expectException(InvalidArgumentException::class); + + (new InstantiatorContextBuilder($instantiator))->buildUnmarshalContext($context); + } + + public function testSkipWhenMarshal() + { + $instantiator = $this->createStub(InstantiatorInterface::class); + + $this->assertSame([], (new InstantiatorContextBuilder($instantiator))->buildMarshalContext([], false)); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Context/ContextBuilder/NameAttributeContextBuilderTest.php b/src/Symfony/Component/Marshaller/Tests/Context/ContextBuilder/NameAttributeContextBuilderTest.php new file mode 100644 index 000000000000..19330fcab1ee --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Context/ContextBuilder/NameAttributeContextBuilderTest.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Context\ContextBuilder; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Context\ContextBuilder\NameAttributeContextBuilder; +use Symfony\Component\Marshaller\MarshallableResolverInterface; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\AnotherDummyWithNameAttributes; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\DummyWithNameAttributes; + +class NameAttributeContextBuilderTest extends TestCase +{ + public function testAddPropertyNameToContext() + { + $marshallableResolver = $this->createStub(MarshallableResolverInterface::class); + $marshallableResolver->method('resolve')->willReturn(new \ArrayIterator([ + DummyWithNameAttributes::class => null, + AnotherDummyWithNameAttributes::class => null, + ])); + + $contextBuilder = new NameAttributeContextBuilder($marshallableResolver); + + $expectedContext = [ + '_symfony' => [ + 'property_name' => [ + sprintf('%s::$id', DummyWithNameAttributes::class) => '@id', + sprintf('%s[@id]', DummyWithNameAttributes::class) => 'id', + sprintf('%s::$name', AnotherDummyWithNameAttributes::class) => 'call_me_with', + sprintf('%s[call_me_with]', AnotherDummyWithNameAttributes::class) => 'name', + ], + ], + ]; + + $this->assertSame($expectedContext, $contextBuilder->buildMarshalContext([], true)); + $this->assertSame($expectedContext, $contextBuilder->buildUnmarshalContext([])); + } + + public function testSkipWhenWontGenerateTemplate() + { + $marshallableResolver = $this->createStub(MarshallableResolverInterface::class); + + $this->assertSame([], (new NameAttributeContextBuilder($marshallableResolver))->buildMarshalContext([], false)); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Context/MarshalContextTest.php b/src/Symfony/Component/Marshaller/Tests/Context/MarshalContextTest.php new file mode 100644 index 000000000000..aeb7fd16ea11 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Context/MarshalContextTest.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Context; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Context\MarshalContext; + +class MarshalContextTest extends TestCase +{ + public function testWithers() + { + $hook = static function () {}; + + $context = (new MarshalContext(['constructor_option' => true, 'hooks' => ['marshal' => ['constructor_hook' => $hook]]])) + ->withType('TYPE') + ->withJsonEncodeFlags(123) + ->withUnionSelector(['int|string' => 'int']) + ->withObjectHook($hook) + ->withObjectHook($hook, 'className') + ->withPropertyHook($hook) + ->withPropertyHook($hook, 'className', 'propertyName'); + + $this->assertSame([ + 'hooks' => [ + 'marshal' => [ + 'constructor_hook' => $hook, + 'object' => $hook, + 'className' => $hook, + 'property' => $hook, + 'className::$propertyName' => $hook, + ], + ], + 'union_selector' => ['int|string' => 'int'], + 'json_encode_flags' => 123, + 'type' => 'TYPE', + 'constructor_option' => true, + ], $context->toArray()); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Context/UnmarshalContextTest.php b/src/Symfony/Component/Marshaller/Tests/Context/UnmarshalContextTest.php new file mode 100644 index 000000000000..c96781c8f769 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Context/UnmarshalContextTest.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Context; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Context\UnmarshalContext; + +class UnmarshalContextTest extends TestCase +{ + public function testWithers() + { + $hook = static function () {}; + + $context = (new UnmarshalContext(['constructor_option' => true, 'hooks' => ['unmarshal' => ['constructor_hook' => $hook]]])) + ->withCollectErrors() + ->withJsonDecodeFlags(123) + ->withUnionSelector(['int|string' => 'int']) + ->withObjectHook($hook) + ->withObjectHook($hook, 'className') + ->withPropertyHook($hook) + ->withPropertyHook($hook, 'className', 'propertyName') + ->withEagerReading() + ->withLazyInstantiation(); + + $this->assertSame([ + 'instantiator' => 'lazy', + 'lazy_reading' => false, + 'hooks' => [ + 'unmarshal' => [ + 'constructor_hook' => $hook, + 'object' => $hook, + 'className' => $hook, + 'property' => $hook, + 'className::$propertyName' => $hook, + ], + ], + 'union_selector' => ['int|string' => 'int'], + 'json_decode_flags' => 123, + 'collect_errors' => true, + 'constructor_option' => true, + ], $context->toArray()); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/DependencyInjection/MarshallerPassTest.php b/src/Symfony/Component/Marshaller/Tests/DependencyInjection/MarshallerPassTest.php new file mode 100644 index 000000000000..21020d560651 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/DependencyInjection/MarshallerPassTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\DependencyInjection; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Marshaller\DependencyInjection\MarshallerPass; + +class MarshallerPassTest extends TestCase +{ + public function testContextBuildersAreOrderedAccordingToPriority() + { + $container = new ContainerBuilder(); + + $definition = $container->register('marshaller')->setArguments([null]); + + $container->register('n2')->addTag('marshaller.context_builder', ['priority' => 100]); + $container->register('n1')->addTag('marshaller.context_builder', ['priority' => 200]); + $container->register('n3')->addTag('marshaller.context_builder'); + + (new MarshallerPass())->process($container); + + $this->assertEquals([new Reference('n1'), new Reference('n2'), new Reference('n3')], $definition->getArgument(0)); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Exception/InvalidResourceExceptionTest.php b/src/Symfony/Component/Marshaller/Tests/Exception/InvalidResourceExceptionTest.php new file mode 100644 index 000000000000..13c9c7c8f308 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Exception/InvalidResourceExceptionTest.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Exception; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Exception\InvalidResourceException; + +class InvalidResourceExceptionTest extends TestCase +{ + public function testMessage() + { + $this->assertSame( + 'Resource "php://memory" is not valid.', + (new InvalidResourceException(fopen('php://memory', 'r')))->getMessage(), + ); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Exception/MissingTypeExceptionTest.php b/src/Symfony/Component/Marshaller/Tests/Exception/MissingTypeExceptionTest.php new file mode 100644 index 000000000000..be496c345c01 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Exception/MissingTypeExceptionTest.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Exception; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Exception\MissingTypeException; + +class MissingTypeExceptionTest extends TestCase +{ + public function testCreateForProperty() + { + $class = $this->createStub(\ReflectionClass::class); + $class->method('getName')->willReturn('class'); + + $property = $this->createStub(\ReflectionProperty::class); + $property->method('getName')->willReturn('property'); + $property->method('getDeclaringClass')->willReturn($class); + + $this->assertSame( + 'Type of "class::$property" property has not been defined.', + MissingTypeException::forProperty($property)->getMessage(), + ); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Exception/PartialUnmarshalExceptionTest.php b/src/Symfony/Component/Marshaller/Tests/Exception/PartialUnmarshalExceptionTest.php new file mode 100644 index 000000000000..e67d435384de --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Exception/PartialUnmarshalExceptionTest.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Exception; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Exception\PartialUnmarshalException; + +class PartialUnmarshalExceptionTest extends TestCase +{ + public function testMessage() + { + $this->assertSame( + 'The "php://memory" resource has been partially unmarshalled.', + (new PartialUnmarshalException(fopen('php://memory', 'r'), null, []))->getMessage(), + ); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Dto/AbstractDummy.php b/src/Symfony/Component/Marshaller/Tests/Fixtures/Dto/AbstractDummy.php new file mode 100644 index 000000000000..be34c76d6efb --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Dto/AbstractDummy.php @@ -0,0 +1,10 @@ +id = $id; + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Dto/DummyWithFormatterAttributes.php b/src/Symfony/Component/Marshaller/Tests/Fixtures/Dto/DummyWithFormatterAttributes.php new file mode 100644 index 000000000000..cbedafec59c6 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Dto/DummyWithFormatterAttributes.php @@ -0,0 +1,28 @@ + + */ + public array $dummies = []; +} diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Dto/DummyWithMethods.php b/src/Symfony/Component/Marshaller/Tests/Fixtures/Dto/DummyWithMethods.php new file mode 100644 index 000000000000..dc8e01f676de --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Dto/DummyWithMethods.php @@ -0,0 +1,40 @@ +id = 2; + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Dto/DummyWithQuotes.php b/src/Symfony/Component/Marshaller/Tests/Fixtures/Dto/DummyWithQuotes.php new file mode 100644 index 000000000000..80c22cb9c01a --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Dto/DummyWithQuotes.php @@ -0,0 +1,13 @@ + */ + public $genericList; + + /** @var array */ + public $genericArrayList; + + /** @var array */ + public $genericDict; + + /** @var string[] */ + public $squareBracketList; + + /** @var array{foo: int, bar: string} */ + public $bracketList; + + /** @var array{} */ + public $emptyBracketList; + + /** @var int&string */ + public $intersection; + + /** @var \ArrayIterator */ + public $generic; + + /** @var Tv */ + public $genericParameter; + + /** @var array[foo] */ + public $unknown; + + public $undefined; + + /** + * @param mixed $_ + * + * @return mixed + */ + public function mixed($_) + { + return $this->mixed; + } + + /** + * @param bool $_ + * + * @return bool + */ + public function bool($_) + { + return $this->bool; + } + + /** + * @param boolean $_ + * + * @return boolean + */ + public function boolean($_) + { + return $this->boolean; + } + + /** + * @param true $_ + * + * @return true + */ + public function true($_) + { + return $this->true; + } + + /** + * @param false $_ + * + * @return false + */ + public function false($_) + { + return $this->false; + } + + /** + * @param int $_ + * + * @return int + */ + public function int($_) + { + return $this->int; + } + + /** + * @param int $_ + * + * @return int + */ + public function integer($_) + { + return $this->integer; + } + + /** + * @param float $_ + * + * @return float + */ + public function float($_) + { + return $this->float; + } + + /** + * @param string $_ + * + * @return string + */ + public function string($_) + { + return $this->string; + } + + /** + * @param resource $_ + * + * @return resource + */ + public function resource($_) + { + return $this->resource; + } + + /** + * @param object $_ + * + * @return object + */ + public function object($_) + { + return $this->object; + } + + /** + * @param callable $_ + * + * @return callable + */ + public function callable($_) + { + return $this->callable; + } + + /** + * @param array $_ + * + * @return array + */ + public function array($_) + { + return $this->array; + } + + /** + * @param list $_ + * + * @return list + */ + public function list($_) + { + return $this->list; + } + + /** + * @param iterable $_ + * + * @return iterable + */ + public function iterable($_) + { + return $this->iterable; + } + + /** + * @param non-empty-array $_ + * + * @return non-empty-array + */ + public function nonEmptyArray($_) + { + return $this->nonEmptyArray; + } + + /** + * @param non-empty-list $_ + * + * @return non-empty-list + */ + public function nonEmptyList($_) + { + return $this->nonEmptyList; + } + + /** + * @param null $_ + * + * @return null + */ + public function null($_) + { + return $this->null; + } + + /** + * @param self $_ + * + * @return self + */ + public function self($_) + { + return $this->self; + } + + /** + * @param static $_ + * + * @return static + */ + public function static($_) + { + return $this->static; + } + + /** + * @param parent $_ + * + * @return parent + */ + public function parent($_) + { + return $this->parent; + } + + /** + * @param scoped $_ + * + * @return scoped + */ + public function scoped($_) + { + return $this->scoped; + } + + /** + * @param int|string $_ + * + * @return int|string + */ + public function union($_) + { + return $this->union; + } + + /** + * @param ?int $_ + * + * @return ?int + */ + public function nullable($_) + { + return $this->nullable; + } + + /** + * @param int|string|null $_ + * + * @return int|string|null + */ + public function nullableUnion($_) + { + return $this->nullableUnion; + } + + /** + * @param list $_ + * + * @return list + */ + public function genericList($_) + { + return $this->genericList; + } + + /** + * @param array $_ + * + * @return array + */ + public function genericArrayList($_) + { + return $this->genericArrayList; + } + + /** + * @param array $_ + * + * @return array + */ + public function genericDict($_) + { + return $this->genericDict; + } + + /** + * @param string[] $_ + * + * @return string[] + */ + public function squareBracketList($_) + { + return $this->squareBracketList; + } + + /** + * @param array{foo: int, bar: string} $_ + * + * @return array{foo: int, bar: string} + */ + public function bracketList($_) + { + return $this->bracketList; + } + + /** + * @param array{} $_ + * + * @return array{} + */ + public function emptyBracketList($_) + { + return $this->emptyBracketList; + } + + /** + * @param int&string $_ + * + * @return int&string + */ + public function intersection($_) + { + return $this->intersection; + } + + /** + * @param \ArrayIterator $_ + * + * @return \ArrayIterator + */ + public function generic($_) + { + return $this->generic; + } + + /** + * @param Tv $_ + * + * @return Tv + */ + public function genericParameter($_) + { + return $this->genericParameter; + } + + /** + * @param array[foo] $_ + * + * @return array[foo] + */ + public function unknown($_) + { + return $this->unknown; + } + + public function void(): void + { + } + + public function never(): never + { + exit; + } + + public function undefined($_) + { + return $this->undefined; + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Dto/ReflectionExtractableDummy.php b/src/Symfony/Component/Marshaller/Tests/Fixtures/Dto/ReflectionExtractableDummy.php new file mode 100644 index 000000000000..9a882e193bec --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Dto/ReflectionExtractableDummy.php @@ -0,0 +1,120 @@ +mixed; + } + + public function int(int $_): int + { + return $this->int; + } + + public function string(string $_): string + { + return $this->string; + } + + public function float(float $_): float + { + return $this->float; + } + + public function bool(bool $_): bool + { + return $this->bool; + } + + public function array(array $_): array + { + return $this->array; + } + + public function self(self $_): self + { + return $this->self; + } + + public function parent(parent $_): parent + { + return $this->parent; + } + + public function class(ClassicDummy $_): ClassicDummy + { + return $this->class; + } + + public function union(string|int $_): string|int + { + return $this->union; + } + + public function intersection(\Stringable&\Countable $_): \Stringable&\Countable + { + return $this->intersection; + } + + public function nullableBuiltin(?int $_): ?int + { + return $this->nullableBuiltin; + } + + public function nullableClass(?ClassicDummy $_): ?ClassicDummy + { + return $this->nullableClass; + } + + public function nullableUnion(string|int|null $_): string|int|null + { + return $this->nullableUnion; + } + + public function void(): void + { + } + + public function never(): never + { + exit; + } + + public function undefined($_) + { + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Dto/SelfReferencingDummy.php b/src/Symfony/Component/Marshaller/Tests/Fixtures/Dto/SelfReferencingDummy.php new file mode 100644 index 000000000000..12b93d077d13 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Dto/SelfReferencingDummy.php @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_angle_bracket_null.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_angle_bracket_null.json new file mode 100755 index 000000000000..617f26254934 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_angle_bracket_null.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_array_trailing_garbage.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_array_trailing_garbage.json new file mode 100644 index 000000000000..5a745e6f3c36 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_array_trailing_garbage.json @@ -0,0 +1 @@ +[1]x \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_array_with_extra_array_close.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_array_with_extra_array_close.json new file mode 100755 index 000000000000..6cfb1398d224 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_array_with_extra_array_close.json @@ -0,0 +1 @@ +[1]] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_array_with_unclosed_string.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_array_with_unclosed_string.json new file mode 100755 index 000000000000..ba6b1788b672 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_array_with_unclosed_string.json @@ -0,0 +1 @@ +["asd] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_ascii-unicode-identifier.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_ascii-unicode-identifier.json new file mode 100644 index 000000000000..ef2ab62fe763 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_ascii-unicode-identifier.json @@ -0,0 +1 @@ +aå \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_capitalized_True.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_capitalized_True.json new file mode 100755 index 000000000000..7cd88469ab3d --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_capitalized_True.json @@ -0,0 +1 @@ +[True] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_close_unopened_array.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_close_unopened_array.json new file mode 100755 index 000000000000..d2af0c646a8f --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_close_unopened_array.json @@ -0,0 +1 @@ +1] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_comma_instead_of_closing_brace.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_comma_instead_of_closing_brace.json new file mode 100644 index 000000000000..ac61b820068b --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_comma_instead_of_closing_brace.json @@ -0,0 +1 @@ +{"x": true, \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_double_array.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_double_array.json new file mode 100755 index 000000000000..058d1626e5af --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_double_array.json @@ -0,0 +1 @@ +[][] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_end_array.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_end_array.json new file mode 100644 index 000000000000..54caf60b1367 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_end_array.json @@ -0,0 +1 @@ +] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_incomplete_UTF8_BOM.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_incomplete_UTF8_BOM.json new file mode 100755 index 000000000000..bfcdd514f330 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_incomplete_UTF8_BOM.json @@ -0,0 +1 @@ +�{} \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_lone-invalid-utf-8.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_lone-invalid-utf-8.json new file mode 100644 index 000000000000..8b1296cad20a --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_lone-invalid-utf-8.json @@ -0,0 +1 @@ +� \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_lone-open-bracket.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_lone-open-bracket.json new file mode 100644 index 000000000000..8e2f0bef135b --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_lone-open-bracket.json @@ -0,0 +1 @@ +[ \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_no_data.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_no_data.json new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_null-byte-outside-string.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_null-byte-outside-string.json new file mode 100644 index 000000000000..326db14422a7 Binary files /dev/null and b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_null-byte-outside-string.json differ diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_number_with_trailing_garbage.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_number_with_trailing_garbage.json new file mode 100644 index 000000000000..0746539d246c --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_number_with_trailing_garbage.json @@ -0,0 +1 @@ +2@ \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_object_followed_by_closing_object.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_object_followed_by_closing_object.json new file mode 100644 index 000000000000..aa9ebaec5709 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_object_followed_by_closing_object.json @@ -0,0 +1 @@ +{}} \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_object_unclosed_no_value.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_object_unclosed_no_value.json new file mode 100644 index 000000000000..17d045147fe5 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_object_unclosed_no_value.json @@ -0,0 +1 @@ +{"": \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_object_with_comment.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_object_with_comment.json new file mode 100644 index 000000000000..ed1b569b7049 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_object_with_comment.json @@ -0,0 +1 @@ +{"a":/*comment*/"b"} \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_object_with_trailing_garbage.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_object_with_trailing_garbage.json new file mode 100644 index 000000000000..9ca2336d7451 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_object_with_trailing_garbage.json @@ -0,0 +1 @@ +{"a": true} "x" \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_array_apostrophe.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_array_apostrophe.json new file mode 100644 index 000000000000..8bebe3af09a7 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_array_apostrophe.json @@ -0,0 +1 @@ +[' \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_array_comma.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_array_comma.json new file mode 100644 index 000000000000..6295fdc36db0 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_array_comma.json @@ -0,0 +1 @@ +[, \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_array_object.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_array_object.json new file mode 100644 index 000000000000..e870445b2e25 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_array_object.json @@ -0,0 +1 @@ +[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"": diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_array_open_object.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_array_open_object.json new file mode 100644 index 000000000000..7a63c8c57c8a --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_array_open_object.json @@ -0,0 +1 @@ +[{ \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_array_open_string.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_array_open_string.json new file mode 100644 index 000000000000..9822a6baf7e5 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_array_open_string.json @@ -0,0 +1 @@ +["a \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_array_string.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_array_string.json new file mode 100644 index 000000000000..42a619362021 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_array_string.json @@ -0,0 +1 @@ +["a" \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_object.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_object.json new file mode 100644 index 000000000000..81750b96f9d8 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_object.json @@ -0,0 +1 @@ +{ \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_object_close_array.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_object_close_array.json new file mode 100755 index 000000000000..eebc700a1013 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_object_close_array.json @@ -0,0 +1 @@ +{] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_object_comma.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_object_comma.json new file mode 100644 index 000000000000..47bc9106f86a --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_object_comma.json @@ -0,0 +1 @@ +{, \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_object_open_array.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_object_open_array.json new file mode 100644 index 000000000000..381ede5dea5f --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_object_open_array.json @@ -0,0 +1 @@ +{[ \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_object_open_string.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_object_open_string.json new file mode 100644 index 000000000000..328c30cd782a --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_object_open_string.json @@ -0,0 +1 @@ +{"a \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_object_string_with_apostrophes.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_object_string_with_apostrophes.json new file mode 100644 index 000000000000..9dba17090cf6 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_object_string_with_apostrophes.json @@ -0,0 +1 @@ +{'a' \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_open.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_open.json new file mode 100644 index 000000000000..841fd5f8640e --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_open_open.json @@ -0,0 +1 @@ +["\{["\{["\{["\{ \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_single_eacute.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_single_eacute.json new file mode 100644 index 000000000000..92a39f398b80 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_single_eacute.json @@ -0,0 +1 @@ +� \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_single_star.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_single_star.json new file mode 100755 index 000000000000..f59ec20aabf5 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_single_star.json @@ -0,0 +1 @@ +* \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_trailing_#.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_trailing_#.json new file mode 100644 index 000000000000..8986110875af --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_trailing_#.json @@ -0,0 +1 @@ +{"a":"b"}#{} \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_uescaped_LF_before_string.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_uescaped_LF_before_string.json new file mode 100755 index 000000000000..df2f0f242244 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_uescaped_LF_before_string.json @@ -0,0 +1 @@ +[\u000A""] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_unclosed_array.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_unclosed_array.json new file mode 100755 index 000000000000..11209515c8dd --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_unclosed_array.json @@ -0,0 +1 @@ +[1 \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_unclosed_array_partial_null.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_unclosed_array_partial_null.json new file mode 100644 index 000000000000..0d591762c127 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_unclosed_array_partial_null.json @@ -0,0 +1 @@ +[ false, nul \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_unclosed_array_unfinished_false.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_unclosed_array_unfinished_false.json new file mode 100644 index 000000000000..a2ff8504a9c6 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_unclosed_array_unfinished_false.json @@ -0,0 +1 @@ +[ true, fals \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_unclosed_array_unfinished_true.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_unclosed_array_unfinished_true.json new file mode 100644 index 000000000000..3149e8f5a7a5 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_unclosed_array_unfinished_true.json @@ -0,0 +1 @@ +[ false, tru \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_unclosed_object.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_unclosed_object.json new file mode 100755 index 000000000000..694d69dbd00c --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_unclosed_object.json @@ -0,0 +1 @@ +{"asd":"asd" \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_unicode-identifier.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_unicode-identifier.json new file mode 100644 index 000000000000..7284aea33d3f --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_unicode-identifier.json @@ -0,0 +1 @@ +å \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_whitespace_U+2060_word_joiner.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_whitespace_U+2060_word_joiner.json new file mode 100755 index 000000000000..81156a6996ed --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_whitespace_U+2060_word_joiner.json @@ -0,0 +1 @@ +[⁠] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_whitespace_formfeed.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_whitespace_formfeed.json new file mode 100755 index 000000000000..a9ea535d1bbe --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/invalid/structure_whitespace_formfeed.json @@ -0,0 +1 @@ +[ ] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/array_empty-string.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/array_empty-string.json new file mode 100644 index 000000000000..93b6be2bccad --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/array_empty-string.json @@ -0,0 +1 @@ +[""] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/array_empty.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/array_empty.json new file mode 100644 index 000000000000..0637a088a01e --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/array_empty.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/array_ending_with_newline.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/array_ending_with_newline.json new file mode 100644 index 000000000000..eac5f7b46e04 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/array_ending_with_newline.json @@ -0,0 +1 @@ +["a"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/array_false.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/array_false.json new file mode 100644 index 000000000000..67b2f07601e4 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/array_false.json @@ -0,0 +1 @@ +[false] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/array_heterogeneous.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/array_heterogeneous.json new file mode 100644 index 000000000000..d3c1e264845e --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/array_heterogeneous.json @@ -0,0 +1 @@ +[null, 1, "1", {}] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/array_null.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/array_null.json new file mode 100644 index 000000000000..500db4a86aa3 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/array_null.json @@ -0,0 +1 @@ +[null] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/array_with_1_and_newline.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/array_with_1_and_newline.json new file mode 100644 index 000000000000..994825500ae3 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/array_with_1_and_newline.json @@ -0,0 +1,2 @@ +[1 +] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/array_with_leading_space.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/array_with_leading_space.json new file mode 100644 index 000000000000..18bfe6422c78 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/array_with_leading_space.json @@ -0,0 +1 @@ + [1] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/array_with_several_null.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/array_with_several_null.json new file mode 100644 index 000000000000..99f6c5d1d883 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/array_with_several_null.json @@ -0,0 +1 @@ +[1,null,null,null,2] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/array_with_spaces.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/array_with_spaces.json new file mode 100644 index 000000000000..582290798f43 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/array_with_spaces.json @@ -0,0 +1 @@ +[[] ] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/array_with_trailing_space.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/array_with_trailing_space.json new file mode 100644 index 000000000000..de9e7a94492d --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/array_with_trailing_space.json @@ -0,0 +1 @@ +[2] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number.json new file mode 100644 index 000000000000..e5f5cc3340d2 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number.json @@ -0,0 +1 @@ +[123e65] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_0e+1.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_0e+1.json new file mode 100644 index 000000000000..d1d3967065cf --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_0e+1.json @@ -0,0 +1 @@ +[0e+1] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_0e1.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_0e1.json new file mode 100644 index 000000000000..3283a7936514 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_0e1.json @@ -0,0 +1 @@ +[0e1] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_after_space.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_after_space.json new file mode 100644 index 000000000000..623570d96040 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_after_space.json @@ -0,0 +1 @@ +[ 4] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_double_close_to_zero.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_double_close_to_zero.json new file mode 100644 index 000000000000..96555ff78230 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_double_close_to_zero.json @@ -0,0 +1 @@ +[-0.000000000000000000000000000000000000000000000000000000000000000000000000000001] diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_int_with_exp.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_int_with_exp.json new file mode 100644 index 000000000000..a4ca9e754fb0 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_int_with_exp.json @@ -0,0 +1 @@ +[20e1] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_minus_zero.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_minus_zero.json new file mode 100644 index 000000000000..37af1312ab5d --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_minus_zero.json @@ -0,0 +1 @@ +[-0] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_negative_int.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_negative_int.json new file mode 100644 index 000000000000..8e30f8bd9660 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_negative_int.json @@ -0,0 +1 @@ +[-123] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_negative_one.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_negative_one.json new file mode 100644 index 000000000000..99d21a2a0f09 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_negative_one.json @@ -0,0 +1 @@ +[-1] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_negative_zero.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_negative_zero.json new file mode 100644 index 000000000000..37af1312ab5d --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_negative_zero.json @@ -0,0 +1 @@ +[-0] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_real_capital_e.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_real_capital_e.json new file mode 100644 index 000000000000..6edbdfcb180d --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_real_capital_e.json @@ -0,0 +1 @@ +[1E22] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_real_capital_e_neg_exp.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_real_capital_e_neg_exp.json new file mode 100644 index 000000000000..0a01bd3ef4ca --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_real_capital_e_neg_exp.json @@ -0,0 +1 @@ +[1E-2] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_real_capital_e_pos_exp.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_real_capital_e_pos_exp.json new file mode 100644 index 000000000000..5a8fc0972425 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_real_capital_e_pos_exp.json @@ -0,0 +1 @@ +[1E+2] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_real_exponent.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_real_exponent.json new file mode 100644 index 000000000000..da2522d61a1e --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_real_exponent.json @@ -0,0 +1 @@ +[123e45] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_real_fraction_exponent.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_real_fraction_exponent.json new file mode 100644 index 000000000000..3944a7a454b3 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_real_fraction_exponent.json @@ -0,0 +1 @@ +[123.456e78] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_real_neg_exp.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_real_neg_exp.json new file mode 100644 index 000000000000..ca40d3c25538 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_real_neg_exp.json @@ -0,0 +1 @@ +[1e-2] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_real_pos_exponent.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_real_pos_exponent.json new file mode 100644 index 000000000000..343601d51fdd --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_real_pos_exponent.json @@ -0,0 +1 @@ +[1e+2] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_simple_int.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_simple_int.json new file mode 100644 index 000000000000..e47f69afcf45 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_simple_int.json @@ -0,0 +1 @@ +[123] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_simple_real.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_simple_real.json new file mode 100644 index 000000000000..b02878e5fc19 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/number_simple_real.json @@ -0,0 +1 @@ +[123.456789] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/object.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/object.json new file mode 100644 index 000000000000..78262eda3fa9 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/object.json @@ -0,0 +1 @@ +{"asd":"sdf", "dfg":"fgh"} \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/object_basic.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/object_basic.json new file mode 100644 index 000000000000..646bbe7bb1f8 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/object_basic.json @@ -0,0 +1 @@ +{"asd":"sdf"} \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/object_duplicated_key.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/object_duplicated_key.json new file mode 100644 index 000000000000..bbc2e1ce433c --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/object_duplicated_key.json @@ -0,0 +1 @@ +{"a":"b","a":"c"} \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/object_duplicated_key_and_value.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/object_duplicated_key_and_value.json new file mode 100644 index 000000000000..211581c20717 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/object_duplicated_key_and_value.json @@ -0,0 +1 @@ +{"a":"b","a":"b"} \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/object_empty.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/object_empty.json new file mode 100644 index 000000000000..9e26dfeeb6e6 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/object_empty.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/object_empty_key.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/object_empty_key.json new file mode 100644 index 000000000000..c0013d3b8bd2 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/object_empty_key.json @@ -0,0 +1 @@ +{"":0} \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/object_escaped_null_in_key.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/object_escaped_null_in_key.json new file mode 100644 index 000000000000..593f0f67f9cb --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/object_escaped_null_in_key.json @@ -0,0 +1 @@ +{"foo\u0000bar": 42} \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/object_extreme_numbers.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/object_extreme_numbers.json new file mode 100644 index 000000000000..a0d3531c32f9 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/object_extreme_numbers.json @@ -0,0 +1 @@ +{ "min": -1.0e+28, "max": 1.0e+28 } \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/object_long_strings.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/object_long_strings.json new file mode 100644 index 000000000000..0fe3e090e6db --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/object_long_strings.json @@ -0,0 +1 @@ +{"x":[{"id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}], "id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"} diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/object_simple.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/object_simple.json new file mode 100644 index 000000000000..dacac917fb7b --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/object_simple.json @@ -0,0 +1 @@ +{"a":[]} \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/object_string_unicode.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/object_string_unicode.json new file mode 100644 index 000000000000..8effdb297c78 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/object_string_unicode.json @@ -0,0 +1 @@ +{"title":"\u041f\u043e\u043b\u0442\u043e\u0440\u0430 \u0417\u0435\u043c\u043b\u0435\u043a\u043e\u043f\u0430" } \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/object_with_newlines.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/object_with_newlines.json new file mode 100644 index 000000000000..246ec6b34d5e --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/object_with_newlines.json @@ -0,0 +1,3 @@ +{ +"a": "b" +} \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_1_2_3_bytes_UTF-8_sequences.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_1_2_3_bytes_UTF-8_sequences.json new file mode 100644 index 000000000000..9967ddeb8b11 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_1_2_3_bytes_UTF-8_sequences.json @@ -0,0 +1 @@ +["\u0060\u012a\u12AB"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_accepted_surrogate_pair.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_accepted_surrogate_pair.json new file mode 100644 index 000000000000..996875cc8c96 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_accepted_surrogate_pair.json @@ -0,0 +1 @@ +["\uD801\udc37"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_accepted_surrogate_pairs.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_accepted_surrogate_pairs.json new file mode 100644 index 000000000000..3401021ecef5 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_accepted_surrogate_pairs.json @@ -0,0 +1 @@ +["\ud83d\ude39\ud83d\udc8d"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_allowed_escapes.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_allowed_escapes.json new file mode 100644 index 000000000000..7f495532fb37 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_allowed_escapes.json @@ -0,0 +1 @@ +["\"\\\/\b\f\n\r\t"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_backslash_and_u_escaped_zero.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_backslash_and_u_escaped_zero.json new file mode 100644 index 000000000000..d4439eda73ac --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_backslash_and_u_escaped_zero.json @@ -0,0 +1 @@ +["\\u0000"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_backslash_doublequotes.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_backslash_doublequotes.json new file mode 100644 index 000000000000..ae03243b6742 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_backslash_doublequotes.json @@ -0,0 +1 @@ +["\""] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_comments.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_comments.json new file mode 100644 index 000000000000..2260c20c2f86 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_comments.json @@ -0,0 +1 @@ +["a/*b*/c/*d//e"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_double_escape_a.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_double_escape_a.json new file mode 100644 index 000000000000..6715d6f4049f --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_double_escape_a.json @@ -0,0 +1 @@ +["\\a"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_double_escape_n.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_double_escape_n.json new file mode 100644 index 000000000000..44ca56c4d942 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_double_escape_n.json @@ -0,0 +1 @@ +["\\n"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_escaped_control_character.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_escaped_control_character.json new file mode 100644 index 000000000000..5b014a9c25b5 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_escaped_control_character.json @@ -0,0 +1 @@ +["\u0012"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_escaped_noncharacter.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_escaped_noncharacter.json new file mode 100644 index 000000000000..2ff52e2c50bc --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_escaped_noncharacter.json @@ -0,0 +1 @@ +["\uFFFF"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_in_array.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_in_array.json new file mode 100644 index 000000000000..21d7ae4cd80b --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_in_array.json @@ -0,0 +1 @@ +["asd"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_in_array_with_leading_space.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_in_array_with_leading_space.json new file mode 100644 index 000000000000..9e1887c1e4d2 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_in_array_with_leading_space.json @@ -0,0 +1 @@ +[ "asd"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_last_surrogates_1_and_2.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_last_surrogates_1_and_2.json new file mode 100644 index 000000000000..3919cef76579 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_last_surrogates_1_and_2.json @@ -0,0 +1 @@ +["\uDBFF\uDFFF"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_nbsp_uescaped.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_nbsp_uescaped.json new file mode 100644 index 000000000000..2085ab1a1c77 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_nbsp_uescaped.json @@ -0,0 +1 @@ +["new\u00A0line"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_nonCharacterInUTF-8_U+10FFFF.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_nonCharacterInUTF-8_U+10FFFF.json new file mode 100644 index 000000000000..059e4d9dd0f4 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_nonCharacterInUTF-8_U+10FFFF.json @@ -0,0 +1 @@ +["􏿿"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_nonCharacterInUTF-8_U+FFFF.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_nonCharacterInUTF-8_U+FFFF.json new file mode 100644 index 000000000000..4c913bd41a08 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_nonCharacterInUTF-8_U+FFFF.json @@ -0,0 +1 @@ +["￿"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_null_escape.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_null_escape.json new file mode 100644 index 000000000000..c1ad844043e6 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_null_escape.json @@ -0,0 +1 @@ +["\u0000"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_one-byte-utf-8.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_one-byte-utf-8.json new file mode 100644 index 000000000000..157185923ac7 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_one-byte-utf-8.json @@ -0,0 +1 @@ +["\u002c"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_pi.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_pi.json new file mode 100644 index 000000000000..9df11ae88bde --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_pi.json @@ -0,0 +1 @@ +["π"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_reservedCharacterInUTF-8_U+1BFFF.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_reservedCharacterInUTF-8_U+1BFFF.json new file mode 100644 index 000000000000..10a33a1717ec --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_reservedCharacterInUTF-8_U+1BFFF.json @@ -0,0 +1 @@ +["𛿿"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_simple_ascii.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_simple_ascii.json new file mode 100644 index 000000000000..8cadf7d051df --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_simple_ascii.json @@ -0,0 +1 @@ +["asd "] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_space.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_space.json new file mode 100644 index 000000000000..efd782cc3250 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_space.json @@ -0,0 +1 @@ +" " \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json new file mode 100644 index 000000000000..7620b66559f3 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json @@ -0,0 +1 @@ +["\uD834\uDd1e"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_three-byte-utf-8.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_three-byte-utf-8.json new file mode 100644 index 000000000000..108f1d67dffc --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_three-byte-utf-8.json @@ -0,0 +1 @@ +["\u0821"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_two-byte-utf-8.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_two-byte-utf-8.json new file mode 100644 index 000000000000..461503c31001 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_two-byte-utf-8.json @@ -0,0 +1 @@ +["\u0123"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_u+2028_line_sep.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_u+2028_line_sep.json new file mode 100644 index 000000000000..897b6021af72 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_u+2028_line_sep.json @@ -0,0 +1 @@ +["
"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_u+2029_par_sep.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_u+2029_par_sep.json new file mode 100644 index 000000000000..8cd998c89ea3 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_u+2029_par_sep.json @@ -0,0 +1 @@ +["
"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_uEscape.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_uEscape.json new file mode 100644 index 000000000000..f7b41a02fa5e --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_uEscape.json @@ -0,0 +1 @@ +["\u0061\u30af\u30EA\u30b9"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_uescaped_newline.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_uescaped_newline.json new file mode 100644 index 000000000000..3a5a220b69cc --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_uescaped_newline.json @@ -0,0 +1 @@ +["new\u000Aline"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_unescaped_char_delete.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_unescaped_char_delete.json new file mode 100644 index 000000000000..7d064f498715 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_unescaped_char_delete.json @@ -0,0 +1 @@ +[""] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_unicode.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_unicode.json new file mode 100644 index 000000000000..3598095b79be --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_unicode.json @@ -0,0 +1 @@ +["\uA66D"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_unicodeEscapedBackslash.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_unicodeEscapedBackslash.json new file mode 100644 index 000000000000..0bb3b51e7eae --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_unicodeEscapedBackslash.json @@ -0,0 +1 @@ +["\u005C"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_unicode_2.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_unicode_2.json new file mode 100644 index 000000000000..a7dcb97683f7 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_unicode_2.json @@ -0,0 +1 @@ +["⍂㈴⍂"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_unicode_U+10FFFE_nonchar.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_unicode_U+10FFFE_nonchar.json new file mode 100644 index 000000000000..9a8370b96a1c --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_unicode_U+10FFFE_nonchar.json @@ -0,0 +1 @@ +["\uDBFF\uDFFE"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_unicode_U+1FFFE_nonchar.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_unicode_U+1FFFE_nonchar.json new file mode 100644 index 000000000000..c51f8ae45c3c --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_unicode_U+1FFFE_nonchar.json @@ -0,0 +1 @@ +["\uD83F\uDFFE"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_unicode_U+200B_ZERO_WIDTH_SPACE.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_unicode_U+200B_ZERO_WIDTH_SPACE.json new file mode 100644 index 000000000000..626d5f81572d --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_unicode_U+200B_ZERO_WIDTH_SPACE.json @@ -0,0 +1 @@ +["\u200B"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_unicode_U+2064_invisible_plus.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_unicode_U+2064_invisible_plus.json new file mode 100644 index 000000000000..1e23972c65e3 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_unicode_U+2064_invisible_plus.json @@ -0,0 +1 @@ +["\u2064"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_unicode_U+FDD0_nonchar.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_unicode_U+FDD0_nonchar.json new file mode 100644 index 000000000000..18ef151b4f21 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_unicode_U+FDD0_nonchar.json @@ -0,0 +1 @@ +["\uFDD0"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_unicode_U+FFFE_nonchar.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_unicode_U+FFFE_nonchar.json new file mode 100644 index 000000000000..13d261fdad59 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_unicode_U+FFFE_nonchar.json @@ -0,0 +1 @@ +["\uFFFE"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_unicode_escaped_double_quote.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_unicode_escaped_double_quote.json new file mode 100644 index 000000000000..4e6257856dd2 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_unicode_escaped_double_quote.json @@ -0,0 +1 @@ +["\u0022"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_utf8.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_utf8.json new file mode 100644 index 000000000000..40878435f978 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_utf8.json @@ -0,0 +1 @@ +["€𝄞"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_with_del_character.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_with_del_character.json new file mode 100644 index 000000000000..8bd24907d9ea --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/string_with_del_character.json @@ -0,0 +1 @@ +["aa"] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/structure_lonely_false.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/structure_lonely_false.json new file mode 100644 index 000000000000..02e4a84d62c4 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/structure_lonely_false.json @@ -0,0 +1 @@ +false \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/structure_lonely_int.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/structure_lonely_int.json new file mode 100644 index 000000000000..f70d7bba4ae1 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/structure_lonely_int.json @@ -0,0 +1 @@ +42 \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/structure_lonely_negative_real.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/structure_lonely_negative_real.json new file mode 100644 index 000000000000..b5135a207dee --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/structure_lonely_negative_real.json @@ -0,0 +1 @@ +-0.1 \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/structure_lonely_null.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/structure_lonely_null.json new file mode 100644 index 000000000000..ec747fa47ddb --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/structure_lonely_null.json @@ -0,0 +1 @@ +null \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/structure_lonely_string.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/structure_lonely_string.json new file mode 100644 index 000000000000..b6e982ca96aa --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/structure_lonely_string.json @@ -0,0 +1 @@ +"asd" \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/structure_lonely_true.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/structure_lonely_true.json new file mode 100644 index 000000000000..f32a5804e292 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/structure_lonely_true.json @@ -0,0 +1 @@ +true \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/structure_string_empty.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/structure_string_empty.json new file mode 100644 index 000000000000..3cc762b5501e --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/structure_string_empty.json @@ -0,0 +1 @@ +"" \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/structure_trailing_newline.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/structure_trailing_newline.json new file mode 100644 index 000000000000..0c3426d4c287 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/structure_trailing_newline.json @@ -0,0 +1 @@ +["a"] diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/structure_true_in_array.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/structure_true_in_array.json new file mode 100644 index 000000000000..de601e305f4f --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/structure_true_in_array.json @@ -0,0 +1 @@ +[true] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/structure_whitespace_array.json b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/structure_whitespace_array.json new file mode 100644 index 000000000000..2bedf7f2de5c --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Fixtures/Resources/json/valid/structure_whitespace_array.json @@ -0,0 +1 @@ + [] \ No newline at end of file diff --git a/src/Symfony/Component/Marshaller/Tests/Hook/Marshal/ObjectHookTest.php b/src/Symfony/Component/Marshaller/Tests/Hook/Marshal/ObjectHookTest.php new file mode 100644 index 000000000000..65810e45536d --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Hook/Marshal/ObjectHookTest.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Hook\Marshal; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Exception\InvalidArgumentException; +use Symfony\Component\Marshaller\Hook\Marshal\ObjectHook; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\ClassicDummy; +use Symfony\Component\Marshaller\Type\TypeExtractorInterface; + +class ObjectHookTest extends TestCase +{ + /** + * @dataProvider addGenericParameterTypesDataProvider + * + * @param array> $expectedGenericParameterTypes + * @param list $templates + */ + public function testAddGenericParameterTypes(array $expectedGenericParameterTypes, string $type, array $templates) + { + $typeExtractor = $this->createStub(TypeExtractorInterface::class); + $typeExtractor->method('extractTemplateFromClass')->willReturn($templates); + + $hookResult = (new ObjectHook($typeExtractor))($type, '$accessor', []); + + $this->assertSame($expectedGenericParameterTypes, $hookResult['context']['_symfony']['generic_parameter_types'] ?? []); + } + + /** + * @return iterable>, 1: string, 2: list}> + */ + public function addGenericParameterTypesDataProvider(): iterable + { + yield [[], 'int', []]; + yield [[], 'Foo', ['T']]; + yield [[ClassicDummy::class => ['T' => 'int']], ClassicDummy::class.'', ['T']]; + yield [[ClassicDummy::class => ['Tk' => 'int', 'Tv' => 'string']], ClassicDummy::class.'', ['Tk', 'Tv']]; + } + + public function testThrowOnWrongGenericTypeCount() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage(sprintf('Given 1 generic parameters in "%s", but 2 templates are defined in "%1$s".', ClassicDummy::class)); + + $typeExtractor = $this->createStub(TypeExtractorInterface::class); + $typeExtractor->method('extractTemplateFromClass')->willReturn(['Tk', 'Tv']); + + (new ObjectHook($typeExtractor))(ClassicDummy::class.'', '$accessor', []); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Hook/Marshal/PropertyHookTest.php b/src/Symfony/Component/Marshaller/Tests/Hook/Marshal/PropertyHookTest.php new file mode 100644 index 000000000000..136aa73139db --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Hook/Marshal/PropertyHookTest.php @@ -0,0 +1,182 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Hook\Marshal; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Exception\InvalidArgumentException; +use Symfony\Component\Marshaller\Hook\Marshal\PropertyHook; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\ClassicDummy; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\DummyWithMethods; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\DummyWithQuotes; +use Symfony\Component\Marshaller\Type\TypeExtractorInterface; + +class PropertyHookTest extends TestCase +{ + /** + * @dataProvider updateNameDataProvider + * + * @param array $propertyNames + */ + public function testUpdateName(string $expectedName, array $propertyNames) + { + $typeExtractor = $this->createStub(TypeExtractorInterface::class); + + $context = [ + '_symfony' => [ + 'property_name' => $propertyNames, + ], + ]; + + $hookResult = (new PropertyHook($typeExtractor))(new \ReflectionProperty(ClassicDummy::class, 'id'), '$accessor', $context); + + $this->assertSame($expectedName, $hookResult['name']); + } + + /** + * @return iterable}> + */ + public function updateNameDataProvider(): iterable + { + yield ['id', []]; + yield ['id', [sprintf('%s::$name', ClassicDummy::class) => 'identifier']]; + yield ['identifier', [sprintf('%s::$id', ClassicDummy::class) => 'identifier']]; + } + + /** + * @dataProvider updateTypeAccessorAndContextFromFormatterDataProvider + * + * @param array $propertyFormatters + */ + public function testUpdateTypeAccessorAndContextFromFormatter(string $expectedType, string $expectedAccessor, array $propertyFormatters) + { + $typeExtractor = $this->createStub(TypeExtractorInterface::class); + $typeExtractor->method('extractFromProperty')->willReturn('int'); + $typeExtractor->method('extractFromFunctionReturn')->willReturn('string'); + + $context = [ + '_symfony' => [ + 'property_formatter' => $propertyFormatters, + ], + ]; + + $hookResult = (new PropertyHook($typeExtractor))(new \ReflectionProperty(ClassicDummy::class, 'id'), '$accessor', $context); + + $this->assertSame($expectedType, $hookResult['type']); + $this->assertSame($expectedAccessor, $hookResult['accessor']); + } + + /** + * @return iterable}> + */ + public function updateTypeAccessorAndContextFromFormatterDataProvider(): iterable + { + yield ['int', '$accessor', []]; + yield ['int', '$accessor', [sprintf('%s::$name', ClassicDummy::class) => ['marshal' => DummyWithMethods::doubleAndCastToString(...)]]]; + yield [ + 'string', + sprintf('%s::doubleAndCastToString($accessor, $context)', DummyWithMethods::class), + [sprintf('%s::$id', ClassicDummy::class) => ['marshal' => DummyWithMethods::doubleAndCastToString(...)]], + ]; + } + + /** + * @dataProvider throwWhenWrongFormatterDataProvider + */ + public function testThrowWhenWrongFormatter(string $exceptionMessage, callable $formatter) + { + $typeExtractor = $this->createStub(TypeExtractorInterface::class); + + $context = [ + '_symfony' => [ + 'property_formatter' => [ + sprintf('%s::$id', ClassicDummy::class) => ['marshal' => $formatter], + ], + ], + ]; + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage($exceptionMessage); + + (new PropertyHook($typeExtractor))(new \ReflectionProperty(ClassicDummy::class, 'id'), '$accessor', $context); + } + + /** + * @return iterable + */ + public function throwWhenWrongFormatterDataProvider(): iterable + { + yield [ + sprintf('Property formatter "%s::$id" must be a static method.', ClassicDummy::class), + (new DummyWithMethods())->nonStatic(...), + ]; + + yield [ + sprintf('Return type of property formatter "%s::$id" must not be "void" nor "never".', ClassicDummy::class), + DummyWithMethods::void(...), + ]; + + yield [ + sprintf('Second argument of property formatter "%s::$id" must be an array.', ClassicDummy::class), + DummyWithMethods::invalidContextType(...), + ]; + } + + public function testConvertGenericTypes() + { + $typeExtractor = $this->createStub(TypeExtractorInterface::class); + $typeExtractor->method('extractFromProperty')->willReturn('T'); + $typeExtractor->method('extractFromFunctionReturn')->willReturn('U'); + + $context = [ + '_symfony' => [ + 'generic_parameter_types' => [ + ClassicDummy::class => ['T' => 'string'], + DummyWithMethods::class => ['U' => 'int'], + ], + 'property_formatter' => [ + sprintf('%s::$id', DummyWithMethods::class) => [ + 'marshal' => DummyWithMethods::doubleAndCastToString(...), + ], + ], + ], + ]; + + $hook = new PropertyHook($typeExtractor); + + $this->assertSame('string', $hook(new \ReflectionProperty(ClassicDummy::class, 'id'), '$accessor', $context)['type']); + $this->assertSame('int', $hook(new \ReflectionProperty(DummyWithMethods::class, 'id'), '$accessor', $context)['type']); + $this->assertSame('T', $hook(new \ReflectionProperty(DummyWithQuotes::class, 'name'), '$accessor', $context)['type']); + } + + public function testDoNotConvertGenericTypesWhenFormatterDoesNotBelongToCurrentClass() + { + $typeExtractor = $this->createStub(TypeExtractorInterface::class); + $typeExtractor->method('extractFromFunctionReturn')->willReturn('T'); + + $context = [ + '_symfony' => [ + 'generic_parameter_types' => [ + ClassicDummy::class => ['T' => 'string'], + ], + 'property_formatter' => [ + sprintf('%s::$id', ClassicDummy::class) => [ + 'marshal' => DummyWithMethods::doubleAndCastToString(...), + ], + ], + ], + ]; + + $hook = new PropertyHook($typeExtractor); + + $this->assertSame('T', $hook(new \ReflectionProperty(ClassicDummy::class, 'id'), '$accessor', $context)['type']); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Hook/Unmarshal/ObjectHookTest.php b/src/Symfony/Component/Marshaller/Tests/Hook/Unmarshal/ObjectHookTest.php new file mode 100644 index 000000000000..8905529164c4 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Hook/Unmarshal/ObjectHookTest.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Hook\Unmarshal; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Exception\InvalidArgumentException; +use Symfony\Component\Marshaller\Hook\Unmarshal\ObjectHook; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\ClassicDummy; +use Symfony\Component\Marshaller\Type\TypeExtractorInterface; + +class ObjectHookTest extends TestCase +{ + /** + * @dataProvider addGenericParameterTypesDataProvider + * + * @param array> $expectedGenericParameterTypes + * @param list $templates + */ + public function testAddGenericParameterTypes(array $expectedGenericParameterTypes, string $type, array $templates) + { + $typeExtractor = $this->createStub(TypeExtractorInterface::class); + $typeExtractor->method('extractTemplateFromClass')->willReturn($templates); + + $hookResult = (new ObjectHook($typeExtractor))($type, []); + + $this->assertSame($expectedGenericParameterTypes, $hookResult['context']['_symfony']['generic_parameter_types'] ?? []); + } + + /** + * @return iterable>, 1: string, 2: list}> + */ + public function addGenericParameterTypesDataProvider(): iterable + { + yield [[], 'int', []]; + yield [[], 'Foo', ['T']]; + yield [[ClassicDummy::class => ['T' => 'int']], ClassicDummy::class.'', ['T']]; + yield [[ClassicDummy::class => ['Tk' => 'int', 'Tv' => 'string']], ClassicDummy::class.'', ['Tk', 'Tv']]; + } + + public function testThrowOnWrongGenericTypeCount() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage(sprintf('Given 1 generic parameters in "%s", but 2 templates are defined in "%1$s".', ClassicDummy::class)); + + $typeExtractor = $this->createStub(TypeExtractorInterface::class); + $typeExtractor->method('extractTemplateFromClass')->willReturn(['Tk', 'Tv']); + + (new ObjectHook($typeExtractor))(ClassicDummy::class.'', []); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Hook/Unmarshal/PropertyHookTest.php b/src/Symfony/Component/Marshaller/Tests/Hook/Unmarshal/PropertyHookTest.php new file mode 100644 index 000000000000..f607d4452490 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Hook/Unmarshal/PropertyHookTest.php @@ -0,0 +1,192 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Hook\Unmarshal; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Hook\Unmarshal\PropertyHook; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\ClassicDummy; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\DummyWithFormatterAttributes; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\DummyWithGenerics; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\DummyWithQuotes; +use Symfony\Component\Marshaller\Type\PhpstanTypeExtractor; +use Symfony\Component\Marshaller\Type\ReflectionTypeExtractor; +use Symfony\Component\Marshaller\Type\TypeExtractorInterface; + +class PropertyHookTest extends TestCase +{ + public function testRetrievePropertyName() + { + $typeExtractor = $this->createStub(TypeExtractorInterface::class); + + $context = [ + '_symfony' => [ + 'property_name' => [ + sprintf('%s[@id]', ClassicDummy::class) => 'id', + ], + ], + ]; + + $result = (new PropertyHook($typeExtractor))(new \ReflectionClass(ClassicDummy::class), '@id', fn () => null, $context); + + $this->assertSame('id', $result['name']); + } + + public function testSkipOnInvalidProperty() + { + $typeExtractor = $this->createStub(TypeExtractorInterface::class); + + $result = (new PropertyHook($typeExtractor))(new \ReflectionClass(ClassicDummy::class), 'invalid', fn () => null, []); + + $this->assertSame([], $result); + } + + public function testRetrievePropertyType() + { + $typeExtractor = new PhpstanTypeExtractor(new ReflectionTypeExtractor()); + + $type = null; + $valueProvider = static function (string $valueType, array $context) use (&$type) { + $type = $valueType; + }; + + (new PropertyHook($typeExtractor))(new \ReflectionClass(ClassicDummy::class), 'name', $valueProvider, [])['value_provider'](); + + $this->assertSame('string', $type); + } + + public function testRetrievePropertyTypeWithGenerics() + { + $typeExtractor = new PhpstanTypeExtractor(new ReflectionTypeExtractor()); + + $context = [ + '_symfony' => [ + 'generic_parameter_types' => [ + DummyWithGenerics::class => ['T' => ClassicDummy::class], + ], + ], + ]; + + $type = null; + $valueProvider = static function (string $valueType, array $context) use (&$type) { + $type = $valueType; + }; + + (new PropertyHook($typeExtractor))(new \ReflectionClass(DummyWithGenerics::class), 'dummies', $valueProvider, $context)['value_provider'](); + + $this->assertSame(sprintf('array', ClassicDummy::class), $type); + } + + public function testRetrievePropertyTypeWithFormatter() + { + $typeExtractor = new PhpstanTypeExtractor(new ReflectionTypeExtractor()); + + $type = null; + $valueProvider = static function (string $valueType, array $context) use (&$type): int { + $type = $valueType; + + return 123; + }; + + $context = [ + '_symfony' => [ + 'property_formatter' => [ + sprintf('%s::$name', ClassicDummy::class) => [ + 'unmarshal' => fn (int $v, array $c): string => (string) $v, + ], + ], + ], + ]; + + (new PropertyHook($typeExtractor))(new \ReflectionClass(ClassicDummy::class), 'name', $valueProvider, $context)['value_provider'](); + + $this->assertSame('int', $type); + } + + public function testRetrievePropertyTypeWithFormatterAndGenerics() + { + $typeExtractor = $this->createStub(TypeExtractorInterface::class); + $typeExtractor->method('extractFromFunctionParameter')->willReturn('T'); + + $type = null; + $valueProvider = static function (string $valueType, array $context) use (&$type): int { + $type = $valueType; + + return 123; + }; + + $context = [ + '_symfony' => [ + 'generic_parameter_types' => [ + DummyWithFormatterAttributes::class => ['T' => 'string'], + ], + 'property_formatter' => [ + sprintf('%s::$name', DummyWithFormatterAttributes::class) => [ + 'unmarshal' => DummyWithFormatterAttributes::doubleAndCastToString(...), + ], + ], + ], + ]; + + (new PropertyHook($typeExtractor))(new \ReflectionClass(DummyWithFormatterAttributes::class), 'name', $valueProvider, $context)['value_provider'](); + + $this->assertSame('string', $type); + } + + public function testDoNotReplaceGenericTypesWhenFormatterDoesNotBelongToCurrentClass() + { + $typeExtractor = $this->createStub(TypeExtractorInterface::class); + $typeExtractor->method('extractFromFunctionParameter')->willReturn('T'); + + $type = null; + $valueProvider = static function (string $valueType, array $context) use (&$type): int { + $type = $valueType; + + return 123; + }; + + $context = [ + '_symfony' => [ + 'generic_parameter_types' => [ + DummyWithQuotes::class => ['T' => 'string'], + ], + 'property_formatter' => [ + sprintf('%s::$name', DummyWithQuotes::class) => [ + 'unmarshal' => fn (mixed $v, array $c): string => (string) $v, + ], + ], + ], + ]; + + (new PropertyHook($typeExtractor))(new \ReflectionClass(DummyWithQuotes::class), 'name', $valueProvider, $context)['value_provider'](); + + $this->assertSame('T', $type); + } + + public function testFormatValue() + { + $typeExtractor = $this->createStub(TypeExtractorInterface::class); + + $context = [ + '_symfony' => [ + 'property_formatter' => [ + sprintf('%s::$name', ClassicDummy::class) => [ + 'unmarshal' => fn (string $v, array $c): string => strtoupper($v), + ], + ], + ], + ]; + + $result = (new PropertyHook($typeExtractor))(new \ReflectionClass(ClassicDummy::class), 'name', fn () => 'the_name', $context); + + $this->assertSame('THE_NAME', $result['value_provider']()); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Instantiator/LazyInstantiatorTest.php b/src/Symfony/Component/Marshaller/Tests/Instantiator/LazyInstantiatorTest.php new file mode 100644 index 000000000000..0661531b0eb8 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Instantiator/LazyInstantiatorTest.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Instantiator; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Instantiator\LazyInstantiator; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\ClassicDummy; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\DummyWithFormatterAttributes; + +class LazyInstantiatorTest extends TestCase +{ + private string $cacheDir; + + protected function setUp(): void + { + parent::setUp(); + + $this->cacheDir = sprintf('%s/symfony_marshaller_lazy_object', sys_get_temp_dir()); + + if (is_dir($this->cacheDir)) { + array_map('unlink', glob($this->cacheDir.'/*')); + rmdir($this->cacheDir); + } + } + + public function testCreateLazyGhost() + { + $ghost = (new LazyInstantiator($this->cacheDir))(new \ReflectionClass(ClassicDummy::class), [], []); + + $this->assertArrayHasKey(sprintf("\0%sGhost\0lazyObjectState", preg_replace('/\\\\/', '', ClassicDummy::class)), (array) $ghost); + } + + public function testCreateCacheFile() + { + (new LazyInstantiator($this->cacheDir))(new \ReflectionClass(DummyWithFormatterAttributes::class), [], []); + + $this->assertCount(1, glob($this->cacheDir.'/*')); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/InstantiatorTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/InstantiatorTest.php new file mode 100644 index 000000000000..18f42e5d890d --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/InstantiatorTest.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Exception\InvalidConstructorArgumentException; +use Symfony\Component\Marshaller\Exception\UnexpectedTypeException; +use Symfony\Component\Marshaller\Internal\Unmarshal\Instantiator; + +class InstantiatorTest extends TestCase +{ + public function testInstantiateWithoutConstructor() + { + $instance = (new Instantiator())(new \ReflectionClass(DummyWithoutConstructor::class), [], []); + + $this->assertInstanceOf(DummyWithoutConstructor::class, $instance); + } + + public function testInstantiateWithoutPublicConstructor() + { + $instance = (new Instantiator())(new \ReflectionClass(DummyWithoutPublicConstructor::class), [], []); + + $this->assertInstanceOf(DummyWithoutPublicConstructor::class, $instance); + $this->assertFalse($instance->updated); + } + + public function testInstantiateWithConstructorWithDefaultValues() + { + $instance = (new Instantiator())(new \ReflectionClass(DummyWithDefaultConstructorValues::class), [], []); + + $this->assertInstanceOf(DummyWithDefaultConstructorValues::class, $instance); + $this->assertTrue($instance->foo); + $this->assertFalse($instance->bar); + } + + public function testInstantiateWithConstructorWithNullableValues() + { + $instance = (new Instantiator())(new \ReflectionClass(DummyWithNullableConstructorValues::class), [], []); + + $this->assertInstanceOf(DummyWithNullableConstructorValues::class, $instance); + $this->assertNull($instance->foo); + } + + public function testInstantiateWithInvalidConstructorArgumentThrow() + { + $this->expectException(InvalidConstructorArgumentException::class); + + (new Instantiator())(new \ReflectionClass(DummyWithRequiredConstructorArguments::class), [], []); + } + + public function testInstantiateWithInvalidConstructorArgumentCollectError() + { + $context = ['collect_errors' => true]; + $errors = &$context['collected_errors']; + + $instance = (new Instantiator())(new \ReflectionClass(DummyWithRequiredConstructorArguments::class), [], $context); + + $this->assertCount(2, $context['collected_errors']); + $this->assertContainsOnlyInstancesOf(InvalidConstructorArgumentException::class, $context['collected_errors']); + + $this->assertInstanceOf(DummyWithRequiredConstructorArguments::class, $instance); + } + + public function testSetProperty() + { + $instance = (new Instantiator())(new \ReflectionClass(DummyWithProperty::class), ['updated' => fn () => true], []); + + $this->assertTrue($instance->updated); + } + + public function testSetPropertyInvalidTypeThrow() + { + $this->expectException(UnexpectedTypeException::class); + + (new Instantiator())(new \ReflectionClass(DummyWithProperty::class), ['updated' => fn () => new \DateTimeImmutable()], []); + } + + public function testSetPropertyInvalidTypeCollectError() + { + $context = ['collect_errors' => true]; + $errors = &$context['collected_errors']; + + $instance = (new Instantiator())(new \ReflectionClass(DummyWithProperty::class), ['updated' => fn () => new \DateTimeImmutable()], $context); + + $this->assertCount(1, $context['collected_errors']); + $this->assertContainsOnlyInstancesOf(UnexpectedTypeException::class, $context['collected_errors']); + + $this->assertInstanceOf(DummyWithProperty::class, $instance); + $this->assertFalse($instance->updated); + } +} + +class DummyWithoutConstructor +{ +} + +class DummyWithoutPublicConstructor +{ + public bool $updated = false; + + protected function __construct() + { + $this->updated = true; + } +} + +class DummyWithDefaultConstructorValues +{ + public function __construct( + public bool $foo = true, + public ?bool $bar = false, + ) { + } +} + +class DummyWithNullableConstructorValues +{ + public function __construct( + public ?bool $foo, + ) { + } +} + +class DummyWithRequiredConstructorArguments +{ + public function __construct( + public bool $required, + public bool $requiredAsWell, + ) { + } +} + +class DummyWithProperty +{ + public function __construct( + public bool $updated = false, + ) { + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/CompilerTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/CompilerTest.php new file mode 100644 index 000000000000..e517c6cde4c2 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/CompilerTest.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal\Marshal; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\Node\ExpressionNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\ScalarNode; + +class CompilerTest extends TestCase +{ + public function testRaw() + { + $this->assertSame('rawString', (new Compiler())->raw('rawString')->source()); + } + + public function testLine() + { + $compiler = new Compiler(); + + $this->assertSame('lineString'.\PHP_EOL, $compiler->line('lineString')->source()); + $this->assertSame('lineString'.\PHP_EOL.' lineString'.\PHP_EOL, $compiler->indent()->line('lineString')->source()); + $this->assertSame('lineString'.\PHP_EOL.' lineString'.\PHP_EOL.'lineString'.\PHP_EOL, $compiler->outdent()->line('lineString')->source()); + } + + public function testCompile() + { + $compiler = new Compiler(); + + $this->assertSame('"foo";'.\PHP_EOL, $compiler->compile(new ExpressionNode(new ScalarNode('foo')))->source()); + + $compiler->indent(); + + $this->assertSame('"foo";'.\PHP_EOL.' "bar";'.\PHP_EOL, $compiler->compile(new ExpressionNode(new ScalarNode('bar')))->source()); + } + + public function testReset() + { + $compiler = new Compiler(); + + $this->assertSame('"foo";'.\PHP_EOL, $compiler->compile(new ExpressionNode(new ScalarNode('foo')))->source()); + + $compiler->indent(); + $compiler->reset(); + + $this->assertSame('"bar";'.\PHP_EOL, $compiler->compile(new ExpressionNode(new ScalarNode('bar')))->source()); + } + + public function testSubcompile() + { + $compiler = new Compiler(); + + $this->assertSame('"foo";'.\PHP_EOL, $compiler->compile(new ExpressionNode(new ScalarNode('foo')))->source()); + $this->assertSame('"bar";'.\PHP_EOL, $compiler->subcompile(new ExpressionNode(new ScalarNode('bar')))); + + $this->assertSame('"foo";'.\PHP_EOL, $compiler->source()); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Json/JsonSyntaxTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Json/JsonSyntaxTest.php new file mode 100644 index 000000000000..7804e2b85588 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Json/JsonSyntaxTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal\Marshal\Json; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Internal\Marshal\Json\JsonSyntax; + +class JsonSyntaxTest extends TestCase +{ + public function testEscapeString() + { + $jsonSyntax = new JsonSyntax(); + + $this->assertSame('foo', $jsonSyntax->escapeString('foo')); + $this->assertSame('f\"oo', $jsonSyntax->escapeString('f"oo')); + $this->assertSame('f\\\\\"oo', $jsonSyntax->escapeString('f\\"oo')); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Json/MarshalGenerateTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Json/MarshalGenerateTest.php new file mode 100644 index 000000000000..33d46b72bfd7 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Json/MarshalGenerateTest.php @@ -0,0 +1,307 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal\Marshal\Json; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\ClassicDummy; + +use function Symfony\Component\Marshaller\marshal_generate; + +class MarshalGenerateTest extends TestCase +{ + /** + * @dataProvider marshalGenerateDataProvider + * + * @param array $context + */ + public function testMarshalGenerate(string $expectedSource, string $type, array $context) + { + $this->assertSame($expectedSource, marshal_generate($type, 'json', $context)); + } + + /** + * @return iterable}> + */ + public function marshalGenerateDataProvider(): iterable + { + yield [ + << \$data + * @param resource \$resource + */ + return static function (mixed \$data, mixed \$resource, array \$context): void { + \\fwrite(\$resource, "["); + \$prefix_0 = ""; + foreach (\$data as \$value_0) { + \\fwrite(\$resource, \$prefix_0); + \\fwrite(\$resource, \json_encode(\$value_0, \$context["json_encode_flags"] ?? 0)); + \$prefix_0 = ","; + } + \\fwrite(\$resource, "]"); + }; + + PHP, + 'array', + [], + ]; + + yield [ + << \$data + * @param resource \$resource + */ + return static function (mixed \$data, mixed \$resource, array \$context): void { + \\fwrite(\$resource, "{"); + \$prefix_0 = ""; + foreach (\$data as \$key_0 => \$value_0) { + \$key_0 = \substr(\json_encode(\$key_0, \$context["json_encode_flags"] ?? 0), 1, -1); + \\fwrite(\$resource, "{\$prefix_0}\"{\$key_0}\":"); + \\fwrite(\$resource, \json_encode(\$value_0, \$context["json_encode_flags"] ?? 0)); + \$prefix_0 = ","; + } + \\fwrite(\$resource, "}"); + }; + + PHP, + 'array', + [], + ]; + + yield [ + << \$data + * @param resource \$resource + */ + return static function (mixed \$data, mixed \$resource, array \$context): void { + \\fwrite(\$resource, "["); + \$prefix_0 = ""; + foreach (\$data as \$value_0) { + \\fwrite(\$resource, \$prefix_0); + \\fwrite(\$resource, \json_encode(\$value_0, \$context["json_encode_flags"] ?? 0)); + \$prefix_0 = ","; + } + \\fwrite(\$resource, "]"); + }; + + PHP, + 'iterable', + [], + ]; + + yield [ + << \$data + * @param resource \$resource + */ + return static function (mixed \$data, mixed \$resource, array \$context): void { + \\fwrite(\$resource, "{"); + \$prefix_0 = ""; + foreach (\$data as \$key_0 => \$value_0) { + \$key_0 = \substr(\json_encode(\$key_0, \$context["json_encode_flags"] ?? 0), 1, -1); + \\fwrite(\$resource, "{\$prefix_0}\"{\$key_0}\":"); + \\fwrite(\$resource, \json_encode(\$value_0, \$context["json_encode_flags"] ?? 0)); + \$prefix_0 = ","; + } + \\fwrite(\$resource, "}"); + }; + + PHP, + 'iterable', + [], + ]; + + yield [ + <<id, \$context["json_encode_flags"] ?? 0)); + \\fwrite(\$resource, ",\"name\":"); + \\fwrite(\$resource, \json_encode(\$object_0->name, \$context["json_encode_flags"] ?? 0)); + \\fwrite(\$resource, "}"); + }; + + PHP, + ClassicDummy::class, + [], + ]; + + yield [ + << \$data + * @param resource \$resource + */ + return static function (mixed \$data, mixed \$resource, array \$context): void { + \\fwrite(\$resource, "["); + \$prefix_0 = ""; + foreach (\$data as \$value_0) { + \\fwrite(\$resource, \$prefix_0); + \$object_0 = \$value_0; + \\fwrite(\$resource, "{\"id\":"); + \\fwrite(\$resource, \json_encode(\$object_0->id, \$context["json_encode_flags"] ?? 0)); + \\fwrite(\$resource, ",\"name\":"); + \\fwrite(\$resource, \json_encode(\$object_0->name, \$context["json_encode_flags"] ?? 0)); + \\fwrite(\$resource, "}"); + \$prefix_0 = ","; + } + \\fwrite(\$resource, "]"); + }; + + PHP, + sprintf('array', + ClassicDummy::class), + [], + ]; + + yield [ + <<name, \$context["json_encode_flags"] ?? 0)); + \\fwrite(\$resource, "}"); + }; + + PHP, ClassicDummy::class, [ + 'hooks' => [ + 'marshal' => [ + sprintf('%s::$id', ClassicDummy::class) => static function (\ReflectionProperty $property, string $accessor, array $context): array { + return [ + 'name' => 'foo', + 'type' => 'string', + 'accessor' => '$bar', + ]; + }, + ], + ], + ], + ]; + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Json/MarshalTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Json/MarshalTest.php new file mode 100644 index 000000000000..53f96f3e40e8 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Json/MarshalTest.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal\Marshal\Json; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\ClassicDummy; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\DummyWithQuotes; + +use function Symfony\Component\Marshaller\marshal; + +class MarshalTest extends TestCase +{ + private string $cacheDir; + + protected function setUp(): void + { + parent::setUp(); + + $this->cacheDir = sprintf('%s/symfony_marshaller', sys_get_temp_dir()); + + if (is_dir($this->cacheDir)) { + array_map('unlink', glob($this->cacheDir.'/*')); + rmdir($this->cacheDir); + } + } + + /** + * @dataProvider marshalDataProvider + */ + public function testMarshal(mixed $data, string $type = null) + { + $this->assertSame(json_encode($data), $this->marshalAsString($data, (null !== $type) ? ['type' => $type] : [])); + } + + /** + * @return iterable + */ + public function marshalDataProvider(): iterable + { + yield [1]; + yield ['1']; + yield ['foo']; + yield [null]; + yield [.01]; + yield [false]; + yield [new ClassicDummy()]; + yield [new DummyWithQuotes()]; + yield [[1, 2, 3], 'array']; + yield [[1, 2, 3.12], 'array']; + yield [[true, false, true], 'iterable']; + yield [[false, null], 'array']; + yield [['a' => 'b', 'c' => 'd'], 'array']; + yield [['a' => false, 'b' => 'd'], 'array']; + yield [['"a"' => '"b"'], 'array']; + yield [['a' => 1, 'b' => null], 'iterable']; + yield [[1, 2.12, new ClassicDummy()], sprintf('array', ClassicDummy::class)]; + } + + public function testMarshalWithJsonEncodeFlags() + { + $this->assertSame('"123"', $this->marshalAsString('123')); + $this->assertSame('123', $this->marshalAsString('123', ['json_encode_flags' => \JSON_NUMERIC_CHECK])); + } + + /** + * @param array $context + */ + private function marshalAsString(mixed $data, array $context = []): string + { + /** @var resource $resource */ + $resource = fopen('php://temp', 'w'); + marshal($data, $resource, 'json', $context); + + rewind($resource); + + return stream_get_contents($resource); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/MarshalGenerateTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/MarshalGenerateTest.php new file mode 100644 index 000000000000..c12e03adbccc --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/MarshalGenerateTest.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal\Marshal; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Exception\CircularReferenceException; +use Symfony\Component\Marshaller\Exception\UnsupportedFormatException; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\CircularReferencingDummyLeft; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\CircularReferencingDummyRight; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\ClassicDummy; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\SelfReferencingDummy; + +use function Symfony\Component\Marshaller\marshal_generate; + +class MarshalGenerateTest extends TestCase +{ + /** + * @dataProvider checkForCircularReferencesDataProvider + */ + public function testCheckForCircularReferences(?string $expectedCircularClassName, string $type) + { + if (null !== $expectedCircularClassName) { + $this->expectException(CircularReferenceException::class); + $this->expectExceptionMessage(sprintf('A circular reference has been detected on class "%s".', $expectedCircularClassName)); + } + + marshal_generate($type, 'json'); + + $this->addToAssertionCount(1); + } + + /** + * @return iterable + */ + public function checkForCircularReferencesDataProvider(): iterable + { + yield [null, ClassicDummy::class]; + yield [null, sprintf('array', ClassicDummy::class)]; + yield [null, sprintf('array', ClassicDummy::class)]; + yield [null, sprintf('%s|%1$s', ClassicDummy::class)]; + + yield [SelfReferencingDummy::class, SelfReferencingDummy::class]; + yield [SelfReferencingDummy::class, sprintf('array', SelfReferencingDummy::class)]; + yield [SelfReferencingDummy::class, sprintf('array', SelfReferencingDummy::class)]; + yield [SelfReferencingDummy::class, sprintf('%s|%1$s', SelfReferencingDummy::class)]; + + yield [CircularReferencingDummyLeft::class, CircularReferencingDummyLeft::class]; + yield [CircularReferencingDummyLeft::class, sprintf('array', CircularReferencingDummyLeft::class)]; + yield [CircularReferencingDummyLeft::class, sprintf('array', CircularReferencingDummyLeft::class)]; + yield [CircularReferencingDummyLeft::class, sprintf('%s|%1$s', CircularReferencingDummyLeft::class)]; + + yield [CircularReferencingDummyRight::class, CircularReferencingDummyRight::class]; + yield [CircularReferencingDummyRight::class, sprintf('array', CircularReferencingDummyRight::class)]; + yield [CircularReferencingDummyRight::class, sprintf('array', CircularReferencingDummyRight::class)]; + yield [CircularReferencingDummyRight::class, sprintf('%s|%1$s', CircularReferencingDummyRight::class)]; + } + + public function testThrowOnUnknownFormat() + { + $this->expectException(UnsupportedFormatException::class); + + marshal_generate('int', 'unknown', []); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/MarshalTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/MarshalTest.php new file mode 100644 index 000000000000..e6712ea9386d --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/MarshalTest.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal\Marshal; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Exception\UnsupportedFormatException; + +use function Symfony\Component\Marshaller\marshal; + +class MarshalTest extends TestCase +{ + private string $cacheDir; + + protected function setUp(): void + { + parent::setUp(); + + $this->cacheDir = sprintf('%s/symfony_marshaller', sys_get_temp_dir()); + + if (is_dir($this->cacheDir)) { + array_map('unlink', glob($this->cacheDir.'/*')); + rmdir($this->cacheDir); + } + } + + public function testCreateCacheFile() + { + marshal(1, fopen('php://temp', 'w'), 'json', []); + + $this->assertCount(1, glob($this->cacheDir.'/*')); + } + + public function testCreateCacheFileInCustomDirectory() + { + $cacheDir = sprintf('%s/%s', sys_get_temp_dir(), uniqid('symfony_marshaller_')); + + if (file_exists($cacheDir)) { + array_map('unlink', glob($cacheDir.'/*')); + rmdir($cacheDir); + } + + marshal(1, fopen('php://temp', 'w'), 'json', ['cache_dir' => $cacheDir]); + + $this->assertFileExists($cacheDir); + $this->assertCount(1, glob($cacheDir.'/*')); + + array_map('unlink', glob($cacheDir.'/*')); + rmdir($cacheDir); + } + + public function testCreateCacheFileOnlyIfNotExists() + { + $cacheFilename = sprintf('%s/%s.json.php', $this->cacheDir, md5('int')); + if (!file_exists($this->cacheDir)) { + mkdir($this->cacheDir, recursive: true); + } + + file_put_contents($cacheFilename, 'assertSame('CACHED_FILE', $marshalled); + } + + public function testThrowOnUnknownFormat() + { + $this->expectException(UnsupportedFormatException::class); + + marshal(null, fopen('php://temp', 'w'), 'unknown', []); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/ArgumentsNodeTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/ArgumentsNodeTest.php new file mode 100644 index 000000000000..9e9fd0f9ab3d --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/ArgumentsNodeTest.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal\Marshal\Node; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\Node\ArgumentsNode; + +class ArgumentsNodeTest extends TestCase +{ + public function testCompile() + { + (new ArgumentsNode(['foo' => '?int']))->compile($compiler = new Compiler()); + $this->assertSame('?int $foo', $compiler->source()); + + (new ArgumentsNode(['foo' => 'string', 'bar' => null]))->compile($compiler = new Compiler()); + $this->assertSame('string $foo, $bar', $compiler->source()); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/ArrayAccessNodeTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/ArrayAccessNodeTest.php new file mode 100644 index 000000000000..644336c9eebf --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/ArrayAccessNodeTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal\Marshal\Node; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\Node\ArrayAccessNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\ScalarNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\VariableNode; + +class ArrayAccessNodeTest extends TestCase +{ + public function testCompile() + { + (new ArrayAccessNode(new VariableNode('foo'), new ScalarNode('bar')))->compile($compiler = new Compiler()); + $this->assertSame('$foo["bar"]', $compiler->source()); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/AssignNodeTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/AssignNodeTest.php new file mode 100644 index 000000000000..d90defb40d7c --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/AssignNodeTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal\Marshal\Node; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\Node\AssignNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\ScalarNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\VariableNode; + +class AssignNodeTest extends TestCase +{ + public function testCompile() + { + (new AssignNode(new VariableNode('foo'), new ScalarNode(true)))->compile($compiler = new Compiler()); + $this->assertSame('$foo = true', $compiler->source()); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/BinaryNodeTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/BinaryNodeTest.php new file mode 100644 index 000000000000..a996e3696e28 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/BinaryNodeTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal\Marshal\Node; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Exception\InvalidArgumentException; +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\Node\BinaryNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\VariableNode; + +class BinaryNodeTest extends TestCase +{ + public function testCompile() + { + (new BinaryNode('&&', new VariableNode('foo'), new VariableNode('bar')))->compile($compiler = new Compiler()); + $this->assertSame('$foo && $bar', $compiler->source()); + } + + public function testThrowOnInvalidOperator() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid "invalid" operator.'); + + new BinaryNode('invalid', new VariableNode('foo'), new VariableNode('bar')); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/ClosureNodeTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/ClosureNodeTest.php new file mode 100644 index 000000000000..63400aaaad1e --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/ClosureNodeTest.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal\Marshal\Node; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\Node\ArgumentsNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\ClosureNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\ExpressionNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\NodeInterface; +use Symfony\Component\Marshaller\Internal\Marshal\Node\ScalarNode; + +class ClosureNodeTest extends TestCase +{ + /** + * @dataProvider compileDataProvider + * + * @param list $body + */ + public function testCompile(string $expectedSource, ArgumentsNode $arguments, ?string $returnType, bool $static, array $body) + { + (new ClosureNode($arguments, $returnType, $static, $body))->compile($compiler = new Compiler()); + $this->assertSame($expectedSource, $compiler->source()); + } + + /** + * @return iterable}> + */ + public function compileDataProvider(): iterable + { + yield [ + << 'string']), + null, + false, + [], + ]; + yield [ + << + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal\Marshal\Node; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\Node\ExpressionNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\VariableNode; + +class ExpressionNodeTest extends TestCase +{ + public function testCompile() + { + (new ExpressionNode(new VariableNode('foo')))->compile($compiler = new Compiler()); + $this->assertSame('$foo;'.\PHP_EOL, $compiler->source()); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/ForEachNodeTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/ForEachNodeTest.php new file mode 100644 index 000000000000..cb48b3420d0a --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/ForEachNodeTest.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal\Marshal\Node; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\Node\ExpressionNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\ForEachNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\ScalarNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\VariableNode; +use Symfony\Component\Marshaller\Internal\Marshal\NodeInterface; + +class ForEachNodeTest extends TestCase +{ + /** + * @dataProvider compileDataProvider + * + * @param list $body + */ + public function testCompile(string $expectedSource, NodeInterface $collection, ?string $keyName, string $valueName, array $body) + { + (new ForEachNode($collection, $keyName, $valueName, $body))->compile($compiler = new Compiler()); + $this->assertSame($expectedSource, $compiler->source()); + } + + /** + * @return iterable}> + */ + public function compileDataProvider(): iterable + { + yield [ + << \$fooValue) { + } + + PHP, + new VariableNode('foo'), + 'fooKey', + 'fooValue', + [], + ]; + yield [ + << \$fooValue) { + "foo"; + "bar"; + } + + PHP, + new VariableNode('foo'), + 'fooKey', + 'fooValue', + [new ExpressionNode(new ScalarNode('foo')), new ExpressionNode(new ScalarNode('bar'))], + ]; + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/FunctionNodeTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/FunctionNodeTest.php new file mode 100644 index 000000000000..5f49509280a8 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/FunctionNodeTest.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal\Marshal\Node; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\Node\FunctionNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\ScalarNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\VariableNode; + +class FunctionNodeTest extends TestCase +{ + public function testCompile() + { + (new FunctionNode('fooFunction', [new VariableNode('foo'), new ScalarNode(true)]))->compile($compiler = new Compiler()); + $this->assertSame('fooFunction($foo, true)', $compiler->source()); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/IfNodeTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/IfNodeTest.php new file mode 100644 index 000000000000..73b320c5ae90 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/IfNodeTest.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal\Marshal\Node; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\Node\ExpressionNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\IfNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\ScalarNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\VariableNode; +use Symfony\Component\Marshaller\Internal\Marshal\NodeInterface; + +class IfNodeTest extends TestCase +{ + /** + * @dataProvider compileDataProvider + * + * @param list $onIf + * @param list $onElse + * @param list}> $elseIfs + */ + public function testCompile(string $expectedSource, NodeInterface $condition, array $onIf, array $onElse, array $elseIfs) + { + (new IfNode($condition, $onIf, $onElse, $elseIfs))->compile($compiler = new Compiler()); + $this->assertSame($expectedSource, $compiler->source()); + } + + /** + * @return iterable, 3: list, 4: list}>}> + */ + public function compileDataProvider(): iterable + { + yield [ + << new VariableNode('elseIfOne'), 'body' => [new ExpressionNode(new ScalarNode('onElseIfOne'))]], + ['condition' => new VariableNode('elseIfTwo'), 'body' => [new ExpressionNode(new ScalarNode('onElseIfTwo'))]], + ], + ]; + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/PhpDocNodeTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/PhpDocNodeTest.php new file mode 100644 index 000000000000..c66623f804ea --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/PhpDocNodeTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal\Marshal\Node; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\Node\PhpDocNode; + +class PhpDocNodeTest extends TestCase +{ + public function testCompile() + { + (new PhpDocNode(['@param string foo', '', '@return bool']))->compile($compiler = new Compiler()); + $this->assertSame( + <<source(), + ); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/PropertyNodeTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/PropertyNodeTest.php new file mode 100644 index 000000000000..7fd48bf5bc48 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/PropertyNodeTest.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal\Marshal\Node; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\Node\PropertyNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\VariableNode; + +class PropertyNodeTest extends TestCase +{ + public function testCompile() + { + (new PropertyNode(new VariableNode('foo'), 'bar'))->compile($compiler = new Compiler()); + $this->assertSame('$foo->bar', $compiler->source()); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/RawNodeTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/RawNodeTest.php new file mode 100644 index 000000000000..8eb2f97fd38a --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/RawNodeTest.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal\Marshal\Node; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\Node\RawNode; + +class RawNodeTest extends TestCase +{ + public function testCompile() + { + (new RawNode('echo "raw php";'))->compile($compiler = new Compiler()); + $this->assertSame('echo "raw php";', $compiler->source()); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/ReturnNodeTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/ReturnNodeTest.php new file mode 100644 index 000000000000..42000558a372 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/ReturnNodeTest.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal\Marshal\Node; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\Node\ReturnNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\ScalarNode; + +class ReturnNodeTest extends TestCase +{ + public function testCompile() + { + (new ReturnNode(new ScalarNode(true)))->compile($compiler = new Compiler()); + $this->assertSame('return true', $compiler->source()); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/ScalarNodeTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/ScalarNodeTest.php new file mode 100644 index 000000000000..52f1768f5073 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/ScalarNodeTest.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal\Marshal\Node; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Exception\InvalidArgumentException; +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\Node\ScalarNode; + +class ScalarNodeTest extends TestCase +{ + /** + * @dataProvider compileDataProvider + */ + public function testCompile(string $expectedSource, mixed $scalar) + { + (new ScalarNode($scalar))->compile($compiler = new Compiler()); + $this->assertSame($expectedSource, $compiler->source()); + } + + /** + * @return iterable + */ + public function compileDataProvider(): iterable + { + yield ['null', null]; + yield ['123', 123]; + yield ['123.456', 123.456]; + yield ['true', true]; + yield ['false', false]; + yield ['"string"', 'string']; + yield ['"\"string\""', '"string"']; + yield ['"str\\\\ing"', 'str\ing']; + } + + public function testCannotCompileNotScalar() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Given value is not a scalar. Got "array".'); + + (new ScalarNode(['foo']))->compile($compiler = new Compiler()); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/TemplateStringNodeTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/TemplateStringNodeTest.php new file mode 100644 index 000000000000..8be27da7e750 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/TemplateStringNodeTest.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal\Marshal\Node; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\Node\TemplateStringNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\VariableNode; + +class TemplateStringNodeTest extends TestCase +{ + /** + * @param list $parts + * + * @dataProvider compileDataProvider + */ + public function testCompile(string $expectedSource, array $parts) + { + (new TemplateStringNode(...$parts))->compile($compiler = new Compiler()); + $this->assertSame($expectedSource, $compiler->source()); + } + + /** + * @return iterable}> + */ + public function compileDataProvider(): iterable + { + yield ['""', []]; + yield ['"foobar"', ['foo', 'bar']]; + yield ['"foo{$bar}baz"', ['foo', new VariableNode('bar'), 'baz']]; + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/TernaryConditionNodeTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/TernaryConditionNodeTest.php new file mode 100644 index 000000000000..dbce8384ac54 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/TernaryConditionNodeTest.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal\Marshal\Node; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\Node\TernaryConditionNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\VariableNode; + +class TernaryConditionNodeTest extends TestCase +{ + public function testCompile() + { + (new TernaryConditionNode(new VariableNode('foo'), new VariableNode('trueFoo'), new VariableNode('falseFoo')))->compile($compiler = new Compiler()); + $this->assertSame('$foo ? $trueFoo : $falseFoo', $compiler->source()); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/UnaryNodeTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/UnaryNodeTest.php new file mode 100644 index 000000000000..673a037b7e76 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/UnaryNodeTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal\Marshal\Node; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Exception\InvalidArgumentException; +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\Node\UnaryNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\VariableNode; + +class UnaryNodeTest extends TestCase +{ + public function testCompile() + { + (new UnaryNode('!', new VariableNode('foo')))->compile($compiler = new Compiler()); + $this->assertSame('!$foo', $compiler->source()); + } + + public function testThrowOnInvalidOperator() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid "invalid" operator.'); + + new UnaryNode('invalid', new VariableNode('foo')); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/VariableNodeTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/VariableNodeTest.php new file mode 100644 index 000000000000..9ae4a14197e9 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/Node/VariableNodeTest.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal\Marshal\Node; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Internal\Marshal\Compiler; +use Symfony\Component\Marshaller\Internal\Marshal\Node\VariableNode; + +class VariableNodeTest extends TestCase +{ + public function testCompile() + { + (new VariableNode('foo'))->compile($compiler = new Compiler()); + $this->assertSame('$foo', $compiler->source()); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/OptimizerTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/OptimizerTest.php new file mode 100644 index 000000000000..79f927b5375c --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/OptimizerTest.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal\Marshal; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Internal\Marshal\Node\ExpressionNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\FunctionNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\ScalarNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\VariableNode; +use Symfony\Component\Marshaller\Internal\Marshal\NodeInterface; +use Symfony\Component\Marshaller\Internal\Marshal\Optimizer; + +class OptimizerTest extends TestCase +{ + /** + * @dataProvider mergeStringFwritesDataProvider + * + * @param list $expectedNodes + * @param list $nodes + */ + public function testMergeStringFwrites(array $expectedNodes, array $nodes) + { + $this->assertEquals($expectedNodes, (new Optimizer())->optimize($nodes)); + } + + /** + * @return iterable, 1: list}> + */ + public function mergeStringFwritesDataProvider(): iterable + { + $createFwriteExpression = fn (NodeInterface $content) => new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), $content])); + + yield [[ + $createFwriteExpression(new ScalarNode('foobar')), + ], [ + $createFwriteExpression(new ScalarNode('foo')), + $createFwriteExpression(new ScalarNode('bar')), + ]]; + + yield [[ + $createFwriteExpression(new ScalarNode('foo')), + $createFwriteExpression(new VariableNode('bar')), + $createFwriteExpression(new ScalarNode('baz')), + ], [ + $createFwriteExpression(new ScalarNode('foo')), + $createFwriteExpression(new VariableNode('bar')), + $createFwriteExpression(new ScalarNode('baz')), + ]]; + + yield [[ + new ExpressionNode(new FunctionNode('fooFunction', [])), + new ExpressionNode(new FunctionNode('barFunction', [])), + $createFwriteExpression(new ScalarNode('baz')), + ], [ + new ExpressionNode(new FunctionNode('fooFunction', [])), + new ExpressionNode(new FunctionNode('barFunction', [])), + $createFwriteExpression(new ScalarNode('baz')), + ]]; + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/TemplateGeneratorTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/TemplateGeneratorTest.php new file mode 100644 index 000000000000..76fd7e6b5410 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/TemplateGeneratorTest.php @@ -0,0 +1,351 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal\Marshal; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Exception\CircularReferenceException; +use Symfony\Component\Marshaller\Exception\LogicException; +use Symfony\Component\Marshaller\Exception\UnsupportedTypeException; +use Symfony\Component\Marshaller\Internal\Marshal\Node\AssignNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\BinaryNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\ExpressionNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\ForEachNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\FunctionNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\IfNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\PropertyNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\RawNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\ScalarNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\TemplateStringNode; +use Symfony\Component\Marshaller\Internal\Marshal\Node\VariableNode; +use Symfony\Component\Marshaller\Internal\Marshal\NodeInterface; +use Symfony\Component\Marshaller\Internal\Marshal\SyntaxInterface; +use Symfony\Component\Marshaller\Internal\Marshal\TemplateGenerator; +use Symfony\Component\Marshaller\Internal\Marshal\TypeSorter; +use Symfony\Component\Marshaller\Internal\TypeFactory; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\ClassicDummy; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\ConstructorPropertyPromotedDummy; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\DummyWithNotPublicProperty; +use Symfony\Component\Marshaller\Type\ReflectionTypeExtractor; + +class TemplateGeneratorTest extends TestCase +{ + private readonly TemplateGenerator $templateGenerator; + + protected function setUp(): void + { + parent::setUp(); + + $syntax = $this->createMock(SyntaxInterface::class); + $syntax->method('startListString')->willReturn('START_LIST'); + $syntax->method('endListString')->willReturn('END_LIST'); + $syntax->method('startDictString')->willReturn('START_DICT'); + $syntax->method('endDictString')->willReturn('END_DICT'); + $syntax->method('startDictKeyString')->willReturn('START_DICT_KEY_STRING'); + $syntax->method('endDictKeyString')->willReturn('END_DICT_KEY_STRING'); + $syntax->method('collectionItemSeparatorString')->willReturn('COLLECTION_ITEM_SEPARATOR'); + $syntax->method('escapeString')->willReturnCallback(fn (string $s) => sprintf('ESCAPE(%s)', $s)); + $syntax->method('escapeStringNode')->willReturnCallback(fn (NodeInterface $n) => new FunctionNode('ESCAPE', [$n])); + $syntax->method('encodeValueNode')->willReturnCallback(fn (NodeInterface $n) => new FunctionNode('ENCODE', [$n])); + + $this->templateGenerator = new TemplateGenerator( + new ReflectionTypeExtractor(), + new TypeSorter(), + $syntax, + ); + } + + public function testThrowOnInvalidType() + { + $this->expectException(UnsupportedTypeException::class); + + $this->templateGenerator->generate(TypeFactory::createFromString('foo'), new VariableNode('accessor'), []); + } + + public function testGenerateNullable() + { + $this->assertEquals([ + new IfNode( + new BinaryNode('===', new ScalarNode(null), new VariableNode('accessor')), + [new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new FunctionNode('ENCODE', [new VariableNode('accessor')])]))], + [new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new FunctionNode('ENCODE', [new VariableNode('accessor')])]))], + ), + ], $this->templateGenerator->generate(TypeFactory::createFromString('?int'), new VariableNode('accessor'), [])); + } + + public function testGenerateUnion() + { + $this->assertEquals([ + new IfNode( + new FunctionNode('\is_int', [new VariableNode('accessor')]), + [new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new FunctionNode('ENCODE', [new VariableNode('accessor')])]))], + [new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new FunctionNode('ENCODE', [new VariableNode('accessor')])]))], + ), + ], $this->templateGenerator->generate(TypeFactory::createFromString('int|string'), new VariableNode('accessor'), [])); + + $this->assertEquals([ + new IfNode( + new FunctionNode('\is_int', [new VariableNode('accessor')]), + [new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new FunctionNode('ENCODE', [new VariableNode('accessor')])]))], + [new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new FunctionNode('ENCODE', [new VariableNode('accessor')])]))], + [[ + 'condition' => new FunctionNode('\is_string', [new VariableNode('accessor')]), + 'body' => [new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new FunctionNode('ENCODE', [new VariableNode('accessor')])]))], + ]] + ), + ], $this->templateGenerator->generate(TypeFactory::createFromString('int|string|float'), new VariableNode('accessor'), [])); + } + + public function testGenerateNull() + { + $this->assertEquals([ + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new FunctionNode('ENCODE', [new VariableNode('accessor')])])), + ], $this->templateGenerator->generate(TypeFactory::createFromString('null'), new VariableNode('accessor'), [])); + } + + public function testGenerateScalar() + { + $this->assertEquals([ + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new FunctionNode('ENCODE', [new VariableNode('accessor')])])), + ], $this->templateGenerator->generate(TypeFactory::createFromString('int'), new VariableNode('accessor'), [])); + } + + public function testGenerateList() + { + $this->assertEquals([ + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('START_LIST')])), + new ExpressionNode(new AssignNode(new VariableNode('prefix_0'), new ScalarNode(''))), + new ForEachNode(new VariableNode('accessor'), null, 'value_0', [ + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new VariableNode('prefix_0')])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new FunctionNode('ENCODE', [new VariableNode('value_0')])])), + new ExpressionNode(new AssignNode(new VariableNode('prefix_0'), new ScalarNode('COLLECTION_ITEM_SEPARATOR'))), + ]), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('END_LIST')])), + ], $this->templateGenerator->generate(TypeFactory::createFromString('array'), new VariableNode('accessor'), [])); + } + + public function testGenerateDict() + { + $this->assertEquals([ + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('START_DICT')])), + new ExpressionNode(new AssignNode(new VariableNode('prefix_0'), new ScalarNode(''))), + new ForEachNode(new VariableNode('accessor'), 'key_0', 'value_0', [ + new ExpressionNode(new AssignNode(new VariableNode('key_0'), new FunctionNode('ESCAPE', [new VariableNode('key_0')]))), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new TemplateStringNode( + new VariableNode('prefix_0'), + 'START_DICT_KEY_STRING', + new VariableNode('key_0'), + 'END_DICT_KEY_STRING', + )])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new FunctionNode('ENCODE', [new VariableNode('value_0')])])), + new ExpressionNode(new AssignNode(new VariableNode('prefix_0'), new ScalarNode('COLLECTION_ITEM_SEPARATOR'))), + ]), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('END_DICT')])), + ], $this->templateGenerator->generate(TypeFactory::createFromString('array'), new VariableNode('accessor'), [])); + } + + public function testGenerateObject() + { + $this->assertEquals([ + new ExpressionNode(new AssignNode(new VariableNode('object_0'), new VariableNode('accessor'))), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('START_DICT')])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('')])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('START_DICT_KEY_STRING')])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('ESCAPE(id)')])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('END_DICT_KEY_STRING')])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new FunctionNode('ENCODE', [new PropertyNode(new VariableNode('object_0'), 'id')])])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('COLLECTION_ITEM_SEPARATOR')])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('START_DICT_KEY_STRING')])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('ESCAPE(name)')])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('END_DICT_KEY_STRING')])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new FunctionNode('ENCODE', [new PropertyNode(new VariableNode('object_0'), 'name')])])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('END_DICT')])), + ], $this->templateGenerator->generate(TypeFactory::createFromString(ClassicDummy::class), new VariableNode('accessor'), [])); + } + + public function testGenerateObjedctWithConstructorPropertyPromotion() + { + $this->assertEquals([ + new ExpressionNode(new AssignNode(new VariableNode('object_0'), new VariableNode('accessor'))), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('START_DICT')])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('')])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('START_DICT_KEY_STRING')])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('ESCAPE(id)')])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('END_DICT_KEY_STRING')])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new FunctionNode('ENCODE', [new PropertyNode(new VariableNode('object_0'), 'id')])])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('END_DICT')])), + ], $this->templateGenerator->generate(TypeFactory::createFromString(ConstructorPropertyPromotedDummy::class), new VariableNode('accessor'), [])); + } + + public function testGenerateObjectThrowWhenPropertyIsNotPublic() + { + $this->expectException(LogicException::class); + $this->expectExceptionMessage(sprintf('"%s::$name" must be public.', DummyWithNotPublicProperty::class)); + + $this->templateGenerator->generate(TypeFactory::createFromString(DummyWithNotPublicProperty::class), new VariableNode('accessor'), []); + } + + public function testGenerateObjectWithObjectHook() + { + $context = [ + 'hooks' => [ + 'marshal' => [ + 'object' => static function (string $type, string $accessor, array $context): array { + return [ + 'type' => ConstructorPropertyPromotedDummy::class, + 'accessor' => '$ACCESSOR', + 'context' => ['CONTEXT'], + ]; + }, + ], + ], + ]; + + $this->assertEquals([ + new ExpressionNode(new AssignNode(new VariableNode('object_0'), new RawNode('$ACCESSOR'))), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('START_DICT')])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('')])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('START_DICT_KEY_STRING')])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('ESCAPE(id)')])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('END_DICT_KEY_STRING')])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new FunctionNode('ENCODE', [new PropertyNode(new VariableNode('object_0'), 'id')])])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('END_DICT')])), + ], $this->templateGenerator->generate(TypeFactory::createFromString(ClassicDummy::class), new VariableNode('accessor'), $context)); + } + + public function testGenerateObjectCallProperObjectHook() + { + $hookCallCount = 0; + + $context = [ + 'custom_context_value' => true, + 'hooks' => [ + 'marshal' => [ + ClassicDummy::class => function (string $type, string $accessor, array $context) use (&$hookCallCount): array { + ++$hookCallCount; + + $this->assertSame(ClassicDummy::class, $type); + $this->assertSame('$accessor', $accessor); + $this->assertArrayHasKey('custom_context_value', $context); + + return ['type' => $type, 'accessor' => $accessor, 'context' => $context]; + }, + ConstructorPropertyPromotedDummy::class => function (string $type, string $accessor, array $context) use (&$hookCallCount): array { + ++$hookCallCount; + + $this->assertSame(ConstructorPropertyPromotedDummy::class, $type); + $this->assertSame('$accessor', $accessor); + $this->assertArrayHasKey('custom_context_value', $context); + + return ['type' => $type, 'accessor' => $accessor, 'context' => $context]; + }, + ], + ], + ]; + + $this->templateGenerator->generate( + TypeFactory::createFromString(sprintf('%s|%s', ClassicDummy::class, ConstructorPropertyPromotedDummy::class)), + new VariableNode('accessor'), + $context, + ); + + $this->assertSame(2, $hookCallCount); + } + + public function testGenerateObjectWithPropertyHook() + { + $context = [ + 'hooks' => [ + 'marshal' => [ + 'property' => static function (\ReflectionProperty $property, string $accessor, array $context): array { + return [ + 'name' => 'NAME', + 'type' => 'string', + 'accessor' => '$ACCESSOR', + 'context' => ['CONTEXT'], + ]; + }, + ], + ], + ]; + + $this->assertEquals([ + new ExpressionNode(new AssignNode(new VariableNode('object_0'), new VariableNode('accessor'))), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('START_DICT')])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('')])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('START_DICT_KEY_STRING')])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('ESCAPE(NAME)')])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('END_DICT_KEY_STRING')])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new FunctionNode('ENCODE', [new RawNode('$ACCESSOR')])])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('COLLECTION_ITEM_SEPARATOR')])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('START_DICT_KEY_STRING')])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('ESCAPE(NAME)')])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('END_DICT_KEY_STRING')])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new FunctionNode('ENCODE', [new RawNode('$ACCESSOR')])])), + new ExpressionNode(new FunctionNode('\fwrite', [new VariableNode('resource'), new ScalarNode('END_DICT')])), + ], $this->templateGenerator->generate(TypeFactory::createFromString(ClassicDummy::class), new VariableNode('accessor'), $context)); + } + + public function testGenerateObjectCallProperPropertyHook() + { + $hookCallCount = 0; + + $context = [ + 'custom_context_value' => true, + 'hooks' => [ + 'marshal' => [ + sprintf('%s::$id', ClassicDummy::class) => function (\ReflectionProperty $property, string $accessor, array $context) use (&$hookCallCount): array { + ++$hookCallCount; + + $this->assertEquals(new \ReflectionProperty(ClassicDummy::class, 'id'), $property); + $this->assertSame('$object_0->id', $accessor); + $this->assertArrayHasKey('custom_context_value', $context); + + return [ + 'name' => 'name', + 'type' => 'string', + 'accessor' => '$accessor', + 'context' => [], + ]; + }, + sprintf('%s::$name', ClassicDummy::class) => function (\ReflectionProperty $property, string $accessor, array $context) use (&$hookCallCount): array { + ++$hookCallCount; + + $this->assertEquals(new \ReflectionProperty(ClassicDummy::class, 'name'), $property); + $this->assertSame('$object_0->name', $accessor); + $this->assertArrayHasKey('custom_context_value', $context); + + return [ + 'name' => 'name', + 'type' => 'string', + 'accessor' => '$accessor', + 'context' => [], + ]; + }, + ], + ], + ]; + + $this->templateGenerator->generate(TypeFactory::createFromString(ClassicDummy::class), new VariableNode('accessor'), $context); + + $this->assertSame(2, $hookCallCount); + } + + public function testThrowOnCircularReference() + { + $this->templateGenerator->generate(TypeFactory::createFromString(ClassicDummy::class), new VariableNode('accessor'), []); + $this->addToAssertionCount(1); + + $this->expectException(CircularReferenceException::class); + + $this->templateGenerator->generate(TypeFactory::createFromString(ClassicDummy::class), new VariableNode('accessor'), ['generated_classes' => [ClassicDummy::class => true]]); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/TypeSorterTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/TypeSorterTest.php new file mode 100644 index 000000000000..414a4f6f7683 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/TypeSorterTest.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal\Marshal; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Exception\LogicException; +use Symfony\Component\Marshaller\Internal\Marshal\TypeSorter; +use Symfony\Component\Marshaller\Internal\Type; +use Symfony\Component\Marshaller\Internal\TypeFactory; + +class TypeSorterTest extends TestCase +{ + /** + * @dataProvider sortByPrecisionDataProvider + * + * @param list $expectedSortedTypes + * @param list $types + */ + public function testSortByPrecision(array $expectedSortedTypes, array $types) + { + $types = array_map(fn (string $t): Type => TypeFactory::createFromString($t), $types); + $sortedTypes = array_map(fn (Type $t): string => (string) $t, (new TypeSorter())->sortByPrecision($types)); + + $this->assertEquals($expectedSortedTypes, $sortedTypes); + } + + /** + * @return iterable, 1: list} + */ + public function sortByPrecisionDataProvider(): iterable + { + yield [['int', 'string'], ['int', 'string']]; + yield [['int'], ['int', 'int']]; + yield [['int', Leaf::class], [Leaf::class, 'int']]; + + yield [[Leaf::class], [Leaf::class, Leaf::class]]; + yield [[Leaf::class, Branch::class, Root::class], [Branch::class, Root::class, Leaf::class]]; + yield [[Branch::class, Root::class], [Root::class, Branch::class]]; + yield [['int', Leaf::class, Root::class], [Leaf::class, Root::class, 'int']]; + } + + /** + * @dataProvider throwIfSameHierarchicalLevelDataProvider + * + * @param list $types + */ + public function testThrowIfSameHierarchicalLevel(bool $expectException, array $types) + { + if ($expectException) { + $this->expectException(LogicException::class); + } + + (new TypeSorter())->sortByPrecision($types); + + $this->addToAssertionCount(1); + } + + /** + * @return iterable} + */ + public function throwIfSameHierarchicalLevelDataProvider(): iterable + { + yield [false, [new Type('object', className: Leaf::class), new Type('int')]]; + yield [false, [new Type('object', className: Branch::class), new Type('object', className: Root::class), new Type('object', className: Leaf::class)]]; + yield [false, [new Type('object', className: Leaf::class), new Type('object', className: Root::class)]]; + yield [true, [new Type('object', className: Leaf::class), new Type('object', className: Leaf2::class)]]; + yield [true, [new Type('object', className: Leaf::class), new Type('object', className: Branch2::class)]]; + } +} + +abstract class Root +{ +} + +abstract class Branch extends Root +{ +} + +abstract class Branch2 extends Root +{ +} + +class Leaf extends Branch +{ +} + +class Leaf2 extends Branch2 +{ +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/VariableNameScoperTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/VariableNameScoperTest.php new file mode 100644 index 000000000000..970e9dc5dc13 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/Marshal/VariableNameScoperTest.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Marshal\Internal; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Internal\Marshal\VariableNameScoperTrait; + +class VariableNameScoperTest extends TestCase +{ + public function testScopeVariableName() + { + $templateGenerator = new class() { + use VariableNameScoperTrait { + scopeVariableName as private doScopeVariableName; + } + + public function scopeVariableName(string $prefix, array &$context): string + { + return $this->doScopeVariableName($prefix, $context); + } + }; + + $context = []; + + $this->assertSame('foo_0', $templateGenerator->scopeVariableName('foo', $context)); + $this->assertSame('foo_1', $templateGenerator->scopeVariableName('foo', $context)); + $this->assertSame(['variable_counters' => ['foo' => 2]], $context); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/MarshalUnmarshalTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/MarshalUnmarshalTest.php new file mode 100644 index 000000000000..8190b5bb8250 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/MarshalUnmarshalTest.php @@ -0,0 +1,140 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\ClassicDummy; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\DummyWithFormatterAttributes; + +use function Symfony\Component\Marshaller\marshal; +use function Symfony\Component\Marshaller\unmarshal; + +class MarshalUnmarshalTest extends TestCase +{ + private string $cacheDir; + + protected function setUp(): void + { + parent::setUp(); + + $this->cacheDir = sprintf('%s/marshal', sys_get_temp_dir()); + + if (is_dir($this->cacheDir)) { + array_map('unlink', glob($this->cacheDir.'/*')); + rmdir($this->cacheDir); + } + } + + /** + * @dataProvider marshalUnmarshalDataProvider + * + * @param array $context + */ + public function testMarshalUnmarshal(mixed $data, string $type, array $context = []) + { + /** @var resource $resource */ + $resource = fopen('php://memory', 'w+'); + + marshal($data, $resource, 'json', ['type' => $type] + $context); + rewind($resource); + + $this->assertEquals($data, unmarshal($resource, $type, 'json', $context)); + } + + /** + * @return iterable}> + */ + public function marshalUnmarshalDataProvider(): iterable + { + yield [1, 'int']; + yield [null, '?int']; + yield ['foo', 'string']; + yield [[1, 2, null], 'array']; + yield [['foo' => 1, 'bar' => 2, 'baz' => null], 'array']; + + $dummy = new ClassicDummy(); + $dummy->id = 100; + $dummy->name = 'Dummy'; + + yield [$dummy, ClassicDummy::class]; + + $dummy = new DummyWithFormatterAttributes(); + $dummy->id = 200; + $dummy->name = '200'; + + yield [$dummy, DummyWithFormatterAttributes::class, ['hooks' => [ + 'marshal' => [ + sprintf('%s::$name', DummyWithFormatterAttributes::class) => fn (\ReflectionProperty $p, string $accessor) => [ + 'name' => '@name', + 'accessor' => sprintf('%s::divideAndCastToInt(%s, $context)', DummyWithFormatterAttributes::class, $accessor), + ], + ], + 'unmarshal' => [ + sprintf('%s[@name]', DummyWithFormatterAttributes::class) => fn (\ReflectionClass $class, string $key, callable $value, array $context) => [ + 'name' => 'name', + 'value_provider' => fn () => DummyWithFormatterAttributes::doubleAndCastToString($value('int', $context)), + ], + ], + ]]]; + } + + /** + * @dataProvider unmarshalMarshalDataProvider + * + * @param array $context + */ + public function testUnmarshalMarshal(string $content, string $type, array $context = []) + { + /** @var resource $resource */ + $resource = fopen('php://memory', 'w+'); + + fwrite($resource, $content); + rewind($resource); + + $data = unmarshal($resource, $type, 'json', $context); + + /** @var resource $resource */ + $newResource = fopen('php://memory', 'w+'); + + marshal($data, $newResource, 'json', ['type' => $type] + $context); + rewind($newResource); + + $this->assertEquals($content, stream_get_contents($newResource)); + } + + /** + * @return iterable}> + */ + public function unmarshalMarshalDataProvider(): iterable + { + yield ['1', 'int']; + yield ['null', '?int']; + yield ['"foo"', 'string']; + yield ['[1,2,null]', 'array']; + yield ['{"foo":1,"bar":2,"baz":null}', 'array']; + yield ['{"id":100,"name":"Dummy"}', ClassicDummy::class]; + yield ['{"id":200,"@name":100}', DummyWithFormatterAttributes::class, ['hooks' => [ + 'marshal' => [ + sprintf('%s::$name', DummyWithFormatterAttributes::class) => fn (\ReflectionProperty $p, string $accessor) => [ + 'name' => '@name', + 'accessor' => sprintf('%s::divideAndCastToInt(%s, $context)', DummyWithFormatterAttributes::class, $accessor), + ], + ], + 'unmarshal' => [ + sprintf('%s[@name]', DummyWithFormatterAttributes::class) => fn (\ReflectionClass $class, string $key, callable $value, array $context) => [ + 'name' => 'name', + 'value_provider' => fn () => DummyWithFormatterAttributes::doubleAndCastToString($value('int', $context)), + ], + ], + ]]]; + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/TypeFactoryTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/TypeFactoryTest.php new file mode 100644 index 000000000000..1e3a73a1391b --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/TypeFactoryTest.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Exception\InvalidArgumentException; +use Symfony\Component\Marshaller\Exception\InvalidTypeException; +use Symfony\Component\Marshaller\Exception\UnsupportedTypeException; +use Symfony\Component\Marshaller\Internal\Type; +use Symfony\Component\Marshaller\Internal\TypeFactory; +use Symfony\Component\Marshaller\Internal\UnionType; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\ClassicDummy; + +class TypeFactoryTest extends TestCase +{ + /** + * @dataProvider createFromStringDataProvider + */ + public function testCreateFromString(Type|UnionType $expectedType, string $string) + { + $this->assertEquals($expectedType, TypeFactory::createFromString($string)); + } + + /** + * @return iterable + */ + public function createFromStringDataProvider(): iterable + { + // scalar types + yield [new Type('null'), 'null']; + yield [new Type('int'), 'int']; + yield [new Type('string'), 'string']; + yield [new Type('float'), 'float']; + yield [new Type('bool'), 'bool']; + yield [new Type('int', isNullable: true), '?int']; + + // object types + yield [new Type('object', className: ClassicDummy::class), ClassicDummy::class]; + yield [new Type('object', isNullable: true, className: ClassicDummy::class), '?'.ClassicDummy::class]; + + // generic types + yield [new Type('object', className: ClassicDummy::class, isGeneric: true, genericParameterTypes: [new Type('int')]), ClassicDummy::class.'']; + yield [new Type( + 'object', + className: ClassicDummy::class, + isGeneric: true, + genericParameterTypes: [new Type('int', isGeneric: true, genericParameterTypes: [new Type('bool', isNullable: true)])], + ), ClassicDummy::class.'>']; + + // collection types + yield [new Type('array', isGeneric: true, genericParameterTypes: [new Type('int'), new Type('int')]), 'array']; + yield [new Type('array', isGeneric: true, genericParameterTypes: [new Type('int'), new Type('float')]), 'array']; + yield [ + new Type( + 'array', + isGeneric: true, + genericParameterTypes: [new Type('string'), new Type('array', isGeneric: true, genericParameterTypes: [new Type('int'), new Type('bool')])], + ), + 'array>', + ]; + yield [ + new Type( + 'array', + isNullable: true, + isGeneric: true, + genericParameterTypes: [ + new Type('string', isNullable: true), + new Type( + 'array', + isNullable: true, + isGeneric: true, + genericParameterTypes: [new Type('int', isNullable: true), new Type('bool', isNullable: true)], + ), + ] + ), + '?array>', + ]; + yield [new Type('iterable', isGeneric: true, genericParameterTypes: [new Type('int'), new Type('int')]), 'iterable']; + yield [ + new Type( + 'iterable', + isGeneric: true, + genericParameterTypes: [new Type('string'), new Type('iterable', isGeneric: true, genericParameterTypes: [new Type('int'), new Type('bool')])], + ), + 'iterable>', + ]; + + // union types + yield [new UnionType([new Type('int'), new Type('string')]), 'int|string']; + yield [new UnionType([new Type('int'), new Type('string'), new Type('null')]), 'int|string|null']; + yield [new UnionType([new Type('int'), new Type('string'), new Type('null')]), 'int|?string|null']; + yield [ + new UnionType([ + new Type( + 'array', + isGeneric: true, + genericParameterTypes: [new Type('string'), new UnionType([ + new Type('object', className: ClassicDummy::class, isGeneric: true, genericParameterTypes: [new Type('int')]), + new Type('float'), + ])], + ), + new Type('array', isGeneric: true, genericParameterTypes: [new Type('int'), new Type('bool')]), + ]), + sprintf('array|float>|array', ClassicDummy::class), + ]; + } + + public function testCreateThrowOnIntersectionTypes() + { + $this->expectException(UnsupportedTypeException::class); + + TypeFactory::createFromString('foo&bar'); + } + + public function testCreateThrowOnInvalidGenericString() + { + $this->expectException(InvalidTypeException::class); + + TypeFactory::createFromString('array'); + } + + public function testCreateThrowOnRawArray() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid generic parameter types of "array" type.'); + + TypeFactory::createFromString('array'); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/TypeTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/TypeTest.php new file mode 100644 index 000000000000..72c1edae7787 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/TypeTest.php @@ -0,0 +1,209 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Exception\InvalidArgumentException; +use Symfony\Component\Marshaller\Exception\LogicException; +use Symfony\Component\Marshaller\Internal\Type; +use Symfony\Component\Marshaller\Internal\UnionType; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\ClassicDummy; + +class TypeTest extends TestCase +{ + /** + * @dataProvider toStringDataProvider + */ + public function testToString(string $expectedString, Type|UnionType $type) + { + $this->assertSame($expectedString, (string) $type); + } + + /** + * @return iterable + */ + public function toStringDataProvider(): iterable + { + // scalar types + yield ['null', new Type('null')]; + yield ['int', new Type('int')]; + yield ['string', new Type('string')]; + yield ['float', new Type('float')]; + yield ['bool', new Type('bool')]; + yield ['?int', new Type('int', isNullable: true)]; + + // object types + yield [ClassicDummy::class, new Type('object', className: ClassicDummy::class)]; + yield ['?'.ClassicDummy::class, new Type('object', isNullable: true, className: ClassicDummy::class)]; + + // generic types + yield [ClassicDummy::class.'', new Type('object', className: ClassicDummy::class, isGeneric: true, genericParameterTypes: [new Type('int')])]; + yield [ + ClassicDummy::class.'>', + new Type( + 'object', + className: ClassicDummy::class, + isGeneric: true, + genericParameterTypes: [new Type('int', isGeneric: true, genericParameterTypes: [new Type('bool', isNullable: true)])], + ), + ]; + + // collection types + yield ['array', new Type('array', isGeneric: true, genericParameterTypes: [new Type('int'), new Type('int')])]; + yield ['array', new Type('array', isGeneric: true, genericParameterTypes: [new Type('int'), new Type('float')])]; + yield [ + 'array>', + new Type( + 'array', + isGeneric: true, + genericParameterTypes: [ + new Type('string'), + new Type('array', isGeneric: true, genericParameterTypes: [new Type('int'), new Type('bool')]), + ], + ), + ]; + yield [ + '?array>', + new Type( + 'array', + isNullable: true, + isGeneric: true, + genericParameterTypes: [ + new Type('string', isNullable: true), + new Type( + 'array', + isNullable: true, + isGeneric: true, + genericParameterTypes: [ + new Type('int', isNullable: true), + new Type('bool', isNullable: true), + ], + ), + ], + ), + ]; + } + + public function testCannotCreateObjectWithoutClassName() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Missing className of "object" type.'); + + new Type('object'); + } + + public function testCannotCreateWithTypeAndWithoutValue() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid generic parameter types of "array" type.'); + + new Type('array', isGeneric: true, genericParameterTypes: [new Type('int')]); + } + + public function testCannotCreateGenericWithoutGenericTypes() + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Missing generic parameter types of "object" type.'); + + new Type('object', className: ClassicDummy::class, isGeneric: true, genericParameterTypes: []); + } + + public function testCannotGetClassNameOnNonObject() + { + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Cannot get class on "int" type as it\'s not an object.'); + + (new Type('int'))->className(); + } + + public function testCannotGetCollectionKeyTypeOnNonCollection() + { + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Cannot get collection key type on "int" type as it\'s not a collection.'); + + (new Type('int'))->collectionKeyType(); + } + + public function testCannotGetCollectionValueTypeOnNonCollection() + { + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Cannot get collection value type on "int" type as it\'s not a collection.'); + + (new Type('int'))->collectionValueType(); + } + + /** + * @dataProvider isserDataProvider + */ + public function testIsser(Type $type, bool $scalar, bool $nullable, bool $object, bool $collection, bool $list, bool $dict, bool $generic) + { + $this->assertSame($scalar, $type->isScalar()); + $this->assertSame($nullable, $type->isNull()); + $this->assertSame($object, $type->isObject()); + $this->assertSame($collection, $type->isCollection()); + $this->assertSame($list, $type->isList()); + $this->assertSame($dict, $type->isDict()); + $this->assertSame($generic, $type->isGeneric()); + } + + /** + * @return iterable + */ + public function isserDataProvider(): iterable + { + yield ['type' => new Type('int'), 'scalar' => true, 'null' => false, 'object' => false, 'collection' => false, 'list' => false, 'dict' => false, 'generic' => false]; + yield ['type' => new Type('string'), 'scalar' => true, 'null' => false, 'object' => false, 'collection' => false, 'list' => false, 'dict' => false, 'generic' => false]; + yield ['type' => new Type('bool'), 'scalar' => true, 'null' => false, 'object' => false, 'collection' => false, 'list' => false, 'dict' => false, 'generic' => false]; + yield ['type' => new Type('float'), 'scalar' => true, 'null' => false, 'object' => false, 'collection' => false, 'list' => false, 'dict' => false, 'generic' => false]; + yield ['type' => new Type('null'), 'scalar' => false, 'null' => true, 'object' => false, 'collection' => false, 'list' => false, 'dict' => false, 'generic' => false]; + yield [ + 'type' => new Type('object', className: ClassicDummy::class), + 'scalar' => false, + 'null' => false, + 'object' => true, + 'collection' => false, + 'list' => false, + 'dict' => false, + 'generic' => false, + ]; + yield [ + 'type' => new Type('object', className: ClassicDummy::class, isGeneric: true, genericParameterTypes: [new Type('int')]), + 'scalar' => false, + 'null' => false, + 'object' => true, + 'collection' => false, + 'list' => false, + 'dict' => false, + 'generic' => true, + ]; + yield [ + 'type' => new Type('array', isGeneric: true, genericParameterTypes: [new Type('int'), new Type('int')]), + 'scalar' => false, + 'null' => false, + 'object' => false, + 'collection' => true, + 'list' => true, + 'dict' => false, + 'generic' => true, + ]; + yield [ + 'type' => new Type('array', isGeneric: true, genericParameterTypes: [new Type('string'), new Type('int')]), + 'scalar' => false, + 'null' => false, + 'object' => false, + 'collection' => true, + 'list' => false, + 'dict' => true, + 'generic' => true, + ]; + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/UnionTypeTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/UnionTypeTest.php new file mode 100644 index 000000000000..ebcc189ff550 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/UnionTypeTest.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Internal\Type; +use Symfony\Component\Marshaller\Internal\UnionType; + +class UnionTypeTest extends TestCase +{ + public function testIsNullable() + { + $this->assertTrue((new UnionType([new Type('int'), new Type('null')]))->isNullable()); + $this->assertFalse((new UnionType([new Type('int'), new Type('string')]))->isNullable()); + } + + public function testAtLeastOneTypeIs() + { + $callable = fn (Type $t): bool => 'int' === $t->name(); + + $this->assertTrue((new UnionType([new Type('int'), new Type('string')]))->atLeastOneTypeIs($callable)); + $this->assertFalse((new UnionType([new Type('float'), new Type('string')]))->atLeastOneTypeIs($callable)); + } + + /** + * @dataProvider toStringDataProvider + */ + public function testToString(string $expectedString, UnionType $type) + { + $this->assertSame($expectedString, (string) $type); + } + + /** + * @return iterable + */ + public function toStringDataProvider(): iterable + { + yield ['int|string', new UnionType([new Type('int'), new Type('string')])]; + yield ['int|string|null', new UnionType([new Type('int'), new Type('string'), new Type('null')])]; + yield [ + 'array|array', + new UnionType([ + new Type( + 'array', + isGeneric: true, + genericParameterTypes: [new Type('string'), new UnionType([new Type('string'), new Type('float')])], + ), + new Type('array', isGeneric: true, genericParameterTypes: [new Type('int'), new Type('bool')]), + ]), + ]; + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/Unmarshal/Json/JsonDecoderTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/Unmarshal/Json/JsonDecoderTest.php new file mode 100644 index 000000000000..f1c0637b746e --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/Unmarshal/Json/JsonDecoderTest.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal\Unmarshal\Json; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Exception\InvalidResourceException; +use Symfony\Component\Marshaller\Exception\RuntimeException; +use Symfony\Component\Marshaller\Internal\Unmarshal\Json\JsonDecoder; + +class JsonDecoderTest extends TestCase +{ + public function testDecode() + { + $this->assertSame('foo', (new JsonDecoder())->decode($this->createResource('"foo"'), 0, -1, [])); + } + + public function testDecodeSubset() + { + $this->assertSame('bar', (new JsonDecoder())->decode($this->createResource('["foo","bar","baz"]'), 7, 5, [])); + } + + public function testDecodeWithJsonDecodeFlags() + { + $this->assertSame( + 1.2345678901234568E+29, + (new JsonDecoder())->decode($this->createResource('123456789012345678901234567890'), 0, -1, []), + ); + + $this->assertSame( + '123456789012345678901234567890', + (new JsonDecoder())->decode($this->createResource('123456789012345678901234567890'), 0, -1, ['json_decode_flags' => \JSON_BIGINT_AS_STRING]), + ); + } + + public function testDecodeThrowOnInvalidResource() + { + $this->expectException(RuntimeException::class); + + (new JsonDecoder())->decode(fopen(sprintf('%s/%s', sys_get_temp_dir(), uniqid()), 'w'), 0, -1, []); + } + + public function testDecodeThrowOnInvalidJson() + { + $this->expectException(InvalidResourceException::class); + + (new JsonDecoder())->decode($this->createResource('foo"'), 0, -1, []); + } + + /** + * @return resource + */ + private function createResource(string $content): mixed + { + /** @var resource $resource */ + $resource = fopen('php://temp', 'w'); + + fwrite($resource, $content); + rewind($resource); + + return $resource; + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/Unmarshal/Json/JsonDictSplitterTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/Unmarshal/Json/JsonDictSplitterTest.php new file mode 100644 index 000000000000..0f15022f7797 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/Unmarshal/Json/JsonDictSplitterTest.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal\Unmarshal\Json; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Exception\InvalidResourceException; +use Symfony\Component\Marshaller\Internal\TypeFactory; +use Symfony\Component\Marshaller\Internal\Unmarshal\Json\JsonDictSplitter; +use Symfony\Component\Marshaller\Internal\Unmarshal\LexerInterface; + +class JsonDictSplitterTest extends TestCase +{ + public function testSplitNull() + { + $lexer = $this->createStub(LexerInterface::class); + $lexer->method('tokens')->willReturn(new \ArrayIterator([['null', 0]])); + + $context = ['boundary' => [0, -1]]; + + $this->assertNull((new JsonDictSplitter($lexer))->split(fopen('php://temp', 'r'), TypeFactory::createFromString('int'), $context)); + } + + /** + * @dataProvider splitDataProvider + * + * @param list $expectedBoundaries + * @param list $tokens + */ + public function testSplit(array $expectedBoundaries, array $tokens) + { + $lexer = $this->createStub(LexerInterface::class); + $lexer->method('tokens')->willReturn(new \ArrayIterator($tokens)); + + $context = ['boundary' => [0, -1]]; + + $boundaries = (new JsonDictSplitter($lexer))->split(fopen('php://temp', 'r'), TypeFactory::createFromString('int'), $context); + + $this->assertSame($expectedBoundaries, iterator_to_array($boundaries)); + } + + /** + * @return iterable, 1: list}> + */ + public function splitDataProvider(): iterable + { + yield [[], [['{', 0], ['}', 1]]]; + yield [['k' => [5, 2]], [['{', 0], ['"k"', 1], [':', 4], ['10', 5], ['}', 7]]]; + yield [['k' => [5, 4]], [['{', 0], ['"k"', 1], [':', 4], ['[', 5], ['10', 6], [']', 8], ['}', 9]]]; + } + + /** + * @dataProvider splitInvalidDataProvider + * + * @param list $tokens + */ + public function testSplitInvalidThrowException(array $tokens) + { + $lexer = $this->createStub(LexerInterface::class); + $lexer->method('tokens')->willReturn(new \ArrayIterator($tokens)); + + $context = ['boundary' => [0, -1]]; + + $this->expectException(InvalidResourceException::class); + + iterator_to_array((new JsonDictSplitter($lexer))->split(fopen('php://temp', 'r'), TypeFactory::createFromString('int'), $context)); + } + + /** + * @return iterable}> + */ + public function splitInvalidDataProvider(): iterable + { + yield [[['{', 0], ['100', 1]]]; + yield [[['{', 0], ['{', 1], ['}', 2]]]; + yield [[['{', 0], ['{', 1], ['}', 2], [']', 3]]]; + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/Unmarshal/Json/JsonLexerTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/Unmarshal/Json/JsonLexerTest.php new file mode 100644 index 000000000000..ec57aba135de --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/Unmarshal/Json/JsonLexerTest.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal\Unmarshal\Json; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Exception\RuntimeException; +use Symfony\Component\Marshaller\Internal\Unmarshal\Json\JsonLexer; + +class JsonLexerTest extends TestCase +{ + /** + * @dataProvider tokensDataProvider + * + * @param list $expectedTokens + */ + public function testTokens(array $expectedTokens, string $content) + { + $this->assertSame($expectedTokens, iterator_to_array((new JsonLexer())->tokens($this->createResource($content), 0, -1, []))); + } + + /** + * @return iterable, 1: string}> + */ + public function tokensDataProvider(): iterable + { + yield [[['1', 0]], '1']; + yield [[['false', 0]], 'false']; + yield [[['null', 0]], 'null']; + yield [[['"string"', 0]], '"string"']; + + yield [[['[', 0], [']', 1]], '[]']; + yield [[['[', 0], ['10', 2], [',', 4], ['20', 6], [']', 9]], '[ 10, 20 ]']; + yield [[['[', 0], ['1', 1], [',', 2], ['[', 4], ['2', 5], [']', 6], [']', 8]], '[1, [2] ]']; + + yield [[['{', 0], ['}', 1]], '{}']; + yield [[['{', 0], ['"foo"', 1], [':', 6], ['{', 8], ['"bar"', 9], [':', 14], ['"baz"', 15], ['}', 20], ['}', 21]], '{"foo": {"bar":"baz"}}']; + + yield [[['[', 3], ['1', 4], [']', 5]], "\xEF\xBB\xBF".'[1]']; + } + + public function testTokensSubset() + { + $this->assertSame([['false', 7]], iterator_to_array((new JsonLexer())->tokens($this->createResource('[1, 2, false]'), 7, 5, []))); + } + + public function testTokensThrowOnInvalidResource() + { + $this->expectException(RuntimeException::class); + + iterator_to_array((new JsonLexer())->tokens(fopen(sprintf('%s/%s', sys_get_temp_dir(), uniqid()), 'w'), 0, -1, [])); + } + + public function testTokenizeOverflowingBuffer() + { + $veryLongString = sprintf('"%s"', str_repeat('.', 20000)); + + $this->assertSame([[$veryLongString, 0]], iterator_to_array((new JsonLexer())->tokens($this->createResource($veryLongString), 0, -1, []))); + } + + /** + * @return resource + */ + private function createResource(string $content): mixed + { + /** @var resource $resource */ + $resource = fopen('php://temp', 'w'); + + fwrite($resource, $content); + rewind($resource); + + return $resource; + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/Unmarshal/Json/JsonListSplitterTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/Unmarshal/Json/JsonListSplitterTest.php new file mode 100644 index 000000000000..4f33ebd9bde0 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/Unmarshal/Json/JsonListSplitterTest.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal\Unmarshal\Json; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Exception\InvalidResourceException; +use Symfony\Component\Marshaller\Internal\TypeFactory; +use Symfony\Component\Marshaller\Internal\Unmarshal\Json\JsonListSplitter; +use Symfony\Component\Marshaller\Internal\Unmarshal\LexerInterface; + +class JsonListSplitterTest extends TestCase +{ + public function testSplitNull() + { + $lexer = $this->createStub(LexerInterface::class); + $lexer->method('tokens')->willReturn(new \ArrayIterator([['null', 0]])); + + $context = ['boundary' => [0, -1]]; + + $this->assertNull((new JsonListSplitter($lexer))->split(fopen('php://temp', 'r'), TypeFactory::createFromString('int'), $context)); + } + + /** + * @dataProvider splitDataProvider + * + * @param list $expectedBoundaries + * @param list $tokens + */ + public function testSplit(array $expectedBoundaries, array $tokens) + { + $lexer = $this->createStub(LexerInterface::class); + $lexer->method('tokens')->willReturn(new \ArrayIterator($tokens)); + + $context = ['boundary' => [0, -1]]; + + $boundaries = (new JsonListSplitter($lexer))->split(fopen('php://temp', 'r'), TypeFactory::createFromString('int'), $context); + + $this->assertSame($expectedBoundaries, iterator_to_array($boundaries)); + } + + /** + * @return iterable, 1: list}> + */ + public function splitDataProvider(): iterable + { + yield [[], [['[', 0], [']', 1]]]; + yield [[[1, 3]], [['[', 0], ['100', 1], [']', 4]]]; + yield [[[1, 3], [5, 3]], [['[', 0], ['100', 1], [',', 4], ['200', 5], [']', 8]]]; + yield [[[1, 6]], [['[', 0], ['1', 1], ['[', 2], ['2', 3], [',', 4], ['3', 5], [']', 6], [']', 7]]]; + yield [[[1, 6]], [['[', 0], ['1', 1], ['{', 2], ['2', 3], [',', 4], ['3', 5], ['}', 6], [']', 7]]]; + } + + /** + * @dataProvider splitInvalidDataProvider + * + * @param list $tokens + */ + public function testSplitInvalidThrowException(array $tokens) + { + $lexer = $this->createStub(LexerInterface::class); + $lexer->method('tokens')->willReturn(new \ArrayIterator($tokens)); + + $context = ['boundary' => [0, -1]]; + + $this->expectException(InvalidResourceException::class); + + iterator_to_array((new JsonListSplitter($lexer))->split(fopen('php://temp', 'r'), TypeFactory::createFromString('int'), $context)); + } + + /** + * @return iterable}> + */ + public function splitInvalidDataProvider(): iterable + { + yield [[['[', 0], ['100', 1]]]; + yield [[['[', 0], ['[', 1], [']', 2]]]; + yield [[['[', 0], ['[', 1], [']', 2], ['}', 3]]]; + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/Unmarshal/Json/UnmarshalTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/Unmarshal/Json/UnmarshalTest.php new file mode 100644 index 000000000000..0e7b31034b28 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/Unmarshal/Json/UnmarshalTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal\Unmarshal\Json; + +use PHPUnit\Framework\TestCase; + +use function Symfony\Component\Marshaller\unmarshal; + +class UnmarshalTest extends TestCase +{ + /** + * @dataProvider unmarshalDataProvider + */ + public function testUnmarshal(string $json, string $type) + { + $expected = json_decode($json, associative: true); + + $this->assertSame($expected, $this->unmarshalString($json, $type, ['lazy_reading' => true])); + $this->assertSame($expected, $this->unmarshalString($json, $type, ['lazy_reading' => false])); + } + + /** + * @return iterable + */ + public function unmarshalDataProvider(): iterable + { + yield ['1', 'int']; + yield ['"foo"', 'string']; + yield ['-1e100', 'float']; + yield ['true', 'bool']; + yield ['null', '?bool']; + yield ['[[1, 2], [3, null], null]', 'array>']; + yield ['{"foo": {"bar": 1, "baz": null}, "foo2": null}', 'array>']; + } + + public function testUnmarshalWithJsonDecodeFlags() + { + $this->assertSame('1.2345678901235E+29', $this->unmarshalString('123456789012345678901234567890', 'string')); + $this->assertSame('123456789012345678901234567890', $this->unmarshalString('123456789012345678901234567890', 'string', ['json_decode_flags' => \JSON_BIGINT_AS_STRING])); + } + + /** + * @param array $context + */ + private function unmarshalString(string $string, string $type, array $context = []): mixed + { + /** @var resource $resource */ + $resource = fopen('php://memory', 'w+'); + + fwrite($resource, $string); + rewind($resource); + + return unmarshal($resource, $type, 'json', $context); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/Unmarshal/Json/ValidatingJsonLexerTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/Unmarshal/Json/ValidatingJsonLexerTest.php new file mode 100644 index 000000000000..0c3511ea5047 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/Unmarshal/Json/ValidatingJsonLexerTest.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal\Unmarshal\Json; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Exception\InvalidResourceException; +use Symfony\Component\Marshaller\Internal\Unmarshal\Json\JsonLexer; +use Symfony\Component\Marshaller\Internal\Unmarshal\Json\ValidatingJsonLexer; + +class ValidatingJsonLexerTest extends TestCase +{ + public function testTokens() + { + /** @var resource $resource */ + $resource = fopen('php://temp', 'w'); + + fwrite($resource, '1'); + rewind($resource); + + $this->assertSame([['1', 0]], iterator_to_array((new ValidatingJsonLexer(new JsonLexer()))->tokens($resource, 0, -1, []))); + } + + /** + * @dataProvider validJsonTokensDataProvider + */ + public function testValidJsonTokens(string $file) + { + $lexer = new ValidatingJsonLexer(new JsonLexer()); + + iterator_to_array($lexer->tokens(fopen($file, 'r'), 0, -1, [])); + + $this->addToAssertionCount(1); + } + + /** + * Pulled from https://github.com/nst/JSONTestSuite. + * + * @return iterable + */ + public function validJsonTokensDataProvider(): iterable + { + foreach (glob(\dirname(__DIR__, 3).'/Fixtures/Resources/json/valid/*') as $file) { + yield [$file]; + } + } + + /** + * @dataProvider invalidJsonTokensDataProvider + */ + public function testInvalidJsonTokens(string $file) + { + $this->expectException(InvalidResourceException::class); + + iterator_to_array((new ValidatingJsonLexer(new JsonLexer()))->tokens(fopen($file, 'r'), 0, -1, [])); + dump(file_get_contents($file)); + } + + /** + * Pulled from https://github.com/nst/JSONTestSuite. + * + * @return iterable + */ + public function invalidJsonTokensDataProvider(): iterable + { + foreach (glob(\dirname(__DIR__, 3).'/Fixtures/Resources/json/invalid/*') as $file) { + yield [$file]; + } + } + + /** + * @return resource + */ + private function createResource(string $content): mixed + { + return $resource; + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Internal/Unmarshal/UnmarshalTest.php b/src/Symfony/Component/Marshaller/Tests/Internal/Unmarshal/UnmarshalTest.php new file mode 100644 index 000000000000..fc250dfcd381 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Internal/Unmarshal/UnmarshalTest.php @@ -0,0 +1,155 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Internal\Unmarshal; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Exception\InvalidConstructorArgumentException; +use Symfony\Component\Marshaller\Exception\InvalidResourceException; +use Symfony\Component\Marshaller\Exception\PartialUnmarshalException; +use Symfony\Component\Marshaller\Exception\UnexpectedTypeException; +use Symfony\Component\Marshaller\Exception\UnsupportedFormatException; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\ClassicDummy; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\DummyWithConstructorWithRequiredValues; + +use function Symfony\Component\Marshaller\unmarshal; + +class UnmarshalTest extends TestCase +{ + public function testUnmarshalUnionType() + { + $this->assertSame([1, 2, 3], $this->unmarshalString('[1, "2", "3"]', 'array', context: ['union_selector' => ['int|string' => 'int']])); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('Cannot guess type to use for "int|string", you may specify a type in "$context[\'union_selector\'][\'int|string\']".'); + + $this->assertSame([1, 2, 3], $this->unmarshalString('[1, "2", "3"]', 'array')); + } + + public function testUnmarshalIterable() + { + $value = $this->unmarshalString('[{"foo": 1, "bar": 2}, {"baz": 3}]', 'iterable>'); + + $this->assertInstanceOf(\Generator::class, $value); + + $result = []; + foreach ($value as $v) { + $this->assertInstanceOf(\Generator::class, $v); + $result[] = iterator_to_array($v); + } + + $this->assertSame([['foo' => 1, 'bar' => 2], ['baz' => 3]], $result); + } + + public function testUnmarshalObject() + { + $value = $this->unmarshalString('{"id": 123, "name": "thename"}', ClassicDummy::class); + + $expectedObject = new ClassicDummy(); + $expectedObject->id = 123; + $expectedObject->name = 'thename'; + + $this->assertEquals($expectedObject, $value); + + $value = $this->unmarshalString('{"@id": 123, "name": "thename"}', ClassicDummy::class, 'json', [ + 'hooks' => [ + 'unmarshal' => [ + sprintf('%s[@id]', ClassicDummy::class) => static function (\ReflectionClass $class, string $key, callable $value, array $context): array { + return [ + 'name' => 'id', + ]; + }, + sprintf('%s[name]', ClassicDummy::class) => static function (\ReflectionClass $class, string $key, callable $value, array $context): array { + return [ + 'value_provider' => fn () => 'HOOK_VALUE', + ]; + }, + ], + ], + ]); + $expectedObject->name = 'HOOK_VALUE'; + + $this->assertEquals($expectedObject, $value); + } + + public function testThrowOnUnknownFormat() + { + $this->expectException(UnsupportedFormatException::class); + + unmarshal(fopen('php://memory', 'w+'), 'int', 'unknown', []); + } + + public function testThrowWhenNotCollecting() + { + $this->expectException(InvalidConstructorArgumentException::class); + + $this->unmarshalString('{}', DummyWithConstructorWithRequiredValues::class); + } + + public function testThrowPartialWhenCollecting() + { + try { + $this->unmarshalString('[{"name": "ok"}, {"name": "ko"}, {"name": "ok"}, {"name": "ko"}]', sprintf('array', ClassicDummy::class), context: [ + 'collect_errors' => true, + 'hooks' => [ + 'unmarshal' => [ + sprintf('%s[name]', ClassicDummy::class) => static function (\ReflectionClass $class, string $key, callable $value, array $context): array { + $ok = $value('string', $context); + + return [ + 'value_provider' => fn () => 'ok' === $ok ? 'ok' : new \DateTimeImmutable(), + ]; + }, + ], + ], + ]); + + $this->fail(sprintf('"%s" has not been thrown.', PartialUnmarshalException::class)); + } catch (PartialUnmarshalException $e) { + $okDummy = new ClassicDummy(); + $okDummy->name = 'ok'; + + $koDummy = new ClassicDummy(); + + $this->assertEquals([$okDummy, $koDummy, $okDummy, $koDummy], $e->unmarshalled); + + $this->assertCount(2, $e->errors); + $this->assertContainsOnlyInstancesOf(UnexpectedTypeException::class, $e->errors); + } + } + + public function testThrowWhenValidateInvalidStream() + { + $this->expectException(InvalidResourceException::class); + + $this->unmarshalString('{[]}', ClassicDummy::class, context: ['validate_stream' => true]); + } + + public function testNotThrowWhenNotValidateInvalidStream() + { + $this->unmarshalString('{[]}', ClassicDummy::class); + $this->addToAssertionCount(1); + } + + /** + * @param array $context + */ + private function unmarshalString(string $string, string $type, string $format = 'json', array $context = []): mixed + { + /** @var resource $resource */ + $resource = fopen('php://memory', 'w+'); + + fwrite($resource, $string); + rewind($resource); + + return unmarshal($resource, $type, $format, $context); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/MarshallableResolverTest.php b/src/Symfony/Component/Marshaller/Tests/MarshallableResolverTest.php new file mode 100644 index 000000000000..a0a62db55ba7 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/MarshallableResolverTest.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Attribute\Marshallable; +use Symfony\Component\Marshaller\MarshallableResolver; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\AbstractDummy; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\ClassicDummy; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\NotMarshallableDummy; + +class MarshallableResolverTest extends TestCase +{ + public function testResolve() + { + $marshallableResolver = new MarshallableResolver([__DIR__.'/Fixtures']); + $marshallableList = iterator_to_array($marshallableResolver->resolve()); + + $this->assertContainsOnlyInstancesOf(Marshallable::class, $marshallableList); + + $this->assertArrayHasKey(ClassicDummy::class, $marshallableList); + $this->assertArrayNotHasKey(NotMarshallableDummy::class, $marshallableList); + $this->assertArrayNotHasKey(AbstractDummy::class, $marshallableList); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/MarshallerTest.php b/src/Symfony/Component/Marshaller/Tests/MarshallerTest.php new file mode 100644 index 000000000000..c90342d04dbd --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/MarshallerTest.php @@ -0,0 +1,264 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Context\ContextBuilder\CachedContextBuilder; +use Symfony\Component\Marshaller\Context\ContextBuilder\FormatterAttributeContextBuilder; +use Symfony\Component\Marshaller\Context\ContextBuilder\HookContextBuilder; +use Symfony\Component\Marshaller\Context\ContextBuilder\InstantiatorContextBuilder; +use Symfony\Component\Marshaller\Context\ContextBuilder\NameAttributeContextBuilder; +use Symfony\Component\Marshaller\Context\ContextBuilderInterface; +use Symfony\Component\Marshaller\Context\MarshalContext; +use Symfony\Component\Marshaller\Context\UnmarshalContext; +use Symfony\Component\Marshaller\Hook\Marshal as MarshalHook; +use Symfony\Component\Marshaller\Hook\Unmarshal as UnmarshalHook; +use Symfony\Component\Marshaller\Instantiator\LazyInstantiator; +use Symfony\Component\Marshaller\MarshallableResolver; +use Symfony\Component\Marshaller\Marshaller; +use Symfony\Component\Marshaller\MarshallerInterface; +use Symfony\Component\Marshaller\Stream\MemoryStream; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\DummyWithFormatterAttributes; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\DummyWithGenerics; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\DummyWithNameAttributes; +use Symfony\Component\Marshaller\Type\PhpstanTypeExtractor; +use Symfony\Component\Marshaller\Type\ReflectionTypeExtractor; + +class MarshallerTest extends TestCase +{ + private string $templateCacheDir; + private string $lazyObjectCacheDir; + + private MarshallerInterface $marshaller; + + protected function setUp(): void + { + parent::setUp(); + + $this->templateCacheDir = sprintf('%s/symfony_marshaller_template', sys_get_temp_dir()); + + if (is_dir($this->templateCacheDir)) { + array_map('unlink', glob($this->templateCacheDir.'/*')); + rmdir($this->templateCacheDir); + } + + $this->lazyObjectCacheDir = sprintf('%s/symfony_marshaller_lazy_object', sys_get_temp_dir()); + + if (is_dir($this->lazyObjectCacheDir)) { + array_map('unlink', glob($this->lazyObjectCacheDir.'/*')); + rmdir($this->lazyObjectCacheDir); + } + + $this->marshaller = $this->createMarshaller(); + } + + public function testMarshal() + { + $this->marshaller->marshal(1, 'json', $output = new MemoryStream(), []); + + $this->assertSame('1', (string) $output); + } + + public function testMarshalOverrideType() + { + $this->marshaller->marshal(['foo' => 'bar'], 'json', $output = new MemoryStream(), ['type' => 'array']); + + $this->assertSame('["bar"]', (string) $output); + } + + public function testMarshalOverrideCacheDir() + { + $cacheDir = sprintf('%s/%s', sys_get_temp_dir(), uniqid('symfony_marshaller_tmp_')); + + $this->marshaller->marshal('foo', 'json', new MemoryStream(), ['cache_dir' => $cacheDir]); + + $this->assertCount(1, glob($cacheDir.'/*')); + + array_map('unlink', glob($cacheDir.'/*')); + rmdir($cacheDir); + } + + public function testMarshalHandleRawResource() + { + $output = fopen('php://memory', 'w+'); + + $this->marshaller->marshal('123', 'json', $output, (new MarshalContext())->withJsonEncodeFlags(\JSON_NUMERIC_CHECK)); + + rewind($output); + + $this->assertSame('123', stream_get_contents($output)); + } + + public function testMarshalCastContext() + { + $this->marshaller->marshal('123', 'json', $output = new MemoryStream(), (new MarshalContext())->withJsonEncodeFlags(\JSON_NUMERIC_CHECK)); + + $this->assertSame('123', (string) $output); + } + + public function testMarshalCheckThatTemplateNotExist() + { + $contextBuilder = $this->createMock(ContextBuilderInterface::class); + $contextBuilder + ->expects($this->exactly(2)) + ->method('buildMarshalContext') + ->withConsecutive( + [['type' => 'int', 'cache_dir' => $this->templateCacheDir], true], + [['type' => 'int', 'cache_dir' => $this->templateCacheDir], false], + ) + ->willReturn(['type' => 'int', 'cache_dir' => $this->templateCacheDir]); + + $marshaller = new Marshaller([$contextBuilder], $this->templateCacheDir); + + $marshaller->marshal(1, 'json', new MemoryStream(), []); + $marshaller->marshal(1, 'json', new MemoryStream(), []); + } + + public function testMarshalReadNameAttribute() + { + $this->marshaller->marshal(new DummyWithNameAttributes(), 'json', $output = new MemoryStream()); + + $this->assertSame('{"@id":1,"name":"dummy"}', (string) $output); + } + + public function testMarshalReadFormatterAttribute() + { + $this->marshaller->marshal(new DummyWithFormatterAttributes(), 'json', $output = new MemoryStream()); + + $this->assertSame('{"id":"2","name":"dummy"}', (string) $output); + } + + public function testMarshalReadGenerics() + { + $dummy = new DummyWithGenerics(); + $dummy->dummies = [new DummyWithNameAttributes(), new DummyWithNameAttributes()]; + + $this->marshaller->marshal($dummy, 'json', $output = new MemoryStream(), [ + 'type' => sprintf('%s<%s>', DummyWithGenerics::class, DummyWithNameAttributes::class), + ]); + + $this->assertSame('{"dummies":[{"@id":1,"name":"dummy"},{"@id":1,"name":"dummy"}]}', (string) $output); + } + + public function testUnmarshal() + { + $input = new MemoryStream(); + + fwrite($input->resource(), '"foo"'); + rewind($input->resource()); + + $result = $this->marshaller->unmarshal($input, 'string', 'json'); + + $this->assertEquals('foo', $result); + } + + public function testUnmarshalHandleRawResource() + { + $input = fopen('php://memory', 'w+'); + + fwrite($input, '"foo"'); + rewind($input); + + $result = $this->marshaller->unmarshal($input, 'string', 'json'); + + $this->assertEquals('foo', $result); + } + + public function testUnmarshalCastContext() + { + $input = new MemoryStream(); + + fwrite($input->resource(), '123456789012345678901234567890'); + rewind($input->resource()); + + $result = $this->marshaller->unmarshal($input, 'string', 'json', (new UnmarshalContext())->withJsonDecodeFlags(\JSON_BIGINT_AS_STRING)); + + $this->assertEquals('123456789012345678901234567890', $result); + } + + public function testUnmarshalReadNameAttribute() + { + $input = new MemoryStream(); + + fwrite($input->resource(), '{"@id":1,"name":"dummy"}'); + rewind($input->resource()); + + $expectedResult = new DummyWithNameAttributes(); + $result = $this->marshaller->unmarshal($input, DummyWithNameAttributes::class, 'json', ['instantiator' => 'eager']); + + $this->assertEquals($expectedResult, $result); + } + + public function testUnmarshalReadFormatterAttribute() + { + $input = new MemoryStream(); + + fwrite($input->resource(), '{"id":"2","name":"dummy"}'); + rewind($input->resource()); + + $expectedResult = new DummyWithFormatterAttributes(); + $result = $this->marshaller->unmarshal($input, DummyWithFormatterAttributes::class, 'json', ['instantiator' => 'eager']); + + $this->assertEquals($expectedResult, $result); + } + + public function testUnmarshalReadGenerics() + { + $input = new MemoryStream(); + + fwrite($input->resource(), '{"dummies":[{"@id":1,"name":"dummy"},{"@id":1,"name":"dummy"}]}'); + rewind($input->resource()); + + $expectedResult = new DummyWithGenerics(); + $expectedResult->dummies = [new DummyWithNameAttributes(), new DummyWithNameAttributes()]; + + $result = $this->marshaller->unmarshal($input, sprintf('%s<%s>', DummyWithGenerics::class, DummyWithNameAttributes::class), 'json', ['instantiator' => 'eager']); + + $this->assertEquals($expectedResult, $result); + } + + public function testUnmarshalInstantiateLazyObject() + { + $input = new MemoryStream(); + + fwrite($input->resource(), '{"id":1,"name":"dummy"}'); + rewind($input->resource()); + + $result = $this->marshaller->unmarshal($input, DummyWithNameAttributes::class, 'json', ['instantiator' => 'lazy']); + + $lazyClassName = sprintf('%sGhost', preg_replace('/\\\\/', '', DummyWithNameAttributes::class)); + + $this->assertInstanceof($lazyClassName, $result); + $this->assertSame(1, $result->id); + } + + private function createMarshaller(): MarshallerInterface + { + $typeExtractor = new PhpstanTypeExtractor(new ReflectionTypeExtractor()); + $marshallableResolver = new MarshallableResolver([__DIR__.'/Fixtures']); + + $contextBuilders = [ + new CachedContextBuilder(new FormatterAttributeContextBuilder($marshallableResolver), 'property_formatter', 'formatter'), + new CachedContextBuilder(new NameAttributeContextBuilder($marshallableResolver), 'property_name', 'name'), + new HookContextBuilder([ + 'object' => (new MarshalHook\ObjectHook($typeExtractor))(...), + 'property' => (new MarshalHook\PropertyHook($typeExtractor))(...), + ], [ + 'object' => (new UnmarshalHook\ObjectHook($typeExtractor))(...), + 'property' => (new UnmarshalHook\PropertyHook($typeExtractor))(...), + ]), + new InstantiatorContextBuilder(new LazyInstantiator($this->lazyObjectCacheDir)), + ]; + + return new Marshaller($contextBuilders, $this->templateCacheDir); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Stream/StreamTest.php b/src/Symfony/Component/Marshaller/Tests/Stream/StreamTest.php new file mode 100644 index 000000000000..88a8a4b22a80 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Stream/StreamTest.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Stream; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Stream\Stream; + +class StreamTest extends TestCase +{ + public function testCreateStream() + { + $stream = new class() extends Stream { + public function __construct() + { + parent::__construct('php://memory', 'w+b'); + } + }; + + $streamMetadata = stream_get_meta_data($stream->resource()); + + $this->assertSame('php://memory', $streamMetadata['uri']); + $this->assertSame('w+b', $streamMetadata['mode']); + } + + public function testToString() + { + $stream = new class() extends Stream { + public function __construct() + { + parent::__construct('php://memory', 'w+b'); + } + }; + + fwrite($stream->resource(), 'content'); + + $this->assertSame('content', (string) $stream); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Type/PhpstanTypeExtractorTest.php b/src/Symfony/Component/Marshaller/Tests/Type/PhpstanTypeExtractorTest.php new file mode 100644 index 000000000000..8e6b605e8abe --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Type/PhpstanTypeExtractorTest.php @@ -0,0 +1,191 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Type; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Exception\UnexpectedValueException; +use Symfony\Component\Marshaller\Exception\UnsupportedTypeException; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\AbstractDummy; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\NonUniqueTemplatePhpstanExtractableDummy; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\PhpstanExtractableDummy; +use Symfony\Component\Marshaller\Type\PhpstanTypeExtractor; +use Symfony\Component\Marshaller\Type\TypeExtractorInterface; + +class PhpstanTypeExtractorTest extends TestCase +{ + /** + * @dataProvider typesDataProvider + */ + public function testExtractFromProperty(string $expectedType, string $property) + { + $fallbackExtractor = $this->createStub(TypeExtractorInterface::class); + $fallbackExtractor->method('extractFromProperty')->willReturn('FALLBACK'); + + $extractor = new PhpstanTypeExtractor($fallbackExtractor); + $reflectionProperty = (new \ReflectionClass(PhpstanExtractableDummy::class))->getProperty($property); + + $this->assertSame($expectedType, $extractor->extractFromProperty($reflectionProperty)); + } + + public function testCannotHandleIntersectionProperty() + { + $fallbackExtractor = $this->createStub(TypeExtractorInterface::class); + + $extractor = new PhpstanTypeExtractor($fallbackExtractor); + $reflectionProperty = (new \ReflectionClass(PhpstanExtractableDummy::class))->getProperty('intersection'); + + $this->expectException(UnsupportedTypeException::class); + + $extractor->extractFromProperty($reflectionProperty); + } + + public function testCannotHandleUnknownNode() + { + $fallbackExtractor = $this->createStub(TypeExtractorInterface::class); + + $extractor = new PhpstanTypeExtractor($fallbackExtractor); + $reflectionProperty = (new \ReflectionClass(PhpstanExtractableDummy::class))->getProperty('unknown'); + + $this->expectException(UnsupportedTypeException::class); + + $extractor->extractFromProperty($reflectionProperty); + } + + /** + * @dataProvider typesDataProvider + */ + public function testExtractFromFunctionReturn(string $expectedType, string $method) + { + $fallbackExtractor = $this->createStub(TypeExtractorInterface::class); + $fallbackExtractor->method('extractFromFunctionReturn')->willReturn('FALLBACK'); + + $extractor = new PhpstanTypeExtractor($fallbackExtractor); + $reflectionMethod = (new \ReflectionClass(PhpstanExtractableDummy::class))->getMethod($method); + + $this->assertSame($expectedType, $extractor->extractFromFunctionReturn($reflectionMethod)); + } + + public function testFallbackOnVoidAndNeverFunctionReturn() + { + $fallbackExtractor = $this->createStub(TypeExtractorInterface::class); + $fallbackExtractor->method('extractFromFunctionReturn')->willReturn('FALLBACK'); + + $extractor = new PhpstanTypeExtractor($fallbackExtractor); + + $voidReflectionMethod = (new \ReflectionClass(PhpstanExtractableDummy::class))->getMethod('void'); + $neverReflectionMethod = (new \ReflectionClass(PhpstanExtractableDummy::class))->getMethod('never'); + + $this->assertSame('FALLBACK', $extractor->extractFromFunctionReturn($voidReflectionMethod)); + $this->assertSame('FALLBACK', $extractor->extractFromFunctionReturn($neverReflectionMethod)); + } + + public function testExtractClassTypeFromFunctionFunctionReturn() + { + $fallbackExtractor = $this->createStub(TypeExtractorInterface::class); + $fallbackExtractor->method('extractFromFunctionReturn')->willReturn('FALLBACK'); + + $extractor = new PhpstanTypeExtractor($fallbackExtractor); + + /** @return self */ + $selfReflectionFunction = new \ReflectionFunction(function () { + return $this; + }); + + $this->assertSame(self::class, $extractor->extractFromFunctionReturn($selfReflectionFunction)); + } + + /** + * @dataProvider typesDataProvider + */ + public function testExtractFromParameter(string $expectedType, string $method) + { + $fallbackExtractor = $this->createStub(TypeExtractorInterface::class); + $fallbackExtractor->method('extractFromFunctionParameter')->willReturn('FALLBACK'); + + $extractor = new PhpstanTypeExtractor($fallbackExtractor); + + $reflectionParameter = (new \ReflectionClass(PhpstanExtractableDummy::class))->getMethod($method)->getParameters()[0]; + + $this->assertSame($expectedType, $extractor->extractFromFunctionParameter($reflectionParameter)); + } + + public function testExtractClassTypeFromParameter() + { + $fallbackExtractor = $this->createStub(TypeExtractorInterface::class); + $fallbackExtractor->method('extractFromFunctionParameter')->willReturn('FALLBACK'); + + $extractor = new PhpstanTypeExtractor($fallbackExtractor); + + /** @param self $_ */ + $selfReflectionFunction = new \ReflectionFunction(function ($_) { + }); + + $this->assertSame(self::class, $extractor->extractFromFunctionParameter($selfReflectionFunction->getParameters()[0])); + } + + /** + * @return iterable + */ + public function typesDataProvider(): iterable + { + yield ['mixed', 'mixed']; + yield ['bool', 'bool']; + yield ['bool', 'boolean']; + yield ['bool', 'true']; + yield ['bool', 'false']; + yield ['int', 'int']; + yield ['int', 'integer']; + yield ['float', 'float']; + yield ['string', 'string']; + yield ['resource', 'resource']; + yield ['object', 'object']; + yield ['callable', 'callable']; + yield ['array', 'array']; + yield ['array', 'list']; + yield ['iterable', 'iterable']; + yield ['array', 'nonEmptyArray']; + yield ['array', 'nonEmptyList']; + yield ['null', 'null']; + yield [PhpstanExtractableDummy::class, 'self']; + yield [PhpstanExtractableDummy::class, 'static']; + yield [AbstractDummy::class, 'parent']; + yield ['Symfony\\Component\\Marshaller\\Tests\\Fixtures\\Dto\\scoped', 'scoped']; + yield ['int|string', 'union']; + yield ['?int', 'nullable']; + yield ['int|string|null', 'nullableUnion']; + yield ['array', 'genericList']; + yield ['array', 'genericArrayList']; + yield ['array', 'genericDict']; + yield ['array', 'squareBracketList']; + yield ['array', 'bracketList']; + yield ['array', 'emptyBracketList']; + yield ['ArrayIterator', 'generic']; + yield ['Tv', 'genericParameter']; + yield ['FALLBACK', 'undefined']; + } + + public function testExtractTemplateFromClass() + { + $extractor = new PhpstanTypeExtractor($this->createStub(TypeExtractorInterface::class)); + + $this->assertSame(['Tk', 'Tv'], $extractor->extractTemplateFromClass(new \ReflectionClass(PhpstanExtractableDummy::class))); + } + + public function testExtractTemplateFromClassThrowWhenNonUniqueTemplate() + { + $extractor = new PhpstanTypeExtractor($this->createStub(TypeExtractorInterface::class)); + + $this->expectException(UnexpectedValueException::class); + + $extractor->extractTemplateFromClass(new \ReflectionClass(NonUniqueTemplatePhpstanExtractableDummy::class)); + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Type/ReflectionTypeExtractorTest.php b/src/Symfony/Component/Marshaller/Tests/Type/ReflectionTypeExtractorTest.php new file mode 100644 index 000000000000..9b15f888fd74 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Type/ReflectionTypeExtractorTest.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Type; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Exception\MissingTypeException; +use Symfony\Component\Marshaller\Exception\UnsupportedTypeException; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\AbstractDummy; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\ClassicDummy; +use Symfony\Component\Marshaller\Tests\Fixtures\Dto\ReflectionExtractableDummy; +use Symfony\Component\Marshaller\Type\ReflectionTypeExtractor; + +class ReflectionTypeExtractorTest extends TestCase +{ + /** + * @dataProvider typesDataProvider + */ + public function testExtractFromProperty(string $expectedType, string $property) + { + $reflectionProperty = (new \ReflectionClass(ReflectionExtractableDummy::class))->getProperty($property); + + $this->assertSame($expectedType, (new ReflectionTypeExtractor())->extractFromProperty($reflectionProperty)); + } + + public function testCannotHandleIntersectionProperty() + { + $reflectionProperty = (new \ReflectionClass(ReflectionExtractableDummy::class))->getProperty('intersection'); + + $this->expectException(UnsupportedTypeException::class); + + (new ReflectionTypeExtractor())->extractFromProperty($reflectionProperty); + } + + public function testThrowIfCannotFindPropertyType() + { + $reflectionProperty = (new \ReflectionClass(ReflectionExtractableDummy::class))->getProperty('undefined'); + + $this->expectException(MissingTypeException::class); + + (new ReflectionTypeExtractor())->extractFromProperty($reflectionProperty); + } + + /** + * @dataProvider typesDataProvider + */ + public function testExtractFromFunctionReturn(string $expectedType, string $method) + { + $reflectionMethod = (new \ReflectionClass(ReflectionExtractableDummy::class))->getMethod($method); + + $this->assertSame($expectedType, (new ReflectionTypeExtractor())->extractFromFunctionReturn($reflectionMethod)); + } + + public function testExtractClassTypeFromFunctionReturnType() + { + $selfReflectionFunction = new \ReflectionFunction(function (): self { + return $this; + }); + + $this->assertSame(self::class, (new ReflectionTypeExtractor())->extractFromFunctionReturn($selfReflectionFunction)); + + $parentReflectionFunction = new \ReflectionFunction(function (): parent { + return $this; + }); + + $this->assertSame(parent::class, (new ReflectionTypeExtractor())->extractFromFunctionReturn($parentReflectionFunction)); + } + + public function testCannotHandleIntersectionReturnType() + { + $reflectionMethod = (new \ReflectionClass(ReflectionExtractableDummy::class))->getMethod('intersection'); + + $this->expectException(UnsupportedTypeException::class); + + (new ReflectionTypeExtractor())->extractFromFunctionReturn($reflectionMethod); + } + + public function testCannotHandleVoidReturnType() + { + $reflectionMethod = (new \ReflectionClass(ReflectionExtractableDummy::class))->getMethod('void'); + + $this->expectException(UnsupportedTypeException::class); + + (new ReflectionTypeExtractor())->extractFromFunctionReturn($reflectionMethod); + } + + public function testCannotHandleNeverReturnType() + { + $reflectionMethod = (new \ReflectionClass(ReflectionExtractableDummy::class))->getMethod('never'); + + $this->expectException(UnsupportedTypeException::class); + + (new ReflectionTypeExtractor())->extractFromFunctionReturn($reflectionMethod); + } + + public function testThrowIfCannotFindReturnType() + { + $this->expectException(MissingTypeException::class); + + $reflectionMethod = (new \ReflectionClass(ReflectionExtractableDummy::class))->getMethod('undefined'); + + (new ReflectionTypeExtractor())->extractFromFunctionReturn($reflectionMethod); + } + + /** + * @dataProvider typesDataProvider + */ + public function testExtractFromParameter(string $expectedType, string $method) + { + $reflectionParameter = (new \ReflectionClass(ReflectionExtractableDummy::class))->getMethod($method)->getParameters()[0]; + + $this->assertSame($expectedType, (new ReflectionTypeExtractor())->extractFromFunctionParameter($reflectionParameter)); + } + + public function testThrowIfCannotFindParameterType() + { + $this->expectException(MissingTypeException::class); + + $reflectionParameter = (new \ReflectionClass(ReflectionExtractableDummy::class))->getMethod('undefined')->getParameters()[0]; + + (new ReflectionTypeExtractor())->extractFromFunctionParameter($reflectionParameter); + } + + /** + * @return iterable + */ + public function typesDataProvider(): iterable + { + yield ['mixed', 'mixed']; + yield ['int', 'int']; + yield ['string', 'string']; + yield ['float', 'float']; + yield ['bool', 'bool']; + yield ['array', 'array']; + yield [ReflectionExtractableDummy::class, 'self']; + yield [AbstractDummy::class, 'parent']; + yield [ClassicDummy::class, 'class']; + yield ['string|int', 'union']; + yield ['?int', 'nullableBuiltin']; + yield ['?'.ClassicDummy::class, 'nullableClass']; + yield ['string|int|null', 'nullableUnion']; + } +} diff --git a/src/Symfony/Component/Marshaller/Tests/Type/TypeGenericsHelperTest.php b/src/Symfony/Component/Marshaller/Tests/Type/TypeGenericsHelperTest.php new file mode 100644 index 000000000000..585f3de4e86e --- /dev/null +++ b/src/Symfony/Component/Marshaller/Tests/Type/TypeGenericsHelperTest.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Tests\Type; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Marshaller\Exception\InvalidTypeException; +use Symfony\Component\Marshaller\Type\TypeGenericsHelper; + +class TypeGenericsHelperTest extends TestCase +{ + /** + * @dataProvider replaceGenericTypesDataProvider + * + * @param array $genericTypes + */ + public function testReplaceGenericTypes(string $expectedType, string $type, array $genericTypes) + { + $this->assertSame($expectedType, (new TypeGenericsHelper())->replaceGenericTypes($type, $genericTypes)); + } + + /** + * @return iterable}> + */ + public function replaceGenericTypesDataProvider(): iterable + { + yield ['T', 'T', []]; + yield ['Foo', 'T', ['T' => 'Foo']]; + + yield ['array', 'array', ['T' => 'Foo']]; + yield ['array', 'array', ['T' => 'Foo']]; + yield ['array', 'array', ['T' => 'Foo']]; + + yield ['int|Foo', 'int|T', ['T' => 'Foo']]; + yield ['int|Foo|Bar', 'int|T|U', ['T' => 'Foo', 'U' => 'Bar']]; + yield ['int|Foo|array', 'int|T|array', ['T' => 'Foo', 'U' => 'Bar']]; + } + + /** + * @dataProvider extractGenericsDataProvider + * + * @param list $expectedGenericParameters + */ + public function testExtractGenerics(string $expectedGenericType, array $expectedGenericParameters, string $type) + { + $this->assertSame( + ['genericType' => $expectedGenericType, 'genericParameters' => $expectedGenericParameters], + (new TypeGenericsHelper())->extractGenerics($type), + ); + } + + /** + * @return iterable, 2: string}> + */ + public function extractGenericsDataProvider(): iterable + { + yield ['int', [], 'int']; + yield ['Foo', ['int'], 'Foo']; + yield ['array', ['int', 'string'], 'array']; + } + + public function testExtractGenericsThrowOnInvalidGenericString() + { + $this->expectException(InvalidTypeException::class); + + (new TypeGenericsHelper())->extractGenerics('Foo', '$accessor', []); + } +} diff --git a/src/Symfony/Component/Marshaller/Type/PhpstanTypeExtractor.php b/src/Symfony/Component/Marshaller/Type/PhpstanTypeExtractor.php new file mode 100644 index 000000000000..5c8d91eafbfe --- /dev/null +++ b/src/Symfony/Component/Marshaller/Type/PhpstanTypeExtractor.php @@ -0,0 +1,150 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Type; + +use PHPStan\PhpDocParser\Ast\PhpDoc\InvalidTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode; +use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode; +use PHPStan\PhpDocParser\Ast\Type\TypeNode; +use PHPStan\PhpDocParser\Lexer\Lexer; +use PHPStan\PhpDocParser\Parser\ConstExprParser; +use PHPStan\PhpDocParser\Parser\PhpDocParser; +use PHPStan\PhpDocParser\Parser\TokenIterator; +use PHPStan\PhpDocParser\Parser\TypeParser; +use Symfony\Component\Marshaller\Exception\UnexpectedValueException; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +final class PhpstanTypeExtractor implements TypeExtractorInterface +{ + private readonly PhpstanTypeHelper $phpstanTypeHelper; + private readonly PhpDocParser $phpstanDocParser; + private readonly Lexer $phpstanLexer; + + public function __construct( + private readonly TypeExtractorInterface $decoratedTypeExtractor, + ) { + $this->phpstanTypeHelper = new PhpstanTypeHelper(); + $this->phpstanDocParser = new PhpDocParser(new TypeParser(new ConstExprParser()), new ConstExprParser()); + $this->phpstanLexer = new Lexer(); + } + + public function extractFromProperty(\ReflectionProperty $property): string + { + if (null === $typeNode = $this->getTypeNode($property)) { + return $this->decoratedTypeExtractor->extractFromProperty($property); + } + + return $this->phpstanTypeHelper->getType($typeNode, $property->getDeclaringClass(), $this->getTemplateNodes($property->getDeclaringClass())); + } + + public function extractFromFunctionReturn(\ReflectionFunctionAbstract $function): string + { + if (null === $typeNode = $this->getTypeNode($function)) { + return $this->decoratedTypeExtractor->extractFromFunctionReturn($function); + } + + $declaringClass = $function instanceof \ReflectionMethod ? $function->getDeclaringClass() : $function->getClosureScopeClass(); + if (null === $declaringClass) { + throw new UnexpectedValueException(sprintf('"%s()" does not have any declaring class.', $function->getName())); + } + + return $this->phpstanTypeHelper->getType($typeNode, $declaringClass, $this->getTemplateNodes($declaringClass)); + } + + public function extractFromFunctionParameter(\ReflectionParameter $parameter): string + { + if (null === $typeNode = $this->getTypeNode($parameter)) { + return $this->decoratedTypeExtractor->extractFromFunctionParameter($parameter); + } + + $function = $parameter->getDeclaringFunction(); + $declaringClass = $function instanceof \ReflectionMethod ? $function->getDeclaringClass() : $function->getClosureScopeClass(); + + if (null === $declaringClass) { + throw new UnexpectedValueException(sprintf('"%s()" does not have any declaring class.', $function->getName())); + } + + return $this->phpstanTypeHelper->getType($typeNode, $declaringClass, $this->getTemplateNodes($declaringClass)); + } + + private function getTypeNode(\ReflectionProperty|\ReflectionFunctionAbstract|\ReflectionParameter $reflection): ?TypeNode + { + $rawDocNode = $reflection instanceof \ReflectionParameter ? $reflection->getDeclaringFunction()->getDocComment() : $reflection->getDocComment(); + if (!$rawDocNode) { + return null; + } + + $tokens = new TokenIterator($this->phpstanLexer->tokenize($rawDocNode)); + $docNode = $this->phpstanDocParser->parse($tokens); + $tokens->consumeTokenType(Lexer::TOKEN_END); + + $tagName = match (true) { + $reflection instanceof \ReflectionProperty => '@var', + $reflection instanceof \ReflectionFunctionAbstract => '@return', + $reflection instanceof \ReflectionParameter => '@param', + }; + + $tags = $docNode->getTagsByName($tagName); + $tag = reset($tags) ?: null; + + /** @var VarTagValueNode|ReturnTagValueNode|ParamTagValueNode|InvalidTagValueNode|null $tagValue */ + $tagValue = $tag?->value; + + if (null === $tagValue || $tagValue instanceof InvalidTagValueNode) { + return null; + } + + return $tagValue->type; + } + + /** + * @param \ReflectionClass $reflection + * + * @return list + */ + private function getTemplateNodes(\ReflectionClass $reflection): array + { + if (null === $rawDocNode = $reflection->getDocComment() ?: null) { + return []; + } + + $tokens = new TokenIterator($this->phpstanLexer->tokenize($rawDocNode)); + $docNode = $this->phpstanDocParser->parse($tokens); + $tokens->consumeTokenType(Lexer::TOKEN_END); + + $tags = $docNode->getTagsByName('@template'); + + return array_values(array_filter( + array_map(fn (PhpDocTagNode $t): PhpDocTagValueNode => $t->value, $tags), + fn (PhpDocTagValueNode $v): bool => $v instanceof TemplateTagValueNode, + )); + } + + public function extractTemplateFromClass(\ReflectionClass $class): array + { + $templates = array_map(fn (TemplateTagValueNode $t): string => $t->name, $this->getTemplateNodes($class)); + + if (array_unique($templates) !== $templates) { + throw new UnexpectedValueException(sprintf('Templates defined in "%s" must be unique.', $class->getName())); + } + + return $templates; + } +} diff --git a/src/Symfony/Component/Marshaller/Type/PhpstanTypeHelper.php b/src/Symfony/Component/Marshaller/Type/PhpstanTypeHelper.php new file mode 100644 index 000000000000..7ba82f2c9f3e --- /dev/null +++ b/src/Symfony/Component/Marshaller/Type/PhpstanTypeHelper.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Type; + +use PHPStan\PhpDocParser\Ast\PhpDoc\TemplateTagValueNode; +use PHPStan\PhpDocParser\Ast\Type\ArrayShapeItemNode; +use PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode; +use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode; +use PHPStan\PhpDocParser\Ast\Type\CallableTypeNode; +use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode; +use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode; +use PHPStan\PhpDocParser\Ast\Type\NullableTypeNode; +use PHPStan\PhpDocParser\Ast\Type\ThisTypeNode; +use PHPStan\PhpDocParser\Ast\Type\TypeNode; +use PHPStan\PhpDocParser\Ast\Type\UnionTypeNode; +use Symfony\Component\Marshaller\Exception\UnsupportedTypeException; + +/** + * @author Mathias Arlaud + * + * @internal + */ +final class PhpstanTypeHelper +{ + /** + * @param \ReflectionClass $declaringClass + * @param list $templateNodes + */ + public function getType(TypeNode $typeNode, \ReflectionClass $declaringClass, array $templateNodes): string + { + $templateNodeNames = array_map(fn (TemplateTagValueNode $t): string => $t->name, $templateNodes); + + return $this->extractType($typeNode, TypeNameResolver::createForClass($declaringClass, $templateNodeNames)); + } + + private function extractType(TypeNode $node, TypeNameResolver $nameResolver): string + { + if ($node instanceof UnionTypeNode) { + return implode('|', array_map(fn (TypeNode $t): string => $this->extractType($t, $nameResolver), $node->types)); + } + + if ($node instanceof NullableTypeNode) { + return sprintf('?%s', $this->extractType($node->type, $nameResolver)); + } + + if ($node instanceof IdentifierTypeNode) { + return $this->extractIdentifierType($node, $nameResolver); + } + + if ($node instanceof GenericTypeNode) { + return $this->extractGenericType($node, $nameResolver); + } + + if ($node instanceof ArrayTypeNode || $node instanceof ArrayShapeNode) { + return $this->extractArrayType($node, $nameResolver); + } + + if ($node instanceof ThisTypeNode) { + return $nameResolver->resolveRootClass(); + } + + if ($node instanceof CallableTypeNode) { + return 'callable'; + } + + throw new UnsupportedTypeException((string) $node); + } + + private function extractIdentifierType(IdentifierTypeNode $node, TypeNameResolver $nameResolver): string + { + return match ($node->name) { + 'bool', 'boolean', 'true', 'false' => 'bool', + 'int', 'integer' => 'int', + 'float' => 'float', + 'string' => 'string', + 'resource' => 'resource', + 'object' => 'object', + 'callable' => 'callable', + 'array', 'list', 'non-empty-array', 'non-empty-list' => 'array', + 'iterable' => 'iterable', + 'mixed' => 'mixed', + 'null' => 'null', + 'static', 'self' => $nameResolver->resolveRootClass(), + 'parent' => $nameResolver->resolveParentClass(), + default => $nameResolver->resolve($node->name), + }; + } + + private function extractGenericType(GenericTypeNode $node, TypeNameResolver $nameResolver): string + { + $genericParameters = array_map(fn (TypeNode $t): string => $this->extractType($t, $nameResolver), $node->genericTypes); + + if ('array' === $mainType = $this->extractType($node->type, $nameResolver)) { + $keyType = 'int'; + $valueType = $genericParameters[0]; + if (2 === \count($genericParameters)) { + $keyType = $valueType; + $valueType = $genericParameters[1]; + } + + $genericParameters = [$keyType, $valueType]; + } + + return sprintf('%s<%s>', $mainType, implode(', ', $genericParameters)); + } + + private function extractArrayType(ArrayTypeNode|ArrayShapeNode $node, TypeNameResolver $nameResolver): string + { + if ($node instanceof ArrayTypeNode) { + return sprintf('array', $this->extractType($node->type, $nameResolver)); + } + + if ([] === $items = $node->items) { + return 'array'; + } + + $valueType = $node->items[0]->valueType; + + if (\count($items) > 1) { + $valueType = new UnionTypeNode(array_map(fn (ArrayShapeItemNode $i): TypeNode => $i->valueType, $node->items)); + } + + return sprintf('array', $this->extractType($valueType, $nameResolver)); + } +} diff --git a/src/Symfony/Component/Marshaller/Type/ReflectionTypeExtractor.php b/src/Symfony/Component/Marshaller/Type/ReflectionTypeExtractor.php new file mode 100644 index 000000000000..1cdc0f0c22d8 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Type/ReflectionTypeExtractor.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Type; + +use Symfony\Component\Marshaller\Exception\MissingTypeException; +use Symfony\Component\Marshaller\Exception\UnsupportedTypeException; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +final class ReflectionTypeExtractor implements TypeExtractorInterface +{ + public function extractFromProperty(\ReflectionProperty $property): string + { + if (null === $type = $property->getType()) { + throw MissingTypeException::forProperty($property); + } + + return $this->extractFromReflection($type, $property->getDeclaringClass()); + } + + public function extractFromFunctionReturn(\ReflectionFunctionAbstract $function): string + { + /** @var \ReflectionClass|null $declaringClass */ + $declaringClass = $function instanceof \ReflectionMethod ? $function->getDeclaringClass() : $function->getClosureScopeClass(); + + if (null === $type = $function->getReturnType()) { + throw MissingTypeException::forFunctionReturn($function); + } + + return $this->extractFromReflection($type, $declaringClass); + } + + public function extractFromFunctionParameter(\ReflectionParameter $parameter): string + { + $function = $parameter->getDeclaringFunction(); + + /** @var \ReflectionClass|null $declaringClass */ + $declaringClass = $function instanceof \ReflectionMethod ? $function->getDeclaringClass() : $function->getClosureScopeClass(); + + if (null === $type = $parameter->getType()) { + throw MissingTypeException::forFunctionParameter($parameter); + } + + return $this->extractFromReflection($type, $declaringClass); + } + + /** + * @param \ReflectionClass|null $declaringClass + */ + private function extractFromReflection(\ReflectionType $reflection, ?\ReflectionClass $declaringClass): string + { + if (!($reflection instanceof \ReflectionUnionType || $reflection instanceof \ReflectionNamedType)) { + throw new UnsupportedTypeException((string) $reflection); + } + + if ($reflection instanceof \ReflectionUnionType) { + return implode('|', array_map(fn (\ReflectionNamedType $t): string => $this->extractFromReflection($t, $declaringClass), $reflection->getTypes())); + } + + $nullablePrefix = $reflection->allowsNull() ? '?' : ''; + $phpTypeOrClass = $reflection->getName(); + + if ('never' === $phpTypeOrClass || 'void' === $phpTypeOrClass) { + throw new UnsupportedTypeException($phpTypeOrClass); + } + + if ('mixed' === $phpTypeOrClass || 'null' === $phpTypeOrClass) { + return $phpTypeOrClass; + } + + if ($reflection->isBuiltin()) { + return $nullablePrefix.$phpTypeOrClass; + } + + $className = $phpTypeOrClass; + + if ($declaringClass && 'self' === strtolower($className)) { + $className = $declaringClass->name; + } elseif ($declaringClass && 'parent' === strtolower($className) && $parent = $declaringClass->getParentClass()) { + $className = $parent->name; + } + + return $nullablePrefix.$className; + } + + public function extractTemplateFromClass(\ReflectionClass $class): array + { + return []; + } +} diff --git a/src/Symfony/Component/Marshaller/Type/TypeExtractorInterface.php b/src/Symfony/Component/Marshaller/Type/TypeExtractorInterface.php new file mode 100644 index 000000000000..16c29db808ad --- /dev/null +++ b/src/Symfony/Component/Marshaller/Type/TypeExtractorInterface.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Type; + +/** + * @author Mathias Arlaud + * + * @experimental in 6.4 + */ +interface TypeExtractorInterface +{ + public function extractFromProperty(\ReflectionProperty $property): string; + + public function extractFromFunctionReturn(\ReflectionFunctionAbstract $function): string; + + public function extractFromFunctionParameter(\ReflectionParameter $parameter): string; + + /** + * @param \ReflectionClass $class + * + * @return list + */ + public function extractTemplateFromClass(\ReflectionClass $class): array; +} diff --git a/src/Symfony/Component/Marshaller/Type/TypeGenericsHelper.php b/src/Symfony/Component/Marshaller/Type/TypeGenericsHelper.php new file mode 100644 index 000000000000..906b47515250 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Type/TypeGenericsHelper.php @@ -0,0 +1,177 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Type; + +use Symfony\Component\Marshaller\Exception\InvalidTypeException; + +/** + * @author Mathias Arlaud + * + * @internal + */ +final class TypeGenericsHelper +{ + /** + * @var array}> + */ + private static array $genericsCache = []; + + /** + * @param array $genericTypes + */ + public function replaceGenericTypes(string $type, array $genericTypes): string + { + $types = $this->explodeUnion($type); + + if (\count($types) > 1) { + return implode('|', array_map(fn (string $t): string => $this->replaceGenericTypes($t, $genericTypes), $types)); + } + + [$type, $genericParameterTypes] = $this->explodeGenerics($type); + + if ([] !== $genericParameterTypes) { + return sprintf( + '%s<%s>', + $this->replaceGenericTypes($type, $genericTypes), + implode(', ', array_map(fn (string $t): string => $this->replaceGenericTypes($t, $genericTypes), $genericParameterTypes)), + ); + } + + return str_replace(array_keys($genericTypes), array_values($genericTypes), $type); + } + + /** + * @return array{genericType: string, genericParameters: list} + */ + public function extractGenerics(string $type): array + { + if (isset(self::$genericsCache[$type])) { + return self::$genericsCache[$type]; + } + + $results = []; + if (!preg_match('/^(?P[^<]+)<(?P.+)>$/', $type, $results)) { + return self::$genericsCache[$type] = [ + 'genericType' => $type, + 'genericParameters' => [], + ]; + } + + $genericType = $results['type']; + $genericParameters = []; + $currentGenericParameter = ''; + $nestedLevel = 0; + + foreach (str_split(str_replace(' ', '', $results['diamond'])) as $char) { + if (',' === $char && 0 === $nestedLevel) { + $genericParameters[] = $currentGenericParameter; + $currentGenericParameter = ''; + + continue; + } + + if ('<' === $char) { + ++$nestedLevel; + } + + if ('>' === $char) { + --$nestedLevel; + } + + $currentGenericParameter .= $char; + } + + if (0 !== $nestedLevel) { + throw new InvalidTypeException($type); + } + + $genericParameters[] = $currentGenericParameter; + + return self::$genericsCache[$type] = [ + 'genericType' => $genericType, + 'genericParameters' => $genericParameters, + ]; + } + + /** + * @return list + */ + private function explodeUnion(string $type): array + { + $currentTypeString = ''; + $typeStrings = []; + $nestedLevel = 0; + + foreach (str_split(str_replace(' ', '', $type)) as $char) { + if ('<' === $char) { + ++$nestedLevel; + } + + if ('>' === $char) { + --$nestedLevel; + } + + if ('|' === $char && 0 === $nestedLevel) { + $typeStrings[] = $currentTypeString; + $currentTypeString = ''; + + continue; + } + + $currentTypeString .= $char; + } + + $typeStrings[] = $currentTypeString; + + return $typeStrings; + } + + /** + * @return array{0: string, 1: list} + */ + private function explodeGenerics(string $type): array + { + $matches = []; + if (!preg_match('/^(?P[^<]+)<(?P.+)>$/', $type, $matches)) { + return [$type, []]; + } + + $genericType = $matches['type']; + $genericParameterTypes = []; + + $currentGenericParameterType = ''; + $nestedLevel = 0; + + foreach (str_split(str_replace(' ', '', $matches['diamond'])) as $char) { + if (',' === $char && 0 === $nestedLevel) { + $genericParameterTypes[] = $currentGenericParameterType; + $currentGenericParameterType = ''; + + continue; + } + + if ('<' === $char) { + ++$nestedLevel; + } + + if ('>' === $char) { + --$nestedLevel; + } + + $currentGenericParameterType .= $char; + } + + $genericParameterTypes[] = $currentGenericParameterType; + + return [$genericType, $genericParameterTypes]; + } +} diff --git a/src/Symfony/Component/Marshaller/Type/TypeNameResolver.php b/src/Symfony/Component/Marshaller/Type/TypeNameResolver.php new file mode 100644 index 000000000000..6eabae2adf97 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Type/TypeNameResolver.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Type; + +use phpDocumentor\Reflection\Types\ContextFactory; +use Symfony\Component\Marshaller\Exception\LogicException; + +/** + * @author Mathias Arlaud + * + * @internal + */ +final class TypeNameResolver +{ + /** + * @param class-string $className + * @param array $uses + * @param list $templateNames + */ + public function __construct( + private readonly string $className, + private readonly string $namespace, + private readonly array $uses, + private readonly array $templateNames, + ) { + } + + /** + * @param \ReflectionClass $class + * @param list $templateNames + */ + public static function createForClass(\ReflectionClass $class, array $templateNames): self + { + $context = (new ContextFactory())->createFromReflector($class); + $namespace = $context->getNamespace(); + + /** @var array $uses */ + $uses = $context->getNamespaceAliases(); + + /** @var class-string $className */ + $className = str_replace($namespace.'\\', '', $class->getName()); + + return new self($className, $namespace, $uses, $templateNames); + } + + /** + * @return class-string + */ + public function resolveRootClass(): string + { + return $this->resolve($this->className); + } + + /** + * @return class-string + */ + public function resolveParentClass(): string + { + $rootClassName = $this->resolveRootClass(); + + if (false === $parentClass = (new \ReflectionClass($rootClassName))->getParentClass()) { + throw new LogicException(sprintf('"%s" class do not extend any class.', $rootClassName)); + } + + /** @var class-string $parentClassName */ + $parentClassName = str_replace($this->namespace.'\\', '', $parentClass->getName()); + + return $this->resolve($parentClassName); + } + + /** + * @template T of string|class-string + * + * @param T $name + * + * @return T + */ + public function resolve(string $name): string + { + if (\in_array($name, $this->templateNames)) { + return $name; + } + + if (str_starts_with($name, '\\')) { + /** @var T $name */ + $name = ltrim($name, '\\'); + + return $name; + } + + $nameParts = explode('\\', $name); + $usedPart = $nameParts[0]; + + if (!isset($this->uses[$usedPart])) { + /** @var T $name */ + $name = sprintf('%s\\%s', $this->namespace, $name); + + return $name; + } + + if (1 === \count($nameParts)) { + /** @var T $name */ + $name = $this->uses[$usedPart]; + + return $name; + } + + array_shift($nameParts); + + /** @var T $name */ + $name = sprintf('%s\\%s', $this->uses[$usedPart], implode('\\', $nameParts)); + + return $name; + } +} diff --git a/src/Symfony/Component/Marshaller/Util/CachedTrait.php b/src/Symfony/Component/Marshaller/Util/CachedTrait.php new file mode 100644 index 000000000000..dd57a8f7b2f5 --- /dev/null +++ b/src/Symfony/Component/Marshaller/Util/CachedTrait.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Marshaller\Util; + +use Psr\Cache\CacheException; +use Psr\Cache\CacheItemPoolInterface; + +/** + * @author Mathias Arlaud + * + * @internal + */ +trait CachedTrait +{ + private readonly CacheItemPoolInterface|null $cacheItemPool; + + /** + * @var array + */ + private array $localCache = []; + + /** + * @template T of mixed + * + * @param callable(): T $getValue + * + * @return T + */ + private function getCached(string $key, callable $getValue): mixed + { + if (isset($this->localCache[$key])) { + return $this->localCache[$key]; + } + + if (null === $this->cacheItemPool) { + return $this->localCache[$key] = $getValue(); + } + + try { + $item = $this->cacheItemPool->getItem($key); + } catch (CacheException) { + return $this->localCache[$key] = $getValue(); + } + + if ($item->isHit()) { + return $this->localCache[$key] = $item->get(); + } + + $item->set($value = $getValue()); + + $this->cacheItemPool->save($item); + + return $this->localCache[$key] = $value; + } +} diff --git a/src/Symfony/Component/Marshaller/composer.json b/src/Symfony/Component/Marshaller/composer.json new file mode 100644 index 000000000000..ffb341af903a --- /dev/null +++ b/src/Symfony/Component/Marshaller/composer.json @@ -0,0 +1,35 @@ +{ + "name": "symfony/marshaller", + "type": "library", + "description": "Provides powerful methods to marshal/unmarshal data structures into/from structured stream like JSON.", + "keywords": ["serialization"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Mathias Arlaud", + "email": "mathias.arlaud@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1", + "phpstan/phpdoc-parser": "^1.13", + "phpdocumentor/reflection-docblock": "^5.3", + "psr/cache": "^3.0", + "psr/log": "^3.0", + "symfony/var-exporter": "^6.2" + }, + "suggest": { + "psr/cache-implementation": "To cache results" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Marshaller\\": "" }, + "files": [ "Resources/functions.php" ], + "exclude-from-classmap": [ "Tests/" ] + }, + "minimum-stability": "dev" +} diff --git a/src/Symfony/Component/Marshaller/phpunit.xml.dist b/src/Symfony/Component/Marshaller/phpunit.xml.dist new file mode 100644 index 000000000000..93b667155d64 --- /dev/null +++ b/src/Symfony/Component/Marshaller/phpunit.xml.dist @@ -0,0 +1,30 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + + ./Tests + ./vendor + + +