diff --git a/composer.json b/composer.json index 397fdf0038ad9df37bfaa54c91672fd2f043b8be..436dae9abb6b681fa57679e86562da48cff507a5 100644 --- a/composer.json +++ b/composer.json @@ -4,6 +4,7 @@ "type": "drupal-core", "license": "GPL-2.0+", "require": { + "sdboyer/gliph": "0.1.*", "symfony/class-loader": "2.3.*", "symfony/dependency-injection": "2.3.*", "symfony/event-dispatcher": "2.3.*", diff --git a/composer.lock b/composer.lock index decd0300483d90dd7a4b0ed5ad24c66f2f979816..5a70a6e5aab63759821208d3ef2bf2af2549e7b8 100644 --- a/composer.lock +++ b/composer.lock @@ -3,7 +3,7 @@ "This file locks the dependencies of your project to a known state", "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file" ], - "hash": "204b3755db988998bcc618e71b2f235c", + "hash": "4c727150110aeae10efe10a92ff256f5", "packages": [ { "name": "doctrine/annotations", @@ -1111,6 +1111,50 @@ ], "time": "2012-12-21 11:40:51" }, + { + "name": "sdboyer/gliph", + "version": "0.1.4", + "source": { + "type": "git", + "url": "https://github.com/sdboyer/gliph.git", + "reference": "aad932ef7d808105341cc9a36538e9fe2cb5ee82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sdboyer/gliph/zipball/aad932ef7d808105341cc9a36538e9fe2cb5ee82", + "reference": "aad932ef7d808105341cc9a36538e9fe2cb5ee82", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "type": "library", + "autoload": { + "psr-0": { + "Gliph": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sam Boyer", + "email": "tech@samboyer.org" + } + ], + "description": "A graph library for PHP.", + "homepage": "http://github.com/sdboyer/gliph", + "keywords": [ + "gliph", + "graph", + "library", + "php", + "spl" + ], + "time": "2013-09-27 01:15:21" + }, { "name": "symfony-cmf/routing", "version": "1.1.0-beta1", diff --git a/core/vendor/autoload.php b/core/vendor/autoload.php index a391d4ec49dd4cde0d3d7410e854d540ed6825ea..aac8b6206a86859fcee9a05d9d1a67bca962d94a 100644 --- a/core/vendor/autoload.php +++ b/core/vendor/autoload.php @@ -4,4 +4,4 @@ require_once __DIR__ . '/composer' . '/autoload_real.php'; -return ComposerAutoloaderInit0b93b0210b8b39c2a0b13410cd082de9::getLoader(); +return ComposerAutoloaderInit7ffa68419492d19fe654de54c86ae5d2::getLoader(); diff --git a/core/vendor/composer/autoload_namespaces.php b/core/vendor/composer/autoload_namespaces.php index 926818b9888c61a839fc08ef210e0cd495259d35..f6dbb7cc1c34109b40d28fe9e88a9cd3ab57d045 100644 --- a/core/vendor/composer/autoload_namespaces.php +++ b/core/vendor/composer/autoload_namespaces.php @@ -28,6 +28,7 @@ 'Guzzle\\Parser' => array($vendorDir . '/guzzle/parser'), 'Guzzle\\Http' => array($vendorDir . '/guzzle/http'), 'Guzzle\\Common' => array($vendorDir . '/guzzle/common'), + 'Gliph' => array($vendorDir . '/sdboyer/gliph/src'), 'EasyRdf_' => array($vendorDir . '/easyrdf/easyrdf/lib'), 'Drupal\\Driver' => array($baseDir . '/drivers/lib'), 'Drupal\\Core' => array($baseDir . '/core/lib'), diff --git a/core/vendor/composer/autoload_real.php b/core/vendor/composer/autoload_real.php index 6bb9081ce43c0e63a0b07d2841ce722d78ac683d..b7dd370c8a2534d2541169efa2aaec38b2509298 100644 --- a/core/vendor/composer/autoload_real.php +++ b/core/vendor/composer/autoload_real.php @@ -2,7 +2,7 @@ // autoload_real.php @generated by Composer -class ComposerAutoloaderInit0b93b0210b8b39c2a0b13410cd082de9 +class ComposerAutoloaderInit7ffa68419492d19fe654de54c86ae5d2 { private static $loader; @@ -19,9 +19,9 @@ public static function getLoader() return self::$loader; } - spl_autoload_register(array('ComposerAutoloaderInit0b93b0210b8b39c2a0b13410cd082de9', 'loadClassLoader'), true, true); + spl_autoload_register(array('ComposerAutoloaderInit7ffa68419492d19fe654de54c86ae5d2', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(); - spl_autoload_unregister(array('ComposerAutoloaderInit0b93b0210b8b39c2a0b13410cd082de9', 'loadClassLoader')); + spl_autoload_unregister(array('ComposerAutoloaderInit7ffa68419492d19fe654de54c86ae5d2', 'loadClassLoader')); $vendorDir = dirname(__DIR__); $baseDir = dirname(dirname($vendorDir)); diff --git a/core/vendor/composer/installed.json b/core/vendor/composer/installed.json index f5d9d725eb74c8f32fafbaabab081f460dddd280..8347a6421cf5092499883e4f65b4511c8000d689 100644 --- a/core/vendor/composer/installed.json +++ b/core/vendor/composer/installed.json @@ -2064,5 +2064,51 @@ ], "description": "Symfony Process Component", "homepage": "http://symfony.com" + }, + { + "name": "sdboyer/gliph", + "version": "0.1.4", + "version_normalized": "0.1.4.0", + "source": { + "type": "git", + "url": "https://github.com/sdboyer/gliph.git", + "reference": "aad932ef7d808105341cc9a36538e9fe2cb5ee82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sdboyer/gliph/zipball/aad932ef7d808105341cc9a36538e9fe2cb5ee82", + "reference": "aad932ef7d808105341cc9a36538e9fe2cb5ee82", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "time": "2013-09-27 01:15:21", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "Gliph": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sam Boyer", + "email": "tech@samboyer.org" + } + ], + "description": "A graph library for PHP.", + "homepage": "http://github.com/sdboyer/gliph", + "keywords": [ + "gliph", + "graph", + "library", + "php", + "spl" + ] } ] diff --git a/core/vendor/sdboyer/gliph/README.md b/core/vendor/sdboyer/gliph/README.md new file mode 100644 index 0000000000000000000000000000000000000000..4415ef0c4874c328e631e2132e5cb25cfa33ac82 --- /dev/null +++ b/core/vendor/sdboyer/gliph/README.md @@ -0,0 +1,38 @@ +# Gliph + +[](https://travis-ci.org/sdboyer/gliph) +[](https://packagist.org/packages/sdboyer/gliph) + +Gliph is a **g**raph **li**brary for **PH**P. It provides graph building blocks and datastructures for use by other PHP applications. It is (currently) designed for use with in-memory graphs, not for interaction with a graph database like [Neo4J](http://neo4j.org/). + +Gliph is designed with performance in mind, but primarily to provide a sane interface. Graphs are hard enough without an arcane API making it worse. + +## Core Concepts + +Gliph has several components that work together: graph classes, algorithms, and visitors. Generally speaking, Gliph is patterned after the [C++ Boost Graph Library](http://www.boost.org/libs/graph/doc); reading their documentation can yield a lot of insight into how Gliph is intended to work. + +Note that Gliph is currently written for compatibility with PHP 5.3, but it is intended to port the library to PHP 5.5. The availability of traits, non-scalar/object keys returnable from iterators, and generators will considerably change both the internal and public-facing implementations. + +### Graphs + +There are a number of different strategies for representing graphs; these strategies are more or less efficient depending on certain properties the graph, and what needs to be done to the graph. The approach taken in Gliph is to offer a roughly consistent 'Graph' interface that is common to all these different strategies. The strategies will have varying levels of efficiency at meeting this common interface, so it is the responsibility of the user to select a graph implementation that is appropriate for their use case. This approach draws heavily from the [taxonomy of graphs](http://www.boost.org/doc/libs/1_54_0/libs/graph/doc/graph_concepts.html) established by the BGL. + +Gliph currently implements only an adjacency list graph strategy, in both directed and undirected flavors. Adjacency lists offer efficient access to out-edges, but inefficient access to in-edges (in a directed graph - in an undirected graph, in-edges and out-edges are the same). Adjacency lists and are generally more space-efficient for sparse graphs. + +## TODOs + +Lots. But, to start with: + +- Port to, or provide a parallel implementation in, PHP 5.5. Generators and non-scalar keys from iterators make this all SO much better. In doing that, also shift as much over to traits as possible. +- Implement a generic breadth-first algorithm and its corresponding visitors. +- Implement a generic iterative deepening depth-first algorithm, and its corresponding visitors. +- Implement other popular connected components algorithms, as well as some shortest path algorithms (starting with Dijkstra) +- Write up some examples showing how to actually use the library. + +## Acknowledgements + +This library draws heavy inspiration from the [C++ Boost Graph Library](http://www.boost.org/libs/graph/doc). + +## License + +MIT diff --git a/core/vendor/sdboyer/gliph/composer.json b/core/vendor/sdboyer/gliph/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..1434d3741a14bceb4e5f84cc35616349fa2ca0f6 --- /dev/null +++ b/core/vendor/sdboyer/gliph/composer.json @@ -0,0 +1,20 @@ +{ + "name": "sdboyer/gliph", + "description": "A graph library for PHP.", + "license": "MIT", + "keywords": ["gliph", "library", "php", "spl", "graph"], + "homepage": "http://github.com/sdboyer/gliph", + "type": "library", + "authors": [ + { + "name": "Sam Boyer", + "email": "tech@samboyer.org" + } + ], + "require": { + "php": ">=5.3" + }, + "autoload": { + "psr-0": { "Gliph": "src/" } + } +} diff --git a/core/vendor/sdboyer/gliph/composer.lock b/core/vendor/sdboyer/gliph/composer.lock new file mode 100644 index 0000000000000000000000000000000000000000..bc1108a22ef699a99754e035c31a62886a3c2ad5 --- /dev/null +++ b/core/vendor/sdboyer/gliph/composer.lock @@ -0,0 +1,439 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file" + ], + "hash": "c2c349f17b3e09198ed1a8335e431197", + "packages": [ + + ], + "packages-dev": [ + { + "name": "phpunit/php-code-coverage", + "version": "1.2.12", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "1.2.12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/1.2.12", + "reference": "1.2.12", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-file-iterator": ">=1.3.0@stable", + "phpunit/php-text-template": ">=1.1.1@stable", + "phpunit/php-token-stream": ">=1.1.3@stable" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*@dev" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.0.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "PHP/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2013-07-06 06:26:16" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.3.3", + "source": { + "type": "git", + "url": "git://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "1.3.3" + }, + "dist": { + "type": "zip", + "url": "https://github.com/sebastianbergmann/php-file-iterator/zipball/1.3.3", + "reference": "1.3.3", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "File/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "http://www.phpunit.de/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2012-10-11 04:44:38" + }, + { + "name": "phpunit/php-text-template", + "version": "1.1.4", + "source": { + "type": "git", + "url": "git://github.com/sebastianbergmann/php-text-template.git", + "reference": "1.1.4" + }, + "dist": { + "type": "zip", + "url": "https://github.com/sebastianbergmann/php-text-template/zipball/1.1.4", + "reference": "1.1.4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "Text/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2012-10-31 11:15:28" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "1.0.5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/1.0.5", + "reference": "1.0.5", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "PHP/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2013-08-02 07:42:54" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "1.2.0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1.2.0", + "reference": "1.2.0", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "classmap": [ + "PHP/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2013-08-04 05:57:48" + }, + { + "name": "phpunit/phpunit", + "version": "3.7.24", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "3.7.24" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3.7.24", + "reference": "3.7.24", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=5.3.3", + "phpunit/php-code-coverage": "~1.2.1", + "phpunit/php-file-iterator": ">=1.3.1", + "phpunit/php-text-template": ">=1.1.1", + "phpunit/php-timer": ">=1.0.4", + "phpunit/phpunit-mock-objects": "~1.2.0", + "symfony/yaml": "~2.0" + }, + "require-dev": { + "pear-pear/pear": "1.9.4" + }, + "suggest": { + "ext-json": "*", + "ext-simplexml": "*", + "ext-tokenizer": "*", + "phpunit/php-invoker": ">=1.1.0,<1.2.0" + }, + "bin": [ + "composer/bin/phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.7.x-dev" + } + }, + "autoload": { + "classmap": [ + "PHPUnit/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "", + "../../symfony/yaml/" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "http://www.phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2013-08-09 06:58:24" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "1.2.3", + "source": { + "type": "git", + "url": "git://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "1.2.3" + }, + "dist": { + "type": "zip", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects/archive/1.2.3.zip", + "reference": "1.2.3", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-text-template": ">=1.1.1@stable" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "autoload": { + "classmap": [ + "PHPUnit/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2013-01-13 10:24:48" + }, + { + "name": "symfony/yaml", + "version": "v2.3.3", + "target-dir": "Symfony/Component/Yaml", + "source": { + "type": "git", + "url": "https://github.com/symfony/Yaml.git", + "reference": "v2.3.3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Yaml/zipball/v2.3.3", + "reference": "v2.3.3", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Yaml\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "http://symfony.com", + "time": "2013-07-21 12:12:18" + } + ], + "aliases": [ + + ], + "minimum-stability": "stable", + "stability-flags": [ + + ], + "platform": { + "php": ">=5.3" + }, + "platform-dev": [ + + ] +} diff --git a/core/vendor/sdboyer/gliph/phpunit.xml.dist b/core/vendor/sdboyer/gliph/phpunit.xml.dist new file mode 100644 index 0000000000000000000000000000000000000000..b847773cac235620b9732eb57dc4ad4aac91c7cf --- /dev/null +++ b/core/vendor/sdboyer/gliph/phpunit.xml.dist @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<phpunit bootstrap="tests/bootstrap.php" colors="true"> + <testsuites> + <testsuite name="PHPUnit"> + <directory>tests/</directory> + </testsuite> + </testsuites> + <filter> + <whitelist addUncoveredFilesFromWhitelist="true"> + <directory>src/Gliph</directory> + <exclude> + <file>src/Gliph/Visitor/DepthFirstNoOpVisitor.php</file> + </exclude> + </whitelist> + </filter> + + <logging> + <log + type="coverage-html" + target="build/coverage" + charset="UTF-8" + yui="true" + highlight="true" + lowUpperBound="35" + highLowerBound="70" + showUncoveredFiles="true" + /> + <log type="coverage-clover" target="build/logs/clover.xml"/> + </logging> +</phpunit> diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Algorithm/ConnectedComponent.php b/core/vendor/sdboyer/gliph/src/Gliph/Algorithm/ConnectedComponent.php new file mode 100644 index 0000000000000000000000000000000000000000..9ea57fe5af563ad2d65e11d6fc9658178c3e5d38 --- /dev/null +++ b/core/vendor/sdboyer/gliph/src/Gliph/Algorithm/ConnectedComponent.php @@ -0,0 +1,65 @@ +<?php + +namespace Gliph\Algorithm; + +use Gliph\Graph\DirectedGraph; +use Gliph\Visitor\TarjanSCCVisitor; + +/** + * Contains algorithms for discovering connected components. + */ +class ConnectedComponent { + + /** + * Finds connected components in the provided directed graph. + * + * @param DirectedGraph $graph + * The DirectedGraph to search for connected components. + * @param TarjanSCCVisitor $visitor + * The visitor that will collect and store the connected components. One + * will be created if not provided. + * + * @return TarjanSCCVisitor + * The finalized visitor. + */ + public static function tarjan_scc(DirectedGraph $graph, TarjanSCCVisitor $visitor = NULL) { + $visitor = $visitor ?: new TarjanSCCVisitor(); + $counter = 0; + $stack = array(); + $indices = new \SplObjectStorage(); + $lowlimits = new \SplObjectStorage(); + + $visit = function($vertex) use (&$visit, &$counter, $graph, &$stack, $indices, $lowlimits, $visitor) { + $indices->attach($vertex, $counter); + $lowlimits->attach($vertex, $counter); + $stack[] = $vertex; + $counter++; + + $graph->eachAdjacent($vertex, function ($to) use (&$visit, $vertex, $indices, $lowlimits, &$stack) { + if (!$indices->contains($to)) { + $visit($to); + $lowlimits[$vertex] = min($lowlimits[$vertex], $lowlimits[$to]); + } + else if (in_array($to, $stack)) { + $lowlimits[$vertex] = min($lowlimits[$vertex], $indices[$to]); + } + }); + + if ($lowlimits[$vertex] === $indices[$vertex]) { + $visitor->newComponent(); + do { + $other = array_pop($stack); + $visitor->addToCurrentComponent($other); + } while ($other != $vertex); + } + }; + + $graph->eachVertex(function($vertex) use (&$visit, $indices) { + if (!$indices->contains($vertex)) { + $visit($vertex); + } + }); + + return $visitor; + } +} \ No newline at end of file diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Exception/InvalidVertexTypeException.php b/core/vendor/sdboyer/gliph/src/Gliph/Exception/InvalidVertexTypeException.php new file mode 100644 index 0000000000000000000000000000000000000000..4b33c815c4859c604b471e6eb6e41af3e6daf68d --- /dev/null +++ b/core/vendor/sdboyer/gliph/src/Gliph/Exception/InvalidVertexTypeException.php @@ -0,0 +1,13 @@ +<?php + +/** + * @file + * Contains \Gliph\Exception\InvalidVertexTypeException. + */ + +namespace Gliph\Exception; + +/** + * Error thrown when attempting to add a vertex of an invalid type. + */ +class InvalidVertexTypeException extends \Exception {} \ No newline at end of file diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Exception/NonexistentVertexException.php b/core/vendor/sdboyer/gliph/src/Gliph/Exception/NonexistentVertexException.php new file mode 100644 index 0000000000000000000000000000000000000000..22d08602f0b8e7c898acc62465c5caba644c52bd --- /dev/null +++ b/core/vendor/sdboyer/gliph/src/Gliph/Exception/NonexistentVertexException.php @@ -0,0 +1,15 @@ +<?php + +/** + * @file + * Contains \Gliph\Exception\NonexistentVertexException. + */ + +namespace Gliph\Exception; + +/** + * Exception thrown when a vertex not present in a Graph is provided as a + * parameter to a method that requires the vertex to be present (e.g., removing + * the vertex, checking the edges of that vertex). + */ +class NonexistentVertexException extends \OutOfBoundsException {} \ No newline at end of file diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Exception/OutOfRangeException.php b/core/vendor/sdboyer/gliph/src/Gliph/Exception/OutOfRangeException.php new file mode 100644 index 0000000000000000000000000000000000000000..f8aa085b6d10534216c866cb213e14227d5cfb1f --- /dev/null +++ b/core/vendor/sdboyer/gliph/src/Gliph/Exception/OutOfRangeException.php @@ -0,0 +1,8 @@ +<?php + +namespace Gliph\Exception; + +/** + * OutOfRangeException for Gliph. + */ +class OutOfRangeException extends \OutOfRangeException {} \ No newline at end of file diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Exception/RuntimeException.php b/core/vendor/sdboyer/gliph/src/Gliph/Exception/RuntimeException.php new file mode 100644 index 0000000000000000000000000000000000000000..39f98b438dee934be2efc52086ab71d49670171e --- /dev/null +++ b/core/vendor/sdboyer/gliph/src/Gliph/Exception/RuntimeException.php @@ -0,0 +1,8 @@ +<?php + +namespace Gliph\Exception; + +/** + * RuntimeException for Gliph. + */ +class RuntimeException extends \RuntimeException {} diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Exception/WrongVisitorStateException.php b/core/vendor/sdboyer/gliph/src/Gliph/Exception/WrongVisitorStateException.php new file mode 100644 index 0000000000000000000000000000000000000000..f6fa2be0c3f8572c48248cd7db0f0942a67aa0cc --- /dev/null +++ b/core/vendor/sdboyer/gliph/src/Gliph/Exception/WrongVisitorStateException.php @@ -0,0 +1,13 @@ +<?php + +namespace Gliph\Exception; + +/** + * An exception thrown when a method is called on a visitor that it does not + * expect in its current state. + * + * For example, this exception should be thrown by a visitor if it has a method + * that returns data produced by a full traversal algorithm, but the algorithm + * has not yet informed the visitor that it is done running. + */ +class WrongVisitorStateException extends \LogicException {} \ No newline at end of file diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Graph/AdjacencyList.php b/core/vendor/sdboyer/gliph/src/Gliph/Graph/AdjacencyList.php new file mode 100644 index 0000000000000000000000000000000000000000..1f4258d3b997413a95fef2031805f3f65f88c88d --- /dev/null +++ b/core/vendor/sdboyer/gliph/src/Gliph/Graph/AdjacencyList.php @@ -0,0 +1,72 @@ +<?php + +namespace Gliph\Graph; + +use Gliph\Exception\InvalidVertexTypeException; +use Gliph\Exception\NonexistentVertexException; + +abstract class AdjacencyList implements Graph { + + protected $vertices; + + public function __construct() { + $this->vertices = new \SplObjectStorage(); + } + + /** + * {@inheritdoc} + */ + public function addVertex($vertex) { + if (!is_object($vertex)) { + throw new InvalidVertexTypeException('Vertices must be objects; non-object provided.'); + } + + if (!$this->hasVertex($vertex)) { + $this->vertices[$vertex] = new \SplObjectStorage(); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function eachAdjacent($vertex, $callback) { + if (!$this->hasVertex($vertex)) { + throw new NonexistentVertexException('Vertex is not in graph; cannot iterate over its adjacent vertices.'); + } + + foreach ($this->vertices[$vertex] as $adjacent_vertex) { + call_user_func($callback, $adjacent_vertex); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function eachVertex($callback) { + $this->fev(function ($v, $adjacent) use ($callback) { + call_user_func($callback, $v, $adjacent); + }); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function hasVertex($vertex) { + return $this->vertices->contains($vertex); + } + + protected function fev($callback) { + foreach ($this->vertices as $vertex) { + $outgoing = $this->vertices->getInfo(); + $callback($vertex, $outgoing); + } + + return $this; + } +} \ No newline at end of file diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Graph/DirectedAdjacencyList.php b/core/vendor/sdboyer/gliph/src/Gliph/Graph/DirectedAdjacencyList.php new file mode 100644 index 0000000000000000000000000000000000000000..a96e70198d1526922addb17f861250325c873e1a --- /dev/null +++ b/core/vendor/sdboyer/gliph/src/Gliph/Graph/DirectedAdjacencyList.php @@ -0,0 +1,101 @@ +<?php + +namespace Gliph\Graph; + +use Gliph\Algorithm\ConnectedComponent; +use Gliph\Exception\NonexistentVertexException; +use Gliph\Exception\RuntimeException; +use Gliph\Traversal\DepthFirst; +use Gliph\Visitor\DepthFirstToposortVisitor; + +class DirectedAdjacencyList extends AdjacencyList implements DirectedGraph { + + /** + * {@inheritdoc} + */ + public function addDirectedEdge($tail, $head) { + if (!$this->hasVertex($tail)) { + $this->addVertex(($tail)); + } + + if (!$this->hasVertex($head)) { + $this->addVertex($head); + } + + $this->vertices[$tail]->attach($head); + } + + /** + * {@inheritdoc} + */ + public function removeVertex($vertex) { + if (!$this->hasVertex($vertex)) { + throw new NonexistentVertexException('Vertex is not in the graph, it cannot be removed.', E_WARNING); + } + + $this->eachVertex(function($v, $outgoing) use ($vertex) { + if ($outgoing->contains($vertex)) { + $outgoing->detach($vertex); + } + }); + unset($this->vertices[$vertex]); + } + + /** + * {@inheritdoc} + */ + public function removeEdge($tail, $head) { + $this->vertices[$tail]->detach($head); + } + + /** + * {@inheritdoc} + */ + public function eachEdge($callback) { + $edges = array(); + $this->fev(function ($from, $outgoing) use (&$edges) { + foreach ($outgoing as $to) { + $edges[] = array($from, $to); + } + }); + + foreach ($edges as $edge) { + call_user_func($callback, $edge); + } + } + + /** + * {@inheritdoc} + */ + public function transpose() { + $graph = new self(); + $this->eachEdge(function($edge) use (&$graph) { + $graph->addDirectedEdge($edge[1], $edge[0]); + }); + + return $graph; + } + + /** + * {@inheritdoc} + */ + public function isAcyclic() { + // The DepthFirstToposortVisitor throws an exception on cycles. + try { + DepthFirst::traverse($this, new DepthFirstToposortVisitor()); + return TRUE; + } + catch (RuntimeException $e) { + return FALSE; + } + } + + /** + * {@inheritdoc} + */ + public function getCycles() { + $scc = ConnectedComponent::tarjan_scc($this); + return $scc->getConnectedComponents(); + } +} + diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Graph/DirectedGraph.php b/core/vendor/sdboyer/gliph/src/Gliph/Graph/DirectedGraph.php new file mode 100644 index 0000000000000000000000000000000000000000..47f1a98592552467b6e514b168bce7d1ba752498 --- /dev/null +++ b/core/vendor/sdboyer/gliph/src/Gliph/Graph/DirectedGraph.php @@ -0,0 +1,54 @@ +<?php + +namespace Gliph\Graph; + +/** + * Interface for directed graph datastructures. + */ +interface DirectedGraph extends Graph { + + /** + * Adds a directed edge to this graph. + * + * Directed edges are also often referred to as 'arcs'. + * + * @param object $tail + * An object vertex from which the edge originates. The vertex will be + * added to the graph if it is not already present. + * @param object $head + * An object vertex to which the edge points. The vertex will be added to + * the graph if it is not already present. + * + * @return DirectedGraph + * The current graph instance. + */ + public function addDirectedEdge($tail, $head); + + /** + * Returns the transpose of this graph. + * + * A transpose is identical to the current graph, except that its edges + * have had their directionality reversed. + * + * Transposed graphs are sometimes called the 'reverse' or 'converse'. + * + * @return DirectedGraph + */ + public function transpose(); + + /** + * Indicates whether or not this graph is acyclic. + * + * @return bool + */ + public function isAcyclic(); + + /** + * Returns the cycles in this graph, if any. + * + * @return array + * An array of arrays, each subarray representing a full cycle in the + * graph. If the array is empty, the graph is acyclic. + */ + public function getCycles(); +} \ No newline at end of file diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Graph/Graph.php b/core/vendor/sdboyer/gliph/src/Gliph/Graph/Graph.php new file mode 100644 index 0000000000000000000000000000000000000000..7de000d44a6cea7d02b57723f2443aa115e17022 --- /dev/null +++ b/core/vendor/sdboyer/gliph/src/Gliph/Graph/Graph.php @@ -0,0 +1,124 @@ +<?php + +namespace Gliph\Graph; + +use Gliph\Exception\InvalidVertexTypeException; +use Gliph\Exception\NonexistentVertexException; + +/** + * The most basic interface for graph datastructures. + */ +interface Graph { + + /** + * Adds a vertex to the graph. + * + * Gliph requires that its graph vertices be objects; beyond that, it does + * not care about vertex type. + * + * @param object $vertex + * An object to use as a vertex in the graph. + * + * @return Graph + * The current graph instance. + * + * @throws InvalidVertexTypeException + * Thrown if an invalid type of data is provided as a vertex. + */ + public function addVertex($vertex); + + /** + * Remove a vertex from the graph. + * + * This will also remove any edges that include the vertex. + * + * @param object $vertex + * A vertex object to remove from the graph. + * + * @return Graph + * The current graph instance. + * + * @throws NonexistentVertexException + * Thrown if the provided vertex is not present in the graph. + */ + public function removeVertex($vertex); + + /** + * Removes an edge from the graph. + * + * @param $a + * The first vertex in the edge pair to remove. In a directed graph, this + * is the tail vertex. + * @param $b + * The second vertex in the edge pair to remove. In a directed graph, this + * is the head vertex. + * + * @return Graph + * The current graph instance. + */ + public function removeEdge($a, $b); + + /** + * Calls the callback with each vertex adjacent to the provided vertex. + * + * The meaning of "adjacency" depends on the type of graph. In a directed + * graph, it refers to all the out-edges of the provided vertex. In an + * undirected graph, in-edges and out-edges are the same, so this method + * will iterate over both. + * + * @param object $vertex + * The vertex whose out-edges should be visited. + * @param callback $callback + * The callback to fire. For each vertex found along an out-edge, this + * callback will be called with that vertex as the sole parameter. + * + * @return Graph + * The current graph instance. + * + * @throws NonexistentVertexException + * Thrown if the vertex provided in the first parameter is not present in + * the graph. + */ + public function eachAdjacent($vertex, $callback); + + /** + * Calls the provided callback for each vertex in the graph. + * + * @param $callback + * The callback is called once for each vertex in the graph. Two + * parameters are provided: + * - The vertex being inspected. + * - An SplObjectStorage containing a list of all the vertices adjacent + * to the vertex being inspected. + * + * @return Graph + * The current graph instance. + */ + public function eachVertex($callback); + + /** + * Calls the provided callback for each edge in the graph. + * + * @param $callback + * The callback is called once for each unique edge in the graph. A single + * parameter is provided: a 2-tuple (indexed array with two elements), + * where the first element is the first vertex (in a directed graph, the + * tail) and the second element is the second vertex (in a directed graph, + * the head). + * + * @return Graph + * The current graph instance. + */ + public function eachEdge($callback); + + /** + * Indicates whether or not the provided vertex is present in the graph. + * + * @param object $vertex + * The vertex object to check for membership in the graph. + * + * @return bool + * TRUE if the vertex is present, FALSE otherwise. + */ + public function hasVertex($vertex); +} \ No newline at end of file diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Graph/UndirectedAdjacencyList.php b/core/vendor/sdboyer/gliph/src/Gliph/Graph/UndirectedAdjacencyList.php new file mode 100644 index 0000000000000000000000000000000000000000..5535a309eee323efa3bb6be3413e2088dac8b1cf --- /dev/null +++ b/core/vendor/sdboyer/gliph/src/Gliph/Graph/UndirectedAdjacencyList.php @@ -0,0 +1,66 @@ +<?php + +namespace Gliph\Graph; + +use Gliph\Exception\NonexistentVertexException; + +class UndirectedAdjacencyList extends AdjacencyList implements UndirectedGraph { + + /** + * {@inheritdoc} + */ + public function addEdge($from, $to) { + if (!$this->hasVertex($from)) { + $this->addVertex(($from)); + } + + if (!$this->hasVertex($to)) { + $this->addVertex($to); + } + + $this->vertices[$from]->attach($to); + $this->vertices[$to]->attach($from); + } + + /** + * {@inheritdoc} + */ + public function removeVertex($vertex) { + if (!$this->hasVertex($vertex)) { + throw new NonexistentVertexException('Vertex is not in the graph, it cannot be removed.', E_WARNING); + } + + foreach ($this->vertices[$vertex] as $adjacent) { + $this->vertices[$adjacent]->detach($vertex); + } + unset($this->vertices[$vertex]); + } + + /** + * {@inheritdoc} + */ + public function removeEdge($from, $to) { + $this->vertices[$from]->detach($to); + $this->vertices[$to]->detach($from); + } + + /** + * {@inheritdoc} + */ + public function eachEdge($callback) { + $edges = array(); + $complete = new \SplObjectStorage(); + $this->fev(function ($a, $adjacent) use (&$edges, &$complete) { + foreach ($adjacent as $b) { + if (!$complete->contains($b)) { + $edges[] = array($a, $b); + } + } + $complete->attach($a); + }); + + foreach ($edges as $edge) { + call_user_func($callback, $edge); + } + } +} \ No newline at end of file diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Graph/UndirectedGraph.php b/core/vendor/sdboyer/gliph/src/Gliph/Graph/UndirectedGraph.php new file mode 100644 index 0000000000000000000000000000000000000000..c29b5d992b9a684b6ac17603a901dcbeb5bd9680 --- /dev/null +++ b/core/vendor/sdboyer/gliph/src/Gliph/Graph/UndirectedGraph.php @@ -0,0 +1,25 @@ +<?php + +namespace Gliph\Graph; + +/** + * Interface for undirected graph datastructures. + */ +interface UndirectedGraph extends Graph { + + /** + * Adds an undirected edge to this graph. + * + * @param object $a + * The first object vertex in the edge pair. The vertex will be added to + * the graph if it is not already present. + * @param object $b + * The second object vertex in the edge pair. The vertex will be added to + * the graph if it is not already present. + * + * @return UndirectedGraph + * The current graph instance. + */ + public function addEdge($a, $b); + +} \ No newline at end of file diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Traversal/DepthFirst.php b/core/vendor/sdboyer/gliph/src/Gliph/Traversal/DepthFirst.php new file mode 100644 index 0000000000000000000000000000000000000000..5a0d4dda40b7928306eaf64cb5d9b166f432cdec --- /dev/null +++ b/core/vendor/sdboyer/gliph/src/Gliph/Traversal/DepthFirst.php @@ -0,0 +1,136 @@ +<?php + +namespace Gliph\Traversal; + +use Gliph\Exception\RuntimeException; +use Gliph\Graph\DirectedGraph; +use Gliph\Visitor\DepthFirstToposortVisitor; +use Gliph\Visitor\DepthFirstVisitorInterface; + +class DepthFirst { + + /** + * Perform a depth-first traversal on the provided graph. + * + * @param DirectedGraph $graph + * The graph on which to perform the depth-first search. + * @param DepthFirstVisitorInterface $visitor + * The visitor object to use during the traversal. + * @param object|\SplDoublyLinkedList $start + * A vertex, or vertices, to use as start points for the traversal. There + * are a few sub-behaviors here: + * - If an SplDoublyLinkedList, SplQueue, or SplStack is provided, the + * traversal will deque and visit vertices contained therein. + * - If a single vertex object is provided, it will be the sole + * originating point for the traversal. + * - If no value is provided, DepthFirst::find_sources() is called to + * search the graph for source vertices. These are place into an + * SplQueue in the order in which they are discovered, and traversal + * is then run over that queue in the same manner as if calling code + * had provided a queue directly. This method *guarantees* that all + * vertices in the graph will be visited. + * + * @throws RuntimeException + * Thrown if an invalid $start parameter is provided. + */ + public static function traverse(DirectedGraph $graph, DepthFirstVisitorInterface $visitor, $start = NULL) { + if ($start === NULL) { + $queue = self::find_sources($graph, $visitor); + } + else if ($start instanceof \SplDoublyLinkedList) { + $queue = $start; + } + else if (is_object($start)) { + $queue = new \SplDoublyLinkedList(); + $queue->push($start); + } + + if ($queue->isEmpty()) { + throw new RuntimeException('No start vertex or vertices were provided, and no source vertices could be found in the provided graph.', E_WARNING); + } + + $visiting = new \SplObjectStorage(); + $visited = new \SplObjectStorage(); + + $visitor->beginTraversal(); + + $visit = function($vertex) use ($graph, $visitor, &$visit, $visiting, $visited) { + if ($visiting->contains($vertex)) { + $visitor->onBackEdge($vertex, $visit); + } + else if (!$visited->contains($vertex)) { + $visiting->attach($vertex); + + $visitor->onStartVertex($vertex, $visit); + + $graph->eachAdjacent($vertex, function($to) use ($vertex, &$visit, $visitor) { + $visitor->onExamineEdge($vertex, $to, $visit); + $visit($to); + }); + + $visitor->onFinishVertex($vertex, $visit); + + $visiting->detach($vertex); + $visited->attach($vertex); + } + }; + + while (!$queue->isEmpty()) { + $vertex = $queue->shift(); + $visit($vertex); + } + + $visitor->endTraversal(); + } + + /** + * Finds source vertices in a DirectedGraph, then enqueues them. + * + * @param DirectedGraph $graph + * @param DepthFirstVisitorInterface $visitor + * + * @return \SplQueue + */ + public static function find_sources(DirectedGraph $graph, DepthFirstVisitorInterface $visitor) { + $incomings = new \SplObjectStorage(); + $queue = new \SplQueue(); + + $graph->eachEdge(function ($edge) use (&$incomings) { + if (!isset($incomings[$edge[1]])) { + $incomings[$edge[1]] = new \SplObjectStorage(); + } + $incomings[$edge[1]]->attach($edge[0]); + }); + + // Prime the queue with vertices that have no incoming edges. + $graph->eachVertex(function($vertex) use ($queue, $incomings, $visitor) { + if (!$incomings->contains($vertex)) { + $queue->push($vertex); + // TRUE second param indicates source vertex + $visitor->onInitializeVertex($vertex, TRUE, $queue); + } + else { + $visitor->onInitializeVertex($vertex, FALSE, $queue); + } + }); + + return $queue; + } + + /** + * Performs a topological sort on the provided graph. + * + * @param DirectedGraph $graph + * @param object|\SplDoublyLinkedList $start + * The starting point(s) for the toposort. @see DepthFirst::traverse() + * + * @return array + * A valid topologically sorted list for the provided graph. + */ + public static function toposort(DirectedGraph $graph, $start = NULL) { + $visitor = new DepthFirstToposortVisitor(); + self::traverse($graph, $visitor, $start); + + return $visitor->getTsl(); + } +} \ No newline at end of file diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Visitor/DepthFirstBasicVisitor.php b/core/vendor/sdboyer/gliph/src/Gliph/Visitor/DepthFirstBasicVisitor.php new file mode 100644 index 0000000000000000000000000000000000000000..8bb08de3b83f694436146fa273237ee3639566f2 --- /dev/null +++ b/core/vendor/sdboyer/gliph/src/Gliph/Visitor/DepthFirstBasicVisitor.php @@ -0,0 +1,89 @@ +<?php + +namespace Gliph\Visitor; + +use Gliph\Exception\WrongVisitorStateException; + +/** + * Basic depth-first visitor. + * + * This visitor records reachability data for each vertex and creates a + * topologically sorted list. + */ +class DepthFirstBasicVisitor extends DepthFirstToposortVisitor { + + /** + * @var \SplObjectStorage + */ + public $active; + + /** + * @var \SplObjectStorage + */ + protected $paths; + + public function __construct() { + $this->active = new \SplObjectStorage(); + $this->paths = new \SplObjectStorage(); + } + + public function onInitializeVertex($vertex, $source, \SplQueue $queue) { + parent::onInitializeVertex($vertex, $source, $queue); + + $this->paths[$vertex] = array(); + } + + public function onStartVertex($vertex, \Closure $visit) { + parent::onStartVertex($vertex, $visit); + + $this->active->attach($vertex); + if (!isset($this->paths[$vertex])) { + $this->paths[$vertex] = array(); + } + } + + public function onExamineEdge($from, $to, \Closure $visit) { + parent::onExamineEdge($from, $to, $visit); + + foreach ($this->active as $vertex) { + // TODO this check makes this less efficient - find a better algo + if (!in_array($to, $this->paths[$vertex])) { + $path = $this->paths[$vertex]; + $path[] = $to; + $this->paths[$vertex] = $path; + } + } + } + + public function onFinishVertex($vertex, \Closure $visit) { + parent::onFinishVertex($vertex, $visit); + + $this->active->detach($vertex); + } + + /** + * Returns an array of all vertices reachable from the given vertex. + * + * @param object $vertex + * A vertex present in the graph for + * + * @return array|bool + * An array of reachable vertices, or FALSE if the vertex could not be + * found in the reachability data. + * + * @throws WrongVisitorStateException + * Thrown if reachability data is requested before the traversal algorithm + * completes. + */ + public function getReachable($vertex) { + if ($this->getState() !== self::COMPLETE) { + throw new WrongVisitorStateException('Reachability data cannot be retrieved until traversal is complete.'); + } + + if (!isset($this->paths[$vertex])) { + return FALSE; + } + + return $this->paths[$vertex]; + } +} \ No newline at end of file diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Visitor/DepthFirstNoOpVisitor.php b/core/vendor/sdboyer/gliph/src/Gliph/Visitor/DepthFirstNoOpVisitor.php new file mode 100644 index 0000000000000000000000000000000000000000..68ba4c38bbf06bf41f3a07c178807eb12eab8755 --- /dev/null +++ b/core/vendor/sdboyer/gliph/src/Gliph/Visitor/DepthFirstNoOpVisitor.php @@ -0,0 +1,16 @@ +<?php + +namespace Gliph\Visitor; + +/** + * A no-op visitor for depth first traversal algorithms. + */ +class DepthFirstNoOpVisitor implements DepthFirstVisitorInterface { + public function onInitializeVertex($vertex, $source, \SplQueue $queue) {} + public function beginTraversal() {} + public function onBackEdge($vertex, \Closure $visit) {} + public function onStartVertex($vertex, \Closure $visit) {} + public function onExamineEdge($from, $to, \Closure $visit) {} + public function onFinishVertex($vertex, \Closure $visit) {} + public function endTraversal() {} +} \ No newline at end of file diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Visitor/DepthFirstToposortVisitor.php b/core/vendor/sdboyer/gliph/src/Gliph/Visitor/DepthFirstToposortVisitor.php new file mode 100644 index 0000000000000000000000000000000000000000..152011f2e9c25718b5b69c1d1afc235c803d837c --- /dev/null +++ b/core/vendor/sdboyer/gliph/src/Gliph/Visitor/DepthFirstToposortVisitor.php @@ -0,0 +1,53 @@ +<?php + +/** + * @file + * Contains \Gliph\Visitor\DepthFirstToposortVisitor. + */ + +namespace Gliph\Visitor; + +use Gliph\Exception\RuntimeException; +use Gliph\Exception\WrongVisitorStateException; + +/** + * Visitor that produces a topologically sorted list on a depth first traversal. + */ +class DepthFirstToposortVisitor extends StatefulDepthFirstVisitor implements DepthFirstVisitorInterface { + + /** + * @var array + */ + protected $tsl = array(); + + public function onBackEdge($vertex, \Closure $visit) { + parent::onBackEdge($vertex, $visit); + throw new RuntimeException(sprintf('Cycle detected in provided graph; toposort is not possible.')); + } + + public function beginTraversal() { + parent::beginTraversal(); + $this->tsl = array(); + } + + public function onFinishVertex($vertex, \Closure $visit) { + parent::onFinishVertex($vertex, $visit); + $this->tsl[] = $vertex; + } + + /** + * Returns a valid topological sort of the visited graph as an array. + * + * @return array + * + * @throws WrongVisitorStateException + * Thrown if called before traversal is complete. + */ + public function getTsl() { + if ($this->getState() !== self::COMPLETE) { + throw new WrongVisitorStateException('Topologically sorted list cannot be retrieved until traversal is complete.'); + } + + return $this->tsl; + } +} \ No newline at end of file diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Visitor/DepthFirstVisitorInterface.php b/core/vendor/sdboyer/gliph/src/Gliph/Visitor/DepthFirstVisitorInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..ae4f388e8b91c53127798bd175a764bb86ce5177 --- /dev/null +++ b/core/vendor/sdboyer/gliph/src/Gliph/Visitor/DepthFirstVisitorInterface.php @@ -0,0 +1,13 @@ +<?php + +namespace Gliph\Visitor; + +interface DepthFirstVisitorInterface { + public function onInitializeVertex($vertex, $source, \SplQueue $queue); + public function beginTraversal(); + public function onBackEdge($vertex, \Closure $visit); + public function onStartVertex($vertex, \Closure $visit); + public function onExamineEdge($from, $to, \Closure $visit); + public function onFinishVertex($vertex, \Closure $visit); + public function endTraversal(); +} \ No newline at end of file diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Visitor/StatefulDepthFirstVisitor.php b/core/vendor/sdboyer/gliph/src/Gliph/Visitor/StatefulDepthFirstVisitor.php new file mode 100644 index 0000000000000000000000000000000000000000..7c2de15a9aef8b28df9d1c256e7041cccf28253d --- /dev/null +++ b/core/vendor/sdboyer/gliph/src/Gliph/Visitor/StatefulDepthFirstVisitor.php @@ -0,0 +1,78 @@ +<?php + +/** + * @file + * Contains \Gliph\Visitor\StatefulDepthFirstVisitor. + */ + +namespace Gliph\Visitor; + +use Gliph\Exception\WrongVisitorStateException; + +/** + * A base class for stateful depth first visitors. + * + * This visitor tracks state as indicated to it by the depth first traversal + * algorithm, throwing exceptions if certain methods are accessed from an + * incorrect state. + */ +abstract class StatefulDepthFirstVisitor implements DepthFirstVisitorInterface, StatefulVisitorInterface { + + /** + * Represents the current state of the visitor. + * + * @var int + */ + protected $state = self::NOT_STARTED; + + public function onInitializeVertex($vertex, $source, \SplQueue $queue) { + if ($this->state != self::NOT_STARTED) { + throw new WrongVisitorStateException('Vertex initialization should only happen before traversal has begun.'); + } + } + + public function beginTraversal() { + if ($this->state != self::NOT_STARTED) { + throw new WrongVisitorStateException('Traversal has already begun; cannot begin twice.'); + } + $this->state = self::IN_PROGRESS; + } + + public function onBackEdge($vertex, \Closure $visit) { + if ($this->state != self::IN_PROGRESS) { + throw new WrongVisitorStateException('onBackEdge should only be called while traversal is in progress.'); + } + } + + public function onStartVertex($vertex, \Closure $visit) { + if ($this->state != self::IN_PROGRESS) { + throw new WrongVisitorStateException('onStartVertex should only be called while traversal is in progress.'); + } + } + + public function onExamineEdge($from, $to, \Closure $visit) { + if ($this->state != self::IN_PROGRESS) { + throw new WrongVisitorStateException('onExamineEdge should only be called while traversal is in progress.'); + } + } + + public function onFinishVertex($vertex, \Closure $visit) { + if ($this->state != self::IN_PROGRESS) { + throw new WrongVisitorStateException('onFinishVertex should only be called while traversal is in progress.'); + } + } + + public function endTraversal() { + if ($this->state != self::IN_PROGRESS) { + throw new WrongVisitorStateException('Cannot end traversal; no traversal is currently in progress.'); + } + $this->state = self::COMPLETE; + } + + /** + * {@inheritdoc} + */ + public function getState() { + return $this->state; + } +} \ No newline at end of file diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Visitor/StatefulVisitorInterface.php b/core/vendor/sdboyer/gliph/src/Gliph/Visitor/StatefulVisitorInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..b73c5c0fff5f2269c8a410bda082b2b42048a82c --- /dev/null +++ b/core/vendor/sdboyer/gliph/src/Gliph/Visitor/StatefulVisitorInterface.php @@ -0,0 +1,23 @@ +<?php + +namespace Gliph\Visitor; + +/** + * Interface for stateful algorithm visitors. + */ +interface StatefulVisitorInterface { + const NOT_STARTED = 0; + const IN_PROGRESS = 1; + const COMPLETE = 2; + + /** + * Returns an integer indicating the current state of the visitor. + * + * @return int + * State should be one of the three StatefulVisitorInterface constants: + * - 0: indicates the algorithm has not yet started. + * - 1: indicates the algorithm is in progress. + * - 2: indicates the algorithm is complete. + */ + public function getState(); +} \ No newline at end of file diff --git a/core/vendor/sdboyer/gliph/src/Gliph/Visitor/TarjanSCCVisitor.php b/core/vendor/sdboyer/gliph/src/Gliph/Visitor/TarjanSCCVisitor.php new file mode 100644 index 0000000000000000000000000000000000000000..73255b24ebbac3718e692886fbfe6327e1fb6b2c --- /dev/null +++ b/core/vendor/sdboyer/gliph/src/Gliph/Visitor/TarjanSCCVisitor.php @@ -0,0 +1,36 @@ +<?php + +namespace Gliph\Visitor; + +/** + * Visitor that collects strongly connected component data from Tarjan's + * strongly connected components algorithm. + */ +class TarjanSCCVisitor { + + protected $components = array(); + + protected $currentComponent; + + public function newComponent() { + // Ensure the reference is broken + unset($this->currentComponent); + $this->currentComponent = array(); + $this->components[] = &$this->currentComponent; + } + + public function addToCurrentComponent($vertex) { + $this->currentComponent[] = $vertex; + } + + public function getComponents() { + return $this->components; + } + + public function getConnectedComponents() { + // TODO make this less stupid + return array_values(array_filter($this->components, function($component) { + return count($component) > 1; + })); + } +} \ No newline at end of file diff --git a/core/vendor/sdboyer/gliph/tests/Gliph/Algorithm/ConnectedComponentTest.php b/core/vendor/sdboyer/gliph/tests/Gliph/Algorithm/ConnectedComponentTest.php new file mode 100644 index 0000000000000000000000000000000000000000..39ce71be0bf83f00ead69e90f7d1f945e12d97be --- /dev/null +++ b/core/vendor/sdboyer/gliph/tests/Gliph/Algorithm/ConnectedComponentTest.php @@ -0,0 +1,55 @@ +<?php + +/** + * @file + * Contains \Gliph\Algorithm\ConnectedComponentTest. + */ + +namespace Gliph\Algorithm; + +use Gliph\Graph\DirectedAdjacencyList; +use Gliph\TestVertex; + +class ConnectedComponentTest extends \PHPUnit_Framework_TestCase { + + /** + * @covers \Gliph\Algorithm\ConnectedComponent::tarjan_scc() + */ + public function testTarjanScc() { + $a = new TestVertex('a'); + $b = new TestVertex('b'); + $c = new TestVertex('c'); + $d = new TestVertex('d'); + $e = new TestVertex('e'); + $f = new TestVertex('f'); + $g = new TestVertex('g'); + $h = new TestVertex('h'); + + $graph = new DirectedAdjacencyList(); + + $graph->addDirectedEdge($a, $d); + $graph->addDirectedEdge($a, $b); + $graph->addDirectedEdge($b, $c); + $graph->addDirectedEdge($c, $d); + $graph->addDirectedEdge($d, $a); + $graph->addDirectedEdge($e, $d); + $graph->addDirectedEdge($f, $g); + $graph->addDirectedEdge($g, $h); + $graph->addDirectedEdge($h, $f); + + $visitor = ConnectedComponent::tarjan_scc($graph); + + $expected_full = array( + array($c, $b, $d, $a), + array($e), + array($h, $g, $f), + ); + $this->assertEquals($expected_full, $visitor->getComponents()); + + $expected_full = array( + array($c, $b, $d, $a), + array($h, $g, $f), + ); + $this->assertEquals($expected_full, $visitor->getConnectedComponents()); + } +} diff --git a/core/vendor/sdboyer/gliph/tests/Gliph/Graph/AdjacencyListBase.php b/core/vendor/sdboyer/gliph/tests/Gliph/Graph/AdjacencyListBase.php new file mode 100644 index 0000000000000000000000000000000000000000..36106d10a6359957a9202de69024f07c9d710406 --- /dev/null +++ b/core/vendor/sdboyer/gliph/tests/Gliph/Graph/AdjacencyListBase.php @@ -0,0 +1,49 @@ +<?php + +namespace Gliph\Graph; + +use Gliph\TestVertex; + +class AdjacencyListBase extends \PHPUnit_Framework_TestCase { + + /** + * Creates a set of vertices and an empty graph for testing. + */ + public function setUp() { + $this->v = array( + 'a' => new TestVertex('a'), + 'b' => new TestVertex('b'), + 'c' => new TestVertex('c'), + 'd' => new TestVertex('d'), + 'e' => new TestVertex('e'), + 'f' => new TestVertex('f'), + 'g' => new TestVertex('g'), + ); + } + + public function doCheckVerticesEqual($vertices, AdjacencyList $graph = null) { + $found = array(); + $graph = is_null($graph) ? $this->g : $graph; + + $graph->eachVertex( + function ($vertex) use (&$found) { + $found[] = $vertex; + } + ); + + $this->assertEquals($vertices, $found); + } + + public function doCheckVertexCount($count, AdjacencyList $graph = null) { + $found = array(); + $graph = is_null($graph) ? $this->g : $graph; + + $graph->eachVertex( + function ($vertex) use (&$found) { + $found[] = $vertex; + } + ); + + $this->assertCount($count, $found); + } +} \ No newline at end of file diff --git a/core/vendor/sdboyer/gliph/tests/Gliph/Graph/AdjacencyListTest.php b/core/vendor/sdboyer/gliph/tests/Gliph/Graph/AdjacencyListTest.php new file mode 100644 index 0000000000000000000000000000000000000000..baad7bbdcaca296e5553f288db393f609e7d725e --- /dev/null +++ b/core/vendor/sdboyer/gliph/tests/Gliph/Graph/AdjacencyListTest.php @@ -0,0 +1,86 @@ +<?php + +namespace Gliph\Graph; + +class AdjacencyListTest extends AdjacencyListBase { + + protected $v = array(); + + /** + * @var AdjacencyList + */ + protected $g; + + public function setUp() { + parent::setUp(); + $this->g = $this->getMockForAbstractClass('\\Gliph\\Graph\\AdjacencyList'); + } + + /** + * Tests that an exception is thrown if a string vertex is provided. + * + * @expectedException \Gliph\Exception\InvalidVertexTypeException + */ + public function testAddStringVertex() { + $this->g->addVertex('a'); + } + + /** + * Tests that an exception is thrown if an integer vertex is provided. + * + * @expectedException \Gliph\Exception\InvalidVertexTypeException + */ + public function testAddIntegerVertex() { + $this->g->addVertex(1); + } + + /** + * Tests that an exception is thrown if a float vertex is provided. + * + * @expectedException \Gliph\Exception\InvalidVertexTypeException + */ + public function testAddFloatVertex() { + $this->g->addVertex((float) 1); + } + + /** + * Tests that an exception is thrown if an array vertex is provided. + * + * @expectedException \Gliph\Exception\InvalidVertexTypeException + */ + public function testAddArrayVertex() { + $this->g->addVertex(array()); + } + + /** + * Tests that an exception is thrown if a resource vertex is provided. + * + * @expectedException \Gliph\Exception\InvalidVertexTypeException + */ + public function testAddResourceVertex() { + $this->g->addVertex(fopen(__FILE__, 'r')); + } + + public function testAddVertex() { + $this->g->addVertex($this->v['a']); + + $this->assertTrue($this->g->hasVertex($this->v['a'])); + $this->doCheckVertexCount(1, $this->g); + } + + public function testAddVertexTwice() { + // Adding a vertex twice should be a no-op. + $this->g->addVertex($this->v['a']); + $this->g->addVertex($this->v['a']); + + $this->assertTrue($this->g->hasVertex($this->v['a'])); + $this->doCheckVertexCount(1, $this->g); + } + + /** + * @expectedException Gliph\Exception\NonexistentVertexException + */ + public function testEachAdjacentMissingVertex() { + $this->g->eachAdjacent($this->v['a'], function() {}); + } +} diff --git a/core/vendor/sdboyer/gliph/tests/Gliph/Graph/DirectedAdjacencyListTest.php b/core/vendor/sdboyer/gliph/tests/Gliph/Graph/DirectedAdjacencyListTest.php new file mode 100644 index 0000000000000000000000000000000000000000..42a58d54909d605672cf5cbd4c7817240da86e22 --- /dev/null +++ b/core/vendor/sdboyer/gliph/tests/Gliph/Graph/DirectedAdjacencyListTest.php @@ -0,0 +1,117 @@ +<?php + +namespace Gliph\Graph; + +class DirectedAdjacencyListTest extends AdjacencyListBase { + + /** + * @var DirectedAdjacencyList + */ + protected $g; + + public function setUp() { + parent::setUp(); + $this->g = new DirectedAdjacencyList(); + } + + + public function testAddDirectedEdge() { + $this->g->addDirectedEdge($this->v['a'], $this->v['b']); + + $this->doCheckVerticesEqual(array($this->v['a'], $this->v['b']), $this->g); + } + + public function testRemoveVertex() { + $this->g->addDirectedEdge($this->v['a'], $this->v['b']); + $this->doCheckVertexCount(2); + + $this->g->removeVertex($this->v['b']); + $this->doCheckVertexCount(1); + + // Ensure that b was correctly removed from a's outgoing edges + $found = array(); + $this->g->eachAdjacent($this->v['a'], function($to) use (&$found) { + $found[] = $to; + }); + + $this->assertEquals(array(), $found); + } + + + public function testRemoveEdge() { + $this->g->addDirectedEdge($this->v['a'], $this->v['b']); + $this->doCheckVerticesEqual(array($this->v['a'], $this->v['b']), $this->g); + + $this->g->removeEdge($this->v['a'], $this->v['b']); + $this->doCheckVertexCount(2); + + $this->assertTrue($this->g->hasVertex($this->v['a'])); + $this->assertTrue($this->g->hasVertex($this->v['b'])); + } + + public function testEachAdjacent() { + $this->g->addDirectedEdge($this->v['a'], $this->v['b']); + $this->g->addDirectedEdge($this->v['a'], $this->v['c']); + + $found = array(); + $this->g->eachAdjacent($this->v['a'], function($to) use (&$found) { + $found[] = $to; + }); + + $this->assertEquals(array($this->v['b'], $this->v['c']), $found); + } + + public function testEachEdge() { + $this->g->addDirectedEdge($this->v['a'], $this->v['b']); + $this->g->addDirectedEdge($this->v['a'], $this->v['c']); + + $found = array(); + $this->g->eachEdge(function($edge) use (&$found) { + $found[] = $edge; + }); + + $this->assertCount(2, $found); + $this->assertEquals(array($this->v['a'], $this->v['b']), $found[0]); + $this->assertEquals(array($this->v['a'], $this->v['c']), $found[1]); + } + + public function testTranspose() { + $this->g->addDirectedEdge($this->v['a'], $this->v['b']); + $this->g->addDirectedEdge($this->v['a'], $this->v['c']); + + $transpose = $this->g->transpose(); + + $this->doCheckVertexCount(3, $transpose); + $this->doCheckVerticesEqual(array($this->v['b'], $this->v['a'], $this->v['c']), $transpose); + } + + /** + * @expectedException Gliph\Exception\NonexistentVertexException + */ + public function testRemoveNonexistentVertex() { + $this->g->removeVertex($this->v['a']); + } + + /** + * @covers \Gliph\Graph\DirectedAdjacencyList::isAcyclic() + */ + public function testIsAcyclic() { + $this->g->addDirectedEdge($this->v['a'], $this->v['b']); + $this->g->addDirectedEdge($this->v['b'], $this->v['c']); + $this->assertTrue($this->g->isAcyclic()); + + $this->g->addDirectedEdge($this->v['c'], $this->v['a']); + $this->assertFalse($this->g->isAcyclic()); + } + + /** + * @covers \Gliph\Graph\DirectedAdjacencyList::getCycles() + */ + public function testGetCycles() { + $this->g->addDirectedEdge($this->v['a'], $this->v['b']); + $this->g->addDirectedEdge($this->v['b'], $this->v['c']); + $this->g->addDirectedEdge($this->v['c'], $this->v['a']); + + $this->assertEquals(array(array($this->v['c'], $this->v['b'], $this->v['a'])), $this->g->getCycles()); + } +} diff --git a/core/vendor/sdboyer/gliph/tests/Gliph/Graph/UndirectedAdjacencyListTest.php b/core/vendor/sdboyer/gliph/tests/Gliph/Graph/UndirectedAdjacencyListTest.php new file mode 100644 index 0000000000000000000000000000000000000000..f7cb554ffa9b611194d8c7922a6f8270cba1bd39 --- /dev/null +++ b/core/vendor/sdboyer/gliph/tests/Gliph/Graph/UndirectedAdjacencyListTest.php @@ -0,0 +1,77 @@ +<?php + +namespace Gliph\Graph; + + +class UndirectedAdjacencyListTest extends AdjacencyListBase { + + /** + * @var UndirectedAdjacencyList + */ + protected $g; + + /** + * Creates a set of vertices and an empty graph for testing. + */ + public function setUp() { + parent::setUp(); + $this->g = new UndirectedAdjacencyList(); + } + + public function testAddUndirectedEdge() { + $this->g->addEdge($this->v['a'], $this->v['b']); + + $this->doCheckVerticesEqual(array($this->v['a'], $this->v['b'])); + } + + public function testRemoveVertex() { + $this->g->addEdge($this->v['a'], $this->v['b']); + + $this->g->removeVertex(($this->v['a'])); + $this->doCheckVertexCount(1); + } + + public function testRemoveEdge() { + $this->g->addEdge($this->v['a'], $this->v['b']); + $this->g->addEdge($this->v['b'], $this->v['c']); + + $this->g->removeEdge($this->v['b'], $this->v['c']); + $this->doCheckVertexCount(3); + + $found = array(); + $this->g->eachAdjacent($this->v['a'], function($adjacent) use (&$found) { + $found[] = $adjacent; + }); + + $this->assertEquals(array($this->v['b']), $found); + } + + public function testEachEdge() { + $this->g->addEdge($this->v['a'], $this->v['b']); + $this->g->addEdge($this->v['b'], $this->v['c']); + + $found = array(); + $this->g->eachEdge(function ($edge) use (&$found) { + $found[] = $edge; + }); + + $this->assertCount(2, $found); + $this->assertEquals(array($this->v['a'], $this->v['b']), $found[0]); + $this->assertEquals(array($this->v['b'], $this->v['c']), $found[1]); + + // Ensure bidirectionality of created edges + $found = array(); + $this->g->eachAdjacent($this->v['b'], function($adjacent) use (&$found) { + $found[] = $adjacent; + }); + + $this->assertCount(2, $found); + } + + /** + * @expectedException Gliph\Exception\NonexistentVertexException + */ + public function testRemoveNonexistentVertex() { + $this->g->removeVertex($this->v['a']); + } +} diff --git a/core/vendor/sdboyer/gliph/tests/Gliph/TestVertex.php b/core/vendor/sdboyer/gliph/tests/Gliph/TestVertex.php new file mode 100644 index 0000000000000000000000000000000000000000..7a8f4848d0325e3335468605a2df043454db440f --- /dev/null +++ b/core/vendor/sdboyer/gliph/tests/Gliph/TestVertex.php @@ -0,0 +1,19 @@ +<?php + +namespace Gliph; + +/** + * A class that acts as a vertex for more convenient use in tests. + */ +class TestVertex { + + protected $name; + + public function __construct($name) { + $this->name = $name; + } + + public function __toString() { + return $this->name; + } +} \ No newline at end of file diff --git a/core/vendor/sdboyer/gliph/tests/Gliph/Traversal/DepthFirstTest.php b/core/vendor/sdboyer/gliph/tests/Gliph/Traversal/DepthFirstTest.php new file mode 100644 index 0000000000000000000000000000000000000000..ef7ca08ad275a905311877f4bba64574eee207a8 --- /dev/null +++ b/core/vendor/sdboyer/gliph/tests/Gliph/Traversal/DepthFirstTest.php @@ -0,0 +1,111 @@ +<?php + +namespace Gliph\Traversal; + + +use Gliph\Exception\NonexistentVertexException; +use Gliph\Graph\DirectedAdjacencyList; +use Gliph\TestVertex; +use Gliph\Visitor\DepthFirstNoOpVisitor; + +class DepthFirstTest extends \PHPUnit_Framework_TestCase { + + /** + * @var DirectedAdjacencyList + */ + protected $g; + protected $v; + + public function setUp() { + $this->g = new DirectedAdjacencyList(); + $this->v = array( + 'a' => new TestVertex('a'), + 'b' => new TestVertex('b'), + 'c' => new TestVertex('c'), + 'd' => new TestVertex('d'), + 'e' => new TestVertex('e'), + 'f' => new TestVertex('f'), + ); + extract($this->v); + + $this->g->addDirectedEdge($a, $b); + $this->g->addDirectedEdge($b, $c); + $this->g->addDirectedEdge($a, $c); + $this->g->addDirectedEdge($b, $d); + } + + public function testBasicAcyclicDepthFirstTraversal() { + $visitor = $this->getMock('Gliph\\Visitor\\DepthFirstNoOpVisitor'); + $visitor->expects($this->exactly(4))->method('onInitializeVertex'); + $visitor->expects($this->exactly(0))->method('onBackEdge'); + $visitor->expects($this->exactly(4))->method('onStartVertex'); + $visitor->expects($this->exactly(4))->method('onExamineEdge'); + $visitor->expects($this->exactly(4))->method('onFinishVertex'); + + DepthFirst::traverse($this->g, $visitor); + } + + public function testDirectCycleDepthFirstTraversal() { + extract($this->v); + + $this->g->addDirectedEdge($d, $b); + + $visitor = $this->getMock('Gliph\\Visitor\\DepthFirstNoOpVisitor'); + $visitor->expects($this->exactly(1))->method('onBackEdge'); + + DepthFirst::traverse($this->g, $visitor); + } + + public function testIndirectCycleDepthFirstTraversal() { + extract($this->v); + + $this->g->addDirectedEdge($d, $a); + + $visitor = $this->getMock('Gliph\\Visitor\\DepthFirstNoOpVisitor'); + $visitor->expects($this->exactly(1))->method('onBackEdge'); + + DepthFirst::traverse($this->g, $visitor, $a); + } + + /** + * @covers Gliph\Traversal\DepthFirst::traverse + * @expectedException Gliph\Exception\RuntimeException + */ + public function testExceptionOnEmptyTraversalQueue() { + extract($this->v); + + // Create a cycle that ensures there are no source vertices + $this->g->addDirectedEdge($d, $a); + DepthFirst::traverse($this->g, new DepthFirstNoOpVisitor()); + } + + /** + * @covers Gliph\Traversal\DepthFirst::traverse + */ + public function testProvideQueueAsStartPoint() { + extract($this->v); + + $queue = new \SplQueue(); + $queue->push($a); + $queue->push($e); + + $this->g->addVertex($a); + $this->g->addVertex($e); + + DepthFirst::traverse($this->g, new DepthFirstNoOpVisitor(), $queue); + } + + /** + * @covers \Gliph\Traversal\DepthFirst::toposort + * @expectedException Gliph\Exception\RuntimeException + * Thrown by the visitor after adding a cycle to the graph. + */ + public function testToposort() { + extract($this->v); + + $this->assertEquals(array($c, $d, $b, $a), DepthFirst::toposort($this->g, $a)); + + $this->g->addDirectedEdge($d, $a); + DepthFirst::toposort($this->g, $a); + } +} diff --git a/core/vendor/sdboyer/gliph/tests/Gliph/Visitor/DepthFirstBasicVisitorTest.php b/core/vendor/sdboyer/gliph/tests/Gliph/Visitor/DepthFirstBasicVisitorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..cd56cc405963cdb53cd8ab4b30f217b9edff8d0d --- /dev/null +++ b/core/vendor/sdboyer/gliph/tests/Gliph/Visitor/DepthFirstBasicVisitorTest.php @@ -0,0 +1,87 @@ +<?php + +namespace Gliph\Visitor; + +use Gliph\Graph\DirectedAdjacencyList; +use Gliph\TestVertex; +use Gliph\Traversal\DepthFirst; + +class DepthFirstBasicVisitorTest extends DepthFirstToposortVisitorTest { + + protected $v; + + /** + * @var DepthFirstBasicVisitor + */ + protected $vis; + + /** + * @var DirectedAdjacencyList + */ + protected $g; + + public function setUp() { + $this->v = array( + 'a' => new TestVertex('a'), + 'b' => new TestVertex('b'), + 'c' => new TestVertex('c'), + 'd' => new TestVertex('d'), + 'e' => new TestVertex('e'), + 'f' => new TestVertex('f'), + ); + + $this->g = new DirectedAdjacencyList(); + $this->vis = new DepthFirstBasicVisitor(); + + $this->g->addDirectedEdge($this->v['a'], $this->v['b']); + $this->g->addDirectedEdge($this->v['b'], $this->v['c']); + $this->g->addDirectedEdge($this->v['a'], $this->v['c']); + $this->g->addDirectedEdge($this->v['b'], $this->v['d']); + } + + public function stateSensitiveMethods() { + $methods = parent::stateSensitiveMethods(); + $methods['completed'][] = array('getReachable', array(new \stdClass())); + return $methods; + } + + /** + * @covers Gliph\Visitor\DepthFirstBasicVisitor::__construct + * @covers Gliph\Visitor\DepthFirstBasicVisitor::onInitializeVertex + * @covers Gliph\Visitor\DepthFirstBasicVisitor::beginTraversal + * @covers Gliph\Visitor\DepthFirstBasicVisitor::onStartVertex + * @covers Gliph\Visitor\DepthFirstBasicVisitor::onExamineEdge + * @covers Gliph\Visitor\DepthFirstBasicVisitor::onFinishVertex + * @covers Gliph\Visitor\DepthFirstBasicVisitor::endTraversal + * @covers Gliph\Visitor\DepthFirstBasicVisitor::getReachable + * @covers Gliph\Visitor\DepthFirstBasicVisitor::getTsl + */ + public function testTraversalWithStartPoint() { + DepthFirst::traverse($this->g, $this->vis, $this->v['a']); + $this->assertCount(3, $this->vis->getReachable($this->v['a'])); + $this->assertCount(2, $this->vis->getReachable($this->v['b'])); + $this->assertCount(0, $this->vis->getReachable($this->v['c'])); + $this->assertCount(0, $this->vis->getReachable($this->v['d'])); + + // Not the greatest test since we're implicitly locking in to one of + // two valid TSL solutions - but that's linked to the determinism in + // the ordering of how the graph class stores vertices, which is a + // much bigger problem than can be solved right here. So, good enough. + $this->assertEquals(array($this->v['c'], $this->v['d'], $this->v['b'], $this->v['a']), $this->vis->getTsl()); + } + + /** + * @expectedException Gliph\Exception\RuntimeException + * @covers Gliph\Visitor\DepthFirstBasicVisitor::onBackEdge + * @covers Gliph\Visitor\DepthFirstBasicVisitor::onInitializeVertex + */ + public function testErrorOnCycle() { + $this->g->addDirectedEdge($this->v['d'], $this->v['b']); + DepthFirst::traverse($this->g, $this->vis); + } + + public function testReachableExceptionOnUnknownVertex() { + DepthFirst::traverse($this->g, $this->vis, $this->v['a']); + $this->assertFalse($this->vis->getReachable($this->v['e'])); + } +} diff --git a/core/vendor/sdboyer/gliph/tests/Gliph/Visitor/DepthFirstToposortVisitorTest.php b/core/vendor/sdboyer/gliph/tests/Gliph/Visitor/DepthFirstToposortVisitorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..ea0169247cc900b8913149b83377a42cab5405d7 --- /dev/null +++ b/core/vendor/sdboyer/gliph/tests/Gliph/Visitor/DepthFirstToposortVisitorTest.php @@ -0,0 +1,83 @@ +<?php + +namespace Gliph\Visitor; + +use Gliph\TestVertex; + +class DepthFirstToposortVisitorTest extends StatefulDepthFirstVisitorBase { + + /** + * Creates a DepthFirstToposortVisitor in NOT_STARTED state. + * + * @return DepthFirstToposortVisitor + */ + public function createNotStartedVisitor() { + return new DepthFirstToposortVisitor(); + } + + /** + * Creates a DepthFirstToposortVisitor in IN_PROGRESS state. + * + * @return DepthFirstToposortVisitor + */ + public function createInProgressVisitor() { + $stub = new DepthFirstToposortVisitor(); + + $prop = new \ReflectionProperty($stub, 'state'); + $prop->setAccessible(TRUE); + $prop->setValue($stub, StatefulVisitorInterface::IN_PROGRESS); + + return $stub; + } + + /** + * Creates a DepthFirstToposortVisitor in COMPLETED state. + * + * @return DepthFirstToposortVisitor + */ + public function createCompletedVisitor() { + $stub = new DepthFirstToposortVisitor(); + + $prop = new \ReflectionProperty($stub, 'state'); + $prop->setAccessible(TRUE); + $prop->setValue($stub, StatefulVisitorInterface::COMPLETE); + + return $stub; + } + + public function inProgressMethods() { + return array( + array('onStartVertex', array(new \stdClass(), function() {})), + array('onExamineEdge', array(new \stdClass(), new \stdClass(), function() {})), + array('onFinishVertex', array(new \stdClass(), function() {})), + ); + } + + public function completedMethods() { + return array( + array('getTsl', array()), + ); + } + + /** + * @expectedException \Gliph\Exception\RuntimeException + */ + public function testOnBackEdge() { + $this->createInProgressVisitor()->onBackEdge(new \stdClass(), function() {}); + } + + public function testGetTsl() { + $a = new TestVertex('a'); + $b = new TestVertex('b'); + $c = new TestVertex('c'); + + $vis = $this->createInProgressVisitor(); + + $vis->onFinishVertex($a, function() {}); + $vis->onFinishVertex($b, function() {}); + $vis->onFinishVertex($c, function() {}); + $vis->endTraversal(); + + $this->assertEquals(array($a, $b, $c), $vis->getTsl()); + } +} diff --git a/core/vendor/sdboyer/gliph/tests/Gliph/Visitor/StatefulDepthFirstVisitorBase.php b/core/vendor/sdboyer/gliph/tests/Gliph/Visitor/StatefulDepthFirstVisitorBase.php new file mode 100644 index 0000000000000000000000000000000000000000..5b1df74d195fd23c380a11fbee58b41224e4e21b --- /dev/null +++ b/core/vendor/sdboyer/gliph/tests/Gliph/Visitor/StatefulDepthFirstVisitorBase.php @@ -0,0 +1,176 @@ +<?php + +namespace Gliph\Visitor; + +class StatefulDepthFirstVisitorStub extends StatefulDepthFirstVisitor {} + +abstract class StatefulDepthFirstVisitorBase extends \PHPUnit_Framework_TestCase { + + /** + * Creates a StatefulDepthFirstVisitor in NOT_STARTED state. + * + * @return StatefulDepthFirstVisitor + */ + public function createNotStartedVisitor() { + return new StatefulDepthFirstVisitorStub(); + } + + /** + * Creates a StatefulDepthFirstVisitor in IN_PROGRESS state. + * + * @return StatefulDepthFirstVisitor + */ + public function createInProgressVisitor() { + $stub = new StatefulDepthFirstVisitorStub(); + + $prop = new \ReflectionProperty($stub, 'state'); + $prop->setAccessible(TRUE); + $prop->setValue($stub, StatefulVisitorInterface::IN_PROGRESS); + + return $stub; + } + + /** + * Creates a StatefulDepthFirstVisitor in COMPLETED state. + * + * @return StatefulDepthFirstVisitor + */ + public function createCompletedVisitor() { + $stub = new StatefulDepthFirstVisitorStub(); + + $prop = new \ReflectionProperty($stub, 'state'); + $prop->setAccessible(TRUE); + $prop->setValue($stub, StatefulVisitorInterface::COMPLETE); + + return $stub; + } + + /** + * Returns A list of methods and arguments that should be tested for state sensitivity. + * + * + * @return array + */ + public function stateSensitiveMethods() { + return array( + 'notStarted' => array( + array('onInitializeVertex', array(new \stdClass(), TRUE, new \SplQueue())), + array('beginTraversal', array()), + ), + 'inProgress' => array( + array('onStartVertex', array(new \stdClass(), function() {})), + array('onBackEdge', array(new \stdClass(), function() {})), + array('onExamineEdge', array(new \stdClass(), new \stdClass(), function() {})), + array('onFinishVertex', array(new \stdClass(), function() {})), + array('endTraversal', array()), + ), + 'completed' => array( + array(), + ), + ); + } + + /** + * Data provider of visitor methods safe to call from IN_PROGRESS state. + */ + public function inProgressMethods() { + $methods = $this->stateSensitiveMethods(); + return $methods['inProgress']; + } + + /** + * Data provider of visitor methods safe to call from NOT_STARTED state. + */ + public function notStartedMethods() { + $methods = $this->stateSensitiveMethods(); + return $methods['notStarted']; + } + + /** + * Data provider of visitor methods safe to call from COMPLETE state. + */ + public function completedMethods() { + $methods = $this->stateSensitiveMethods(); + return $methods['completed']; + } + + /** + * Data provider of visitor methods not safe to call from NOT_STARTED state. + */ + public function invalidNotStartedMethods() { + return array_filter(array_merge($this->inProgressMethods(), $this->completedMethods())); + } + + /** + * Data provider of visitor methods not safe to call from COMPLETED state. + */ + public function invalidCompletedMethods() { + return array_filter(array_merge($this->notStartedMethods(), $this->inProgressMethods())); + } + + /** + * Data provider of visitor methods not safe to call from IN_PROGRESS state. + */ + public function invalidInProgressMethods() { + return array_filter(array_merge($this->notStartedMethods(), $this->completedMethods())); + } + + public function testInitialState() { + $this->assertEquals(StatefulVisitorInterface::NOT_STARTED, $this->createNotStartedVisitor()->getState()); + } + + public function testBeginTraversal() { + $vis = $this->createNotStartedVisitor(); + + $vis->beginTraversal(); + $this->assertEquals(StatefulVisitorInterface::IN_PROGRESS, $vis->getState()); + } + + public function testEndTraversal() { + $vis = $this->createInProgressVisitor(); + + $vis->endTraversal(); + $this->assertEquals(StatefulVisitorInterface::COMPLETE, $vis->getState()); + } + + /** + * @dataProvider notStartedMethods + */ + public function testNotStartedMethods($method, $args) { + $vis = $this->createNotStartedVisitor(); + call_user_func_array(array($vis, $method), $args); + } + + /** + * @dataProvider inProgressMethods + */ + public function testInProgressMethods($method, $args) { + $vis = $this->createInProgressVisitor(); + call_user_func_array(array($vis, $method), $args); + } + + /** + * @dataProvider invalidInProgressMethods + * @expectedException \Gliph\Exception\WrongVisitorStateException + */ + public function testInvalidInProgressMethods($method, $args) { + call_user_func_array(array($this->createInProgressVisitor(), $method), $args); + } + + /** + * @dataProvider invalidNotStartedMethods + * @expectedException \Gliph\Exception\WrongVisitorStateException + */ + public function testInvalidNotStartedMethods($method, $args) { + call_user_func_array(array($this->createNotStartedVisitor(), $method), $args); + } + + /** + * @dataProvider invalidCompletedMethods + * @expectedException \Gliph\Exception\WrongVisitorStateException + */ + public function testInvalidCompletedMethods($method, $args) { + call_user_func_array(array($this->createCompletedVisitor(), $method), $args); + } +} + diff --git a/core/vendor/sdboyer/gliph/tests/Gliph/Visitor/StatefulDepthFirstVisitorTest.php b/core/vendor/sdboyer/gliph/tests/Gliph/Visitor/StatefulDepthFirstVisitorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..0de4346e46d5df029a88e47d9a397f005e86c245 --- /dev/null +++ b/core/vendor/sdboyer/gliph/tests/Gliph/Visitor/StatefulDepthFirstVisitorTest.php @@ -0,0 +1,5 @@ +<?php + +namespace Gliph\Visitor; + +class StatefulDepthFirstVisitorTest extends StatefulDepthFirstVisitorBase {} diff --git a/core/vendor/sdboyer/gliph/tests/bootstrap.php b/core/vendor/sdboyer/gliph/tests/bootstrap.php new file mode 100644 index 0000000000000000000000000000000000000000..1324983d7e0e3b965854a1d4aa4e3df94b9245e3 --- /dev/null +++ b/core/vendor/sdboyer/gliph/tests/bootstrap.php @@ -0,0 +1,4 @@ +<?php + +$loader = require_once __DIR__ . "/../vendor/autoload.php"; +$loader->add('Gliph\\', __DIR__); \ No newline at end of file