mediawiki/vendor (master)

sourcepatches
From 918c9678b05a759fb32fa76f3bf0b5642d3a9f73 Mon Sep 17 00:00:00 2001
From: libraryupgrader <tools.libraryupgrader@tools.wmflabs.org>
Date: Wed, 5 May 2021 13:48:31 +0000
Subject: [PATCH] build: Updating php-parallel-lint/php-parallel-lint to 1.3.0

Change-Id: I6405496ba1c3788e2bd1a6e9edbee571e10ae6c8
---
 composer.json                                 |    2 +-
 composer.lock                                 |   16 +-
 composer/ClassLoader.php                      |    2 +-
 composer/LICENSE                              |    2 -
 composer/autoload_classmap.php                |  103 ++
 composer/autoload_psr4.php                    |    3 +
 composer/autoload_static.php                  |  118 ++
 composer/installed.json                       |  188 ++
 .../php-parallel-lint/CHANGELOG.md            |   66 +
 php-parallel-lint/php-parallel-lint/LICENSE   |   26 +
 php-parallel-lint/php-parallel-lint/README.md |  111 ++
 .../php-parallel-lint/bin/skip-linting.php    |   19 +
 .../php-parallel-lint/composer.json           |   46 +
 .../php-parallel-lint/parallel-lint           |   67 +
 .../php-parallel-lint/src/Application.php     |  124 ++
 .../src/Contracts/SyntaxErrorCallback.php     |   13 +
 .../php-parallel-lint/src/Error.php           |  222 +++
 .../php-parallel-lint/src/ErrorFormatter.php  |  127 ++
 .../php-parallel-lint/src/Manager.php         |  287 +++
 .../php-parallel-lint/src/Output.php          |  513 ++++++
 .../php-parallel-lint/src/ParallelLint.php    |  286 +++
 .../src/Process/GitBlameProcess.php           |  147 ++
 .../src/Process/LintProcess.php               |  137 ++
 .../src/Process/PhpExecutable.php             |  128 ++
 .../src/Process/PhpProcess.php                |   35 +
 .../php-parallel-lint/src/Process/Process.php |  153 ++
 .../src/Process/SkipLintProcess.php           |   91 +
 .../php-parallel-lint/src/Result.php          |  168 ++
 .../php-parallel-lint/src/Settings.php        |  242 +++
 .../php-parallel-lint/src/exceptions.php      |   90 +
 .../php-parallel-lint/src/polyfill.php        |   15 +
 phplang/scope-exit/.gitignore                 |    2 +
 phplang/scope-exit/README.md                  |   18 +
 phplang/scope-exit/composer.json              |   28 +
 phplang/scope-exit/src/ScopeExit.php          |   40 +
 phplang/scope-exit/tests/BasicTest.php        |   29 +
 swaggest/json-diff/.gitlab-ci.yml             |   39 +
 swaggest/json-diff/CHANGELOG.md               |   66 +
 swaggest/json-diff/LICENSE                    |   21 +
 swaggest/json-diff/README.md                  |  285 +++
 swaggest/json-diff/composer.json              |   33 +
 swaggest/json-diff/src/Exception.php          |    9 +
 swaggest/json-diff/src/JsonDiff.php           |  542 ++++++
 swaggest/json-diff/src/JsonHash.php           |   76 +
 swaggest/json-diff/src/JsonMergePatch.php     |   31 +
 swaggest/json-diff/src/JsonPatch.php          |  187 ++
 swaggest/json-diff/src/JsonPatch/Add.php      |    8 +
 swaggest/json-diff/src/JsonPatch/Copy.php     |    8 +
 swaggest/json-diff/src/JsonPatch/Move.php     |    8 +
 swaggest/json-diff/src/JsonPatch/OpPath.php   |   18 +
 .../json-diff/src/JsonPatch/OpPathFrom.php    |   14 +
 .../json-diff/src/JsonPatch/OpPathValue.php   |   16 +
 swaggest/json-diff/src/JsonPatch/Remove.php   |    8 +
 swaggest/json-diff/src/JsonPatch/Replace.php  |    8 +
 swaggest/json-diff/src/JsonPatch/Test.php     |    8 +
 swaggest/json-diff/src/JsonPointer.php        |  305 ++++
 swaggest/json-diff/src/JsonValueReplace.php   |   79 +
 swaggest/json-diff/src/ModifiedPathDiff.php   |   30 +
 swaggest/json-schema/CHANGELOG.md             |   70 +
 swaggest/json-schema/LICENSE                  |   21 +
 swaggest/json-schema/Makefile                 |   32 +
 swaggest/json-schema/README.md                |  480 +++++
 .../benchmarks/AjvSchemasBench.php            |  144 ++
 .../json-schema/benchmarks/Draft7Bench.php    |  144 ++
 swaggest/json-schema/composer.json            |   42 +
 swaggest/json-schema/composer.lock            | 1132 ++++++++++++
 .../json-schema/spec/json-schema-draft6.json  |  154 ++
 .../json-schema/spec/json-schema-draft7.json  |  168 ++
 swaggest/json-schema/spec/json-schema.json    |  152 ++
 .../json-schema/spec/petstore-swagger.json    | 1035 +++++++++++
 swaggest/json-schema/spec/swagger-schema.json | 1608 +++++++++++++++++
 .../json-schema/src/Constraint/Constraint.php |    7 +
 .../json-schema/src/Constraint/Content.php    |   85 +
 .../json-schema/src/Constraint/Format.php     |  128 ++
 .../src/Constraint/Format/IdnHostname.php     |   21 +
 .../json-schema/src/Constraint/Format/Iri.php |  703 +++++++
 .../json-schema/src/Constraint/Format/Uri.php |   80 +
 .../json-schema/src/Constraint/Properties.php |  182 ++
 swaggest/json-schema/src/Constraint/Ref.php   |   71 +
 swaggest/json-schema/src/Constraint/Type.php  |   99 +
 .../src/Constraint/UniqueItems.php            |   40 +
 swaggest/json-schema/src/Context.php          |  137 ++
 swaggest/json-schema/src/DataPreProcessor.php |   15 +
 swaggest/json-schema/src/Exception.php        |   12 +
 .../src/Exception/ArrayException.php          |   11 +
 .../src/Exception/ConstException.php          |   10 +
 .../src/Exception/ContentException.php        |   11 +
 .../src/Exception/EnumException.php           |   10 +
 swaggest/json-schema/src/Exception/Error.php  |   19 +
 .../src/Exception/LogicException.php          |   11 +
 .../src/Exception/NumericException.php        |   14 +
 .../src/Exception/ObjectException.php         |   14 +
 .../src/Exception/StringException.php         |   14 +
 .../src/Exception/TypeException.php           |   11 +
 swaggest/json-schema/src/Helper.php           |   72 +
 swaggest/json-schema/src/InvalidValue.php     |   84 +
 swaggest/json-schema/src/JsonSchema.php       |  734 ++++++++
 swaggest/json-schema/src/MagicMap.php         |    8 +
 swaggest/json-schema/src/MagicMapTrait.php    |  133 ++
 .../json-schema/src/Meta/AbstractMeta.php     |   16 +
 swaggest/json-schema/src/Meta/MetaHolder.php  |   12 +
 swaggest/json-schema/src/NameMirror.php       |   16 +
 swaggest/json-schema/src/Path/PointerUtil.php |  103 ++
 swaggest/json-schema/src/RefResolver.php      |  267 +++
 .../src/RemoteRef/BasicFetcher.php            |   23 +
 .../json-schema/src/RemoteRef/Preloaded.php   |   75 +
 .../json-schema/src/RemoteRefProvider.php     |   13 +
 swaggest/json-schema/src/Schema.php           | 1445 +++++++++++++++
 swaggest/json-schema/src/SchemaContract.php   |   64 +
 swaggest/json-schema/src/SchemaExporter.php   |   12 +
 .../src/Structure/ClassStructure.php          |    8 +
 .../src/Structure/ClassStructureContract.php  |   15 +
 .../src/Structure/ClassStructureTrait.php     |  182 ++
 .../json-schema/src/Structure/Composition.php |   33 +
 swaggest/json-schema/src/Structure/Egg.php    |   31 +
 swaggest/json-schema/src/Structure/Nested.php |   15 +
 .../json-schema/src/Structure/ObjectItem.php  |   17 +
 .../src/Structure/ObjectItemContract.php      |   25 +
 .../src/Structure/ObjectItemTrait.php         |  189 ++
 .../src/Structure/WithResolvedValue.php       |   15 +
 swaggest/json-schema/src/Wrapper.php          |  219 +++
 121 files changed, 16164 insertions(+), 12 deletions(-)
 create mode 100644 php-parallel-lint/php-parallel-lint/CHANGELOG.md
 create mode 100644 php-parallel-lint/php-parallel-lint/LICENSE
 create mode 100644 php-parallel-lint/php-parallel-lint/README.md
 create mode 100644 php-parallel-lint/php-parallel-lint/bin/skip-linting.php
 create mode 100644 php-parallel-lint/php-parallel-lint/composer.json
 create mode 100755 php-parallel-lint/php-parallel-lint/parallel-lint
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Application.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Contracts/SyntaxErrorCallback.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Error.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/ErrorFormatter.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Manager.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Output.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/ParallelLint.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Process/GitBlameProcess.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Process/LintProcess.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Process/PhpExecutable.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Process/PhpProcess.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Process/Process.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Process/SkipLintProcess.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Result.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Settings.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/exceptions.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/polyfill.php
 create mode 100644 phplang/scope-exit/.gitignore
 create mode 100644 phplang/scope-exit/README.md
 create mode 100644 phplang/scope-exit/composer.json
 create mode 100644 phplang/scope-exit/src/ScopeExit.php
 create mode 100644 phplang/scope-exit/tests/BasicTest.php
 create mode 100644 swaggest/json-diff/.gitlab-ci.yml
 create mode 100644 swaggest/json-diff/CHANGELOG.md
 create mode 100644 swaggest/json-diff/LICENSE
 create mode 100644 swaggest/json-diff/README.md
 create mode 100644 swaggest/json-diff/composer.json
 create mode 100644 swaggest/json-diff/src/Exception.php
 create mode 100644 swaggest/json-diff/src/JsonDiff.php
 create mode 100644 swaggest/json-diff/src/JsonHash.php
 create mode 100644 swaggest/json-diff/src/JsonMergePatch.php
 create mode 100644 swaggest/json-diff/src/JsonPatch.php
 create mode 100644 swaggest/json-diff/src/JsonPatch/Add.php
 create mode 100644 swaggest/json-diff/src/JsonPatch/Copy.php
 create mode 100644 swaggest/json-diff/src/JsonPatch/Move.php
 create mode 100644 swaggest/json-diff/src/JsonPatch/OpPath.php
 create mode 100644 swaggest/json-diff/src/JsonPatch/OpPathFrom.php
 create mode 100644 swaggest/json-diff/src/JsonPatch/OpPathValue.php
 create mode 100644 swaggest/json-diff/src/JsonPatch/Remove.php
 create mode 100644 swaggest/json-diff/src/JsonPatch/Replace.php
 create mode 100644 swaggest/json-diff/src/JsonPatch/Test.php
 create mode 100644 swaggest/json-diff/src/JsonPointer.php
 create mode 100644 swaggest/json-diff/src/JsonValueReplace.php
 create mode 100644 swaggest/json-diff/src/ModifiedPathDiff.php
 create mode 100644 swaggest/json-schema/CHANGELOG.md
 create mode 100644 swaggest/json-schema/LICENSE
 create mode 100644 swaggest/json-schema/Makefile
 create mode 100644 swaggest/json-schema/README.md
 create mode 100644 swaggest/json-schema/benchmarks/AjvSchemasBench.php
 create mode 100644 swaggest/json-schema/benchmarks/Draft7Bench.php
 create mode 100644 swaggest/json-schema/composer.json
 create mode 100644 swaggest/json-schema/composer.lock
 create mode 100644 swaggest/json-schema/spec/json-schema-draft6.json
 create mode 100644 swaggest/json-schema/spec/json-schema-draft7.json
 create mode 100644 swaggest/json-schema/spec/json-schema.json
 create mode 100644 swaggest/json-schema/spec/petstore-swagger.json
 create mode 100644 swaggest/json-schema/spec/swagger-schema.json
 create mode 100644 swaggest/json-schema/src/Constraint/Constraint.php
 create mode 100644 swaggest/json-schema/src/Constraint/Content.php
 create mode 100644 swaggest/json-schema/src/Constraint/Format.php
 create mode 100644 swaggest/json-schema/src/Constraint/Format/IdnHostname.php
 create mode 100644 swaggest/json-schema/src/Constraint/Format/Iri.php
 create mode 100644 swaggest/json-schema/src/Constraint/Format/Uri.php
 create mode 100644 swaggest/json-schema/src/Constraint/Properties.php
 create mode 100644 swaggest/json-schema/src/Constraint/Ref.php
 create mode 100644 swaggest/json-schema/src/Constraint/Type.php
 create mode 100644 swaggest/json-schema/src/Constraint/UniqueItems.php
 create mode 100644 swaggest/json-schema/src/Context.php
 create mode 100644 swaggest/json-schema/src/DataPreProcessor.php
 create mode 100644 swaggest/json-schema/src/Exception.php
 create mode 100644 swaggest/json-schema/src/Exception/ArrayException.php
 create mode 100644 swaggest/json-schema/src/Exception/ConstException.php
 create mode 100644 swaggest/json-schema/src/Exception/ContentException.php
 create mode 100644 swaggest/json-schema/src/Exception/EnumException.php
 create mode 100644 swaggest/json-schema/src/Exception/Error.php
 create mode 100644 swaggest/json-schema/src/Exception/LogicException.php
 create mode 100644 swaggest/json-schema/src/Exception/NumericException.php
 create mode 100644 swaggest/json-schema/src/Exception/ObjectException.php
 create mode 100644 swaggest/json-schema/src/Exception/StringException.php
 create mode 100644 swaggest/json-schema/src/Exception/TypeException.php
 create mode 100644 swaggest/json-schema/src/Helper.php
 create mode 100644 swaggest/json-schema/src/InvalidValue.php
 create mode 100644 swaggest/json-schema/src/JsonSchema.php
 create mode 100644 swaggest/json-schema/src/MagicMap.php
 create mode 100644 swaggest/json-schema/src/MagicMapTrait.php
 create mode 100644 swaggest/json-schema/src/Meta/AbstractMeta.php
 create mode 100644 swaggest/json-schema/src/Meta/MetaHolder.php
 create mode 100644 swaggest/json-schema/src/NameMirror.php
 create mode 100644 swaggest/json-schema/src/Path/PointerUtil.php
 create mode 100644 swaggest/json-schema/src/RefResolver.php
 create mode 100644 swaggest/json-schema/src/RemoteRef/BasicFetcher.php
 create mode 100644 swaggest/json-schema/src/RemoteRef/Preloaded.php
 create mode 100644 swaggest/json-schema/src/RemoteRefProvider.php
 create mode 100644 swaggest/json-schema/src/Schema.php
 create mode 100644 swaggest/json-schema/src/SchemaContract.php
 create mode 100644 swaggest/json-schema/src/SchemaExporter.php
 create mode 100644 swaggest/json-schema/src/Structure/ClassStructure.php
 create mode 100644 swaggest/json-schema/src/Structure/ClassStructureContract.php
 create mode 100644 swaggest/json-schema/src/Structure/ClassStructureTrait.php
 create mode 100644 swaggest/json-schema/src/Structure/Composition.php
 create mode 100644 swaggest/json-schema/src/Structure/Egg.php
 create mode 100644 swaggest/json-schema/src/Structure/Nested.php
 create mode 100644 swaggest/json-schema/src/Structure/ObjectItem.php
 create mode 100644 swaggest/json-schema/src/Structure/ObjectItemContract.php
 create mode 100644 swaggest/json-schema/src/Structure/ObjectItemTrait.php
 create mode 100644 swaggest/json-schema/src/Structure/WithResolvedValue.php
 create mode 100644 swaggest/json-schema/src/Wrapper.php

diff --git a/composer.json b/composer.json
index 2939036..da9751e 100644
--- a/composer.json
+++ b/composer.json
@@ -145,7 +145,7 @@
 		"symfony/polyfill-php72": "1.12.0"
 	},
 	"require-dev": {
-		"php-parallel-lint/php-parallel-lint": "1.2.0",
+		"php-parallel-lint/php-parallel-lint": "1.3.0",
 		"swaggest/json-schema": "0.12.29"
 	},
 	"scripts": {
diff --git a/composer.lock b/composer.lock
index 0e7780b..6d2dda7 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "c01c6df9e9a08d87cad0ee2c742bea44",
+    "content-hash": "4d2686ca2df540b72f6e1330a2995b4e",
     "packages": [
         {
             "name": "beberlei/assert",
@@ -7475,21 +7475,21 @@
     "packages-dev": [
         {
             "name": "php-parallel-lint/php-parallel-lint",
-            "version": "v1.2.0",
+            "version": "v1.3.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/php-parallel-lint/PHP-Parallel-Lint.git",
-                "reference": "474f18bc6cc6aca61ca40bfab55139de614e51ca"
+                "reference": "772a954e5f119f6f5871d015b23eabed8cbdadfb"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/php-parallel-lint/PHP-Parallel-Lint/zipball/474f18bc6cc6aca61ca40bfab55139de614e51ca",
-                "reference": "474f18bc6cc6aca61ca40bfab55139de614e51ca",
+                "url": "https://api.github.com/repos/php-parallel-lint/PHP-Parallel-Lint/zipball/772a954e5f119f6f5871d015b23eabed8cbdadfb",
+                "reference": "772a954e5f119f6f5871d015b23eabed8cbdadfb",
                 "shasum": ""
             },
             "require": {
                 "ext-json": "*",
-                "php": ">=5.4.0"
+                "php": ">=5.3.0"
             },
             "replace": {
                 "grogy/php-parallel-lint": "*",
@@ -7498,7 +7498,7 @@
             "require-dev": {
                 "nette/tester": "^1.3 || ^2.0",
                 "php-parallel-lint/php-console-highlighter": "~0.3",
-                "squizlabs/php_codesniffer": "~3.0"
+                "squizlabs/php_codesniffer": "^3.5"
             },
             "suggest": {
                 "php-parallel-lint/php-console-highlighter": "Highlight syntax in code snippet"
@@ -7524,7 +7524,7 @@
             ],
             "description": "This tool check syntax of PHP files about 20x faster than serial check.",
             "homepage": "https://github.com/php-parallel-lint/PHP-Parallel-Lint",
-            "time": "2020-04-04T12:18:32+00:00"
+            "time": "2021-04-07T14:42:48+00:00"
         },
         {
             "name": "phplang/scope-exit",
diff --git a/composer/ClassLoader.php b/composer/ClassLoader.php
index 03b9bb9..fce8549 100644
--- a/composer/ClassLoader.php
+++ b/composer/ClassLoader.php
@@ -60,7 +60,7 @@ class ClassLoader
     public function getPrefixes()
     {
         if (!empty($this->prefixesPsr0)) {
-            return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
+            return call_user_func_array('array_merge', $this->prefixesPsr0);
         }
 
         return array();
diff --git a/composer/LICENSE b/composer/LICENSE
index f27399a..62ecfd8 100644
--- a/composer/LICENSE
+++ b/composer/LICENSE
@@ -1,4 +1,3 @@
-
 Copyright (c) Nils Adermann, Jordi Boggiano
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -18,4 +17,3 @@ 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/composer/autoload_classmap.php b/composer/autoload_classmap.php
index 2b54c6e..8e93e74 100644
--- a/composer/autoload_classmap.php
+++ b/composer/autoload_classmap.php
@@ -867,6 +867,40 @@ return array(
     'GuzzleHttp\\Utils' => $vendorDir . '/guzzlehttp/guzzle/src/Utils.php',
     'HtmlFormatter\\HtmlFormatter' => $vendorDir . '/wikimedia/html-formatter/src/HtmlFormatter.php',
     'Image_XMP' => $vendorDir . '/james-heinrich/getid3/getid3/module.tag.xmp.php',
+    'JakubOnderka\\PhpParallelLint\\Application' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Application.php',
+    'JakubOnderka\\PhpParallelLint\\ArrayIterator' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Settings.php',
+    'JakubOnderka\\PhpParallelLint\\Blame' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Error.php',
+    'JakubOnderka\\PhpParallelLint\\CheckstyleOutput' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Output.php',
+    'JakubOnderka\\PhpParallelLint\\ConsoleWriter' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Output.php',
+    'JakubOnderka\\PhpParallelLint\\Contracts\\SyntaxErrorCallback' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Contracts/SyntaxErrorCallback.php',
+    'JakubOnderka\\PhpParallelLint\\Error' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Error.php',
+    'JakubOnderka\\PhpParallelLint\\ErrorFormatter' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/ErrorFormatter.php',
+    'JakubOnderka\\PhpParallelLint\\Exception' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/exceptions.php',
+    'JakubOnderka\\PhpParallelLint\\FileWriter' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Output.php',
+    'JakubOnderka\\PhpParallelLint\\IWriter' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Output.php',
+    'JakubOnderka\\PhpParallelLint\\InvalidArgumentException' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/exceptions.php',
+    'JakubOnderka\\PhpParallelLint\\JsonOutput' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Output.php',
+    'JakubOnderka\\PhpParallelLint\\Manager' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Manager.php',
+    'JakubOnderka\\PhpParallelLint\\MultipleWriter' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Output.php',
+    'JakubOnderka\\PhpParallelLint\\NotExistsClassException' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/exceptions.php',
+    'JakubOnderka\\PhpParallelLint\\NotExistsPathException' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/exceptions.php',
+    'JakubOnderka\\PhpParallelLint\\NotImplementCallbackException' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/exceptions.php',
+    'JakubOnderka\\PhpParallelLint\\NullWriter' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Output.php',
+    'JakubOnderka\\PhpParallelLint\\Output' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Output.php',
+    'JakubOnderka\\PhpParallelLint\\ParallelLint' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/ParallelLint.php',
+    'JakubOnderka\\PhpParallelLint\\Process\\GitBlameProcess' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Process/GitBlameProcess.php',
+    'JakubOnderka\\PhpParallelLint\\Process\\LintProcess' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Process/LintProcess.php',
+    'JakubOnderka\\PhpParallelLint\\Process\\PhpExecutable' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Process/PhpExecutable.php',
+    'JakubOnderka\\PhpParallelLint\\Process\\PhpProcess' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Process/PhpProcess.php',
+    'JakubOnderka\\PhpParallelLint\\Process\\Process' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Process/Process.php',
+    'JakubOnderka\\PhpParallelLint\\Process\\SkipLintProcess' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Process/SkipLintProcess.php',
+    'JakubOnderka\\PhpParallelLint\\RecursiveDirectoryFilterIterator' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Manager.php',
+    'JakubOnderka\\PhpParallelLint\\Result' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Result.php',
+    'JakubOnderka\\PhpParallelLint\\RunTimeException' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/exceptions.php',
+    'JakubOnderka\\PhpParallelLint\\Settings' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Settings.php',
+    'JakubOnderka\\PhpParallelLint\\SyntaxError' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Error.php',
+    'JakubOnderka\\PhpParallelLint\\TextOutput' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Output.php',
+    'JakubOnderka\\PhpParallelLint\\TextOutputColored' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Output.php',
     'JsonException' => $vendorDir . '/symfony/polyfill-php73/Resources/stubs/JsonException.php',
     'JsonSchema\\Constraints\\BaseConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/BaseConstraint.php',
     'JsonSchema\\Constraints\\CollectionConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/CollectionConstraint.php',
@@ -911,6 +945,7 @@ return array(
     'JsonSchema\\Uri\\UriResolver' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/UriResolver.php',
     'JsonSchema\\Uri\\UriRetriever' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/UriRetriever.php',
     'JsonSchema\\Validator' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Validator.php',
+    'JsonSerializable' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/polyfill.php',
     'Kafka\\Client' => $vendorDir . '/nmred/kafka-php/src/Kafka/Client.php',
     'Kafka\\ClusterMetaData' => $vendorDir . '/nmred/kafka-php/src/Kafka/ClusterMetaData.php',
     'Kafka\\Consumer' => $vendorDir . '/nmred/kafka-php/src/Kafka/Consumer.php',
@@ -1437,6 +1472,7 @@ return array(
     'PEAR_Error' => $vendorDir . '/pear/pear-core-minimal/src/PEAR.php',
     'PEAR_ErrorStack' => $vendorDir . '/pear/pear-core-minimal/src/PEAR/ErrorStack.php',
     'PEAR_Exception' => $vendorDir . '/pear/pear_exception/PEAR/Exception.php',
+    'PhpLang\\ScopeExit' => $vendorDir . '/phplang/scope-exit/src/ScopeExit.php',
     'PhpParser\\Builder' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder.php',
     'PhpParser\\BuilderFactory' => $vendorDir . '/nikic/php-parser/lib/PhpParser/BuilderFactory.php',
     'PhpParser\\BuilderHelpers' => $vendorDir . '/nikic/php-parser/lib/PhpParser/BuilderHelpers.php',
@@ -2247,6 +2283,73 @@ return array(
     'Shellbox\\ShellboxError' => $vendorDir . '/wikimedia/shellbox/src/ShellboxError.php',
     'Shellbox\\TempDirManager' => $vendorDir . '/wikimedia/shellbox/src/TempDirManager.php',
     'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
+    'Swaggest\\JsonDiff\\Exception' => $vendorDir . '/swaggest/json-diff/src/Exception.php',
+    'Swaggest\\JsonDiff\\JsonDiff' => $vendorDir . '/swaggest/json-diff/src/JsonDiff.php',
+    'Swaggest\\JsonDiff\\JsonHash' => $vendorDir . '/swaggest/json-diff/src/JsonHash.php',
+    'Swaggest\\JsonDiff\\JsonMergePatch' => $vendorDir . '/swaggest/json-diff/src/JsonMergePatch.php',
+    'Swaggest\\JsonDiff\\JsonPatch' => $vendorDir . '/swaggest/json-diff/src/JsonPatch.php',
+    'Swaggest\\JsonDiff\\JsonPatch\\Add' => $vendorDir . '/swaggest/json-diff/src/JsonPatch/Add.php',
+    'Swaggest\\JsonDiff\\JsonPatch\\Copy' => $vendorDir . '/swaggest/json-diff/src/JsonPatch/Copy.php',
+    'Swaggest\\JsonDiff\\JsonPatch\\Move' => $vendorDir . '/swaggest/json-diff/src/JsonPatch/Move.php',
+    'Swaggest\\JsonDiff\\JsonPatch\\OpPath' => $vendorDir . '/swaggest/json-diff/src/JsonPatch/OpPath.php',
+    'Swaggest\\JsonDiff\\JsonPatch\\OpPathFrom' => $vendorDir . '/swaggest/json-diff/src/JsonPatch/OpPathFrom.php',
+    'Swaggest\\JsonDiff\\JsonPatch\\OpPathValue' => $vendorDir . '/swaggest/json-diff/src/JsonPatch/OpPathValue.php',
+    'Swaggest\\JsonDiff\\JsonPatch\\Remove' => $vendorDir . '/swaggest/json-diff/src/JsonPatch/Remove.php',
+    'Swaggest\\JsonDiff\\JsonPatch\\Replace' => $vendorDir . '/swaggest/json-diff/src/JsonPatch/Replace.php',
+    'Swaggest\\JsonDiff\\JsonPatch\\Test' => $vendorDir . '/swaggest/json-diff/src/JsonPatch/Test.php',
+    'Swaggest\\JsonDiff\\JsonPointer' => $vendorDir . '/swaggest/json-diff/src/JsonPointer.php',
+    'Swaggest\\JsonDiff\\JsonValueReplace' => $vendorDir . '/swaggest/json-diff/src/JsonValueReplace.php',
+    'Swaggest\\JsonDiff\\ModifiedPathDiff' => $vendorDir . '/swaggest/json-diff/src/ModifiedPathDiff.php',
+    'Swaggest\\JsonSchema\\Constraint\\Constraint' => $vendorDir . '/swaggest/json-schema/src/Constraint/Constraint.php',
+    'Swaggest\\JsonSchema\\Constraint\\Content' => $vendorDir . '/swaggest/json-schema/src/Constraint/Content.php',
+    'Swaggest\\JsonSchema\\Constraint\\Format' => $vendorDir . '/swaggest/json-schema/src/Constraint/Format.php',
+    'Swaggest\\JsonSchema\\Constraint\\Format\\IdnHostname' => $vendorDir . '/swaggest/json-schema/src/Constraint/Format/IdnHostname.php',
+    'Swaggest\\JsonSchema\\Constraint\\Format\\Iri' => $vendorDir . '/swaggest/json-schema/src/Constraint/Format/Iri.php',
+    'Swaggest\\JsonSchema\\Constraint\\Format\\Uri' => $vendorDir . '/swaggest/json-schema/src/Constraint/Format/Uri.php',
+    'Swaggest\\JsonSchema\\Constraint\\Properties' => $vendorDir . '/swaggest/json-schema/src/Constraint/Properties.php',
+    'Swaggest\\JsonSchema\\Constraint\\Ref' => $vendorDir . '/swaggest/json-schema/src/Constraint/Ref.php',
+    'Swaggest\\JsonSchema\\Constraint\\Type' => $vendorDir . '/swaggest/json-schema/src/Constraint/Type.php',
+    'Swaggest\\JsonSchema\\Constraint\\UniqueItems' => $vendorDir . '/swaggest/json-schema/src/Constraint/UniqueItems.php',
+    'Swaggest\\JsonSchema\\Context' => $vendorDir . '/swaggest/json-schema/src/Context.php',
+    'Swaggest\\JsonSchema\\DataPreProcessor' => $vendorDir . '/swaggest/json-schema/src/DataPreProcessor.php',
+    'Swaggest\\JsonSchema\\Exception' => $vendorDir . '/swaggest/json-schema/src/Exception.php',
+    'Swaggest\\JsonSchema\\Exception\\ArrayException' => $vendorDir . '/swaggest/json-schema/src/Exception/ArrayException.php',
+    'Swaggest\\JsonSchema\\Exception\\ConstException' => $vendorDir . '/swaggest/json-schema/src/Exception/ConstException.php',
+    'Swaggest\\JsonSchema\\Exception\\ContentException' => $vendorDir . '/swaggest/json-schema/src/Exception/ContentException.php',
+    'Swaggest\\JsonSchema\\Exception\\EnumException' => $vendorDir . '/swaggest/json-schema/src/Exception/EnumException.php',
+    'Swaggest\\JsonSchema\\Exception\\Error' => $vendorDir . '/swaggest/json-schema/src/Exception/Error.php',
+    'Swaggest\\JsonSchema\\Exception\\LogicException' => $vendorDir . '/swaggest/json-schema/src/Exception/LogicException.php',
+    'Swaggest\\JsonSchema\\Exception\\NumericException' => $vendorDir . '/swaggest/json-schema/src/Exception/NumericException.php',
+    'Swaggest\\JsonSchema\\Exception\\ObjectException' => $vendorDir . '/swaggest/json-schema/src/Exception/ObjectException.php',
+    'Swaggest\\JsonSchema\\Exception\\StringException' => $vendorDir . '/swaggest/json-schema/src/Exception/StringException.php',
+    'Swaggest\\JsonSchema\\Exception\\TypeException' => $vendorDir . '/swaggest/json-schema/src/Exception/TypeException.php',
+    'Swaggest\\JsonSchema\\Helper' => $vendorDir . '/swaggest/json-schema/src/Helper.php',
+    'Swaggest\\JsonSchema\\InvalidValue' => $vendorDir . '/swaggest/json-schema/src/InvalidValue.php',
+    'Swaggest\\JsonSchema\\JsonSchema' => $vendorDir . '/swaggest/json-schema/src/JsonSchema.php',
+    'Swaggest\\JsonSchema\\MagicMap' => $vendorDir . '/swaggest/json-schema/src/MagicMap.php',
+    'Swaggest\\JsonSchema\\MagicMapTrait' => $vendorDir . '/swaggest/json-schema/src/MagicMapTrait.php',
+    'Swaggest\\JsonSchema\\Meta\\AbstractMeta' => $vendorDir . '/swaggest/json-schema/src/Meta/AbstractMeta.php',
+    'Swaggest\\JsonSchema\\Meta\\MetaHolder' => $vendorDir . '/swaggest/json-schema/src/Meta/MetaHolder.php',
+    'Swaggest\\JsonSchema\\NameMirror' => $vendorDir . '/swaggest/json-schema/src/NameMirror.php',
+    'Swaggest\\JsonSchema\\Path\\PointerUtil' => $vendorDir . '/swaggest/json-schema/src/Path/PointerUtil.php',
+    'Swaggest\\JsonSchema\\RefResolver' => $vendorDir . '/swaggest/json-schema/src/RefResolver.php',
+    'Swaggest\\JsonSchema\\RemoteRefProvider' => $vendorDir . '/swaggest/json-schema/src/RemoteRefProvider.php',
+    'Swaggest\\JsonSchema\\RemoteRef\\BasicFetcher' => $vendorDir . '/swaggest/json-schema/src/RemoteRef/BasicFetcher.php',
+    'Swaggest\\JsonSchema\\RemoteRef\\Preloaded' => $vendorDir . '/swaggest/json-schema/src/RemoteRef/Preloaded.php',
+    'Swaggest\\JsonSchema\\Schema' => $vendorDir . '/swaggest/json-schema/src/Schema.php',
+    'Swaggest\\JsonSchema\\SchemaContract' => $vendorDir . '/swaggest/json-schema/src/SchemaContract.php',
+    'Swaggest\\JsonSchema\\SchemaExporter' => $vendorDir . '/swaggest/json-schema/src/SchemaExporter.php',
+    'Swaggest\\JsonSchema\\Structure\\ClassStructure' => $vendorDir . '/swaggest/json-schema/src/Structure/ClassStructure.php',
+    'Swaggest\\JsonSchema\\Structure\\ClassStructureContract' => $vendorDir . '/swaggest/json-schema/src/Structure/ClassStructureContract.php',
+    'Swaggest\\JsonSchema\\Structure\\ClassStructureTrait' => $vendorDir . '/swaggest/json-schema/src/Structure/ClassStructureTrait.php',
+    'Swaggest\\JsonSchema\\Structure\\Composition' => $vendorDir . '/swaggest/json-schema/src/Structure/Composition.php',
+    'Swaggest\\JsonSchema\\Structure\\Egg' => $vendorDir . '/swaggest/json-schema/src/Structure/Egg.php',
+    'Swaggest\\JsonSchema\\Structure\\Nested' => $vendorDir . '/swaggest/json-schema/src/Structure/Nested.php',
+    'Swaggest\\JsonSchema\\Structure\\ObjectItem' => $vendorDir . '/swaggest/json-schema/src/Structure/ObjectItem.php',
+    'Swaggest\\JsonSchema\\Structure\\ObjectItemContract' => $vendorDir . '/swaggest/json-schema/src/Structure/ObjectItemContract.php',
+    'Swaggest\\JsonSchema\\Structure\\ObjectItemTrait' => $vendorDir . '/swaggest/json-schema/src/Structure/ObjectItemTrait.php',
+    'Swaggest\\JsonSchema\\Structure\\WithResolvedValue' => $vendorDir . '/swaggest/json-schema/src/Structure/WithResolvedValue.php',
+    'Swaggest\\JsonSchema\\Wrapper' => $vendorDir . '/swaggest/json-schema/src/Wrapper.php',
     'Symfony\\Component\\Console\\Application' => $vendorDir . '/symfony/console/Application.php',
     'Symfony\\Component\\Console\\Color' => $vendorDir . '/symfony/console/Color.php',
     'Symfony\\Component\\Console\\CommandLoader\\CommandLoaderInterface' => $vendorDir . '/symfony/console/CommandLoader/CommandLoaderInterface.php',
diff --git a/composer/autoload_psr4.php b/composer/autoload_psr4.php
index 0686773..63e30d1 100644
--- a/composer/autoload_psr4.php
+++ b/composer/autoload_psr4.php
@@ -47,6 +47,8 @@ return array(
     'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'),
     'Symfony\\Component\\Debug\\' => array($vendorDir . '/symfony/debug'),
     'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'),
+    'Swaggest\\JsonSchema\\' => array($vendorDir . '/swaggest/json-schema/src'),
+    'Swaggest\\JsonDiff\\' => array($vendorDir . '/swaggest/json-diff/src'),
     'Shellbox\\' => array($vendorDir . '/wikimedia/shellbox/src'),
     'Serializers\\' => array($vendorDir . '/serialization/serialization/src/Serializers'),
     'Safe\\' => array($vendorDir . '/thecodingmachine/safe/lib', $vendorDir . '/thecodingmachine/safe/deprecated', $vendorDir . '/thecodingmachine/safe/generated'),
@@ -62,6 +64,7 @@ return array(
     'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
     'Pleo\\BloomFilter\\' => array($vendorDir . '/pleonasm/bloom-filter/src'),
     'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'),
+    'PhpLang\\' => array($vendorDir . '/phplang/scope-exit/src'),
     'Onoi\\MessageReporter\\' => array($vendorDir . '/onoi/message-reporter/src'),
     'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'),
     'MaxMind\\WebService\\' => array($vendorDir . '/maxmind/web-service-common/src/WebService'),
diff --git a/composer/autoload_static.php b/composer/autoload_static.php
index a22d716..b3ec1f6 100644
--- a/composer/autoload_static.php
+++ b/composer/autoload_static.php
@@ -179,6 +179,8 @@ class ComposerStaticInit_mediawiki_vendor
             'Symfony\\Component\\Process\\' => 26,
             'Symfony\\Component\\Debug\\' => 24,
             'Symfony\\Component\\Console\\' => 26,
+            'Swaggest\\JsonSchema\\' => 20,
+            'Swaggest\\JsonDiff\\' => 18,
             'Shellbox\\' => 9,
             'Serializers\\' => 12,
             'Safe\\' => 5,
@@ -200,6 +202,7 @@ class ComposerStaticInit_mediawiki_vendor
             'Psr\\Container\\' => 14,
             'Pleo\\BloomFilter\\' => 17,
             'PhpParser\\' => 10,
+            'PhpLang\\' => 8,
         ),
         'O' => 
         array (
@@ -452,6 +455,14 @@ class ComposerStaticInit_mediawiki_vendor
         array (
             0 => __DIR__ . '/..' . '/symfony/console',
         ),
+        'Swaggest\\JsonSchema\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/swaggest/json-schema/src',
+        ),
+        'Swaggest\\JsonDiff\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/swaggest/json-diff/src',
+        ),
         'Shellbox\\' => 
         array (
             0 => __DIR__ . '/..' . '/wikimedia/shellbox/src',
@@ -515,6 +526,10 @@ class ComposerStaticInit_mediawiki_vendor
         array (
             0 => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser',
         ),
+        'PhpLang\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/phplang/scope-exit/src',
+        ),
         'Onoi\\MessageReporter\\' => 
         array (
             0 => __DIR__ . '/..' . '/onoi/message-reporter/src',
@@ -1626,6 +1641,40 @@ class ComposerStaticInit_mediawiki_vendor
         'GuzzleHttp\\Utils' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Utils.php',
         'HtmlFormatter\\HtmlFormatter' => __DIR__ . '/..' . '/wikimedia/html-formatter/src/HtmlFormatter.php',
         'Image_XMP' => __DIR__ . '/..' . '/james-heinrich/getid3/getid3/module.tag.xmp.php',
+        'JakubOnderka\\PhpParallelLint\\Application' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Application.php',
+        'JakubOnderka\\PhpParallelLint\\ArrayIterator' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Settings.php',
+        'JakubOnderka\\PhpParallelLint\\Blame' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Error.php',
+        'JakubOnderka\\PhpParallelLint\\CheckstyleOutput' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Output.php',
+        'JakubOnderka\\PhpParallelLint\\ConsoleWriter' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Output.php',
+        'JakubOnderka\\PhpParallelLint\\Contracts\\SyntaxErrorCallback' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Contracts/SyntaxErrorCallback.php',
+        'JakubOnderka\\PhpParallelLint\\Error' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Error.php',
+        'JakubOnderka\\PhpParallelLint\\ErrorFormatter' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/ErrorFormatter.php',
+        'JakubOnderka\\PhpParallelLint\\Exception' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/exceptions.php',
+        'JakubOnderka\\PhpParallelLint\\FileWriter' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Output.php',
+        'JakubOnderka\\PhpParallelLint\\IWriter' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Output.php',
+        'JakubOnderka\\PhpParallelLint\\InvalidArgumentException' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/exceptions.php',
+        'JakubOnderka\\PhpParallelLint\\JsonOutput' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Output.php',
+        'JakubOnderka\\PhpParallelLint\\Manager' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Manager.php',
+        'JakubOnderka\\PhpParallelLint\\MultipleWriter' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Output.php',
+        'JakubOnderka\\PhpParallelLint\\NotExistsClassException' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/exceptions.php',
+        'JakubOnderka\\PhpParallelLint\\NotExistsPathException' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/exceptions.php',
+        'JakubOnderka\\PhpParallelLint\\NotImplementCallbackException' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/exceptions.php',
+        'JakubOnderka\\PhpParallelLint\\NullWriter' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Output.php',
+        'JakubOnderka\\PhpParallelLint\\Output' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Output.php',
+        'JakubOnderka\\PhpParallelLint\\ParallelLint' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/ParallelLint.php',
+        'JakubOnderka\\PhpParallelLint\\Process\\GitBlameProcess' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Process/GitBlameProcess.php',
+        'JakubOnderka\\PhpParallelLint\\Process\\LintProcess' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Process/LintProcess.php',
+        'JakubOnderka\\PhpParallelLint\\Process\\PhpExecutable' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Process/PhpExecutable.php',
+        'JakubOnderka\\PhpParallelLint\\Process\\PhpProcess' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Process/PhpProcess.php',
+        'JakubOnderka\\PhpParallelLint\\Process\\Process' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Process/Process.php',
+        'JakubOnderka\\PhpParallelLint\\Process\\SkipLintProcess' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Process/SkipLintProcess.php',
+        'JakubOnderka\\PhpParallelLint\\RecursiveDirectoryFilterIterator' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Manager.php',
+        'JakubOnderka\\PhpParallelLint\\Result' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Result.php',
+        'JakubOnderka\\PhpParallelLint\\RunTimeException' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/exceptions.php',
+        'JakubOnderka\\PhpParallelLint\\Settings' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Settings.php',
+        'JakubOnderka\\PhpParallelLint\\SyntaxError' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Error.php',
+        'JakubOnderka\\PhpParallelLint\\TextOutput' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Output.php',
+        'JakubOnderka\\PhpParallelLint\\TextOutputColored' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Output.php',
         'JsonException' => __DIR__ . '/..' . '/symfony/polyfill-php73/Resources/stubs/JsonException.php',
         'JsonSchema\\Constraints\\BaseConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/BaseConstraint.php',
         'JsonSchema\\Constraints\\CollectionConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/CollectionConstraint.php',
@@ -1670,6 +1719,7 @@ class ComposerStaticInit_mediawiki_vendor
         'JsonSchema\\Uri\\UriResolver' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Uri/UriResolver.php',
         'JsonSchema\\Uri\\UriRetriever' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Uri/UriRetriever.php',
         'JsonSchema\\Validator' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Validator.php',
+        'JsonSerializable' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/polyfill.php',
         'Kafka\\Client' => __DIR__ . '/..' . '/nmred/kafka-php/src/Kafka/Client.php',
         'Kafka\\ClusterMetaData' => __DIR__ . '/..' . '/nmred/kafka-php/src/Kafka/ClusterMetaData.php',
         'Kafka\\Consumer' => __DIR__ . '/..' . '/nmred/kafka-php/src/Kafka/Consumer.php',
@@ -2196,6 +2246,7 @@ class ComposerStaticInit_mediawiki_vendor
         'PEAR_Error' => __DIR__ . '/..' . '/pear/pear-core-minimal/src/PEAR.php',
         'PEAR_ErrorStack' => __DIR__ . '/..' . '/pear/pear-core-minimal/src/PEAR/ErrorStack.php',
         'PEAR_Exception' => __DIR__ . '/..' . '/pear/pear_exception/PEAR/Exception.php',
+        'PhpLang\\ScopeExit' => __DIR__ . '/..' . '/phplang/scope-exit/src/ScopeExit.php',
         'PhpParser\\Builder' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder.php',
         'PhpParser\\BuilderFactory' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/BuilderFactory.php',
         'PhpParser\\BuilderHelpers' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/BuilderHelpers.php',
@@ -3006,6 +3057,73 @@ class ComposerStaticInit_mediawiki_vendor
         'Shellbox\\ShellboxError' => __DIR__ . '/..' . '/wikimedia/shellbox/src/ShellboxError.php',
         'Shellbox\\TempDirManager' => __DIR__ . '/..' . '/wikimedia/shellbox/src/TempDirManager.php',
         'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
+        'Swaggest\\JsonDiff\\Exception' => __DIR__ . '/..' . '/swaggest/json-diff/src/Exception.php',
+        'Swaggest\\JsonDiff\\JsonDiff' => __DIR__ . '/..' . '/swaggest/json-diff/src/JsonDiff.php',
+        'Swaggest\\JsonDiff\\JsonHash' => __DIR__ . '/..' . '/swaggest/json-diff/src/JsonHash.php',
+        'Swaggest\\JsonDiff\\JsonMergePatch' => __DIR__ . '/..' . '/swaggest/json-diff/src/JsonMergePatch.php',
+        'Swaggest\\JsonDiff\\JsonPatch' => __DIR__ . '/..' . '/swaggest/json-diff/src/JsonPatch.php',
+        'Swaggest\\JsonDiff\\JsonPatch\\Add' => __DIR__ . '/..' . '/swaggest/json-diff/src/JsonPatch/Add.php',
+        'Swaggest\\JsonDiff\\JsonPatch\\Copy' => __DIR__ . '/..' . '/swaggest/json-diff/src/JsonPatch/Copy.php',
+        'Swaggest\\JsonDiff\\JsonPatch\\Move' => __DIR__ . '/..' . '/swaggest/json-diff/src/JsonPatch/Move.php',
+        'Swaggest\\JsonDiff\\JsonPatch\\OpPath' => __DIR__ . '/..' . '/swaggest/json-diff/src/JsonPatch/OpPath.php',
+        'Swaggest\\JsonDiff\\JsonPatch\\OpPathFrom' => __DIR__ . '/..' . '/swaggest/json-diff/src/JsonPatch/OpPathFrom.php',
+        'Swaggest\\JsonDiff\\JsonPatch\\OpPathValue' => __DIR__ . '/..' . '/swaggest/json-diff/src/JsonPatch/OpPathValue.php',
+        'Swaggest\\JsonDiff\\JsonPatch\\Remove' => __DIR__ . '/..' . '/swaggest/json-diff/src/JsonPatch/Remove.php',
+        'Swaggest\\JsonDiff\\JsonPatch\\Replace' => __DIR__ . '/..' . '/swaggest/json-diff/src/JsonPatch/Replace.php',
+        'Swaggest\\JsonDiff\\JsonPatch\\Test' => __DIR__ . '/..' . '/swaggest/json-diff/src/JsonPatch/Test.php',
+        'Swaggest\\JsonDiff\\JsonPointer' => __DIR__ . '/..' . '/swaggest/json-diff/src/JsonPointer.php',
+        'Swaggest\\JsonDiff\\JsonValueReplace' => __DIR__ . '/..' . '/swaggest/json-diff/src/JsonValueReplace.php',
+        'Swaggest\\JsonDiff\\ModifiedPathDiff' => __DIR__ . '/..' . '/swaggest/json-diff/src/ModifiedPathDiff.php',
+        'Swaggest\\JsonSchema\\Constraint\\Constraint' => __DIR__ . '/..' . '/swaggest/json-schema/src/Constraint/Constraint.php',
+        'Swaggest\\JsonSchema\\Constraint\\Content' => __DIR__ . '/..' . '/swaggest/json-schema/src/Constraint/Content.php',
+        'Swaggest\\JsonSchema\\Constraint\\Format' => __DIR__ . '/..' . '/swaggest/json-schema/src/Constraint/Format.php',
+        'Swaggest\\JsonSchema\\Constraint\\Format\\IdnHostname' => __DIR__ . '/..' . '/swaggest/json-schema/src/Constraint/Format/IdnHostname.php',
+        'Swaggest\\JsonSchema\\Constraint\\Format\\Iri' => __DIR__ . '/..' . '/swaggest/json-schema/src/Constraint/Format/Iri.php',
+        'Swaggest\\JsonSchema\\Constraint\\Format\\Uri' => __DIR__ . '/..' . '/swaggest/json-schema/src/Constraint/Format/Uri.php',
+        'Swaggest\\JsonSchema\\Constraint\\Properties' => __DIR__ . '/..' . '/swaggest/json-schema/src/Constraint/Properties.php',
+        'Swaggest\\JsonSchema\\Constraint\\Ref' => __DIR__ . '/..' . '/swaggest/json-schema/src/Constraint/Ref.php',
+        'Swaggest\\JsonSchema\\Constraint\\Type' => __DIR__ . '/..' . '/swaggest/json-schema/src/Constraint/Type.php',
+        'Swaggest\\JsonSchema\\Constraint\\UniqueItems' => __DIR__ . '/..' . '/swaggest/json-schema/src/Constraint/UniqueItems.php',
+        'Swaggest\\JsonSchema\\Context' => __DIR__ . '/..' . '/swaggest/json-schema/src/Context.php',
+        'Swaggest\\JsonSchema\\DataPreProcessor' => __DIR__ . '/..' . '/swaggest/json-schema/src/DataPreProcessor.php',
+        'Swaggest\\JsonSchema\\Exception' => __DIR__ . '/..' . '/swaggest/json-schema/src/Exception.php',
+        'Swaggest\\JsonSchema\\Exception\\ArrayException' => __DIR__ . '/..' . '/swaggest/json-schema/src/Exception/ArrayException.php',
+        'Swaggest\\JsonSchema\\Exception\\ConstException' => __DIR__ . '/..' . '/swaggest/json-schema/src/Exception/ConstException.php',
+        'Swaggest\\JsonSchema\\Exception\\ContentException' => __DIR__ . '/..' . '/swaggest/json-schema/src/Exception/ContentException.php',
+        'Swaggest\\JsonSchema\\Exception\\EnumException' => __DIR__ . '/..' . '/swaggest/json-schema/src/Exception/EnumException.php',
+        'Swaggest\\JsonSchema\\Exception\\Error' => __DIR__ . '/..' . '/swaggest/json-schema/src/Exception/Error.php',
+        'Swaggest\\JsonSchema\\Exception\\LogicException' => __DIR__ . '/..' . '/swaggest/json-schema/src/Exception/LogicException.php',
+        'Swaggest\\JsonSchema\\Exception\\NumericException' => __DIR__ . '/..' . '/swaggest/json-schema/src/Exception/NumericException.php',
+        'Swaggest\\JsonSchema\\Exception\\ObjectException' => __DIR__ . '/..' . '/swaggest/json-schema/src/Exception/ObjectException.php',
+        'Swaggest\\JsonSchema\\Exception\\StringException' => __DIR__ . '/..' . '/swaggest/json-schema/src/Exception/StringException.php',
+        'Swaggest\\JsonSchema\\Exception\\TypeException' => __DIR__ . '/..' . '/swaggest/json-schema/src/Exception/TypeException.php',
+        'Swaggest\\JsonSchema\\Helper' => __DIR__ . '/..' . '/swaggest/json-schema/src/Helper.php',
+        'Swaggest\\JsonSchema\\InvalidValue' => __DIR__ . '/..' . '/swaggest/json-schema/src/InvalidValue.php',
+        'Swaggest\\JsonSchema\\JsonSchema' => __DIR__ . '/..' . '/swaggest/json-schema/src/JsonSchema.php',
+        'Swaggest\\JsonSchema\\MagicMap' => __DIR__ . '/..' . '/swaggest/json-schema/src/MagicMap.php',
+        'Swaggest\\JsonSchema\\MagicMapTrait' => __DIR__ . '/..' . '/swaggest/json-schema/src/MagicMapTrait.php',
+        'Swaggest\\JsonSchema\\Meta\\AbstractMeta' => __DIR__ . '/..' . '/swaggest/json-schema/src/Meta/AbstractMeta.php',
+        'Swaggest\\JsonSchema\\Meta\\MetaHolder' => __DIR__ . '/..' . '/swaggest/json-schema/src/Meta/MetaHolder.php',
+        'Swaggest\\JsonSchema\\NameMirror' => __DIR__ . '/..' . '/swaggest/json-schema/src/NameMirror.php',
+        'Swaggest\\JsonSchema\\Path\\PointerUtil' => __DIR__ . '/..' . '/swaggest/json-schema/src/Path/PointerUtil.php',
+        'Swaggest\\JsonSchema\\RefResolver' => __DIR__ . '/..' . '/swaggest/json-schema/src/RefResolver.php',
+        'Swaggest\\JsonSchema\\RemoteRefProvider' => __DIR__ . '/..' . '/swaggest/json-schema/src/RemoteRefProvider.php',
+        'Swaggest\\JsonSchema\\RemoteRef\\BasicFetcher' => __DIR__ . '/..' . '/swaggest/json-schema/src/RemoteRef/BasicFetcher.php',
+        'Swaggest\\JsonSchema\\RemoteRef\\Preloaded' => __DIR__ . '/..' . '/swaggest/json-schema/src/RemoteRef/Preloaded.php',
+        'Swaggest\\JsonSchema\\Schema' => __DIR__ . '/..' . '/swaggest/json-schema/src/Schema.php',
+        'Swaggest\\JsonSchema\\SchemaContract' => __DIR__ . '/..' . '/swaggest/json-schema/src/SchemaContract.php',
+        'Swaggest\\JsonSchema\\SchemaExporter' => __DIR__ . '/..' . '/swaggest/json-schema/src/SchemaExporter.php',
+        'Swaggest\\JsonSchema\\Structure\\ClassStructure' => __DIR__ . '/..' . '/swaggest/json-schema/src/Structure/ClassStructure.php',
+        'Swaggest\\JsonSchema\\Structure\\ClassStructureContract' => __DIR__ . '/..' . '/swaggest/json-schema/src/Structure/ClassStructureContract.php',
+        'Swaggest\\JsonSchema\\Structure\\ClassStructureTrait' => __DIR__ . '/..' . '/swaggest/json-schema/src/Structure/ClassStructureTrait.php',
+        'Swaggest\\JsonSchema\\Structure\\Composition' => __DIR__ . '/..' . '/swaggest/json-schema/src/Structure/Composition.php',
+        'Swaggest\\JsonSchema\\Structure\\Egg' => __DIR__ . '/..' . '/swaggest/json-schema/src/Structure/Egg.php',
+        'Swaggest\\JsonSchema\\Structure\\Nested' => __DIR__ . '/..' . '/swaggest/json-schema/src/Structure/Nested.php',
+        'Swaggest\\JsonSchema\\Structure\\ObjectItem' => __DIR__ . '/..' . '/swaggest/json-schema/src/Structure/ObjectItem.php',
+        'Swaggest\\JsonSchema\\Structure\\ObjectItemContract' => __DIR__ . '/..' . '/swaggest/json-schema/src/Structure/ObjectItemContract.php',
+        'Swaggest\\JsonSchema\\Structure\\ObjectItemTrait' => __DIR__ . '/..' . '/swaggest/json-schema/src/Structure/ObjectItemTrait.php',
+        'Swaggest\\JsonSchema\\Structure\\WithResolvedValue' => __DIR__ . '/..' . '/swaggest/json-schema/src/Structure/WithResolvedValue.php',
+        'Swaggest\\JsonSchema\\Wrapper' => __DIR__ . '/..' . '/swaggest/json-schema/src/Wrapper.php',
         'Symfony\\Component\\Console\\Application' => __DIR__ . '/..' . '/symfony/console/Application.php',
         'Symfony\\Component\\Console\\Color' => __DIR__ . '/..' . '/symfony/console/Color.php',
         'Symfony\\Component\\Console\\CommandLoader\\CommandLoaderInterface' => __DIR__ . '/..' . '/symfony/console/CommandLoader/CommandLoaderInterface.php',
diff --git a/composer/installed.json b/composer/installed.json
index 6a22402..808c3c2 100644
--- a/composer/installed.json
+++ b/composer/installed.json
@@ -3154,6 +3154,107 @@
             "exception"
         ]
     },
+    {
+        "name": "php-parallel-lint/php-parallel-lint",
+        "version": "v1.3.0",
+        "version_normalized": "1.3.0.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/php-parallel-lint/PHP-Parallel-Lint.git",
+            "reference": "772a954e5f119f6f5871d015b23eabed8cbdadfb"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/php-parallel-lint/PHP-Parallel-Lint/zipball/772a954e5f119f6f5871d015b23eabed8cbdadfb",
+            "reference": "772a954e5f119f6f5871d015b23eabed8cbdadfb",
+            "shasum": ""
+        },
+        "require": {
+            "ext-json": "*",
+            "php": ">=5.3.0"
+        },
+        "replace": {
+            "grogy/php-parallel-lint": "*",
+            "jakub-onderka/php-parallel-lint": "*"
+        },
+        "require-dev": {
+            "nette/tester": "^1.3 || ^2.0",
+            "php-parallel-lint/php-console-highlighter": "~0.3",
+            "squizlabs/php_codesniffer": "^3.5"
+        },
+        "suggest": {
+            "php-parallel-lint/php-console-highlighter": "Highlight syntax in code snippet"
+        },
+        "time": "2021-04-07T14:42:48+00:00",
+        "bin": [
+            "parallel-lint"
+        ],
+        "type": "library",
+        "installation-source": "dist",
+        "autoload": {
+            "classmap": [
+                "./"
+            ]
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "BSD-2-Clause"
+        ],
+        "authors": [
+            {
+                "name": "Jakub Onderka",
+                "email": "ahoj@jakubonderka.cz"
+            }
+        ],
+        "description": "This tool check syntax of PHP files about 20x faster than serial check.",
+        "homepage": "https://github.com/php-parallel-lint/PHP-Parallel-Lint"
+    },
+    {
+        "name": "phplang/scope-exit",
+        "version": "1.0.0",
+        "version_normalized": "1.0.0.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/phplang/scope-exit.git",
+            "reference": "239b73abe89f9414aa85a7ca075ec9445629192b"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/phplang/scope-exit/zipball/239b73abe89f9414aa85a7ca075ec9445629192b",
+            "reference": "239b73abe89f9414aa85a7ca075ec9445629192b",
+            "shasum": ""
+        },
+        "require-dev": {
+            "phpunit/phpunit": "*"
+        },
+        "time": "2016-09-17T00:15:18+00:00",
+        "type": "library",
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "PhpLang\\": "src/"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "BSD"
+        ],
+        "authors": [
+            {
+                "name": "Sara Golemon",
+                "email": "pollita@php.net",
+                "homepage": "https://twitter.com/SaraMG",
+                "role": "Developer"
+            }
+        ],
+        "description": "Emulation of SCOPE_EXIT construct from C++",
+        "homepage": "https://github.com/phplang/scope-exit",
+        "keywords": [
+            "cleanup",
+            "exit",
+            "scope"
+        ]
+    },
     {
         "name": "pimple/pimple",
         "version": "v3.3.1",
@@ -4126,6 +4227,93 @@
             }
         ]
     },
+    {
+        "name": "swaggest/json-diff",
+        "version": "v3.8.1",
+        "version_normalized": "3.8.1.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/swaggest/json-diff.git",
+            "reference": "d2184358c5ef5ecaa1f6b4c2bce175fac2d25670"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/swaggest/json-diff/zipball/d2184358c5ef5ecaa1f6b4c2bce175fac2d25670",
+            "reference": "d2184358c5ef5ecaa1f6b4c2bce175fac2d25670",
+            "shasum": ""
+        },
+        "require": {
+            "ext-json": "*"
+        },
+        "require-dev": {
+            "phpunit/phpunit": "^4.8.23"
+        },
+        "time": "2020-09-25T17:47:07+00:00",
+        "type": "library",
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "Swaggest\\JsonDiff\\": "src/"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Viacheslav Poturaev",
+                "email": "vearutop@gmail.com"
+            }
+        ],
+        "description": "JSON diff/rearrange/patch/pointer library for PHP"
+    },
+    {
+        "name": "swaggest/json-schema",
+        "version": "v0.12.29",
+        "version_normalized": "0.12.29.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/swaggest/php-json-schema.git",
+            "reference": "7564d4a5fc8c068479698a30e5a7c589ea32a9c6"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/swaggest/php-json-schema/zipball/7564d4a5fc8c068479698a30e5a7c589ea32a9c6",
+            "reference": "7564d4a5fc8c068479698a30e5a7c589ea32a9c6",
+            "shasum": ""
+        },
+        "require": {
+            "ext-json": "*",
+            "ext-mbstring": "*",
+            "php": ">=5.4",
+            "phplang/scope-exit": "^1.0",
+            "swaggest/json-diff": "^3.5.1"
+        },
+        "require-dev": {
+            "phpunit/php-code-coverage": "2.2.4",
+            "phpunit/phpunit": "4.8.35"
+        },
+        "time": "2020-03-19T08:41:40+00:00",
+        "type": "library",
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "Swaggest\\JsonSchema\\": "src/"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Viacheslav Poturaev",
+                "email": "vearutop@gmail.com"
+            }
+        ],
+        "description": "High definition PHP structures with JSON-schema based validation"
+    },
     {
         "name": "symfony/console",
         "version": "v5.2.2",
diff --git a/php-parallel-lint/php-parallel-lint/CHANGELOG.md b/php-parallel-lint/php-parallel-lint/CHANGELOG.md
new file mode 100644
index 0000000..16c15e5
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/CHANGELOG.md
@@ -0,0 +1,66 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
+
+## [Unreleased]
+
+[Unreleased]: https://github.com/php-parallel-lint/PHP-Parallel-Lint/compare/v1.3.0...HEAD
+
+## [1.3.0] - 2021-04-07
+
+### Added
+
+- Allow for multi-part file extensions to be passed using -e (like `-e php,php.dist`) from [@jrfnl](https://github.com/jrfnl).
+- Added syntax error callback [#30](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/30) from [@arxeiss](https://github.com/arxeiss).
+- Ignore PHP startup errors [#34](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/34) from [@jrfnl](https://github.com/jrfnl).
+- Restore php 5.3 support [#51](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/51) from [@glensc](https://github.com/glensc).
+
+### Fixed
+
+- Determine skip lint process failure by status code instead of stderr content [#48](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/48) from [@jankonas](https://github.com/jankonas).
+
+### Changed
+
+- Improve wording in the readme [#52](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/52) from [@glensc](https://github.com/glensc).
+
+### Internal
+
+- Normalized composer.json from [@OndraM](https://github.com/OndraM).
+- Updated PHPCS dependency from [@jrfnl](https://github.com/jrfnl).
+- Cleaned coding style from [@jrfnl](https://github.com/jrfnl).
+- Provide one true way to run the test suite [#37](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/37) from [@mfn](https://github.com/mfn).
+- Travis: add build against PHP 8.0 and fix failing test [#41](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/41) from [@jrfnl](https://github.com/jrfnl).
+- GitHub Actions for testing, and automatic phar creation [#46](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/46) from [@roelofr](https://github.com/roelofr).
+- Add .github folder to .gitattributes export-ignore [#54](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/54) from [@glensc](https://github.com/glensc).
+- Suggest to curl composer install via HTTPS [#53](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/53) from [@reedy](https://github.com/reedy).
+- GH Actions: allow for manually triggering a workflow [#55](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/55) from [@jrfnl](https://github.com/jrfnl).
+- GH Actions: fix phar creation [#55](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/55) from [@jrfnl](https://github.com/jrfnl).
+- GH Actions: run the tests against all supported PHP versions [#55](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/55) from [@jrfnl](https://github.com/jrfnl).
+- GH Actions: report CS violations in the PR [#55](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/55) from [@jrfnl](https://github.com/jrfnl).
+
+[1.3.0]: https://github.com/php-parallel-lint/PHP-Parallel-Lint/compare/v1.2.0...v1.3.0
+
+## [1.2.0] - 2020-04-04
+
+### Added
+
+- Added changelog.
+
+### Fixed
+
+- Fixed vendor location for running from other folder from [@Erkens](https://github.com/Erkens).
+
+### Internal
+
+- Added a .gitattributes file from [@jrfnl](https://github.com/jrfnl), thanks for issue to [@ondrejmirtes](https://github.com/ondrejmirtes).
+- Fixed incorrect unit tests from [@jrfnl](https://github.com/jrfnl).
+- Fixed minor grammatical errors from [@jrfnl](https://github.com/jrfnl).
+- Added Travis: test against nightly (= PHP 8) from [@jrfnl](https://github.com/jrfnl).
+- Travis: removed sudo from [@jrfnl](https://github.com/jrfnl).
+- Added info about installing like not a dependency.
+- Cleaned readme - new organization from previous package.
+- Added checklist for new version from [@szepeviktor](https://github.com/szepeviktor).
+
+[1.2.0]: https://github.com/php-parallel-lint/PHP-Parallel-Lint/compare/v1.1.0...v1.2.0
diff --git a/php-parallel-lint/php-parallel-lint/LICENSE b/php-parallel-lint/php-parallel-lint/LICENSE
new file mode 100644
index 0000000..09429bb
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/LICENSE
@@ -0,0 +1,26 @@
+Copyright (c) 2012, Jakub Onderka
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+The views and conclusions contained in the software and documentation are those
+of the authors and should not be interpreted as representing official policies,
+either expressed or implied, of the FreeBSD Project.
diff --git a/php-parallel-lint/php-parallel-lint/README.md b/php-parallel-lint/php-parallel-lint/README.md
new file mode 100644
index 0000000..e5c762f
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/README.md
@@ -0,0 +1,111 @@
+# PHP Parallel Lint
+
+[![Downloads this Month](https://img.shields.io/packagist/dm/php-parallel-lint/php-parallel-lint.svg)](https://packagist.org/packages/php-parallel-lint/php-parallel-lint)
+[![Build Status](https://github.com/php-parallel-lint/PHP-Parallel-Lint/actions/workflows/test.yml/badge.svg)](https://github.com/php-parallel-lint/PHP-Parallel-Lint/actions/workflows/test.yml)
+[![License](https://poser.pugx.org/php-parallel-lint/php-parallel-lint/license.svg)](https://packagist.org/packages/php-parallel-lint/php-parallel-lint)
+
+This application checks syntax of PHP files in parallel.
+It can output in plain text, colored text, json and checksyntax formats.
+Additionally `blame` can be used to show commits that introduced the breakage.
+
+Running parallel jobs in PHP is inspired by Nette framework tests.
+
+The application is officially supported for use with PHP 5.3 to 8.0.
+
+## Table of contents
+
+1. [Installation](#installation)
+2. [Example output](#example-output)
+3. [History](#history)
+4. [Command line options](#command-line-options)
+5. [Recommended excludes for Symfony framework](#recommended-excludes-for-symfony-framework)
+6. [Create Phar package](#create-phar-package)
+7. [How to upgrade](#how-to-upgrade)
+
+## Installation
+
+Install with `composer` as development dependency:
+
+    composer require --dev php-parallel-lint/php-parallel-lint
+
+Alternatively you can install as a standalone `composer` project:
+
+    composer create-project php-parallel-lint/php-parallel-lint /path/to/folder/php-parallel-lint
+    /path/to/folder/php-parallel-lint/parallel-lint # running tool
+
+For colored output, install the suggested package `php-parallel-lint/php-console-highlighter`:
+
+    composer require --dev php-parallel-lint/php-console-highlighter
+
+## Example output
+
+![Example use of tool with error](/tests/examples/example-images/use-error.png?raw=true "Example use of tool with error")
+
+
+## History
+
+This project was originally created by [@JakubOnderka] and released as
+[jakub-onderka/php-parallel-lint].
+
+Since then, Jakub has moved on to other interests and as of January 2020, the
+second most active maintainer [@grogy] has taken over maintenance of the project
+and given the project - and related dependencies - a new home in the PHP
+Parallel Lint organisation.
+
+It is strongly recommended for existing users of the (unmaintained)
+[jakub-onderka/php-parallel-lint] package to switch their dependency to
+[php-parallel-lint/php-parallel-lint], see [How to upgrade](#how-to-upgrade) below.
+
+[php-parallel-lint/php-parallel-lint]: https://github.com/php-parallel-lint/PHP-Parallel-Lint
+[grogy/php-parallel-lint]: https://github.com/grogy/PHP-Parallel-Lint
+[jakub-onderka/php-parallel-lint]: https://github.com/JakubOnderka/PHP-Parallel-Lint
+[@JakubOnderka]: https://github.com/JakubOnderka
+[@grogy]: https://github.com/grogy
+
+## Command line options
+
+- `-p <php>`        		Specify PHP-CGI executable to run (default: 'php').
+- `-s, --short`     		Set short_open_tag to On (default: Off).
+- `-a, --asp`       		Set asp_tags to On (default: Off).
+- `-e <ext>`        		Check only files with selected extensions separated by comma. (default: php,php3,php4,php5,phtml,phpt)
+- `--exclude`       		Exclude a file or directory. If you want exclude multiple items, use multiple exclude parameters.
+- `-j <num>`        		Run <num> jobs in parallel (default: 10).
+- `--colors`        		Force enable colors in console output.
+- `--no-colors`     		Disable colors in console output.
+- `--no-progress`   		Disable progress in console output.
+- `--checkstyle`    		Output results as Checkstyle XML.
+- `--json`          		Output results as JSON string (requires PHP 5.4).
+- `--blame`         		Try to show git blame for row with error.
+- `--git <git>`     		Path to Git executable to show blame message (default: 'git').
+- `--stdin`         		Load files and folder to test from standard input.
+- `--ignore-fails`  		Ignore failed tests.
+- `--syntax-error-callback` File with syntax error callback for ability to modify error, see more in [example](doc/syntax-error-callback.md)
+- `-h, --help`      		Print this help.
+- `-V, --version`   		Display this application version.
+
+
+## Recommended excludes for Symfony framework
+
+To run from the command line:
+
+    vendor/bin/parallel-lint --exclude app --exclude vendor .
+
+## Create Phar package
+
+PHP Parallel Lint supports [Box app](https://box-project.github.io/box2/) for creating Phar package. First, install box app:
+
+
+    curl -LSs https://box-project.github.io/box2/installer.php | php
+
+
+then run the build command in parallel lint folder, which creates `parallel-lint.phar` file.
+
+
+    box build
+
+## How to upgrade
+
+Are you using `jakub-onderka/php-parallel-lint` package? You can switch to `php-parallel-lint/php-parallel-lint` using:
+
+    composer remove --dev jakub-onderka/php-parallel-lint
+    composer require --dev php-parallel-lint/php-parallel-lint
diff --git a/php-parallel-lint/php-parallel-lint/bin/skip-linting.php b/php-parallel-lint/php-parallel-lint/bin/skip-linting.php
new file mode 100644
index 0000000..2c69554
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/bin/skip-linting.php
@@ -0,0 +1,19 @@
+<?php
+$stdin = fopen('php://stdin', 'r');
+$input = stream_get_contents($stdin);
+fclose($stdin);
+
+foreach (explode(PHP_EOL, $input) as $file) {
+    $skip = false;
+    $f = @fopen($file, 'r');
+    if ($f) {
+        $firstLine = fgets($f);
+        @fclose($f);
+
+        if (preg_match('~<?php\\s*\\/\\/\s*lint\s*([^\d\s]+)\s*([^\s]+)\s*~i', $firstLine, $m)) {
+            $skip = version_compare(PHP_VERSION, $m[2], $m[1]) === false;
+        }
+    }
+
+    echo $file . ';' . ($skip ? '1' : '0') . PHP_EOL;
+}
diff --git a/php-parallel-lint/php-parallel-lint/composer.json b/php-parallel-lint/php-parallel-lint/composer.json
new file mode 100644
index 0000000..ce7ed04
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/composer.json
@@ -0,0 +1,46 @@
+{
+    "name": "php-parallel-lint/php-parallel-lint",
+    "description": "This tool check syntax of PHP files about 20x faster than serial check.",
+    "homepage": "https://github.com/php-parallel-lint/PHP-Parallel-Lint",
+    "license": "BSD-2-Clause",
+    "authors": [
+        {
+            "name": "Jakub Onderka",
+            "email": "ahoj@jakubonderka.cz"
+        }
+    ],
+    "require": {
+        "php": ">=5.3.0",
+        "ext-json": "*"
+    },
+    "replace": {
+        "grogy/php-parallel-lint": "*",
+        "jakub-onderka/php-parallel-lint": "*"
+    },
+    "require-dev": {
+        "nette/tester": "^1.3 || ^2.0",
+        "php-parallel-lint/php-console-highlighter": "~0.3",
+        "squizlabs/php_codesniffer": "^3.5"
+    },
+    "suggest": {
+        "php-parallel-lint/php-console-highlighter": "Highlight syntax in code snippet"
+    },
+    "config": {
+        "sort-packages": true
+    },
+    "autoload": {
+        "classmap": [
+            "./"
+        ]
+    },
+    "bin": [
+        "parallel-lint"
+    ],
+    "scripts": {
+        "test": "@php vendor/bin/tester -C -p php tests",
+        "testphp5": "@php vendor/bin/tester -p php tests"
+    },
+    "scripts-descriptions": {
+        "test": "Run all tests!"
+    }
+}
diff --git a/php-parallel-lint/php-parallel-lint/parallel-lint b/php-parallel-lint/php-parallel-lint/parallel-lint
new file mode 100755
index 0000000..f44e110
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/parallel-lint
@@ -0,0 +1,67 @@
+#!/usr/bin/env php
+<?php
+
+/*
+Copyright (c) 2014, Jakub Onderka
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+The views and conclusions contained in the software and documentation are those
+of the authors and should not be interpreted as representing official policies,
+either expressed or implied, of the FreeBSD Project.
+ */
+
+if (!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 50300) {
+    fwrite(STDERR, "PHP Parallel Lint requires PHP 5.3.0 or newer." . PHP_EOL);
+    exit(254);
+}
+
+$autoloadLocations = array(
+    getcwd() . '/vendor/autoload.php',
+    getcwd() . '/../../autoload.php',
+    __DIR__ . '/vendor/autoload.php',
+    __DIR__ . '/../vendor/autoload.php',
+    __DIR__ . '/../../../autoload.php',
+    __DIR__ . '/../../autoload.php',
+);
+
+$loaded = false;
+foreach ($autoloadLocations as $autoload) {
+    if (is_file($autoload)) {
+        require_once($autoload);
+        $loaded = true;
+    }
+}
+
+if (!$loaded) {
+    fwrite(STDERR,
+        'You must set up the project dependencies, run the following commands:' . PHP_EOL .
+        'curl -s https://getcomposer.org/installer | php' . PHP_EOL .
+        'php composer.phar install' . PHP_EOL
+    );
+    exit(254);
+}
+
+require_once __DIR__ . '/src/polyfill.php';
+
+$app = new JakubOnderka\PhpParallelLint\Application();
+exit($app->run());
diff --git a/php-parallel-lint/php-parallel-lint/src/Application.php b/php-parallel-lint/php-parallel-lint/src/Application.php
new file mode 100644
index 0000000..d38e42d
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/src/Application.php
@@ -0,0 +1,124 @@
+<?php
+
+namespace JakubOnderka\PhpParallelLint;
+
+class Application
+{
+    const VERSION = '1.3.0';
+
+    // Return codes
+    const SUCCESS = 0,
+        WITH_ERRORS = 1,
+        FAILED = 254; // Error code 255 is reserved for PHP itself
+
+    /**
+     * Run the application
+     * @return int Return code
+     */
+    public function run()
+    {
+        if (in_array('proc_open', explode(',', ini_get('disable_functions')))) {
+            echo "Function 'proc_open' is required, but it is disabled by the 'disable_functions' ini setting.", PHP_EOL;
+            return self::FAILED;
+        }
+
+        if (in_array('-h', $_SERVER['argv']) || in_array('--help', $_SERVER['argv'])) {
+            $this->showUsage();
+            return self::SUCCESS;
+        }
+
+        if (in_array('-V', $_SERVER['argv']) || in_array('--version', $_SERVER['argv'])) {
+            $this->showVersion();
+            return self::SUCCESS;
+        }
+
+        try {
+            $settings = Settings::parseArguments($_SERVER['argv']);
+            if ($settings->stdin) {
+                $settings->addPaths(Settings::getPathsFromStdIn());
+            }
+            if (empty($settings->paths)) {
+                $this->showUsage();
+                return self::FAILED;
+            }
+            $manager = new Manager;
+            $result = $manager->run($settings);
+            if ($settings->ignoreFails) {
+                return $result->hasSyntaxError() ? self::WITH_ERRORS : self::SUCCESS;
+            } else {
+                return $result->hasError() ? self::WITH_ERRORS : self::SUCCESS;
+            }
+
+        } catch (InvalidArgumentException $e) {
+            echo "Invalid option {$e->getArgument()}", PHP_EOL, PHP_EOL;
+            $this->showOptions();
+            return self::FAILED;
+
+        } catch (Exception $e) {
+            if (isset($settings) && $settings->format === Settings::FORMAT_JSON) {
+                echo json_encode($e);
+            } else {
+                echo $e->getMessage(), PHP_EOL;
+            }
+            return self::FAILED;
+
+        } catch (\Exception $e) {
+            echo $e->getMessage(), PHP_EOL;
+            return self::FAILED;
+        }
+    }
+
+    /**
+     * Outputs the options
+     */
+    private function showOptions()
+    {
+        echo <<<HELP
+Options:
+    -p <php>                Specify PHP-CGI executable to run (default: 'php').
+    -s, --short             Set short_open_tag to On (default: Off).
+    -a, -asp                Set asp_tags to On (default: Off).
+    -e <ext>                Check only files with selected extensions separated by comma.
+                            (default: php,php3,php4,php5,phtml,phpt)
+    --exclude               Exclude a file or directory. If you want exclude multiple items,
+                            use multiple exclude parameters.
+    -j <num>                Run <num> jobs in parallel (default: 10).
+    --colors                Enable colors in console output. (disables auto detection of color support)
+    --no-colors             Disable colors in console output.
+    --no-progress           Disable progress in console output.
+    --json                  Output results as JSON string.
+    --checkstyle            Output results as Checkstyle XML.
+    --blame                 Try to show git blame for row with error.
+    --git <git>             Path to Git executable to show blame message (default: 'git').
+    --stdin                 Load files and folder to test from standard input.
+    --ignore-fails          Ignore failed tests.
+    --syntax-error-callback File with syntax error callback for ability to modify error
+    -h, --help              Print this help.
+    -V, --version           Display this application version
+
+HELP;
+    }
+
+    /**
+     * Outputs the current version
+     */
+    private function showVersion()
+    {
+        echo 'PHP Parallel Lint version ' . self::VERSION.PHP_EOL;
+    }
+
+    /**
+     * Shows usage
+     */
+    private function showUsage()
+    {
+        $this->showVersion();
+        echo <<<USAGE
+-------------------------------
+Usage:
+parallel-lint [sa] [-p php] [-e ext] [-j num] [--exclude dir] [files or directories]
+
+USAGE;
+        $this->showOptions();
+    }
+}
diff --git a/php-parallel-lint/php-parallel-lint/src/Contracts/SyntaxErrorCallback.php b/php-parallel-lint/php-parallel-lint/src/Contracts/SyntaxErrorCallback.php
new file mode 100644
index 0000000..0db055f
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/src/Contracts/SyntaxErrorCallback.php
@@ -0,0 +1,13 @@
+<?php
+namespace JakubOnderka\PhpParallelLint\Contracts;
+
+use JakubOnderka\PhpParallelLint\SyntaxError;
+
+interface SyntaxErrorCallback
+{
+    /**
+     * @param SyntaxError $error
+     * @return SyntaxError
+     */
+    public function errorFound(SyntaxError $error);
+}
diff --git a/php-parallel-lint/php-parallel-lint/src/Error.php b/php-parallel-lint/php-parallel-lint/src/Error.php
new file mode 100644
index 0000000..9041ab1
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/src/Error.php
@@ -0,0 +1,222 @@
+<?php
+namespace JakubOnderka\PhpParallelLint;
+
+class Error implements \JsonSerializable
+{
+    /** @var string */
+    protected $filePath;
+
+    /** @var string */
+    protected $message;
+
+    /**
+     * @param string $filePath
+     * @param string $message
+     */
+    public function __construct($filePath, $message)
+    {
+        $this->filePath = $filePath;
+        $this->message = rtrim($message);
+    }
+
+    /**
+     * @return string
+     */
+    public function getMessage()
+    {
+        return $this->message;
+    }
+
+    /**
+     * @return string
+     */
+    public function getFilePath()
+    {
+        return $this->filePath;
+    }
+
+    /**
+     * @return string
+     */
+    public function getShortFilePath()
+    {
+        $cwd = getcwd();
+
+        if ($cwd === '/') {
+            // For root directory in unix, do not modify path
+            return $this->filePath;
+        }
+
+        return preg_replace('/' . preg_quote($cwd, '/') . '/', '', $this->filePath, 1);
+    }
+
+    /**
+     * (PHP 5 &gt;= 5.4.0)<br/>
+     * Specify data which should be serialized to JSON
+     * @link http://php.net/manual/en/jsonserializable.jsonserialize.php
+     * @return mixed data which can be serialized by <b>json_encode</b>,
+     * which is a value of any type other than a resource.
+     */
+    public function jsonSerialize()
+    {
+        return array(
+            'type' => 'error',
+            'file' => $this->getFilePath(),
+            'message' => $this->getMessage(),
+        );
+    }
+}
+
+class Blame implements \JsonSerializable
+{
+    public $name;
+
+    public $email;
+
+    /** @var \DateTime */
+    public $datetime;
+
+    public $commitHash;
+
+    public $summary;
+
+    /**
+     * (PHP 5 &gt;= 5.4.0)<br/>
+     * Specify data which should be serialized to JSON
+     * @link http://php.net/manual/en/jsonserializable.jsonserialize.php
+     * @return mixed data which can be serialized by <b>json_encode</b>,
+     * which is a value of any type other than a resource.
+     */
+    function jsonSerialize()
+    {
+        return array(
+            'name' => $this->name,
+            'email' => $this->email,
+            'datetime' => $this->datetime,
+            'commitHash' => $this->commitHash,
+            'summary' => $this->summary,
+        );
+    }
+
+
+}
+
+class SyntaxError extends Error
+{
+    /** @var Blame */
+    private $blame;
+
+    /**
+     * @return int|null
+     */
+    public function getLine()
+    {
+        preg_match('~on line ([0-9]+)$~', $this->message, $matches);
+
+        if ($matches && isset($matches[1])) {
+            $onLine = (int) $matches[1];
+            return $onLine;
+        }
+
+        return null;
+    }
+
+    /**
+     * @param bool $translateTokens
+     * @return mixed|string
+     */
+    public function getNormalizedMessage($translateTokens = false)
+    {
+        $message = preg_replace('~^(Parse|Fatal) error: (syntax error, )?~', '', $this->message);
+        $message = preg_replace('~ in ' . preg_quote(basename($this->filePath)) . ' on line [0-9]+$~', '', $message);
+        $message = ucfirst($message);
+
+        if ($translateTokens) {
+            $message = $this->translateTokens($message);
+        }
+
+        return $message;
+    }
+
+    /**
+     * @param Blame $blame
+     */
+    public function setBlame(Blame $blame)
+    {
+        $this->blame = $blame;
+    }
+
+    /**
+     * @return Blame
+     */
+    public function getBlame()
+    {
+        return $this->blame;
+    }
+
+    /**
+     * @param string $message
+     * @return string
+     */
+    protected function translateTokens($message)
+    {
+        static $translateTokens = array(
+            'T_FILE' => '__FILE__',
+            'T_FUNC_C' => '__FUNCTION__',
+            'T_HALT_COMPILER' => '__halt_compiler()',
+            'T_INC' => '++',
+            'T_IS_EQUAL' => '==',
+            'T_IS_GREATER_OR_EQUAL' => '>=',
+            'T_IS_IDENTICAL' => '===',
+            'T_IS_NOT_IDENTICAL' => '!==',
+            'T_IS_SMALLER_OR_EQUAL' => '<=',
+            'T_LINE' => '__LINE__',
+            'T_METHOD_C' => '__METHOD__',
+            'T_MINUS_EQUAL' => '-=',
+            'T_MOD_EQUAL' => '%=',
+            'T_MUL_EQUAL' => '*=',
+            'T_NS_C' => '__NAMESPACE__',
+            'T_NS_SEPARATOR' => '\\',
+            'T_OBJECT_OPERATOR' => '->',
+            'T_OR_EQUAL' => '|=',
+            'T_PAAMAYIM_NEKUDOTAYIM' => '::',
+            'T_PLUS_EQUAL' => '+=',
+            'T_SL' => '<<',
+            'T_SL_EQUAL' => '<<=',
+            'T_SR' => '>>',
+            'T_SR_EQUAL' => '>>=',
+            'T_START_HEREDOC' => '<<<',
+            'T_XOR_EQUAL' => '^=',
+            'T_ECHO' => 'echo'
+        );
+
+        return preg_replace_callback('~T_([A-Z_]*)~', function ($matches) use ($translateTokens) {
+            list($tokenName) = $matches;
+            if (isset($translateTokens[$tokenName])) {
+                $operator = $translateTokens[$tokenName];
+                return "$operator ($tokenName)";
+            }
+
+            return $tokenName;
+        }, $message);
+    }
+
+    /**
+     * (PHP 5 &gt;= 5.4.0)<br/>
+     * Specify data which should be serialized to JSON
+     * @link http://php.net/manual/en/jsonserializable.jsonserialize.php
+     * @return mixed data which can be serialized by <b>json_encode</b>,
+     * which is a value of any type other than a resource.
+     */
+    public function jsonSerialize()
+    {
+        return array(
+            'type' => 'syntaxError',
+            'file' => $this->getFilePath(),
+            'line' => $this->getLine(),
+            'message' => $this->getMessage(),
+            'normalizeMessage' => $this->getNormalizedMessage(),
+            'blame' => $this->blame,
+        );
+    }
+}
\ No newline at end of file
diff --git a/php-parallel-lint/php-parallel-lint/src/ErrorFormatter.php b/php-parallel-lint/php-parallel-lint/src/ErrorFormatter.php
new file mode 100644
index 0000000..76b4466
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/src/ErrorFormatter.php
@@ -0,0 +1,127 @@
+<?php
+namespace JakubOnderka\PhpParallelLint;
+
+use JakubOnderka\PhpConsoleColor\ConsoleColor;
+use JakubOnderka\PhpConsoleHighlighter\Highlighter;
+
+class ErrorFormatter
+{
+    /** @var string */
+    private $useColors;
+
+    /** @var bool */
+    private $forceColors;
+
+    /** @var bool */
+    private $translateTokens;
+
+    public function __construct($useColors = Settings::AUTODETECT, $translateTokens = false, $forceColors = false)
+    {
+        $this->useColors = $useColors;
+        $this->forceColors = $forceColors;
+        $this->translateTokens = $translateTokens;
+    }
+
+    /**
+     * @param Error $error
+     * @return string
+     */
+    public function format(Error $error)
+    {
+        if ($error instanceof SyntaxError) {
+            return $this->formatSyntaxErrorMessage($error);
+        } else {
+            if ($error->getMessage()) {
+                return $error->getMessage();
+            } else {
+                return "Unknown error for file '{$error->getFilePath()}'.";
+            }
+        }
+    }
+
+    /**
+     * @param SyntaxError $error
+     * @param bool $withCodeSnipped
+     * @return string
+     */
+    public function formatSyntaxErrorMessage(SyntaxError $error, $withCodeSnipped = true)
+    {
+        $string = "Parse error: {$error->getShortFilePath()}";
+
+        if ($error->getLine()) {
+            $onLine = $error->getLine();
+            $string .= ":$onLine" . PHP_EOL;
+
+            if ($withCodeSnipped) {
+                if ($this->useColors !== Settings::DISABLED) {
+                    $string .= $this->getColoredCodeSnippet($error->getFilePath(), $onLine);
+                } else {
+                    $string .= $this->getCodeSnippet($error->getFilePath(), $onLine);
+                }
+            }
+        }
+
+        $string .= $error->getNormalizedMessage($this->translateTokens);
+
+        if ($error->getBlame()) {
+            $blame = $error->getBlame();
+            $shortCommitHash = substr($blame->commitHash, 0, 8);
+            $dateTime = $blame->datetime->format('c');
+            $string .= PHP_EOL . "Blame {$blame->name} <{$blame->email}>, commit '$shortCommitHash' from $dateTime";
+        }
+
+        return $string;
+    }
+
+    /**
+     * @param string $filePath
+     * @param int $lineNumber
+     * @param int $linesBefore
+     * @param int $linesAfter
+     * @return string
+     */
+    protected function getCodeSnippet($filePath, $lineNumber, $linesBefore = 2, $linesAfter = 2)
+    {
+        $lines = file($filePath);
+
+        $offset = $lineNumber - $linesBefore - 1;
+        $offset = max($offset, 0);
+        $length = $linesAfter + $linesBefore + 1;
+        $lines = array_slice($lines, $offset, $length, $preserveKeys = true);
+
+        end($lines);
+        $lineStrlen = strlen(key($lines) + 1);
+
+        $snippet = '';
+        foreach ($lines as $i => $line) {
+            $snippet .= ($lineNumber === $i + 1 ? '  > ' : '    ');
+            $snippet .= str_pad($i + 1, $lineStrlen, ' ', STR_PAD_LEFT) . '| ' . rtrim($line) . PHP_EOL;
+        }
+
+        return $snippet;
+    }
+
+    /**
+     * @param string $filePath
+     * @param int $lineNumber
+     * @param int $linesBefore
+     * @param int $linesAfter
+     * @return string
+     */
+    protected function getColoredCodeSnippet($filePath, $lineNumber, $linesBefore = 2, $linesAfter = 2)
+    {
+        if (
+            !class_exists('\JakubOnderka\PhpConsoleHighlighter\Highlighter') ||
+            !class_exists('\JakubOnderka\PhpConsoleColor\ConsoleColor')
+        ) {
+            return $this->getCodeSnippet($filePath, $lineNumber, $linesBefore, $linesAfter);
+        }
+
+        $colors = new ConsoleColor();
+        $colors->setForceStyle($this->forceColors);
+        $highlighter = new Highlighter($colors);
+
+        $fileContent = file_get_contents($filePath);
+        return $highlighter->getCodeSnippet($fileContent, $lineNumber, $linesBefore, $linesAfter);
+    }
+}
diff --git a/php-parallel-lint/php-parallel-lint/src/Manager.php b/php-parallel-lint/php-parallel-lint/src/Manager.php
new file mode 100644
index 0000000..63091b1
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/src/Manager.php
@@ -0,0 +1,287 @@
+<?php
+namespace JakubOnderka\PhpParallelLint;
+
+use JakubOnderka\PhpParallelLint\Contracts\SyntaxErrorCallback;
+use JakubOnderka\PhpParallelLint\Process\GitBlameProcess;
+use JakubOnderka\PhpParallelLint\Process\PhpExecutable;
+
+class Manager
+{
+    /** @var Output */
+    protected $output;
+
+    /**
+     * @param null|Settings $settings
+     * @return Result
+     * @throws Exception
+     * @throws \Exception
+     */
+    public function run(Settings $settings = null)
+    {
+        $settings = $settings ?: new Settings;
+        $output = $this->output ?: $this->getDefaultOutput($settings);
+
+        $phpExecutable = PhpExecutable::getPhpExecutable($settings->phpExecutable);
+        $olderThanPhp54 = $phpExecutable->getVersionId() < 50400; // From PHP version 5.4 are tokens translated by default
+        $translateTokens = $phpExecutable->isIsHhvmType() || $olderThanPhp54;
+
+        $output->writeHeader($phpExecutable->getVersionId(), $settings->parallelJobs, $phpExecutable->getHhvmVersion());
+
+        $files = $this->getFilesFromPaths($settings->paths, $settings->extensions, $settings->excluded);
+
+        if (empty($files)) {
+            throw new Exception('No file found to check.');
+        }
+
+        $output->setTotalFileCount(count($files));
+
+        $parallelLint = new ParallelLint($phpExecutable, $settings->parallelJobs);
+        $parallelLint->setAspTagsEnabled($settings->aspTags);
+        $parallelLint->setShortTagEnabled($settings->shortTag);
+        $parallelLint->setShowDeprecated($settings->showDeprecated);
+        $parallelLint->setSyntaxErrorCallback($this->createSyntaxErrorCallback($settings));
+
+        $parallelLint->setProcessCallback(function ($status, $file) use ($output) {
+            if ($status === ParallelLint::STATUS_OK) {
+                $output->ok();
+            } else if ($status === ParallelLint::STATUS_SKIP) {
+                $output->skip();
+            } else if ($status === ParallelLint::STATUS_ERROR) {
+                $output->error();
+            } else {
+                $output->fail();
+            }
+        });
+
+        $result = $parallelLint->lint($files);
+
+        if ($settings->blame) {
+            $this->gitBlame($result, $settings);
+        }
+
+        $output->writeResult($result, new ErrorFormatter($settings->colors, $translateTokens), $settings->ignoreFails);
+
+        return $result;
+    }
+
+    /**
+     * @param Output $output
+     */
+    public function setOutput(Output $output)
+    {
+        $this->output = $output;
+    }
+
+    /**
+     * @param Settings $settings
+     * @return Output
+     */
+    protected function getDefaultOutput(Settings $settings)
+    {
+        $writer = new ConsoleWriter;
+        switch ($settings->format) {
+            case Settings::FORMAT_JSON:
+                return new JsonOutput($writer);
+            case Settings::FORMAT_CHECKSTYLE:
+                return new CheckstyleOutput($writer);
+        }
+
+        if ($settings->colors === Settings::DISABLED) {
+            $output = new TextOutput($writer);
+        } else {
+            $output = new TextOutputColored($writer, $settings->colors);
+        }
+
+        $output->showProgress = $settings->showProgress;
+
+        return $output;
+    }
+
+    /**
+     * @param Result $result
+     * @param Settings $settings
+     * @throws Exception
+     */
+    protected function gitBlame(Result $result, Settings $settings)
+    {
+        if (!GitBlameProcess::gitExists($settings->gitExecutable)) {
+            return;
+        }
+
+        foreach ($result->getErrors() as $error) {
+            if ($error instanceof SyntaxError) {
+                $process = new GitBlameProcess($settings->gitExecutable, $error->getFilePath(), $error->getLine());
+                $process->waitForFinish();
+
+                if ($process->isSuccess()) {
+                    $blame = new Blame;
+                    $blame->name = $process->getAuthor();
+                    $blame->email = $process->getAuthorEmail();
+                    $blame->datetime = $process->getAuthorTime();
+                    $blame->commitHash = $process->getCommitHash();
+                    $blame->summary = $process->getSummary();
+
+                    $error->setBlame($blame);
+                }
+            }
+        }
+    }
+
+    /**
+     * @param array $paths
+     * @param array $extensions
+     * @param array $excluded
+     * @return array
+     * @throws NotExistsPathException
+     */
+    protected function getFilesFromPaths(array $paths, array $extensions, array $excluded = array())
+    {
+        $extensions = array_map('preg_quote', $extensions, array_fill(0, count($extensions), '`'));
+        $regex = '`\.(?:' . implode('|', $extensions) . ')$`iD';
+        $files = array();
+
+        foreach ($paths as $path) {
+            if (is_file($path)) {
+                $files[] = $path;
+            } else if (is_dir($path)) {
+                $iterator = new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS);
+                if (!empty($excluded)) {
+                    $iterator = new RecursiveDirectoryFilterIterator($iterator, $excluded);
+                }
+                $iterator = new \RecursiveIteratorIterator(
+                    $iterator,
+                    \RecursiveIteratorIterator::LEAVES_ONLY,
+                    \RecursiveIteratorIterator::CATCH_GET_CHILD
+                );
+
+                $iterator = new \RegexIterator($iterator, $regex);
+
+                /** @var \SplFileInfo[] $iterator */
+                foreach ($iterator as $directoryFile) {
+                    $files[] = (string) $directoryFile;
+                }
+            } else {
+                throw new NotExistsPathException($path);
+            }
+        }
+
+        $files = array_unique($files);
+
+        return $files;
+    }
+
+    protected function createSyntaxErrorCallback(Settings $settings)
+    {
+        if ($settings->syntaxErrorCallbackFile === null) {
+            return null;
+        }
+
+        $fullFilePath = realpath($settings->syntaxErrorCallbackFile);
+        if ($fullFilePath === false) {
+            throw new NotExistsPathException($settings->syntaxErrorCallbackFile);
+        }
+
+        require_once $fullFilePath;
+
+        $expectedClassName = basename($fullFilePath, '.php');
+        if (!class_exists($expectedClassName)) {
+            throw new NotExistsClassException($expectedClassName, $settings->syntaxErrorCallbackFile);
+        }
+
+        $callbackInstance = new $expectedClassName;
+
+        if (!($callbackInstance instanceof SyntaxErrorCallback)) {
+            throw new NotImplementCallbackException($expectedClassName);
+        }
+
+        return $callbackInstance;
+    }
+}
+
+class RecursiveDirectoryFilterIterator extends \RecursiveFilterIterator
+{
+    /** @var \RecursiveDirectoryIterator */
+    private $iterator;
+
+    /** @var array */
+    private $excluded = array();
+
+    /**
+     * @param \RecursiveDirectoryIterator $iterator
+     * @param array $excluded
+     */
+    public function __construct(\RecursiveDirectoryIterator $iterator, array $excluded)
+    {
+        parent::__construct($iterator);
+        $this->iterator = $iterator;
+        $this->excluded = array_map(array($this, 'getPathname'), $excluded);
+    }
+
+    /**
+     * (PHP 5 &gt;= 5.1.0)<br/>
+     * Check whether the current element of the iterator is acceptable
+     *
+     * @link http://php.net/manual/en/filteriterator.accept.php
+     * @return bool true if the current element is acceptable, otherwise false.
+     */
+    public function accept()
+    {
+        $current = $this->current()->getPathname();
+        $current = $this->normalizeDirectorySeparator($current);
+
+        if ('.' . DIRECTORY_SEPARATOR !== $current[0] . $current[1]) {
+            $current = '.' . DIRECTORY_SEPARATOR . $current;
+        }
+
+        return !in_array($current, $this->excluded);
+    }
+
+    /**
+     * (PHP 5 &gt;= 5.1.0)<br/>
+     * Check whether the inner iterator's current element has children
+     *
+     * @link http://php.net/manual/en/recursivefilteriterator.haschildren.php
+     * @return bool true if the inner iterator has children, otherwise false
+     */
+    public function hasChildren()
+    {
+        return $this->iterator->hasChildren();
+    }
+
+    /**
+     * (PHP 5 &gt;= 5.1.0)<br/>
+     * Return the inner iterator's children contained in a RecursiveFilterIterator
+     *
+     * @link http://php.net/manual/en/recursivefilteriterator.getchildren.php
+     * @return \RecursiveFilterIterator containing the inner iterator's children.
+     */
+    public function getChildren()
+    {
+        return new self($this->iterator->getChildren(), $this->excluded);
+    }
+
+    /**
+     * @param string $file
+     * @return string
+     */
+    private function getPathname($file)
+    {
+        $file = $this->normalizeDirectorySeparator($file);
+
+        if ('.' . DIRECTORY_SEPARATOR !== $file[0] . $file[1]) {
+            $file = '.' . DIRECTORY_SEPARATOR . $file;
+        }
+
+        $directoryFile = new \SplFileInfo($file);
+        return $directoryFile->getPathname();
+    }
+
+    /**
+     * @param string $file
+     * @return string
+     */
+    private function normalizeDirectorySeparator($file)
+    {
+        return str_replace(array('\\', '/'), DIRECTORY_SEPARATOR, $file);
+    }
+}
diff --git a/php-parallel-lint/php-parallel-lint/src/Output.php b/php-parallel-lint/php-parallel-lint/src/Output.php
new file mode 100644
index 0000000..c51d11b
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/src/Output.php
@@ -0,0 +1,513 @@
+<?php
+namespace JakubOnderka\PhpParallelLint;
+
+interface Output
+{
+    public function __construct(IWriter $writer);
+
+    public function ok();
+
+    public function skip();
+
+    public function error();
+
+    public function fail();
+
+    public function setTotalFileCount($count);
+
+    public function writeHeader($phpVersion, $parallelJobs, $hhvmVersion = null);
+
+    public function writeResult(Result $result, ErrorFormatter $errorFormatter, $ignoreFails);
+}
+
+class JsonOutput implements Output
+{
+    /** @var IWriter */
+    protected $writer;
+
+    /** @var int */
+    protected $phpVersion;
+
+    /** @var int */
+    protected $parallelJobs;
+
+    /** @var string */
+    protected $hhvmVersion;
+
+    /**
+     * @param IWriter $writer
+     */
+    public function __construct(IWriter $writer)
+    {
+        $this->writer = $writer;
+    }
+
+    public function ok()
+    {
+
+    }
+
+    public function skip()
+    {
+
+    }
+
+    public function error()
+    {
+
+    }
+
+    public function fail()
+    {
+
+    }
+
+    public function setTotalFileCount($count)
+    {
+
+    }
+
+    public function writeHeader($phpVersion, $parallelJobs, $hhvmVersion = null)
+    {
+        $this->phpVersion = $phpVersion;
+        $this->parallelJobs = $parallelJobs;
+        $this->hhvmVersion = $hhvmVersion;
+    }
+
+    public function writeResult(Result $result, ErrorFormatter $errorFormatter, $ignoreFails)
+    {
+        echo json_encode(array(
+            'phpVersion' => $this->phpVersion,
+            'hhvmVersion' => $this->hhvmVersion,
+            'parallelJobs' => $this->parallelJobs,
+            'results' => $result,
+        ));
+    }
+}
+
+class TextOutput implements Output
+{
+    const TYPE_DEFAULT = 'default',
+        TYPE_SKIP = 'skip',
+        TYPE_ERROR = 'error',
+        TYPE_FAIL = 'fail',
+        TYPE_OK = 'ok';
+
+    /** @var int */
+    public $filesPerLine = 60;
+
+    /** @var bool */
+    public $showProgress = true;
+
+    /** @var int */
+    protected $checkedFiles;
+
+    /** @var int */
+    protected $totalFileCount;
+
+    /** @var IWriter */
+    protected $writer;
+
+    /**
+     * @param IWriter $writer
+     */
+    public function __construct(IWriter $writer)
+    {
+        $this->writer = $writer;
+    }
+
+    public function ok()
+    {
+        $this->writeMark(self::TYPE_OK);
+    }
+
+    public function skip()
+    {
+        $this->writeMark(self::TYPE_SKIP);
+    }
+
+    public function error()
+    {
+        $this->writeMark(self::TYPE_ERROR);
+    }
+
+    public function fail()
+    {
+        $this->writeMark(self::TYPE_FAIL);
+    }
+
+    /**
+     * @param string $string
+     * @param string $type
+     */
+    public function write($string, $type = self::TYPE_DEFAULT)
+    {
+        $this->writer->write($string);
+    }
+
+    /**
+     * @param string|null $line
+     * @param string $type
+     */
+    public function writeLine($line = null, $type = self::TYPE_DEFAULT)
+    {
+        $this->write($line, $type);
+        $this->writeNewLine();
+    }
+
+    /**
+     * @param int $count
+     */
+    public function writeNewLine($count = 1)
+    {
+        $this->write(str_repeat(PHP_EOL, $count));
+    }
+
+    /**
+     * @param int $count
+     */
+    public function setTotalFileCount($count)
+    {
+        $this->totalFileCount = $count;
+    }
+
+    /**
+     * @param int $phpVersion
+     * @param int $parallelJobs
+     * @param string $hhvmVersion
+     */
+    public function writeHeader($phpVersion, $parallelJobs, $hhvmVersion = null)
+    {
+        $this->write("PHP {$this->phpVersionIdToString($phpVersion)} | ");
+
+        if ($hhvmVersion) {
+            $this->write("HHVM $hhvmVersion | ");
+        }
+
+        if ($parallelJobs === 1) {
+            $this->writeLine("1 job");
+        } else {
+            $this->writeLine("{$parallelJobs} parallel jobs");
+        }
+    }
+
+    /**
+     * @param Result $result
+     * @param ErrorFormatter $errorFormatter
+     * @param bool $ignoreFails
+     */
+    public function writeResult(Result $result, ErrorFormatter $errorFormatter, $ignoreFails)
+    {
+        if ($this->showProgress) {
+            if ($this->checkedFiles % $this->filesPerLine !== 0) {
+                $rest = $this->filesPerLine - ($this->checkedFiles % $this->filesPerLine);
+                $this->write(str_repeat(' ', $rest));
+                $this->writePercent();
+            }
+
+            $this->writeNewLine(2);
+        }
+
+        $testTime = round($result->getTestTime(), 1);
+        $message = "Checked {$result->getCheckedFilesCount()} files in $testTime ";
+        $message .= $testTime == 1 ? 'second' : 'seconds';
+
+        if ($result->getSkippedFilesCount() > 0) {
+            $message .= ", skipped {$result->getSkippedFilesCount()} ";
+            $message .= ($result->getSkippedFilesCount() === 1 ? 'file' : 'files');
+        }
+
+        $this->writeLine($message);
+
+        if (!$result->hasSyntaxError()) {
+            $message = "No syntax error found";
+        } else {
+            $message = "Syntax error found in {$result->getFilesWithSyntaxErrorCount()} ";
+            $message .= ($result->getFilesWithSyntaxErrorCount() === 1 ? 'file' : 'files');
+        }
+
+        if ($result->hasFilesWithFail()) {
+            $message .= ", failed to check {$result->getFilesWithFailCount()} ";
+            $message .= ($result->getFilesWithFailCount() === 1 ? 'file' : 'files');
+
+            if ($ignoreFails) {
+                $message .= ' (ignored)';
+            }
+        }
+
+        $hasError = $ignoreFails ? $result->hasSyntaxError() : $result->hasError();
+        $this->writeLine($message, $hasError ? self::TYPE_ERROR : self::TYPE_OK);
+
+        if ($result->hasError()) {
+            $this->writeNewLine();
+            foreach ($result->getErrors() as $error) {
+                $this->writeLine(str_repeat('-', 60));
+                $this->writeLine($errorFormatter->format($error));
+            }
+        }
+    }
+
+    protected function writeMark($type)
+    {
+        ++$this->checkedFiles;
+
+        if ($this->showProgress) {
+            if ($type === self::TYPE_OK) {
+                $this->writer->write('.');
+
+            } else if ($type === self::TYPE_SKIP) {
+                $this->write('S', self::TYPE_SKIP);
+
+            } else if ($type === self::TYPE_ERROR) {
+                $this->write('X', self::TYPE_ERROR);
+
+            } else if ($type === self::TYPE_FAIL) {
+                $this->writer->write('-');
+            }
+
+            if ($this->checkedFiles % $this->filesPerLine === 0) {
+                $this->writePercent();
+            }
+        }
+    }
+
+    protected function writePercent()
+    {
+        $percent = floor($this->checkedFiles / $this->totalFileCount * 100);
+        $current = $this->stringWidth($this->checkedFiles, strlen($this->totalFileCount));
+        $this->writeLine(" $current/$this->totalFileCount ($percent %)");
+    }
+
+    /**
+     * @param string $input
+     * @param int $width
+     * @return string
+     */
+    protected function stringWidth($input, $width = 3)
+    {
+        $multiplier = $width - strlen($input);
+        return str_repeat(' ', $multiplier > 0 ? $multiplier : 0) . $input;
+    }
+
+    /**
+     * @param int $phpVersionId
+     * @return string
+     */
+    protected function phpVersionIdToString($phpVersionId)
+    {
+        $releaseVersion = (int) substr($phpVersionId, -2, 2);
+        $minorVersion = (int) substr($phpVersionId, -4, 2);
+        $majorVersion = (int) substr($phpVersionId, 0, strlen($phpVersionId) - 4);
+
+        return "$majorVersion.$minorVersion.$releaseVersion";
+    }
+}
+
+class CheckstyleOutput implements Output
+{
+    private $writer;
+
+    public function __construct(IWriter $writer)
+    {
+        $this->writer = $writer;
+    }
+
+    public function ok()
+    {
+    }
+
+    public function skip()
+    {
+    }
+
+    public function error()
+    {
+    }
+
+    public function fail()
+    {
+    }
+
+    public function setTotalFileCount($count)
+    {
+    }
+
+    public function writeHeader($phpVersion, $parallelJobs, $hhvmVersion = null)
+    {
+        $this->writer->write('<?xml version="1.0" encoding="UTF-8"?>' . PHP_EOL);
+    }
+
+    public function writeResult(Result $result, ErrorFormatter $errorFormatter, $ignoreFails)
+    {
+        $this->writer->write('<checkstyle>' . PHP_EOL);
+        $errors = array();
+
+        foreach ($result->getErrors() as $error) {
+            $message = $error->getMessage();
+            if ($error instanceof SyntaxError) {
+                $line = $error->getLine();
+                $source = "Syntax Error";
+            } else {
+                $line = 1;
+                $source = "Linter Error";
+            }
+
+            $errors[$error->getShortFilePath()][] = array(
+                'message' => $message,
+                'line' => $line,
+                'source' => $source
+            );
+        }
+
+        foreach ($errors as $file => $fileErrors) {
+            $this->writer->write(sprintf('    <file name="%s">', $file) . PHP_EOL);
+            foreach ($fileErrors as $fileError) {
+                $this->writer->write(
+                    sprintf(
+                        '        <error line="%d" severity="ERROR" message="%s" source="%s" />',
+                        $fileError['line'],
+                        $fileError['message'],
+                        $fileError['source']
+                    ) .
+                    PHP_EOL
+                );
+            }
+            $this->writer->write('    </file>' . PHP_EOL);
+        }
+
+        $this->writer->write('</checkstyle>' . PHP_EOL);
+    }
+}
+
+class TextOutputColored extends TextOutput
+{
+    /** @var \JakubOnderka\PhpConsoleColor\ConsoleColor */
+    private $colors;
+
+    public function __construct(IWriter $writer, $colors = Settings::AUTODETECT)
+    {
+        parent::__construct($writer);
+
+        if (class_exists('\JakubOnderka\PhpConsoleColor\ConsoleColor')) {
+            $this->colors = new \JakubOnderka\PhpConsoleColor\ConsoleColor();
+            $this->colors->setForceStyle($colors === Settings::FORCED);
+        }
+    }
+
+    /**
+     * @param string $string
+     * @param string $type
+     * @throws \JakubOnderka\PhpConsoleColor\InvalidStyleException
+     */
+    public function write($string, $type = self::TYPE_DEFAULT)
+    {
+        if (!$this->colors instanceof \JakubOnderka\PhpConsoleColor\ConsoleColor) {
+            parent::write($string, $type);
+        } else {
+            switch ($type) {
+                case self::TYPE_OK:
+                    parent::write($this->colors->apply('bg_green', $string));
+                    break;
+
+                case self::TYPE_SKIP:
+                    parent::write($this->colors->apply('bg_yellow', $string));
+                    break;
+
+                case self::TYPE_ERROR:
+                    parent::write($this->colors->apply('bg_red', $string));
+                    break;
+
+                default:
+                    parent::write($string);
+            }
+        }
+    }
+}
+
+interface IWriter
+{
+    /**
+     * @param string $string
+     */
+    public function write($string);
+}
+
+class NullWriter implements IWriter
+{
+    /**
+     * @param string $string
+     */
+    public function write($string)
+    {
+
+    }
+}
+
+class ConsoleWriter implements IWriter
+{
+    /**
+     * @param string $string
+     */
+    public function write($string)
+    {
+        echo $string;
+    }
+}
+
+class FileWriter implements IWriter
+{
+    /** @var string */
+    protected $logFile;
+
+    /** @var string */
+    protected $buffer;
+
+    public function __construct($logFile)
+    {
+        $this->logFile = $logFile;
+    }
+
+    public function write($string)
+    {
+        $this->buffer .= $string;
+    }
+
+    public function __destruct()
+    {
+        file_put_contents($this->logFile, $this->buffer);
+    }
+}
+
+class MultipleWriter implements IWriter
+{
+    /** @var IWriter[] */
+    protected $writers;
+
+    /**
+     * @param IWriter[] $writers
+     */
+    public function __construct(array $writers)
+    {
+        foreach ($writers as $writer) {
+            $this->addWriter($writer);
+        }
+    }
+
+    /**
+     * @param IWriter $writer
+     */
+    public function addWriter(IWriter $writer)
+    {
+        $this->writers[] = $writer;
+    }
+
+    /**
+     * @param $string
+     */
+    public function write($string)
+    {
+        foreach ($this->writers as $writer) {
+            $writer->write($string);
+        }
+    }
+}
diff --git a/php-parallel-lint/php-parallel-lint/src/ParallelLint.php b/php-parallel-lint/php-parallel-lint/src/ParallelLint.php
new file mode 100644
index 0000000..b1825f3
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/src/ParallelLint.php
@@ -0,0 +1,286 @@
+<?php
+namespace JakubOnderka\PhpParallelLint;
+
+use JakubOnderka\PhpParallelLint\Contracts\SyntaxErrorCallback;
+use JakubOnderka\PhpParallelLint\Process\LintProcess;
+use JakubOnderka\PhpParallelLint\Process\PhpExecutable;
+use JakubOnderka\PhpParallelLint\Process\SkipLintProcess;
+
+class ParallelLint
+{
+    const STATUS_OK = 'ok',
+        STATUS_SKIP = 'skip',
+        STATUS_FAIL = 'fail',
+        STATUS_ERROR = 'error';
+
+    /** @var int */
+    private $parallelJobs;
+
+    /** @var PhpExecutable */
+    private $phpExecutable;
+
+    /** @var bool */
+    private $aspTagsEnabled = false;
+
+    /** @var bool */
+    private $shortTagEnabled = false;
+
+    /** @var callable */
+    private $processCallback;
+
+    /** @var bool */
+    private $showDeprecated = false;
+
+    /** @var SyntaxErrorCallback|null */
+    private $syntaxErrorCallback = null;
+
+    public function __construct(PhpExecutable $phpExecutable, $parallelJobs = 10)
+    {
+        $this->phpExecutable = $phpExecutable;
+        $this->parallelJobs = $parallelJobs;
+    }
+
+    /**
+     * @param array $files
+     * @return Result
+     * @throws \Exception
+     */
+    public function lint(array $files)
+    {
+        $startTime = microtime(true);
+
+        $skipLintProcess = new SkipLintProcess($this->phpExecutable, $files);
+
+        $processCallback = is_callable($this->processCallback) ? $this->processCallback : function () {
+        };
+
+        /**
+         * @var LintProcess[] $running
+         * @var LintProcess[] $waiting
+         */
+        $errors = $running = $waiting = array();
+        $skippedFiles = $checkedFiles = array();
+
+        while ($files || $running) {
+            for ($i = count($running); $files && $i < $this->parallelJobs; $i++) {
+                $file = array_shift($files);
+
+                if ($skipLintProcess->isSkipped($file) === true) {
+                    $skippedFiles[] = $file;
+                    $processCallback(self::STATUS_SKIP, $file);
+                } else {
+                    $running[$file] = new LintProcess(
+                        $this->phpExecutable,
+                        $file,
+                        $this->aspTagsEnabled,
+                        $this->shortTagEnabled,
+                        $this->showDeprecated
+                    );
+                }
+            }
+
+            $skipLintProcess->getChunk();
+            usleep(100);
+
+            foreach ($running as $file => $process) {
+                if ($process->isFinished()) {
+                    unset($running[$file]);
+
+                    $skipStatus = $skipLintProcess->isSkipped($file);
+                    if ($skipStatus === null) {
+                        $waiting[$file] = $process;
+
+                    } else if ($skipStatus === true) {
+                        $skippedFiles[] = $file;
+                        $processCallback(self::STATUS_SKIP, $file);
+
+                    } else if ($process->containsError()) {
+                        $checkedFiles[] = $file;
+                        $errors[] = $this->triggerSyntaxErrorCallback(new SyntaxError($file, $process->getSyntaxError()));
+                        $processCallback(self::STATUS_ERROR, $file);
+
+                    } else if ($process->isSuccess()) {
+                        $checkedFiles[] = $file;
+                        $processCallback(self::STATUS_OK, $file);
+
+
+                    } else {
+                        $errors[] = new Error($file, $process->getOutput());
+                        $processCallback(self::STATUS_FAIL, $file);
+                    }
+                }
+            }
+        }
+
+        if (!empty($waiting)) {
+            $skipLintProcess->waitForFinish();
+
+            if ($skipLintProcess->isFail()) {
+                $message = "Error in skip-linting.php process\nError output: {$skipLintProcess->getErrorOutput()}";
+                throw new \Exception($message);
+            }
+
+            foreach ($waiting as $file => $process) {
+                $skipStatus = $skipLintProcess->isSkipped($file);
+                if ($skipStatus === null) {
+                    throw new \Exception("File $file has empty skip status. Please contact the author of PHP Parallel Lint.");
+
+                } else if ($skipStatus === true) {
+                    $skippedFiles[] = $file;
+                    $processCallback(self::STATUS_SKIP, $file);
+
+                } else if ($process->isSuccess()) {
+                    $checkedFiles[] = $file;
+                    $processCallback(self::STATUS_OK, $file);
+
+                } else if ($process->containsError()) {
+                    $checkedFiles[] = $file;
+                    $errors[] = $this->triggerSyntaxErrorCallback(new SyntaxError($file, $process->getSyntaxError()));
+                    $processCallback(self::STATUS_ERROR, $file);
+
+                } else {
+                    $errors[] = new Error($file, $process->getOutput());
+                    $processCallback(self::STATUS_FAIL, $file);
+                }
+            }
+        }
+
+        $testTime = microtime(true) - $startTime;
+
+        return new Result($errors, $checkedFiles, $skippedFiles, $testTime);
+    }
+
+    /**
+     * @return int
+     */
+    public function getParallelJobs()
+    {
+        return $this->parallelJobs;
+    }
+
+    /**
+     * @param int $parallelJobs
+     * @return ParallelLint
+     */
+    public function setParallelJobs($parallelJobs)
+    {
+        $this->parallelJobs = $parallelJobs;
+
+        return $this;
+    }
+
+    /**
+     * @return string
+     */
+    public function getPhpExecutable()
+    {
+        return $this->phpExecutable;
+    }
+
+    /**
+     * @param string $phpExecutable
+     * @return ParallelLint
+     */
+    public function setPhpExecutable($phpExecutable)
+    {
+        $this->phpExecutable = $phpExecutable;
+
+        return $this;
+    }
+
+    /**
+     * @return callable
+     */
+    public function getProcessCallback()
+    {
+        return $this->processCallback;
+    }
+
+    /**
+     * @param callable $processCallback
+     * @return ParallelLint
+     */
+    public function setProcessCallback($processCallback)
+    {
+        $this->processCallback = $processCallback;
+
+        return $this;
+    }
+
+    /**
+     * @return boolean
+     */
+    public function isAspTagsEnabled()
+    {
+        return $this->aspTagsEnabled;
+    }
+
+    /**
+     * @param boolean $aspTagsEnabled
+     * @return ParallelLint
+     */
+    public function setAspTagsEnabled($aspTagsEnabled)
+    {
+        $this->aspTagsEnabled = $aspTagsEnabled;
+
+        return $this;
+    }
+
+    /**
+     * @return boolean
+     */
+    public function isShortTagEnabled()
+    {
+        return $this->shortTagEnabled;
+    }
+
+    /**
+     * @param boolean $shortTagEnabled
+     * @return ParallelLint
+     */
+    public function setShortTagEnabled($shortTagEnabled)
+    {
+        $this->shortTagEnabled = $shortTagEnabled;
+
+        return $this;
+    }
+
+    /**
+     * @return boolean
+     */
+    public function isShowDeprecated()
+    {
+        return $this->showDeprecated;
+    }
+
+    /**
+     * @param $showDeprecated
+     * @return ParallelLint
+     */
+    public function setShowDeprecated($showDeprecated)
+    {
+        $this->showDeprecated = $showDeprecated;
+
+        return $this;
+    }
+
+    public function triggerSyntaxErrorCallback($syntaxError)
+    {
+        if ($this->syntaxErrorCallback === null) {
+            return $syntaxError;
+        }
+
+        return $this->syntaxErrorCallback->errorFound($syntaxError);
+    }
+
+    /**
+     * @param SyntaxErrorCallback|null $syntaxErrorCallback
+     * @return ParallelLint
+     */
+    public function setSyntaxErrorCallback($syntaxErrorCallback)
+    {
+        $this->syntaxErrorCallback = $syntaxErrorCallback;
+
+        return $this;
+    }
+}
diff --git a/php-parallel-lint/php-parallel-lint/src/Process/GitBlameProcess.php b/php-parallel-lint/php-parallel-lint/src/Process/GitBlameProcess.php
new file mode 100644
index 0000000..2d675b8
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/src/Process/GitBlameProcess.php
@@ -0,0 +1,147 @@
+<?php
+namespace JakubOnderka\PhpParallelLint\Process;
+
+use JakubOnderka\PhpParallelLint\RunTimeException;
+
+class GitBlameProcess extends Process
+{
+    /**
+     * @param string $gitExecutable
+     * @param string $file
+     * @param int $line
+     * @throws RunTimeException
+     */
+    public function __construct($gitExecutable, $file, $line)
+    {
+        $arguments = array('blame', '-p', '-L', "$line,+1", $file);
+        parent::__construct($gitExecutable, $arguments);
+    }
+
+    /**
+     * @return bool
+     * @throws RunTimeException
+     */
+    public function isSuccess()
+    {
+        return $this->getStatusCode() === 0;
+    }
+
+    /**
+     * @return string
+     * @throws RunTimeException
+     */
+    public function getAuthor()
+    {
+        if (!$this->isSuccess()) {
+            throw new RunTimeException("Author can only be retrieved for successful process output.");
+        }
+
+        $output = $this->getOutput();
+        preg_match('~^author (.*)~m', $output, $matches);
+        return $matches[1];
+    }
+
+    /**
+     * @return string
+     * @throws RunTimeException
+     */
+    public function getAuthorEmail()
+    {
+        if (!$this->isSuccess()) {
+            throw new RunTimeException("Author e-mail can only be retrieved for successful process output.");
+        }
+
+        $output = $this->getOutput();
+        preg_match('~^author-mail <(.*)>~m', $output, $matches);
+        return $matches[1];
+    }
+
+    /**
+     * @return \DateTime
+     * @throws RunTimeException
+     */
+    public function getAuthorTime()
+    {
+        if (!$this->isSuccess()) {
+            throw new RunTimeException("Author time can only be retrieved for successful process output.");
+        }
+
+        $output = $this->getOutput();
+
+        preg_match('~^author-time (.*)~m', $output, $matches);
+        $time = $matches[1];
+
+        preg_match('~^author-tz (.*)~m', $output, $matches);
+        $zone = $matches[1];
+
+        return $this->getDateTime($time, $zone);
+    }
+
+    /**
+     * @return string
+     * @throws RunTimeException
+     */
+    public function getCommitHash()
+    {
+        if (!$this->isSuccess()) {
+            throw new RunTimeException("Commit hash can only be retrieved for successful process output.");
+        }
+
+        return substr($this->getOutput(), 0, strpos($this->getOutput(), ' '));
+    }
+
+    /**
+     * @return string
+     * @throws RunTimeException
+     */
+    public function getSummary()
+    {
+        if (!$this->isSuccess()) {
+            throw new RunTimeException("Commit summary can only be retrieved for successful process output.");
+        }
+
+        $output = $this->getOutput();
+        preg_match('~^summary (.*)~m', $output, $matches);
+        return $matches[1];
+    }
+
+    /**
+     * @param string $gitExecutable
+     * @return bool
+     * @throws RunTimeException
+     */
+    public static function gitExists($gitExecutable)
+    {
+        $process = new Process($gitExecutable, array('--version'));
+        $process->waitForFinish();
+        return $process->getStatusCode() === 0;
+    }
+
+    /**
+     * This harakiri method is required to correct support time zone in PHP 5.4
+     *
+     * @param int $time
+     * @param string $zone
+     * @return \DateTime
+     * @throws \Exception
+     */
+    protected function getDateTime($time, $zone)
+    {
+        $utcTimeZone = new \DateTimeZone('UTC');
+        $datetime = \DateTime::createFromFormat('U', $time, $utcTimeZone);
+
+        $way = substr($zone, 0, 1);
+        $hours = (int) substr($zone, 1, 2);
+        $minutes = (int) substr($zone, 3, 2);
+
+        $interval = new \DateInterval("PT{$hours}H{$minutes}M");
+
+        if ($way === '+') {
+            $datetime->add($interval);
+        } else {
+            $datetime->sub($interval);
+        }
+
+        return new \DateTime($datetime->format('Y-m-d\TH:i:s') . $zone, $utcTimeZone);
+    }
+}
diff --git a/php-parallel-lint/php-parallel-lint/src/Process/LintProcess.php b/php-parallel-lint/php-parallel-lint/src/Process/LintProcess.php
new file mode 100644
index 0000000..e2e6b2d
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/src/Process/LintProcess.php
@@ -0,0 +1,137 @@
+<?php
+namespace JakubOnderka\PhpParallelLint\Process;
+
+use JakubOnderka\PhpParallelLint\RunTimeException;
+
+class LintProcess extends PhpProcess
+{
+    const FATAL_ERROR = 'Fatal error';
+    const PARSE_ERROR = 'Parse error';
+    const DEPRECATED_ERROR = 'Deprecated:';
+
+    /**
+     * @var bool
+     */
+    private $showDeprecatedErrors;
+
+    /**
+     * @param PhpExecutable $phpExecutable
+     * @param string $fileToCheck Path to file to check
+     * @param bool $aspTags
+     * @param bool $shortTag
+     * @param bool $deprecated
+     * @throws RunTimeException
+     */
+    public function __construct(PhpExecutable $phpExecutable, $fileToCheck, $aspTags = false, $shortTag = false, $deprecated = false)
+    {
+        if (empty($fileToCheck)) {
+            throw new \InvalidArgumentException("File to check must be set.");
+        }
+
+        $parameters = array(
+            '-d asp_tags=' . ($aspTags ? 'On' : 'Off'),
+            '-d short_open_tag=' . ($shortTag ? 'On' : 'Off'),
+            '-d error_reporting=E_ALL',
+            '-n',
+            '-l',
+            $fileToCheck,
+        );
+
+        $this->showDeprecatedErrors = $deprecated;
+        parent::__construct($phpExecutable, $parameters);
+    }
+
+    /**
+     * @return bool
+     * @throws
+     */
+    public function containsError()
+    {
+        return $this->containsParserError($this->getOutput()) ||
+            $this->containsFatalError($this->getOutput()) ||
+            $this->containsDeprecatedError($this->getOutput());
+    }
+
+    /**
+     * @return string
+     * @throws RunTimeException
+     */
+    public function getSyntaxError()
+    {
+        if ($this->containsError()) {
+            // Look for fatal errors first
+            foreach (explode("\n", $this->getOutput()) as $line) {
+                if ($this->containsFatalError($line)) {
+                    return $line;
+                }
+            }
+
+            // Look for parser errors second
+            foreach (explode("\n", $this->getOutput()) as $line) {
+                if ($this->containsParserError($line)) {
+                    return $line;
+                }
+            }
+
+            // Look for deprecated errors third
+            foreach (explode("\n", $this->getOutput()) as $line) {
+                if ($this->containsDeprecatedError($line)) {
+                    return $line;
+                }
+            }
+
+            throw new RunTimeException("The output '{$this->getOutput()}' does not contain Parse or Syntax errors");
+        }
+
+        return false;
+    }
+
+    /**
+     * @return bool
+     * @throws RunTimeException
+     */
+    public function isFail()
+    {
+        return defined('PHP_WINDOWS_VERSION_MAJOR') ? $this->getStatusCode() === 1 : parent::isFail();
+    }
+
+    /**
+     * @return bool
+     * @throws RunTimeException
+     */
+    public function isSuccess()
+    {
+        return $this->getStatusCode() === 0;
+    }
+
+    /**
+     * @param string $string
+     * @return bool
+     */
+    private function containsParserError($string)
+    {
+        return strpos($string, self::PARSE_ERROR) !== false;
+    }
+
+    /**
+     * @param string $string
+     * @return bool
+     */
+    private function containsFatalError($string)
+    {
+        return strpos($string, self::FATAL_ERROR) !== false;
+    }
+
+    /**
+     * @param string $string
+     * @return bool
+     */
+    private function containsDeprecatedError($string)
+    {
+        if ($this->showDeprecatedErrors === false) {
+            return false;
+        }
+
+        return strpos($string, self::DEPRECATED_ERROR) !== false;
+    }
+}
diff --git a/php-parallel-lint/php-parallel-lint/src/Process/PhpExecutable.php b/php-parallel-lint/php-parallel-lint/src/Process/PhpExecutable.php
new file mode 100644
index 0000000..001b1e9
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/src/Process/PhpExecutable.php
@@ -0,0 +1,128 @@
+<?php
+
+namespace JakubOnderka\PhpParallelLint\Process;
+
+use JakubOnderka\PhpParallelLint\RunTimeException;
+
+class PhpExecutable
+{
+    /** @var string */
+    private $path;
+
+    /**
+     * Version as PHP_VERSION_ID constant
+     * @var int
+     */
+    private $versionId;
+
+    /** @var string */
+    private $hhvmVersion;
+
+    /** @var bool */
+    private $isHhvmType;
+
+    /**
+     * @param string $path
+     * @param int $versionId
+     * @param string $hhvmVersion
+     * @param bool $isHhvmType
+     */
+    public function __construct($path, $versionId, $hhvmVersion, $isHhvmType)
+    {
+        $this->path = $path;
+        $this->versionId = $versionId;
+        $this->hhvmVersion = $hhvmVersion;
+        $this->isHhvmType = $isHhvmType;
+    }
+
+    /**
+     * @return string
+     */
+    public function getHhvmVersion()
+    {
+        return $this->hhvmVersion;
+    }
+
+    /**
+     * @return boolean
+     */
+    public function isIsHhvmType()
+    {
+        return $this->isHhvmType;
+    }
+
+    /**
+     * @return string
+     */
+    public function getPath()
+    {
+        return $this->path;
+    }
+
+    /**
+     * @return int
+     */
+    public function getVersionId()
+    {
+        return $this->versionId;
+    }
+
+    /**
+     * @param string $phpExecutable
+     * @return PhpExecutable
+     * @throws \Exception
+     */
+    public static function getPhpExecutable($phpExecutable)
+    {
+        $codeToExecute = <<<PHP
+echo 'PHP;', PHP_VERSION_ID, ';', defined('HPHP_VERSION') ? HPHP_VERSION : null;
+PHP;
+
+        $process = new Process($phpExecutable, array('-n', '-r', $codeToExecute));
+        $process->waitForFinish();
+
+        try {
+            if ($process->getStatusCode() !== 0 && $process->getStatusCode() !== 255) {
+                throw new RunTimeException("Unable to execute '{$phpExecutable}'.");
+            }
+
+            return self::getPhpExecutableFromOutput($phpExecutable, $process->getOutput());
+
+        } catch (RunTimeException $e) {
+            // Try HHVM type
+            $process = new Process($phpExecutable, array('--php', '-r', $codeToExecute));
+            $process->waitForFinish();
+
+            if ($process->getStatusCode() !== 0 && $process->getStatusCode() !== 255) {
+                throw new RunTimeException("Unable to execute '{$phpExecutable}'.");
+            }
+
+            return self::getPhpExecutableFromOutput($phpExecutable, $process->getOutput(), $isHhvmType = true);
+        }
+    }
+
+    /**
+     * @param string $phpExecutable
+     * @param string $output
+     * @param bool $isHhvmType
+     * @return PhpExecutable
+     * @throws RunTimeException
+     */
+    private static function getPhpExecutableFromOutput($phpExecutable, $output, $isHhvmType = false)
+    {
+        $parts = explode(';', $output);
+
+        if ($parts[0] !== 'PHP' || !preg_match('~([0-9]+)~', $parts[1], $matches)) {
+            throw new RunTimeException("'{$phpExecutable}' is not valid PHP binary.");
+        }
+
+        $hhvmVersion = isset($parts[2]) ? $parts[2] : false;
+
+        return new PhpExecutable(
+            $phpExecutable,
+            intval($matches[1]),
+            $hhvmVersion,
+            $isHhvmType
+        );
+    }
+}
diff --git a/php-parallel-lint/php-parallel-lint/src/Process/PhpProcess.php b/php-parallel-lint/php-parallel-lint/src/Process/PhpProcess.php
new file mode 100644
index 0000000..0df293a
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/src/Process/PhpProcess.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace JakubOnderka\PhpParallelLint\Process;
+
+class PhpProcess extends Process
+{
+    /**
+     * @param PhpExecutable $phpExecutable
+     * @param array $parameters
+     * @param string|null $stdIn
+     * @throws \JakubOnderka\PhpParallelLint\RunTimeException
+     */
+    public function __construct(PhpExecutable $phpExecutable, array $parameters = array(), $stdIn = null)
+    {
+        $constructedParameters = $this->constructParameters($parameters, $phpExecutable->isIsHhvmType());
+        parent::__construct($phpExecutable->getPath(), $constructedParameters, $stdIn);
+    }
+
+    /**
+     * @param array $parameters
+     * @param bool $isHhvm
+     * @return array
+     */
+    private function constructParameters(array $parameters, $isHhvm)
+    {
+        // Always ignore PHP startup errors ("Unable to load library...") in sub-processes.
+        array_unshift($parameters, '-d display_startup_errors=0');
+
+        if ($isHhvm) {
+            array_unshift($parameters, '-php');
+        }
+
+        return $parameters;
+    }
+}
diff --git a/php-parallel-lint/php-parallel-lint/src/Process/Process.php b/php-parallel-lint/php-parallel-lint/src/Process/Process.php
new file mode 100644
index 0000000..190511e
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/src/Process/Process.php
@@ -0,0 +1,153 @@
+<?php
+
+namespace JakubOnderka\PhpParallelLint\Process;
+
+use JakubOnderka\PhpParallelLint\RunTimeException;
+
+class Process
+{
+    const STDIN = 0,
+        STDOUT = 1,
+        STDERR = 2;
+
+    const READ = 'r',
+        WRITE = 'w';
+
+    /** @var resource */
+    protected $process;
+
+    /** @var resource */
+    protected $stdout;
+
+    /** @var resource */
+    protected $stderr;
+
+    /** @var string */
+    private $output;
+
+    /** @var string */
+    private $errorOutput;
+
+    /** @var int */
+    private $statusCode;
+
+    /**
+     * @param string $executable
+     * @param string[] $arguments
+     * @param string $stdInInput
+     * @throws RunTimeException
+     */
+    public function __construct($executable, array $arguments = array(), $stdInInput = null)
+    {
+        $descriptors = array(
+            self::STDIN  => array('pipe', self::READ),
+            self::STDOUT => array('pipe', self::WRITE),
+            self::STDERR => array('pipe', self::WRITE),
+        );
+
+        $cmdLine = $executable . ' ' . implode(' ', array_map('escapeshellarg', $arguments));
+        $this->process = proc_open($cmdLine, $descriptors, $pipes, null, null, array('bypass_shell' => true));
+
+        if ($this->process === false || $this->process === null) {
+            throw new RunTimeException("Cannot create new process $cmdLine");
+        }
+
+        list($stdin, $this->stdout, $this->stderr) = $pipes;
+
+        if ($stdInInput) {
+            fwrite($stdin, $stdInInput);
+        }
+
+        fclose($stdin);
+    }
+
+    /**
+     * @return bool
+     */
+    public function isFinished()
+    {
+        if ($this->statusCode !== null) {
+            return true;
+        }
+
+        $status = proc_get_status($this->process);
+
+        if ($status['running']) {
+            return false;
+        } else if ($this->statusCode === null) {
+            $this->statusCode = (int) $status['exitcode'];
+        }
+
+        // Process outputs
+        $this->output = stream_get_contents($this->stdout);
+        fclose($this->stdout);
+
+        $this->errorOutput = stream_get_contents($this->stderr);
+        fclose($this->stderr);
+
+        $statusCode = proc_close($this->process);
+
+        if ($this->statusCode === null) {
+            $this->statusCode = $statusCode;
+        }
+
+        $this->process = null;
+
+        return true;
+    }
+
+    public function waitForFinish()
+    {
+        while (!$this->isFinished()) {
+            usleep(100);
+        }
+    }
+
+    /**
+     * @return string
+     * @throws RunTimeException
+     */
+    public function getOutput()
+    {
+        if (!$this->isFinished()) {
+            throw new RunTimeException("Cannot get output for running process");
+        }
+
+        return $this->output;
+    }
+
+    /**
+     * @return string
+     * @throws RunTimeException
+     */
+    public function getErrorOutput()
+    {
+        if (!$this->isFinished()) {
+            throw new RunTimeException("Cannot get error output for running process");
+        }
+
+        return $this->errorOutput;
+    }
+
+    /**
+     * @return int
+     * @throws RunTimeException
+     */
+    public function getStatusCode()
+    {
+        if (!$this->isFinished()) {
+            throw new RunTimeException("Cannot get status code for running process");
+        }
+
+        return $this->statusCode;
+    }
+
+    /**
+     * @return bool
+     * @throws RunTimeException
+     */
+    public function isFail()
+    {
+        return $this->getStatusCode() === 1;
+    }
+}
diff --git a/php-parallel-lint/php-parallel-lint/src/Process/SkipLintProcess.php b/php-parallel-lint/php-parallel-lint/src/Process/SkipLintProcess.php
new file mode 100644
index 0000000..52f4e76
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/src/Process/SkipLintProcess.php
@@ -0,0 +1,91 @@
+<?php
+namespace JakubOnderka\PhpParallelLint\Process;
+
+use JakubOnderka\PhpParallelLint\RunTimeException;
+
+class SkipLintProcess extends PhpProcess
+{
+    /** @var array */
+    private $skipped = array();
+
+    /** @var bool */
+    private $done = false;
+
+    /** @var string */
+    private $endLastChunk = '';
+
+    /**
+     * @param PhpExecutable $phpExecutable
+     * @param array $filesToCheck
+     * @throws RunTimeException
+     */
+    public function __construct(PhpExecutable $phpExecutable, array $filesToCheck)
+    {
+        $scriptPath = __DIR__ . '/../../bin/skip-linting.php';
+        $script = file_get_contents($scriptPath);
+
+        if (!$script) {
+            throw new RunTimeException("skip-linting.php script not found in '$scriptPath'.");
+        }
+
+        $script = str_replace('<?php', '', $script);
+
+        $parameters = array('-d', 'display_errors=stderr', '-r', $script);
+        parent::__construct($phpExecutable, $parameters, implode(PHP_EOL, $filesToCheck));
+    }
+
+    /**
+     * @throws RunTimeException
+     */
+    public function getChunk()
+    {
+        if (!$this->isFinished()) {
+            $this->processLines(fread($this->stdout, 8192));
+        }
+    }
+
+    /**
+     * @return bool
+     * @throws \JakubOnderka\PhpParallelLint\RunTimeException
+     */
+    public function isFinished()
+    {
+        $isFinished = parent::isFinished();
+        if ($isFinished && !$this->done) {
+            $this->done = true;
+            $output = $this->getOutput();
+            $this->processLines($output);
+        }
+
+        return $isFinished;
+    }
+
+    /**
+     * @param string $file
+     * @return bool|null
+     */
+    public function isSkipped($file)
+    {
+        if (isset($this->skipped[$file])) {
+            return $this->skipped[$file];
+        }
+
+        return null;
+    }
+
+    /**
+     * @param string $content
+     */
+    private function processLines($content)
+    {
+        if (!empty($content)) {
+            $lines = explode(PHP_EOL, $this->endLastChunk . $content);
+            $this->endLastChunk = array_pop($lines);
+            foreach ($lines as $line) {
+                $parts = explode(';', $line);
+                list($file, $status) = $parts;
+                $this->skipped[$file] = $status === '1' ? true : false;
+            }
+        }
+    }
+}
diff --git a/php-parallel-lint/php-parallel-lint/src/Result.php b/php-parallel-lint/php-parallel-lint/src/Result.php
new file mode 100644
index 0000000..6004374
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/src/Result.php
@@ -0,0 +1,168 @@
+<?php
+namespace JakubOnderka\PhpParallelLint;
+
+class Result implements \JsonSerializable
+{
+    /** @var Error[] */
+    private $errors;
+
+    /** @var array */
+    private $checkedFiles;
+
+    /** @var array */
+    private $skippedFiles;
+
+    /** @var float */
+    private $testTime;
+
+    /**
+     * @param Error[] $errors
+     * @param array $checkedFiles
+     * @param array $skippedFiles
+     * @param float $testTime
+     */
+    public function __construct(array $errors, array $checkedFiles, array $skippedFiles, $testTime)
+    {
+        $this->errors = $errors;
+        $this->checkedFiles = $checkedFiles;
+        $this->skippedFiles = $skippedFiles;
+        $this->testTime = $testTime;
+    }
+
+    /**
+     * @return Error[]
+     */
+    public function getErrors()
+    {
+        return $this->errors;
+    }
+
+    /**
+     * @return bool
+     */
+    public function hasError()
+    {
+        return !empty($this->errors);
+    }
+
+    /**
+     * @return array
+     */
+    public function getFilesWithFail()
+    {
+        $filesWithFail = array();
+        foreach ($this->errors as $error) {
+            if (!$error instanceof SyntaxError) {
+                $filesWithFail[] = $error->getFilePath();
+            }
+        }
+
+        return $filesWithFail;
+    }
+
+    /**
+     * @return int
+     */
+    public function getFilesWithFailCount()
+    {
+        return count($this->getFilesWithFail());
+    }
+
+    /**
+     * @return bool
+     */
+    public function hasFilesWithFail()
+    {
+        return $this->getFilesWithFailCount() !== 0;
+    }
+
+    /**
+     * @return array
+     */
+    public function getCheckedFiles()
+    {
+        return $this->checkedFiles;
+    }
+
+    /**
+     * @return int
+     */
+    public function getCheckedFilesCount()
+    {
+        return count($this->checkedFiles);
+    }
+
+    /**
+     * @return array
+     */
+    public function getSkippedFiles()
+    {
+        return $this->skippedFiles;
+    }
+
+    /**
+     * @return int
+     */
+    public function getSkippedFilesCount()
+    {
+        return count($this->skippedFiles);
+    }
+
+    /**
+     * @return array
+     */
+    public function getFilesWithSyntaxError()
+    {
+        $filesWithSyntaxError = array();
+        foreach ($this->errors as $error) {
+            if ($error instanceof SyntaxError) {
+                $filesWithSyntaxError[] = $error->getFilePath();
+            }
+        }
+
+        return $filesWithSyntaxError;
+    }
+
+    /**
+     * @return int
+     */
+    public function getFilesWithSyntaxErrorCount()
+    {
+        return count($this->getFilesWithSyntaxError());
+    }
+
+    /**
+     * @return bool
+     */
+    public function hasSyntaxError()
+    {
+        return $this->getFilesWithSyntaxErrorCount() !== 0;
+    }
+
+    /**
+     * @return float
+     */
+    public function getTestTime()
+    {
+        return $this->testTime;
+    }
+
+    /**
+     * (PHP 5 &gt;= 5.4.0)<br/>
+     * Specify data which should be serialized to JSON
+     * @link http://php.net/manual/en/jsonserializable.jsonserialize.php
+     * @return mixed data which can be serialized by <b>json_encode</b>,
+     * which is a value of any type other than a resource.
+     */
+    function jsonSerialize()
+    {
+        return array(
+            'checkedFiles' => $this->getCheckedFiles(),
+            'filesWithSyntaxError' => $this->getFilesWithSyntaxError(),
+            'skippedFiles' => $this->getSkippedFiles(),
+            'errors' => $this->getErrors(),
+        );
+    }
+
+
+}
\ No newline at end of file
diff --git a/php-parallel-lint/php-parallel-lint/src/Settings.php b/php-parallel-lint/php-parallel-lint/src/Settings.php
new file mode 100644
index 0000000..3e03a71
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/src/Settings.php
@@ -0,0 +1,242 @@
+<?php
+namespace JakubOnderka\PhpParallelLint;
+
+class Settings
+{
+
+    /**
+     * constants for enum settings
+     */
+    const FORCED = 'FORCED';
+    const DISABLED = 'DISABLED';
+    const AUTODETECT = 'AUTODETECT';
+
+    const FORMAT_TEXT = 'text';
+    const FORMAT_JSON = 'json';
+    const FORMAT_CHECKSTYLE = 'checkstyle';
+
+    /**
+     * Path to PHP executable
+     * @var string
+     */
+    public $phpExecutable = 'php';
+
+    /**
+     * Check code inside PHP opening short tag <? or <?= in PHP 5.3
+     * @var bool
+     */
+    public $shortTag = false;
+
+    /**
+     * Check PHP code inside ASP-style <% %> tags.
+     * @var bool
+     */
+    public $aspTags = false;
+
+    /**
+     * Number of jobs running in same time
+     * @var int
+     */
+    public $parallelJobs = 10;
+
+    /**
+     * If path contains directory, only file with these extensions are checked
+     * @var array
+     */
+    public $extensions = array('php', 'phtml', 'php3', 'php4', 'php5', 'phpt');
+
+    /**
+     * Array of file or directories to check
+     * @var array
+     */
+    public $paths = array();
+
+    /**
+     * Don't check files or directories
+     * @var array
+     */
+    public $excluded = array();
+
+    /**
+     * Mode for color detection. Possible values: self::FORCED, self::DISABLED and self::AUTODETECT
+     * @var string
+     */
+    public $colors = self::AUTODETECT;
+
+    /**
+     * Show progress in text output
+     * @var bool
+     */
+    public $showProgress = true;
+
+    /**
+     * Output format (see FORMAT_* constants)
+     * @var string
+     */
+    public $format = self::FORMAT_TEXT;
+
+    /**
+     * Read files and folder to tests from standard input (blocking)
+     * @var bool
+     */
+    public $stdin = false;
+
+    /**
+     * Try to show git blame for row with error
+     * @var bool
+     */
+    public $blame = false;
+
+    /**
+     * Path to git executable for blame
+     * @var string
+     */
+    public $gitExecutable = 'git';
+
+    /**
+     * @var bool
+     */
+    public $ignoreFails = false;
+
+    /**
+     * @var bool
+     */
+    public $showDeprecated = false;
+
+    /**
+     * Path to a file with syntax error callback
+     * @var string|null
+     */
+    public $syntaxErrorCallbackFile = null;
+
+    /**
+     * @param array $paths
+     */
+    public function addPaths(array $paths)
+    {
+        $this->paths = array_merge($this->paths, $paths);
+    }
+
+    /**
+     * @param array $arguments
+     * @return Settings
+     * @throws InvalidArgumentException
+     */
+    public static function parseArguments(array $arguments)
+    {
+        $arguments = new ArrayIterator(array_slice($arguments, 1));
+        $settings = new self;
+
+        // Use the currently invoked php as the default if possible
+        if (defined('PHP_BINARY')) {
+            $settings->phpExecutable = PHP_BINARY;
+        }
+
+        foreach ($arguments as $argument) {
+            if ($argument[0] !== '-') {
+                $settings->paths[] = $argument;
+            } else {
+                switch ($argument) {
+                    case '-p':
+                        $settings->phpExecutable = $arguments->getNext();
+                        break;
+
+                    case '-s':
+                    case '--short':
+                        $settings->shortTag = true;
+                        break;
+
+                    case '-a':
+                    case '--asp':
+                        $settings->aspTags = true;
+                        break;
+
+                    case '--exclude':
+                        $settings->excluded[] = $arguments->getNext();
+                        break;
+
+                    case '-e':
+                        $settings->extensions = array_map('trim', explode(',', $arguments->getNext()));
+                        break;
+
+                    case '-j':
+                        $settings->parallelJobs = max((int) $arguments->getNext(), 1);
+                        break;
+
+                    case '--colors':
+                        $settings->colors = self::FORCED;
+                        break;
+
+                    case '--no-colors':
+                        $settings->colors = self::DISABLED;
+                        break;
+
+                    case '--no-progress':
+                        $settings->showProgress = false;
+                        break;
+
+                    case '--json':
+                        $settings->format = self::FORMAT_JSON;
+                        break;
+
+                    case '--checkstyle':
+                        $settings->format = self::FORMAT_CHECKSTYLE;
+                        break;
+
+                    case '--git':
+                        $settings->gitExecutable = $arguments->getNext();
+                        break;
+
+                    case '--stdin':
+                        $settings->stdin = true;
+                        break;
+
+                    case '--blame':
+                        $settings->blame = true;
+                        break;
+
+                    case '--ignore-fails':
+                        $settings->ignoreFails = true;
+                        break;
+
+                    case '--show-deprecated':
+                        $settings->showDeprecated = true;
+                        break;
+
+                    case '--syntax-error-callback':
+                        $settings->syntaxErrorCallbackFile = $arguments->getNext();
+                        break;
+
+                    default:
+                        throw new InvalidArgumentException($argument);
+                }
+            }
+        }
+
+        return $settings;
+    }
+
+    /**
+     * @return array
+     */
+    public static function getPathsFromStdIn()
+    {
+        $content = stream_get_contents(STDIN);
+
+        if (empty($content)) {
+            return array();
+        }
+
+        $lines = explode("\n", rtrim($content));
+        return array_map('rtrim', $lines);
+    }
+}
+
+class ArrayIterator extends \ArrayIterator
+{
+    public function getNext()
+    {
+        $this->next();
+        return $this->current();
+    }
+}
diff --git a/php-parallel-lint/php-parallel-lint/src/exceptions.php b/php-parallel-lint/php-parallel-lint/src/exceptions.php
new file mode 100644
index 0000000..3893b83
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/src/exceptions.php
@@ -0,0 +1,90 @@
+<?php
+namespace JakubOnderka\PhpParallelLint;
+
+class Exception extends \Exception implements \JsonSerializable
+{
+    public function jsonSerialize()
+    {
+        return array(
+            'type' => get_class($this),
+            'message' => $this->getMessage(),
+            'code' => $this->getCode(),
+        );
+    }
+}
+
+class RunTimeException extends Exception
+{
+
+}
+
+class InvalidArgumentException extends Exception
+{
+    protected $argument;
+
+    public function __construct($argument)
+    {
+        $this->argument = $argument;
+        $this->message = "Invalid argument $argument";
+    }
+
+    public function getArgument()
+    {
+        return $this->argument;
+    }
+}
+
+class NotExistsPathException extends Exception
+{
+    protected $path;
+
+    public function __construct($path)
+    {
+        $this->path = $path;
+        $this->message = "Path '$path' not found";
+    }
+
+    public function getPath()
+    {
+        return $this->path;
+    }
+}
+
+class NotExistsClassException extends Exception
+{
+    protected $className;
+    protected $fileName;
+
+    public function __construct($className, $fileName)
+    {
+        $this->className = $className;
+        $this->fileName = $fileName;
+        $this->message = "Class with name '$className' does not exists in file '$fileName'";
+    }
+
+    public function getClassName()
+    {
+        return $this->className;
+    }
+
+    public function getFileName()
+    {
+        return $this->fileName;
+    }
+}
+
+class NotImplementCallbackException extends Exception
+{
+    protected $className;
+
+    public function __construct($className)
+    {
+        $this->className = $className;
+        $this->message = "Class '$className' does not implement SyntaxErrorCallback interface.";
+    }
+
+    public function getClassName()
+    {
+        return $this->className;
+    }
+}
diff --git a/php-parallel-lint/php-parallel-lint/src/polyfill.php b/php-parallel-lint/php-parallel-lint/src/polyfill.php
new file mode 100644
index 0000000..79b3049
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/src/polyfill.php
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * Polyfill for PHP < 5.4
+ */
+if (!interface_exists('JsonSerializable', false)) {
+    interface JsonSerializable
+    {
+        /**
+         * @param void
+         * @return mixed
+         */
+        function jsonSerialize();
+    }
+}
diff --git a/phplang/scope-exit/.gitignore b/phplang/scope-exit/.gitignore
new file mode 100644
index 0000000..d8a7996
--- /dev/null
+++ b/phplang/scope-exit/.gitignore
@@ -0,0 +1,2 @@
+composer.lock
+vendor/
diff --git a/phplang/scope-exit/README.md b/phplang/scope-exit/README.md
new file mode 100644
index 0000000..5d495c2
--- /dev/null
+++ b/phplang/scope-exit/README.md
@@ -0,0 +1,18 @@
+# ScopeExit
+
+This simple class provides an implementation of C++'s `SCOPE_EXIT`, or GoLang's `defer`.
+
+To use, assign an instance of this object to a local variable.  When that variable falls out of scope (or is explicitly `unset`, the callback passed to the constructor will be invoked.  This is useful, for example, to aid cleanup at the end of a funciton.
+
+```
+function f(&$x) {
+  $x = 1;
+  $_ = new \PhpLang\ScopeExit(function() use (&$x) { $x = 2; });
+  // $x is still 1 at this point.
+  return 42;
+  // After the return, the local scope is cleaned up, the closure is invoked, and it's set to 2
+}
+
+f($a);
+var_dump($a); // int(2)
+```
diff --git a/phplang/scope-exit/composer.json b/phplang/scope-exit/composer.json
new file mode 100644
index 0000000..3b3e223
--- /dev/null
+++ b/phplang/scope-exit/composer.json
@@ -0,0 +1,28 @@
+{
+  "name": "phplang/scope-exit",
+  "description": "Emulation of SCOPE_EXIT construct from C++",
+  "type": "library",
+  "keywords": [
+    "cleanup",
+    "scope",
+    "exit"
+  ],
+  "homepage": "https://github.com/phplang/scope-exit",
+  "license": "BSD",
+  "authors": [
+    {
+      "name": "Sara Golemon",
+      "email": "pollita@php.net",
+      "homepage": "https://twitter.com/SaraMG",
+      "role": "Developer"
+    }
+  ],
+  "autoload": {
+    "psr-4": {
+      "PhpLang\\": "src/"
+    }
+  },
+  "require-dev": {
+    "phpunit/phpunit": "*"
+  }
+}
diff --git a/phplang/scope-exit/src/ScopeExit.php b/phplang/scope-exit/src/ScopeExit.php
new file mode 100644
index 0000000..e776aba
--- /dev/null
+++ b/phplang/scope-exit/src/ScopeExit.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace PhpLang;
+
+/**
+ * Emulate C++'s SCOPE_EXIT { expr; }; construct by means of a
+ * short-lived object which takes a callable as its ctor arg
+ * and invokes it on destruction (i.e. when it drops out of scope).
+ *
+ * Note that if you retain a copy of this object, such as in
+ * another object's property.  It's scope will not fully exit
+ * until that object dies.
+ *
+ * This allows ScopeExit to apply to more than just lexical scopes.
+ */
+class ScopeExit {
+    /** @var Callable Method to invoke on object destruction */
+    private $onexit = null;
+
+    /**
+     * Initialize object from Callable
+     *
+     * When this object drops out of scope,
+     * either by leaving the scope, or by explicit unset()
+     * The $onexit property is invoked.
+     *
+     * @param Callable $onexit - Invocable callback with zero arg
+     */
+    public function __construct(Callable $onexit) {
+        $this->onexit = $onexit;
+    }
+
+    /**
+     * Invoke the callback since the object is falling out of scope
+     */
+    public function __destruct() {
+        $cb = $this->onexit;
+        $cb();
+    }
+}
diff --git a/phplang/scope-exit/tests/BasicTest.php b/phplang/scope-exit/tests/BasicTest.php
new file mode 100644
index 0000000..b0f320b
--- /dev/null
+++ b/phplang/scope-exit/tests/BasicTest.php
@@ -0,0 +1,29 @@
+<?php
+
+require __DIR__ . '/../vendor/autoload.php';
+
+use PhpLang\ScopeExit;
+
+class ScopeExitBasicTest extends PHPUnit_Framework_TestCase {
+
+    private function fCall(&$arg) {
+        $arg = 1;
+        $_ = new ScopeExit(function() use (&$arg) { $arg = 2; });
+        $this->assertEquals(1, $arg);
+        return;
+    }
+
+    public function testFCall() {
+        $x = 0;
+        $this->fCall($x);
+        $this->assertEquals(2, $x);
+    }
+
+    public function testUnset() {
+      $x = 0;
+      $_ = new ScopeExit(function() use (&$x) { $x = 123; });
+      $this->assertEquals(0, $x);
+      unset($_);
+      $this->assertEquals(123, $x);
+    }
+}
diff --git a/swaggest/json-diff/.gitlab-ci.yml b/swaggest/json-diff/.gitlab-ci.yml
new file mode 100644
index 0000000..35b70e3
--- /dev/null
+++ b/swaggest/json-diff/.gitlab-ci.yml
@@ -0,0 +1,39 @@
+before_script:
+- apt-get update -yqq
+- apt-get install git unzip -yqq
+- curl https://composer.github.io/installer.sig | tr -d '\n' > installer.sig
+- php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
+- php -r "if (hash_file('SHA384', 'composer-setup.php') === file_get_contents('installer.sig')) { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
+- php composer-setup.php
+- php -r "unlink('composer-setup.php'); unlink('installer.sig');"
+- php composer.phar install --prefer-dist --no-ansi --no-interaction --no-progress
+
+test:5.6:
+  image: php:5.6
+  script:
+  - pecl install xdebug-2.5.5
+  - docker-php-ext-enable xdebug
+  - vendor/bin/phpunit --configuration phpunit.xml -v --coverage-text --colors=never --stderr
+
+test:7.0:
+  image: php:7.0
+  script:
+  - pecl install xdebug
+  - docker-php-ext-enable xdebug
+  - vendor/bin/phpunit --configuration phpunit.xml -v --coverage-text --colors=never --stderr
+
+test:7.1:
+  image: php:7.1
+  script:
+  - pecl install xdebug
+  - docker-php-ext-enable xdebug
+  - vendor/bin/phpunit --configuration phpunit.xml -v --coverage-text --colors=never --stderr
+
+test:7.2:
+  image: php:7.2
+  script:
+  - pecl install xdebug
+  - docker-php-ext-enable xdebug
+  - vendor/bin/phpunit --configuration phpunit.xml -v --coverage-text --colors=never --stderr
+  - curl https://github.com/phpstan/phpstan/releases/download/0.9.2/phpstan.phar -sLo ./phpstan.phar
+  - php phpstan.phar analyze -l 7 ./src
diff --git a/swaggest/json-diff/CHANGELOG.md b/swaggest/json-diff/CHANGELOG.md
new file mode 100644
index 0000000..a873ae8
--- /dev/null
+++ b/swaggest/json-diff/CHANGELOG.md
@@ -0,0 +1,66 @@
+# Changelog
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [3.8.1] - 2020-09-25
+
+### Fixed
+- Rearrangement of equal array items is corrupting data by redundant replaces.
+
+## [3.8.0] - 2020-09-25
+
+### Added
+- Rearrangement of equal items for non-homogeneous arrays with `JsonDiff::REARRANGE_ARRAYS` option.
+
+## [3.7.5] - 2020-05-26
+
+### Fixed
+- Accidental array to associative array conversion ([#31](https://github.com/swaggest/json-diff/issues/31)).
+
+## [3.7.4] - 2020-01-26
+
+### Fixed
+- PHP version check for empty property name support.
+
+## [3.7.3] - 2020-01-24
+
+### Fixed
+- Merge patch was not replacing partially different arrays.
+
+## [3.7.2] - 2019-10-23
+
+### Added
+- Change log.
+
+### Fixed
+- Few irrelevant files not mentioned in `.gitattributes`.
+
+## [3.7.1] - 2019-09-26
+
+### Added
+- Benchmarks to CI.
+
+### Fixed
+- Unstable array rearrange order.
+
+## [3.7.0] - 2019-04-25
+
+### Added
+- `getModifiedDiff()` and `COLLECT_MODIFIED_DIFF` option to return paths with original and new values.
+
+## [3.6.0] - 2019-04-24
+
+### Added
+- Compatibility option to `TOLERATE_ASSOCIATIVE_ARRAYS` that mimic JSON objects.
+
+[3.8.1]: https://github.com/swaggest/json-diff/compare/v3.8.0...v3.8.1
+[3.8.0]: https://github.com/swaggest/json-diff/compare/v3.7.5...v3.8.0
+[3.7.5]: https://github.com/swaggest/json-diff/compare/v3.7.4...v3.7.5
+[3.7.4]: https://github.com/swaggest/json-diff/compare/v3.7.3...v3.7.4
+[3.7.3]: https://github.com/swaggest/json-diff/compare/v3.7.2...v3.7.3
+[3.7.2]: https://github.com/swaggest/json-diff/compare/v3.7.1...v3.7.2
+[3.7.1]: https://github.com/swaggest/json-diff/compare/v3.7.0...v3.7.1
+[3.7.0]: https://github.com/swaggest/json-diff/compare/v3.6.0...v3.7.0
+[3.6.0]: https://github.com/swaggest/json-diff/compare/v3.5.1...v3.6.0
diff --git a/swaggest/json-diff/LICENSE b/swaggest/json-diff/LICENSE
new file mode 100644
index 0000000..f23a483
--- /dev/null
+++ b/swaggest/json-diff/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 swaggest
+
+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/swaggest/json-diff/README.md b/swaggest/json-diff/README.md
new file mode 100644
index 0000000..2111c69
--- /dev/null
+++ b/swaggest/json-diff/README.md
@@ -0,0 +1,285 @@
+# JSON diff/rearrange/patch/pointer library for PHP
+
+A PHP implementation for finding unordered diff between two `JSON` documents.
+
+[![Build Status](https://travis-ci.org/swaggest/json-diff.svg?branch=master)](https://travis-ci.org/swaggest/json-diff)
+[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/swaggest/json-diff/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/swaggest/json-diff/?branch=master)
+[![Code Climate](https://codeclimate.com/github/swaggest/json-diff/badges/gpa.svg)](https://codeclimate.com/github/swaggest/json-diff)
+[![Code Coverage](https://scrutinizer-ci.com/g/swaggest/json-diff/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/swaggest/json-diff/code-structure/master/code-coverage)
+
+## Purpose
+
+ * To simplify changes review between two `JSON` files you can use a standard `diff` tool on rearranged pretty-printed `JSON`.
+ * To detect breaking changes by analyzing removals and changes from original `JSON`.
+ * To keep original order of object sets (for example `swagger.json` [parameters](https://swagger.io/docs/specification/describing-parameters/) list).
+ * To [make](#getpatch) and [apply](#jsonpatch) JSON Patches, specified in [RFC 6902](http://tools.ietf.org/html/rfc6902) from the IETF.
+ * To [make](#getmergepatch) and [apply](#jsonmergepatch) JSON Merge Patches, specified in [RFC 7386](https://tools.ietf.org/html/rfc7386) from the IETF.
+ * To retrieve and modify data by [JSON Pointer](http://tools.ietf.org/html/rfc6901).
+ * To recursively replace by JSON value.
+
+## Installation
+
+### Library
+
+```bash
+git clone https://github.com/swaggest/json-diff.git
+```
+
+### Composer
+
+[Install PHP Composer](https://getcomposer.org/doc/00-intro.md)
+
+```bash
+composer require swaggest/json-diff
+```
+
+## Library usage
+
+### `JsonDiff`
+
+Create `JsonDiff` object from two values (`original` and `new`).
+
+```php
+$r = new JsonDiff(json_decode($originalJson), json_decode($newJson));
+```
+
+On construction `JsonDiff` will build `rearranged` value of `new` recursively keeping `original` keys order where possible. 
+Keys that are missing in `original` will be appended to the end of `rearranged` value in same order they had in `new` value.
+
+If two values are arrays of objects, `JsonDiff` will try to find a common unique field in those objects and use it as criteria for rearranging. 
+You can enable this behaviour with `JsonDiff::REARRANGE_ARRAYS` option:
+```php
+$r = new JsonDiff(
+    json_decode($originalJson), 
+    json_decode($newJson),
+    JsonDiff::REARRANGE_ARRAYS
+);
+```
+
+Available options:
+ * `REARRANGE_ARRAYS` is an option to enable [arrays rearrangement](#arrays-rearrangement) to minimize the difference.
+ * `STOP_ON_DIFF` is an option to improve performance by stopping comparison when a difference is found.
+ * `JSON_URI_FRAGMENT_ID` is an option to use URI Fragment Identifier Representation (example: "#/c%25d"). If not set default JSON String Representation (example: "/c%d").
+ * `SKIP_JSON_PATCH` is an option to improve performance by not building JsonPatch for this diff.
+ * `SKIP_JSON_MERGE_PATCH` is an option to improve performance by not building JSON Merge Patch value for this diff.
+ * `TOLERATE_ASSOCIATIVE_ARRAYS` is an option to allow associative arrays to mimic JSON objects (not recommended).
+ * `COLLECT_MODIFIED_DIFF` is an option to enable [getModifiedDiff](#getmodifieddiff).
+
+Options can be combined, e.g. `JsonDiff::REARRANGE_ARRAYS + JsonDiff::STOP_ON_DIFF`.
+
+#### `getDiffCnt`
+Returns total number of differences
+
+#### `getPatch`
+Returns [`JsonPatch`](#jsonpatch) of difference
+
+#### `getMergePatch`
+Returns [JSON Merge Patch](https://tools.ietf.org/html/rfc7386) value of difference
+
+#### `getRearranged`
+Returns new value, rearranged with original order.
+
+#### `getRemoved`
+Returns removals as partial value of original.
+
+#### `getRemovedPaths`
+Returns list of `JSON` paths that were removed from original.
+
+#### `getRemovedCnt`
+Returns number of removals.
+
+#### `getAdded`
+Returns additions as partial value of new.
+
+#### `getAddedPaths`
+Returns list of `JSON` paths that were added to new.
+
+#### `getAddedCnt`
+Returns number of additions.
+
+#### `getModifiedOriginal`
+Returns modifications as partial value of original.
+
+#### `getModifiedNew`
+Returns modifications as partial value of new.
+
+#### `getModifiedDiff`
+Returns list of [`ModifiedPathDiff`](src/ModifiedPathDiff.php) containing paths with original and new values.
+
+Not collected by default, requires `JsonDiff::COLLECT_MODIFIED_DIFF` option.
+
+#### `getModifiedPaths`
+Returns list of `JSON` paths that were modified from original to new.
+
+#### `getModifiedCnt`
+Returns number of modifications.
+
+### `JsonPatch`
+
+#### `import`
+Creates `JsonPatch` instance from `JSON`-decoded data.
+
+#### `export`
+Creates patch data from `JsonPatch` object.
+
+#### `op`
+Adds operation to `JsonPatch`.
+
+#### `apply`
+Applies patch to `JSON`-decoded data.
+
+#### `setFlags`
+Alters default behavior.
+
+Available flags:
+
+* `JsonPatch::STRICT_MODE` Disallow converting empty array to object for key creation.
+* `JsonPatch::TOLERATE_ASSOCIATIVE_ARRAYS` Allow associative arrays to mimic JSON objects (not recommended).
+
+### `JsonPointer`
+
+#### `escapeSegment`
+Escapes path segment.
+
+#### `splitPath`
+Creates array of unescaped segments from `JSON Pointer` string.
+
+#### `buildPath`
+Creates `JSON Pointer` string from array of unescaped segments.
+
+#### `add`
+Adds value to data at path specified by segments.
+
+#### `get`
+Gets value from data at path specified by segments.
+
+#### `getByPointer`
+Gets value from data at path specified `JSON Pointer` string.
+
+#### `remove`
+Removes value from data at path specified by segments.
+
+### `JsonMergePatch`
+
+#### `apply`
+Applies patch to `JSON`-decoded data.
+
+### `JsonValueReplace`
+
+#### `process`
+Recursively replaces all nodes equal to `search` value with `replace` value.
+
+## Example
+
+```php
+$originalJson = <<<'JSON'
+{
+    "key1": [4, 1, 2, 3],
+    "key2": 2,
+    "key3": {
+        "sub0": 0,
+        "sub1": "a",
+        "sub2": "b"
+    },
+    "key4": [
+        {"a":1, "b":true, "subs": [{"s":1}, {"s":2}, {"s":3}]}, {"a":2, "b":false}, {"a":3}
+    ]
+}
+JSON;
+
+$newJson = <<<'JSON'
+{
+    "key5": "wat",
+    "key1": [5, 1, 2, 3],
+    "key4": [
+        {"c":false, "a":2}, {"a":1, "b":true, "subs": [{"s":3, "add": true}, {"s":2}, {"s":1}]}, {"c":1, "a":3}
+    ],
+    "key3": {
+        "sub3": 0,
+        "sub2": false,
+        "sub1": "c"
+    }
+}
+JSON;
+
+$patchJson = <<<'JSON'
+[
+    {"value":4,"op":"test","path":"/key1/0"},
+    {"value":5,"op":"replace","path":"/key1/0"},
+    
+    {"op":"remove","path":"/key2"},
+    
+    {"op":"remove","path":"/key3/sub0"},
+    
+    {"value":"a","op":"test","path":"/key3/sub1"},
+    {"value":"c","op":"replace","path":"/key3/sub1"},
+    
+    {"value":"b","op":"test","path":"/key3/sub2"},
+    {"value":false,"op":"replace","path":"/key3/sub2"},
+    
+    {"value":0,"op":"add","path":"/key3/sub3"},
+
+    {"value":true,"op":"add","path":"/key4/0/subs/2/add"},
+    
+    {"op":"remove","path":"/key4/1/b"},
+    
+    {"value":false,"op":"add","path":"/key4/1/c"},
+    
+    {"value":1,"op":"add","path":"/key4/2/c"},
+    
+    {"value":"wat","op":"add","path":"/key5"}
+]
+JSON;
+
+$diff = new JsonDiff(json_decode($originalJson), json_decode($newJson), JsonDiff::REARRANGE_ARRAYS);
+$this->assertEquals(json_decode($patchJson), $diff->getPatch()->jsonSerialize());
+
+$original = json_decode($originalJson);
+$patch = JsonPatch::import(json_decode($patchJson));
+$patch->apply($original);
+$this->assertEquals($diff->getRearranged(), $original);
+```
+
+## PHP Classes as JSON objects
+
+Due to magical methods and other restrictions PHP classes can not be reliably mapped to/from JSON objects.
+There is support for objects of PHP classes in `JsonPointer` with limitations:
+* `null` is equal to non-existent
+
+## Arrays Rearrangement
+
+When `JsonDiff::REARRANGE_ARRAYS` option is enabled, array items are ordered to match the original array.
+
+If arrays contain homogenous objects, and those objects have a common property with unique values, array is
+ordered to match placement of items with same value of such property in the original array.
+
+Example:
+original
+```json
+[{"name": "Alex", "height": 180},{"name": "Joe", "height": 179},{"name": "Jane", "height": 165}]
+```
+vs new
+```json
+[{"name": "Joe", "height": 179},{"name": "Jane", "height": 168},{"name": "Alex", "height": 180}]
+```
+would produce a patch:
+```json
+[{"value":165,"op":"test","path":"/2/height"},{"value":168,"op":"replace","path":"/2/height"}]
+```
+
+If qualifying indexing property is not found, rearrangement is done based on items equality.
+
+Example:
+original
+```json
+{"data": [{"A": 1, "C": [1, 2, 3]}, {"B": 2}]}
+```
+vs new
+```json
+{"data": [{"B": 2}, {"A": 1, "C": [3, 2, 1]}]}
+```
+would produce no difference.
+
+## CLI tool
+
+Moved to [`swaggest/json-cli`](https://github.com/swaggest/json-cli)
\ No newline at end of file
diff --git a/swaggest/json-diff/composer.json b/swaggest/json-diff/composer.json
new file mode 100644
index 0000000..bb0ce49
--- /dev/null
+++ b/swaggest/json-diff/composer.json
@@ -0,0 +1,33 @@
+{
+  "name": "swaggest/json-diff",
+  "description": "JSON diff/rearrange/patch/pointer library for PHP",
+  "type": "library",
+  "license": "MIT",
+  "authors": [
+    {
+      "name": "Viacheslav Poturaev",
+      "email": "vearutop@gmail.com"
+    }
+  ],
+  "require": {
+    "ext-json": "*"
+  },
+  "require-dev": {
+    "phpunit/phpunit": "^4.8.23"
+  },
+  "autoload": {
+    "psr-4": {
+      "Swaggest\\JsonDiff\\": "src/"
+    }
+  },
+  "autoload-dev": {
+    "psr-4": {
+      "Swaggest\\JsonDiff\\Tests\\": "tests/src"
+    }
+  },
+  "config": {
+    "platform": {
+      "php": "5.4.45"
+    }
+  }
+}
diff --git a/swaggest/json-diff/src/Exception.php b/swaggest/json-diff/src/Exception.php
new file mode 100644
index 0000000..1af1431
--- /dev/null
+++ b/swaggest/json-diff/src/Exception.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Swaggest\JsonDiff;
+
+
+class Exception extends \Exception
+{
+    const EMPTY_PROPERTY_NAME_UNSUPPORTED = 1;
+}
\ No newline at end of file
diff --git a/swaggest/json-diff/src/JsonDiff.php b/swaggest/json-diff/src/JsonDiff.php
new file mode 100644
index 0000000..dfd3c90
--- /dev/null
+++ b/swaggest/json-diff/src/JsonDiff.php
@@ -0,0 +1,542 @@
+<?php
+
+namespace Swaggest\JsonDiff;
+
+use Swaggest\JsonDiff\JsonPatch\Add;
+use Swaggest\JsonDiff\JsonPatch\Remove;
+use Swaggest\JsonDiff\JsonPatch\Replace;
+use Swaggest\JsonDiff\JsonPatch\Test;
+
+class JsonDiff
+{
+    /**
+     * REARRANGE_ARRAYS is an option to enable arrays rearrangement to minimize the difference.
+     */
+    const REARRANGE_ARRAYS = 1;
+
+    /**
+     * STOP_ON_DIFF is an option to improve performance by stopping comparison when a difference is found.
+     */
+    const STOP_ON_DIFF = 2;
+
+    /**
+     * JSON_URI_FRAGMENT_ID is an option to use URI Fragment Identifier Representation (example: "#/c%25d").
+     * If not set default JSON String Representation (example: "/c%d").
+     */
+    const JSON_URI_FRAGMENT_ID = 4;
+
+    /**
+     * SKIP_JSON_PATCH is an option to improve performance by not building JsonPatch for this diff.
+     */
+    const SKIP_JSON_PATCH = 8;
+
+    /**
+     * SKIP_JSON_MERGE_PATCH is an option to improve performance by not building JSON Merge Patch value for this diff.
+     */
+    const SKIP_JSON_MERGE_PATCH = 16;
+
+    /**
+     * TOLERATE_ASSOCIATIVE_ARRAYS is an option to allow associative arrays to mimic JSON objects (not recommended)
+     */
+    const TOLERATE_ASSOCIATIVE_ARRAYS = 32;
+
+    /**
+     * COLLECT_MODIFIED_DIFF is an option to enable getModifiedDiff.
+     */
+    const COLLECT_MODIFIED_DIFF = 64;
+
+
+    private $options = 0;
+
+    /**
+     * @var mixed Merge patch container
+     */
+    private $merge;
+
+    private $added;
+    private $addedCnt = 0;
+    private $addedPaths = array();
+
+    private $removed;
+    private $removedCnt = 0;
+    private $removedPaths = array();
+
+    private $modifiedOriginal;
+    private $modifiedNew;
+    private $modifiedCnt = 0;
+    private $modifiedPaths = array();
+    /**
+     * @var ModifiedPathDiff[]
+     */
+    private $modifiedDiff = array();
+
+    private $path = '';
+    private $pathItems = array();
+
+    private $rearranged;
+
+    /** @var JsonPatch */
+    private $jsonPatch;
+
+    /** @var JsonHash */
+    private $jsonHashOriginal;
+
+    /** @var JsonHash */
+    private $jsonHashNew;
+
+    /**
+     * @param mixed $original
+     * @param mixed $new
+     * @param int $options
+     * @throws Exception
+     */
+    public function __construct($original, $new, $options = 0)
+    {
+        if (!($options & self::SKIP_JSON_PATCH)) {
+            $this->jsonPatch = new JsonPatch();
+        }
+
+        $this->options = $options;
+
+        if ($options & self::JSON_URI_FRAGMENT_ID) {
+            $this->path = '#';
+        }
+
+        $this->rearranged = $this->process($original, $new);
+        if (($new !== null) && $this->merge === null) {
+            $this->merge = new \stdClass();
+        }
+    }
+
+    /**
+     * Returns total number of differences
+     * @return int
+     */
+    public function getDiffCnt()
+    {
+        return $this->addedCnt + $this->modifiedCnt + $this->removedCnt;
+    }
+
+    /**
+     * Returns removals as partial value of original.
+     * @return mixed
+     */
+    public function getRemoved()
+    {
+        return $this->removed;
+    }
+
+    /**
+     * Returns list of `JSON` paths that were removed from original.
+     * @return array
+     */
+    public function getRemovedPaths()
+    {
+        return $this->removedPaths;
+    }
+
+    /**
+     * Returns number of removals.
+     * @return int
+     */
+    public function getRemovedCnt()
+    {
+        return $this->removedCnt;
+    }
+
+    /**
+     * Returns additions as partial value of new.
+     * @return mixed
+     */
+    public function getAdded()
+    {
+        return $this->added;
+    }
+
+    /**
+     * Returns number of additions.
+     * @return int
+     */
+    public function getAddedCnt()
+    {
+        return $this->addedCnt;
+    }
+
+    /**
+     * Returns list of `JSON` paths that were added to new.
+     * @return array
+     */
+    public function getAddedPaths()
+    {
+        return $this->addedPaths;
+    }
+
+    /**
+     * Returns changes as partial value of original.
+     * @return mixed
+     */
+    public function getModifiedOriginal()
+    {
+        return $this->modifiedOriginal;
+    }
+
+    /**
+     * Returns changes as partial value of new.
+     * @return mixed
+     */
+    public function getModifiedNew()
+    {
+        return $this->modifiedNew;
+    }
+
+    /**
+     * Returns number of changes.
+     * @return int
+     */
+    public function getModifiedCnt()
+    {
+        return $this->modifiedCnt;
+    }
+
+    /**
+     * Returns list of `JSON` paths that were changed from original to new.
+     * @return array
+     */
+    public function getModifiedPaths()
+    {
+        return $this->modifiedPaths;
+    }
+
+    /**
+     * Returns list of paths with original and new values.
+     * @return ModifiedPathDiff[]
+     */
+    public function getModifiedDiff()
+    {
+        return $this->modifiedDiff;
+    }
+
+    /**
+     * Returns new value, rearranged with original order.
+     * @return array|object
+     */
+    public function getRearranged()
+    {
+        return $this->rearranged;
+    }
+
+    /**
+     * Returns JsonPatch of difference
+     * @return JsonPatch
+     */
+    public function getPatch()
+    {
+        return $this->jsonPatch;
+    }
+
+    /**
+     * Returns JSON Merge Patch value of difference
+     */
+    public function getMergePatch()
+    {
+        return $this->merge;
+
+    }
+
+
+    /**
+     * @param mixed $original
+     * @param mixed $new
+     * @return array|null|object|\stdClass
+     * @throws Exception
+     */
+    private function process($original, $new)
+    {
+        $merge = !($this->options & self::SKIP_JSON_MERGE_PATCH);
+
+        if ($this->options & self::TOLERATE_ASSOCIATIVE_ARRAYS) {
+            if (is_array($original) && !empty($original) && !array_key_exists(0, $original)) {
+                $original = (object)$original;
+            }
+
+            if (is_array($new) && !empty($new) && !array_key_exists(0, $new)) {
+                $new = (object)$new;
+            }
+        }
+
+        if (
+            (!$original instanceof \stdClass && !is_array($original))
+            || (!$new instanceof \stdClass && !is_array($new))
+        ) {
+            if ($original !== $new) {
+                $this->modifiedCnt++;
+                if ($this->options & self::STOP_ON_DIFF) {
+                    return null;
+                }
+                $this->modifiedPaths [] = $this->path;
+
+                if ($this->jsonPatch !== null) {
+                    $this->jsonPatch->op(new Test($this->path, $original));
+                    $this->jsonPatch->op(new Replace($this->path, $new));
+                }
+
+                JsonPointer::add($this->modifiedOriginal, $this->pathItems, $original);
+                JsonPointer::add($this->modifiedNew, $this->pathItems, $new);
+
+                if ($merge) {
+                    JsonPointer::add($this->merge, $this->pathItems, $new, JsonPointer::RECURSIVE_KEY_CREATION);
+                }
+
+                if ($this->options & self::COLLECT_MODIFIED_DIFF) {
+                    $this->modifiedDiff[] = new ModifiedPathDiff($this->path, $original, $new);
+                }
+            }
+            return $new;
+        }
+
+        if (
+            ($this->options & self::REARRANGE_ARRAYS)
+            && is_array($original) && is_array($new)
+        ) {
+            $new = $this->rearrangeArray($original, $new);
+        }
+
+        $newArray = $new instanceof \stdClass ? get_object_vars($new) : $new;
+        $newOrdered = array();
+
+        $originalKeys = $original instanceof \stdClass ? get_object_vars($original) : $original;
+        $isArray = is_array($original);
+        $removedOffset = 0;
+
+        if ($merge && is_array($new) && !is_array($original)) {
+            $merge = false;
+            JsonPointer::add($this->merge, $this->pathItems, $new);
+        } elseif ($merge && $new instanceof \stdClass && !$original instanceof \stdClass) {
+            $merge = false;
+            JsonPointer::add($this->merge, $this->pathItems, $new);
+        }
+
+        $isUriFragment = (bool)($this->options & self::JSON_URI_FRAGMENT_ID);
+        $diffCnt = $this->addedCnt + $this->modifiedCnt + $this->removedCnt;
+        foreach ($originalKeys as $key => $originalValue) {
+            if ($this->options & self::STOP_ON_DIFF) {
+                if ($this->modifiedCnt || $this->addedCnt || $this->removedCnt) {
+                    return null;
+                }
+            }
+
+            $path = $this->path;
+            $pathItems = $this->pathItems;
+            $actualKey = $key;
+            if ($isArray && is_int($actualKey)) {
+                $actualKey -= $removedOffset;
+            }
+            $this->path .= '/' . JsonPointer::escapeSegment((string)$actualKey, $isUriFragment);
+            $this->pathItems[] = $actualKey;
+
+            if (array_key_exists($key, $newArray)) {
+                $newOrdered[$key] = $this->process($originalValue, $newArray[$key]);
+                unset($newArray[$key]);
+            } else {
+                $this->removedCnt++;
+                if ($this->options & self::STOP_ON_DIFF) {
+                    return null;
+                }
+                $this->removedPaths [] = $this->path;
+                if ($isArray) {
+                    $removedOffset++;
+                }
+
+                if ($this->jsonPatch !== null) {
+                    $this->jsonPatch->op(new Remove($this->path));
+                }
+
+                JsonPointer::add($this->removed, $this->pathItems, $originalValue);
+                if ($merge) {
+                    JsonPointer::add($this->merge, $this->pathItems, null);
+                }
+
+            }
+            $this->path = $path;
+            $this->pathItems = $pathItems;
+        }
+
+        if ($merge && $isArray && $this->addedCnt + $this->modifiedCnt + $this->removedCnt > $diffCnt) {
+            JsonPointer::add($this->merge, $this->pathItems, $new);
+        }
+
+        // additions
+        foreach ($newArray as $key => $value) {
+            $this->addedCnt++;
+            if ($this->options & self::STOP_ON_DIFF) {
+                return null;
+            }
+            $newOrdered[$key] = $value;
+            $path = $this->path . '/' . JsonPointer::escapeSegment($key, $isUriFragment);
+            $pathItems = $this->pathItems;
+            $pathItems[] = $key;
+            JsonPointer::add($this->added, $pathItems, $value);
+            if ($merge) {
+                JsonPointer::add($this->merge, $pathItems, $value);
+            }
+
+            $this->addedPaths [] = $path;
+
+            if ($this->jsonPatch !== null) {
+                $this->jsonPatch->op(new Add($path, $value));
+            }
+
+        }
+
+        return is_array($new) ? $newOrdered : (object)$newOrdered;
+    }
+
+    /**
+     * @param array $original
+     * @param array $new
+     * @return array
+     */
+    private function rearrangeArray(array $original, array $new)
+    {
+        $first = reset($original);
+        if (!$first instanceof \stdClass) {
+            return $this->rearrangeEqualItems($original, $new);
+        }
+
+        $uniqueKey = false;
+        $uniqueIdx = array();
+
+        // find unique key for all items
+        /** @var mixed[string]  $f */
+        $f = get_object_vars($first);
+        foreach ($f as $key => $value) {
+            if (is_array($value) || $value instanceof \stdClass) {
+                continue;
+            }
+
+            $keyIsUnique = true;
+            $uniqueIdx = array();
+            foreach ($original as $idx => $item) {
+                if (!$item instanceof \stdClass) {
+                    return $new;
+                }
+                if (!isset($item->$key)) {
+                    $keyIsUnique = false;
+                    break;
+                }
+                $value = $item->$key;
+                if ($value instanceof \stdClass || is_array($value)) {
+                    $keyIsUnique = false;
+                    break;
+                }
+
+                if (isset($uniqueIdx[$value])) {
+                    $keyIsUnique = false;
+                    break;
+                }
+                $uniqueIdx[$value] = $idx;
+            }
+
+            if ($keyIsUnique) {
+                $uniqueKey = $key;
+                break;
+            }
+        }
+
+        if (!$uniqueKey) {
+            return $this->rearrangeEqualItems($original, $new);
+        }
+
+        $newRearranged = [];
+        $changedItems = [];
+
+        foreach ($new as $item) {
+            if (!$item instanceof \stdClass) {
+                return $new;
+            }
+
+            if (!property_exists($item, $uniqueKey)) {
+                return $new;
+            }
+
+            $value = $item->$uniqueKey;
+
+            if ($value instanceof \stdClass || is_array($value)) {
+                return $new;
+            }
+
+
+            if (isset($uniqueIdx[$value])) {
+                $idx = $uniqueIdx[$value];
+                // Abandon rearrangement if key is not unique in new array.
+                if (isset($newRearranged[$idx])) {
+                    return $new;
+                }
+
+                $newRearranged[$idx] = $item;
+            } else {
+                $changedItems[] = $item;
+            }
+
+            $newIdx[$value] = $item;
+        }
+
+        $idx = 0;
+        foreach ($changedItems as $item) {
+            while (array_key_exists($idx, $newRearranged)) {
+                $idx++;
+            }
+            $newRearranged[$idx] = $item;
+        }
+
+        ksort($newRearranged);
+        $newRearranged = array_values($newRearranged);
+        return $newRearranged;
+    }
+
+    private function rearrangeEqualItems(array $original, array $new)
+    {
+        if ($this->jsonHashOriginal === null) {
+            $this->jsonHashOriginal = new JsonHash($this->options);
+            $this->jsonHashNew = new JsonHash($this->options);
+        }
+
+        $origIdx = [];
+        foreach ($original as $i => $item) {
+            $hash = $this->jsonHashOriginal->xorHash($item);
+            $origIdx[$hash][] = $i;
+        }
+
+        $newIdx = [];
+        foreach ($new as $i => $item) {
+            $hash = $this->jsonHashNew->xorHash($item);
+            $newIdx[$i] = $hash;
+        }
+
+        $newRearranged = [];
+        $changedItems = [];
+        foreach ($newIdx as $i => $hash) {
+            if (!empty($origIdx[$hash])) {
+                $j = array_shift($origIdx[$hash]);
+
+                $newRearranged[$j] = $new[$i];
+            } else {
+                $changedItems []= $new[$i];
+            }
+
+        }
+
+        $idx = 0;
+        foreach ($changedItems as $item) {
+            while (array_key_exists($idx, $newRearranged)) {
+                $idx++;
+            }
+            $newRearranged[$idx] = $item;
+        }
+
+        ksort($newRearranged);
+        $newRearranged = array_values($newRearranged);
+
+        return $newRearranged;
+    }
+}
\ No newline at end of file
diff --git a/swaggest/json-diff/src/JsonHash.php b/swaggest/json-diff/src/JsonHash.php
new file mode 100644
index 0000000..4b03e86
--- /dev/null
+++ b/swaggest/json-diff/src/JsonHash.php
@@ -0,0 +1,76 @@
+<?php
+
+namespace Swaggest\JsonDiff;
+
+class JsonHash
+{
+    private $options = 0;
+
+    public function __construct($options = 0)
+    {
+        $this->options = $options;
+    }
+
+    /**
+     * @param mixed $data
+     * @param string $path
+     * @return string
+     */
+    public function xorHash($data, $path = '')
+    {
+        $xorHash = '';
+
+        if (!$data instanceof \stdClass && !is_array($data)) {
+            $s = $path . (string)$data;
+            if (strlen($xorHash) < strlen($s)) {
+                $xorHash = str_pad($xorHash, strlen($s));
+            }
+            $xorHash ^= $s;
+
+            return $xorHash;
+        }
+
+        if ($this->options & JsonDiff::TOLERATE_ASSOCIATIVE_ARRAYS) {
+            if (is_array($data) && !empty($data) && !array_key_exists(0, $data)) {
+                $data = (object)$data;
+            }
+        }
+
+        if (is_array($data)) {
+            if ($this->options & JsonDiff::REARRANGE_ARRAYS) {
+                foreach ($data as $key => $item) {
+                    $itemPath = $path . '/' . $key;
+                    $itemHash = $path . $this->xorHash($item, $itemPath);
+                    if (strlen($xorHash) < strlen($itemHash)) {
+                        $xorHash = str_pad($xorHash, strlen($itemHash));
+                    }
+                    $xorHash ^= $itemHash;
+                }
+            } else {
+                foreach ($data as $key => $item) {
+                    $itemPath = $path . '/' . $key;
+                    $itemHash = md5($itemPath . $this->xorHash($item, $itemPath), true);
+                    if (strlen($xorHash) < strlen($itemHash)) {
+                        $xorHash = str_pad($xorHash, strlen($itemHash));
+                    }
+                    $xorHash ^= $itemHash;
+                }
+            }
+
+            return $xorHash;
+        }
+
+        $dataKeys = get_object_vars($data);
+        foreach ($dataKeys as $key => $value) {
+            $propertyPath = $path . '/' .
+                JsonPointer::escapeSegment($key, (bool)($this->options & JsonDiff::JSON_URI_FRAGMENT_ID));
+            $propertyHash = $propertyPath . $this->xorHash($value, $propertyPath);
+            if (strlen($xorHash) < strlen($propertyHash)) {
+                $xorHash = str_pad($xorHash, strlen($propertyHash));
+            }
+            $xorHash ^= $propertyHash;
+        }
+
+        return $xorHash;
+    }
+}
\ No newline at end of file
diff --git a/swaggest/json-diff/src/JsonMergePatch.php b/swaggest/json-diff/src/JsonMergePatch.php
new file mode 100644
index 0000000..9b49543
--- /dev/null
+++ b/swaggest/json-diff/src/JsonMergePatch.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Swaggest\JsonDiff;
+
+
+class JsonMergePatch
+{
+    public static function apply(&$original, $patch)
+    {
+        if (null === $patch) {
+            $original = null;
+        } elseif (is_object($patch)) {
+            foreach (get_object_vars($patch) as $key => $val) {
+                if ($val === null) {
+                    unset($original->$key);
+                } else {
+                    if (!is_object($original)) {
+                        $original = new \stdClass();
+                    }
+                    $branch = &$original->$key;
+                    if (null === $branch) {
+                        $branch = new \stdClass();
+                    }
+                    self::apply($branch, $val);
+                }
+            }
+        } else {
+            $original = $patch;
+        }
+    }
+}
\ No newline at end of file
diff --git a/swaggest/json-diff/src/JsonPatch.php b/swaggest/json-diff/src/JsonPatch.php
new file mode 100644
index 0000000..eac3510
--- /dev/null
+++ b/swaggest/json-diff/src/JsonPatch.php
@@ -0,0 +1,187 @@
+<?php
+
+namespace Swaggest\JsonDiff;
+
+use Swaggest\JsonDiff\JsonPatch\Add;
+use Swaggest\JsonDiff\JsonPatch\Copy;
+use Swaggest\JsonDiff\JsonPatch\Move;
+use Swaggest\JsonDiff\JsonPatch\OpPath;
+use Swaggest\JsonDiff\JsonPatch\OpPathFrom;
+use Swaggest\JsonDiff\JsonPatch\OpPathValue;
+use Swaggest\JsonDiff\JsonPatch\Remove;
+use Swaggest\JsonDiff\JsonPatch\Replace;
+use Swaggest\JsonDiff\JsonPatch\Test;
+
+/**
+ * JSON Patch is specified in [RFC 6902](http://tools.ietf.org/html/rfc6902) from the IETF.
+ *
+ * Class JsonPatch
+ */
+class JsonPatch implements \JsonSerializable
+{
+    /**
+     * Disallow converting empty array to object for key creation
+     * @see JsonPointer::STRICT_MODE
+     */
+    const STRICT_MODE = 2;
+
+    /**
+     * Allow associative arrays to mimic JSON objects (not recommended)
+     */
+    const TOLERATE_ASSOCIATIVE_ARRAYS = 8;
+
+
+    private $flags = 0;
+
+    /**
+     * @param int $options
+     * @return $this
+     */
+    public function setFlags($options)
+    {
+        $this->flags = $options;
+        return $this;
+    }
+
+    /** @var OpPath[] */
+    private $operations = array();
+
+    /**
+     * @param array $data
+     * @return JsonPatch
+     * @throws Exception
+     */
+    public static function import(array $data)
+    {
+        $result = new JsonPatch();
+        foreach ($data as $operation) {
+            /** @var OpPath|OpPathValue|OpPathFrom $operation */
+            if (is_array($operation)) {
+                $operation = (object)$operation;
+            }
+
+            if (!isset($operation->op)) {
+                throw new Exception('Missing "op" in operation data');
+            }
+            if (!isset($operation->path)) {
+                throw new Exception('Missing "path" in operation data');
+            }
+
+            $op = null;
+            switch ($operation->op) {
+                case Add::OP:
+                    $op = new Add();
+                    break;
+                case Copy::OP:
+                    $op = new Copy();
+                    break;
+                case Move::OP:
+                    $op = new Move();
+                    break;
+                case Remove::OP:
+                    $op = new Remove();
+                    break;
+                case Replace::OP:
+                    $op = new Replace();
+                    break;
+                case Test::OP:
+                    $op = new Test();
+                    break;
+                default:
+                    throw new Exception('Unknown "op": ' . $operation->op);
+            }
+            $op->path = $operation->path;
+            if ($op instanceof OpPathValue) {
+                if (property_exists($operation, 'value')) {
+                    $op->value = $operation->value;
+                } else {
+                    throw new Exception('Missing "value" in operation data');
+                }
+            } elseif ($op instanceof OpPathFrom) {
+                if (!isset($operation->from)) {
+                    throw new Exception('Missing "from" in operation data');
+                }
+                $op->from = $operation->from;
+            }
+            $result->operations[] = $op;
+        }
+        return $result;
+    }
+
+    public static function export(JsonPatch $patch)
+    {
+        $result = array();
+        foreach ($patch->operations as $operation) {
+            $result[] = (object)(array)$operation;
+        }
+
+        return $result;
+    }
+
+    public function op(OpPath $op)
+    {
+        $this->operations[] = $op;
+        return $this;
+    }
+
+    public function jsonSerialize()
+    {
+        return self::export($this);
+    }
+
+    /**
+     * @param mixed $original
+     * @param bool $stopOnError
+     * @return Exception[] array of errors
+     * @throws Exception
+     */
+    public function apply(&$original, $stopOnError = true)
+    {
+        $errors = array();
+        foreach ($this->operations as $operation) {
+            try {
+                $pathItems = JsonPointer::splitPath($operation->path);
+                switch (true) {
+                    case $operation instanceof Add:
+                        JsonPointer::add($original, $pathItems, $operation->value, $this->flags);
+                        break;
+                    case $operation instanceof Copy:
+                        $fromItems = JsonPointer::splitPath($operation->from);
+                        $value = JsonPointer::get($original, $fromItems);
+                        JsonPointer::add($original, $pathItems, $value, $this->flags);
+                        break;
+                    case $operation instanceof Move:
+                        $fromItems = JsonPointer::splitPath($operation->from);
+                        $value = JsonPointer::get($original, $fromItems);
+                        JsonPointer::remove($original, $fromItems, $this->flags);
+                        JsonPointer::add($original, $pathItems, $value, $this->flags);
+                        break;
+                    case $operation instanceof Remove:
+                        JsonPointer::remove($original, $pathItems, $this->flags);
+                        break;
+                    case $operation instanceof Replace:
+                        JsonPointer::get($original, $pathItems);
+                        JsonPointer::remove($original, $pathItems, $this->flags);
+                        JsonPointer::add($original, $pathItems, $operation->value, $this->flags);
+                        break;
+                    case $operation instanceof Test:
+                        $value = JsonPointer::get($original, $pathItems);
+                        $diff = new JsonDiff($operation->value, $value,
+                            JsonDiff::STOP_ON_DIFF);
+                        if ($diff->getDiffCnt() !== 0) {
+                            throw new Exception('Test operation ' . json_encode($operation, JSON_UNESCAPED_SLASHES)
+                                . ' failed: ' . json_encode($value));
+                        }
+                        break;
+                }
+            } catch (Exception $exception) {
+                if ($stopOnError) {
+                    throw $exception;
+                } else {
+                    $errors[] = $exception;
+                }
+            }
+        }
+        return $errors;
+    }
+}
\ No newline at end of file
diff --git a/swaggest/json-diff/src/JsonPatch/Add.php b/swaggest/json-diff/src/JsonPatch/Add.php
new file mode 100644
index 0000000..41f3713
--- /dev/null
+++ b/swaggest/json-diff/src/JsonPatch/Add.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace Swaggest\JsonDiff\JsonPatch;
+
+class Add extends OpPathValue
+{
+    const OP = 'add';
+}
\ No newline at end of file
diff --git a/swaggest/json-diff/src/JsonPatch/Copy.php b/swaggest/json-diff/src/JsonPatch/Copy.php
new file mode 100644
index 0000000..1448df8
--- /dev/null
+++ b/swaggest/json-diff/src/JsonPatch/Copy.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace Swaggest\JsonDiff\JsonPatch;
+
+class Copy extends OpPathFrom
+{
+    const OP = 'copy';
+}
\ No newline at end of file
diff --git a/swaggest/json-diff/src/JsonPatch/Move.php b/swaggest/json-diff/src/JsonPatch/Move.php
new file mode 100644
index 0000000..6a643d9
--- /dev/null
+++ b/swaggest/json-diff/src/JsonPatch/Move.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace Swaggest\JsonDiff\JsonPatch;
+
+class Move extends OpPathFrom
+{
+    const OP = 'move';
+}
\ No newline at end of file
diff --git a/swaggest/json-diff/src/JsonPatch/OpPath.php b/swaggest/json-diff/src/JsonPatch/OpPath.php
new file mode 100644
index 0000000..14df806
--- /dev/null
+++ b/swaggest/json-diff/src/JsonPatch/OpPath.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Swaggest\JsonDiff\JsonPatch;
+
+
+abstract class OpPath
+{
+    const OP = null;
+
+    public $op;
+    public $path;
+
+    public function __construct($path = null)
+    {
+        $this->op = static::OP;
+        $this->path = $path;
+    }
+}
\ No newline at end of file
diff --git a/swaggest/json-diff/src/JsonPatch/OpPathFrom.php b/swaggest/json-diff/src/JsonPatch/OpPathFrom.php
new file mode 100644
index 0000000..fa6205c
--- /dev/null
+++ b/swaggest/json-diff/src/JsonPatch/OpPathFrom.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Swaggest\JsonDiff\JsonPatch;
+
+abstract class OpPathFrom extends OpPath
+{
+    public $from;
+
+    public function __construct($path = null, $from = null)
+    {
+        parent::__construct($path);
+        $this->from = $from;
+    }
+}
\ No newline at end of file
diff --git a/swaggest/json-diff/src/JsonPatch/OpPathValue.php b/swaggest/json-diff/src/JsonPatch/OpPathValue.php
new file mode 100644
index 0000000..5e06665
--- /dev/null
+++ b/swaggest/json-diff/src/JsonPatch/OpPathValue.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Swaggest\JsonDiff\JsonPatch;
+
+
+abstract class OpPathValue extends OpPath
+{
+    public $value;
+
+    public function __construct($path = null, $value = null)
+    {
+        parent::__construct($path);
+        $this->value = $value;
+    }
+
+}
\ No newline at end of file
diff --git a/swaggest/json-diff/src/JsonPatch/Remove.php b/swaggest/json-diff/src/JsonPatch/Remove.php
new file mode 100644
index 0000000..6420e7b
--- /dev/null
+++ b/swaggest/json-diff/src/JsonPatch/Remove.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace Swaggest\JsonDiff\JsonPatch;
+
+class Remove extends OpPath
+{
+    const OP = 'remove';
+}
\ No newline at end of file
diff --git a/swaggest/json-diff/src/JsonPatch/Replace.php b/swaggest/json-diff/src/JsonPatch/Replace.php
new file mode 100644
index 0000000..8efe4a8
--- /dev/null
+++ b/swaggest/json-diff/src/JsonPatch/Replace.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace Swaggest\JsonDiff\JsonPatch;
+
+class Replace extends OpPathValue
+{
+    const OP = 'replace';
+}
\ No newline at end of file
diff --git a/swaggest/json-diff/src/JsonPatch/Test.php b/swaggest/json-diff/src/JsonPatch/Test.php
new file mode 100644
index 0000000..eab9d53
--- /dev/null
+++ b/swaggest/json-diff/src/JsonPatch/Test.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace Swaggest\JsonDiff\JsonPatch;
+
+class Test extends OpPathValue
+{
+    const OP = 'test';
+}
\ No newline at end of file
diff --git a/swaggest/json-diff/src/JsonPointer.php b/swaggest/json-diff/src/JsonPointer.php
new file mode 100644
index 0000000..d9fc2e0
--- /dev/null
+++ b/swaggest/json-diff/src/JsonPointer.php
@@ -0,0 +1,305 @@
+<?php
+
+namespace Swaggest\JsonDiff;
+
+
+class JsonPointer
+{
+    /**
+     * Create intermediate keys if they don't exist
+     */
+    const RECURSIVE_KEY_CREATION = 1;
+
+    /**
+     * Disallow converting empty array to object for key creation
+     */
+    const STRICT_MODE = 2;
+
+    /**
+     * Skip action if holder already has a non-null value at path
+     */
+    const SKIP_IF_ISSET = 4;
+
+    /**
+     * Allow associative arrays to mimic JSON objects (not recommended)
+     */
+    const TOLERATE_ASSOCIATIVE_ARRAYS = 8;
+
+    /**
+     * @param string $key
+     * @param bool $isURIFragmentId
+     * @return string
+     */
+    public static function escapeSegment($key, $isURIFragmentId = false)
+    {
+        if ($isURIFragmentId) {
+            return str_replace(array('%7E', '%2F'), array('~0', '~1'), urlencode($key));
+        } else {
+            return str_replace(array('~', '/'), array('~0', '~1'), $key);
+        }
+    }
+
+    /**
+     * @param string[] $pathItems
+     * @param bool $isURIFragmentId
+     * @return string
+     */
+    public static function buildPath(array $pathItems, $isURIFragmentId = false)
+    {
+        $result = $isURIFragmentId ? '#' : '';
+        foreach ($pathItems as $pathItem) {
+            $result .= '/' . self::escapeSegment($pathItem, $isURIFragmentId);
+        }
+        return $result;
+    }
+
+    /**
+     * @param string $path
+     * @return string[]
+     * @throws Exception
+     */
+    public static function splitPath($path)
+    {
+        $pathItems = explode('/', $path);
+        $first = array_shift($pathItems);
+        if ($first === '#') {
+            return self::splitPathURIFragment($pathItems);
+        } else {
+            if ($first !== '') {
+                throw new Exception('Path must start with "/": ' . $path);
+            }
+            return self::splitPathJsonString($pathItems);
+        }
+    }
+
+    private static function splitPathURIFragment(array $pathItems)
+    {
+        $result = array();
+        foreach ($pathItems as $key) {
+            $key = str_replace(array('~1', '~0'), array('/', '~'), urldecode($key));
+            $result[] = $key;
+        }
+        return $result;
+    }
+
+    private static function splitPathJsonString(array $pathItems)
+    {
+        $result = array();
+        foreach ($pathItems as $key) {
+            $key = str_replace(array('~1', '~0'), array('/', '~'), $key);
+            $result[] = $key;
+        }
+        return $result;
+    }
+
+    /**
+     * @param mixed $holder
+     * @param string[] $pathItems
+     * @param mixed $value
+     * @param int $flags
+     * @throws Exception
+     */
+    public static function add(&$holder, $pathItems, $value, $flags = self::RECURSIVE_KEY_CREATION)
+    {
+        $ref = &$holder;
+        while (null !== $key = array_shift($pathItems)) {
+            if ($ref instanceof \stdClass || is_object($ref)) {
+                if (PHP_VERSION_ID < 70100 && '' === $key) {
+                    throw new Exception('Empty property name is not supported by PHP <7.1',
+                        Exception::EMPTY_PROPERTY_NAME_UNSUPPORTED);
+                }
+
+                if ($flags & self::RECURSIVE_KEY_CREATION) {
+                    $ref = &$ref->$key;
+                } else {
+                    if (!isset($ref->$key) && count($pathItems)) {
+                        throw new Exception('Non-existent path item: ' . $key);
+                    } else {
+                        $ref = &$ref->$key;
+                    }
+                }
+            } else { // null or array
+                $intKey = filter_var($key, FILTER_VALIDATE_INT);
+                if ($ref === null && (false === $intKey || $intKey !== 0)) {
+                    $key = (string)$key;
+                    if ($flags & self::RECURSIVE_KEY_CREATION) {
+                        $ref = new \stdClass();
+                        $ref = &$ref->{$key};
+                    } else {
+                        throw new Exception('Non-existent path item: ' . $key);
+                    }
+                } elseif ([] === $ref && 0 === ($flags & self::STRICT_MODE) && false === $intKey && '-' !== $key) {
+                    $ref = new \stdClass();
+                    $ref = &$ref->{$key};
+                } else {
+                    if ($flags & self::RECURSIVE_KEY_CREATION && $ref === null) $ref = array();
+                    if ('-' === $key) {
+                        $ref = &$ref[count($ref)];
+                    } else {
+                        if (false === $intKey) {
+                            if (0 === ($flags & self::TOLERATE_ASSOCIATIVE_ARRAYS)) {
+                                throw new Exception('Invalid key for array operation');
+                            }
+                            $ref = &$ref[$key];
+                            continue;
+                        }
+                        if (is_array($ref) && array_key_exists($key, $ref) && empty($pathItems)) {
+                            array_splice($ref, $intKey, 0, array($value));
+                        }
+                        if (0 === ($flags & self::TOLERATE_ASSOCIATIVE_ARRAYS)) {
+                            if ($intKey > count($ref) && 0 === ($flags & self::RECURSIVE_KEY_CREATION)) {
+                                throw new Exception('Index is greater than number of items in array');
+                            } elseif ($intKey < 0) {
+                                throw new Exception('Negative index');
+                            }
+                        }
+
+                        $ref = &$ref[$intKey];
+                    }
+                }
+            }
+        }
+        if ($ref !== null && $flags & self::SKIP_IF_ISSET) {
+            return;
+        }
+        $ref = $value;
+    }
+
+    private static function arrayKeyExists($key, array $a)
+    {
+        if (array_key_exists($key, $a)) {
+            return true;
+        }
+        $key = (string)$key;
+        foreach ($a as $k => $v) {
+            if ((string)$k === $key) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static function arrayGet($key, array $a)
+    {
+        $key = (string)$key;
+        foreach ($a as $k => $v) {
+            if ((string)$k === $key) {
+                return $v;
+            }
+        }
+        return false;
+    }
+
+
+    /**
+     * @param mixed $holder
+     * @param string[] $pathItems
+     * @return bool|mixed
+     * @throws Exception
+     */
+    public static function get($holder, $pathItems)
+    {
+        $ref = $holder;
+        while (null !== $key = array_shift($pathItems)) {
+            if ($ref instanceof \stdClass) {
+                if (PHP_VERSION_ID < 70100 && '' === $key) {
+                    throw new Exception('Empty property name is not supported by PHP <7.1',
+                        Exception::EMPTY_PROPERTY_NAME_UNSUPPORTED);
+                }
+
+                $vars = (array)$ref;
+                if (self::arrayKeyExists($key, $vars)) {
+                    $ref = self::arrayGet($key, $vars);
+                } else {
+                    throw new Exception('Key not found: ' . $key);
+                }
+            } elseif (is_array($ref)) {
+                if (self::arrayKeyExists($key, $ref)) {
+                    $ref = $ref[$key];
+                } else {
+                    throw new Exception('Key not found: ' . $key);
+                }
+            } elseif (is_object($ref)) {
+                if (isset($ref->$key)) {
+                    $ref = $ref->$key;
+                } else {
+                    throw new Exception('Key not found: ' . $key);
+                }
+            } else {
+                throw new Exception('Key not found: ' . $key);
+            }
+        }
+        return $ref;
+    }
+
+    /**
+     * @param mixed $holder
+     * @param string $pointer
+     * @return bool|mixed
+     * @throws Exception
+     */
+    public static function getByPointer($holder, $pointer)
+    {
+        return self::get($holder, self::splitPath($pointer));
+    }
+
+    /**
+     * @param mixed $holder
+     * @param string[] $pathItems
+     * @param int $flags
+     * @return mixed
+     * @throws Exception
+     */
+    public static function remove(&$holder, $pathItems, $flags = 0)
+    {
+        $ref = &$holder;
+        while (null !== $key = array_shift($pathItems)) {
+            $parent = &$ref;
+            $refKey = $key;
+            if ($ref instanceof \stdClass) {
+                if (property_exists($ref, $key)) {
+                    $ref = &$ref->$key;
+                } else {
+                    throw new Exception('Key not found: ' . $key);
+                }
+            } elseif (is_object($ref)) {
+                if (isset($ref->$key)) {
+                    $ref = &$ref->$key;
+                } else {
+                    throw new Exception('Key not found: ' . $key);
+                }
+            } else {
+                if (array_key_exists($key, $ref)) {
+                    $ref = &$ref[$key];
+                } else {
+                    throw new Exception('Key not found: ' . $key);
+                }
+            }
+        }
+
+        if (isset($parent) && isset($refKey)) {
+            if ($parent instanceof \stdClass || is_object($parent)) {
+                unset($parent->$refKey);
+            } else {
+                $isAssociative = false;
+                if ($flags & self::TOLERATE_ASSOCIATIVE_ARRAYS) {
+                    $i = 0;
+                    foreach ($parent as $index => $value) {
+                        if ($i !== $index) {
+                            $isAssociative = true;
+                            break;
+                        }
+                    }
+                }
+
+                unset($parent[$refKey]);
+                if (!$isAssociative && (int)$refKey !== count($parent)) {
+                    $parent = array_values($parent);
+                }
+            }
+        }
+
+        return $ref;
+    }
+
+}
diff --git a/swaggest/json-diff/src/JsonValueReplace.php b/swaggest/json-diff/src/JsonValueReplace.php
new file mode 100644
index 0000000..82099da
--- /dev/null
+++ b/swaggest/json-diff/src/JsonValueReplace.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace Swaggest\JsonDiff;
+
+
+class JsonValueReplace
+{
+    private $search;
+    private $replace;
+    private $pathFilterRegex;
+    private $path = '';
+    private $pathItems = array();
+
+    public $affectedPaths = array();
+
+    /**
+     * JsonReplace constructor.
+     * @param mixed $search
+     * @param mixed $replace
+     * @param null|string $pathFilter Regular expression to check path
+     */
+    public function __construct($search, $replace, $pathFilter = null)
+    {
+        $this->search = $search;
+        $this->replace = $replace;
+        $this->pathFilterRegex = $pathFilter;
+    }
+
+    /**
+     * Recursively replaces all nodes equal to `search` value with `replace` value.
+     * @param mixed $data
+     * @return mixed
+     * @throws Exception
+     */
+    public function process($data)
+    {
+        $check = true;
+        if ($this->pathFilterRegex && !preg_match($this->pathFilterRegex, $this->path)) {
+            $check = false;
+        }
+
+        if (!is_array($data) && !is_object($data)) {
+            if ($check && $data === $this->search) {
+                $this->affectedPaths[] = $this->path;
+                return $this->replace;
+            } else {
+                return $data;
+            }
+        }
+
+        /** @var string[] $originalKeys */
+        $originalKeys = $data instanceof \stdClass ? get_object_vars($data) : $data;
+
+        if ($check) {
+            $diff = new JsonDiff($data, $this->search, JsonDiff::STOP_ON_DIFF);
+            if ($diff->getDiffCnt() === 0) {
+                $this->affectedPaths[] = $this->path;
+                return $this->replace;
+            }
+        }
+
+        $result = array();
+
+        foreach ($originalKeys as $key => $originalValue) {
+            $path = $this->path;
+            $pathItems = $this->pathItems;
+            $actualKey = $key;
+            $this->path .= '/' . JsonPointer::escapeSegment($actualKey);
+            $this->pathItems[] = $actualKey;
+
+            $result[$key] = $this->process($originalValue);
+
+            $this->path = $path;
+            $this->pathItems = $pathItems;
+        }
+
+        return $data instanceof \stdClass ? (object)$result : $result;
+    }
+}
\ No newline at end of file
diff --git a/swaggest/json-diff/src/ModifiedPathDiff.php b/swaggest/json-diff/src/ModifiedPathDiff.php
new file mode 100644
index 0000000..c981e94
--- /dev/null
+++ b/swaggest/json-diff/src/ModifiedPathDiff.php
@@ -0,0 +1,30 @@
+<?php
+
+
+namespace Swaggest\JsonDiff;
+
+
+class ModifiedPathDiff
+{
+    public function __construct($path, $original, $new)
+    {
+        $this->path = $path;
+        $this->original = $original;
+        $this->new = $new;
+    }
+
+    /**
+     * @var string
+     */
+    public $path;
+
+    /**
+     * @var mixed
+     */
+    public $original;
+
+    /**
+     * @var mixed
+     */
+    public $new;
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/CHANGELOG.md b/swaggest/json-schema/CHANGELOG.md
new file mode 100644
index 0000000..f225929
--- /dev/null
+++ b/swaggest/json-schema/CHANGELOG.md
@@ -0,0 +1,70 @@
+# Changelog
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [0.12.29] - 2020-03-19
+
+### Added
+- AJV and JSON Shema Test suites updated. 
+
+### Fixed
+- Validating `enum` and `const` in case of float vs. int comparison for equal values.  
+- Validation of time format edge cases.
+
+## [0.12.28] - 2020-01-27
+
+### Fixed
+- Trying to access array offset on value of type null in PHP 7.4, [#101](https://github.com/swaggest/php-json-schema/pull/101).
+
+## [0.12.27] - 2020-01-26
+
+### Fixed
+- PHP version check for empty property name support.
+
+## [0.12.26] - 2020-01-24
+
+### Fixed
+- Export additional and pattern properties, [#97](https://github.com/swaggest/php-json-schema/pull/97), [#99](https://github.com/swaggest/php-json-schema/pull/99).
+
+## [0.12.25] - 2020-01-07
+
+### Fixed
+- Validation always passes with invalid JSON schema, [#96](https://github.com/swaggest/php-json-schema/pull/96).
+
+## [0.12.24] - 2019-12-03
+
+### Fixed
+- Behavior of `tolerateStrings` when decoding bool and applying values to original data (now for real).
+
+## [0.12.23] - 2019-12-02
+
+### Fixed
+- Behavior of `tolerateStrings` when decoding bool and applying values to original data.
+
+## [0.12.22] - 2019-10-22
+
+### Added
+- `Schema::unboolSchemaData` and `Schema::unboolSchema` to public visibility.
+
+## [0.12.21] - 2019-10-01
+
+### Fixed
+- Treating unresolvable schema as a prohibitive `false` schema instead of failing with exception.
+
+## [0.12.20] - 2019-09-22
+
+### Changed
+- Export `null` value instead of skipping it for properties having `null` type.
+
+[0.12.29]: https://github.com/swaggest/php-json-schema/compare/v0.12.28...v0.12.29
+[0.12.28]: https://github.com/swaggest/php-json-schema/compare/v0.12.27...v0.12.28
+[0.12.27]: https://github.com/swaggest/php-json-schema/compare/v0.12.26...v0.12.27
+[0.12.26]: https://github.com/swaggest/php-json-schema/compare/v0.12.25...v0.12.26
+[0.12.25]: https://github.com/swaggest/php-json-schema/compare/v0.12.24...v0.12.25
+[0.12.24]: https://github.com/swaggest/php-json-schema/compare/v0.12.23...v0.12.24
+[0.12.23]: https://github.com/swaggest/php-json-schema/compare/v0.12.22...v0.12.23
+[0.12.22]: https://github.com/swaggest/php-json-schema/compare/v0.12.21...v0.12.22
+[0.12.21]: https://github.com/swaggest/php-json-schema/compare/v0.12.20...v0.12.21
+[0.12.20]: https://github.com/swaggest/php-json-schema/compare/v0.12.19...v0.12.20
diff --git a/swaggest/json-schema/LICENSE b/swaggest/json-schema/LICENSE
new file mode 100644
index 0000000..223d849
--- /dev/null
+++ b/swaggest/json-schema/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Viacheslav Poturaev
+
+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/swaggest/json-schema/Makefile b/swaggest/json-schema/Makefile
new file mode 100644
index 0000000..cdfc0f6
--- /dev/null
+++ b/swaggest/json-schema/Makefile
@@ -0,0 +1,32 @@
+PHPSTAN_VERSION ?= 0.11.15
+PHPBENCH_VERSION ?= 0.16.10
+
+deps:
+	@git submodule init && git submodule update
+
+lint:
+	@test -f ${HOME}/.cache/composer/phpstan-${PHPSTAN_VERSION}.phar || (mkdir -p ${HOME}/.cache/composer/ && wget https://github.com/phpstan/phpstan/releases/download/${PHPSTAN_VERSION}/phpstan.phar -O ${HOME}/.cache/composer/phpstan-${PHPSTAN_VERSION}.phar)
+	@php $$HOME/.cache/composer/phpstan-${PHPSTAN_VERSION}.phar analyze -l 7 -c phpstan.neon ./src
+
+docker-lint:
+	@docker run -v $$PWD:/app --rm phpstan/phpstan analyze -l 7 -c phpstan.neon ./src
+
+test:
+	@php -derror_reporting="E_ALL & ~E_DEPRECATED" vendor/bin/phpunit --configuration phpunit.xml
+
+test-coverage:
+	@php -derror_reporting="E_ALL & ~E_DEPRECATED" -dzend_extension=xdebug.so vendor/bin/phpunit --configuration phpunit.xml --coverage-text --coverage-clover=coverage.xml
+
+phpbench:
+	@test -f ${HOME}/.cache/composer/phpbench-${PHPBENCH_VERSION}.phar || (mkdir -p ${HOME}/.cache/composer/ && wget https://github.com/phpbench/phpbench/releases/download/${PHPBENCH_VERSION}/phpbench.phar -O ${HOME}/.cache/composer/phpbench-${PHPBENCH_VERSION}.phar)
+
+bench: phpbench
+	@php $$HOME/.cache/composer/phpbench-${PHPBENCH_VERSION}.phar run benchmarks --tag=candidate --progress=none --bootstrap=vendor/autoload.php --revs=50 --iterations=5 --retry-threshold=3 --dump-file=phpbench-candidate.xml
+
+bench-master: phpbench
+	@git checkout --detach && git fetch origin '+refs/heads/master:refs/heads/master' && git checkout master -- ./src
+	@composer install --dev --no-interaction --prefer-dist
+	@php $$HOME/.cache/composer/phpbench-${PHPBENCH_VERSION}.phar run benchmarks --tag=master --progress=none --bootstrap=vendor/autoload.php --revs=50 --iterations=5 --retry-threshold=3 --dump-file=phpbench-master.xml
+
+bench-compare: phpbench
+	@php $$HOME/.cache/composer/phpbench-${PHPBENCH_VERSION}.phar report --file phpbench-master.xml --file phpbench-candidate.xml --report='generator: "table", cols: [ "set" ], compare: "tag", compare_fields: ["mean"], break: ["benchmark"]'
diff --git a/swaggest/json-schema/README.md b/swaggest/json-schema/README.md
new file mode 100644
index 0000000..e4c9186
--- /dev/null
+++ b/swaggest/json-schema/README.md
@@ -0,0 +1,480 @@
+# Swaggest JSON-schema implementation for PHP
+
+[![Build Status](https://travis-ci.org/swaggest/php-json-schema.svg?branch=master)](https://travis-ci.org/swaggest/php-json-schema)
+[![Code Climate](https://codeclimate.com/github/swaggest/php-json-schema/badges/gpa.svg)](https://codeclimate.com/github/swaggest/php-json-schema)
+[![codecov](https://codecov.io/gh/swaggest/php-json-schema/branch/master/graph/badge.svg)](https://codecov.io/gh/swaggest/php-json-schema)
+![Code lines](https://sloc.xyz/github/swaggest/php-json-schema/?category=code)
+![Comments](https://sloc.xyz/github/swaggest/php-json-schema/?category=comments)
+
+High definition PHP structures with JSON-schema based validation.
+
+Supported schemas:
+* [JSON Schema Draft 7](http://json-schema.org/specification-links.html#draft-7)
+* [JSON Schema Draft 6](http://json-schema.org/specification-links.html#draft-6)
+* [JSON Schema Draft 4](http://json-schema.org/specification-links.html#draft-4)
+
+## Installation
+
+```
+composer require swaggest/json-schema
+```
+
+## Usage
+
+Structure definition can be done either with `json-schema` or with
+`PHP` class extending `Swaggest\JsonSchema\Structure\ClassStructure`
+
+### Validating JSON data against given schema
+
+Define your json-schema      
+```php
+$schemaJson = <<<'JSON'
+{
+    "type": "object",
+    "properties": {
+        "id": {
+            "type": "integer"
+        },
+        "name": {
+            "type": "string"
+        },
+        "orders": {
+            "type": "array",
+            "items": {
+                "$ref": "#/definitions/order"
+            }
+        }
+    },
+    "required":["id"],
+    "definitions": {
+        "order": {
+            "type": "object",
+            "properties": {
+                "id": {
+                    "type": "integer"
+                },
+                "price": {
+                    "type": "number"
+                },
+                "updated": {
+                    "type": "string",
+                    "format": "date-time"
+                }
+            },
+            "required":["id"]
+        }
+    }
+}
+JSON;
+```
+
+Load it
+```php
+$schema = Schema::import(json_decode($schemaJson));
+```
+
+Validate data
+```php
+$schema->in(json_decode(<<<'JSON'
+{
+    "id": 1,
+    "name":"John Doe",
+    "orders":[
+        {
+            "id":1
+        },
+        {
+            "price":1.0
+        }
+    ]
+}
+JSON
+)); // Exception: Required property missing: id at #->properties:orders->items[1]->#/definitions/order
+```
+
+You can also call `Schema::import` on string `uri` to schema json data.
+```php
+$schema = Schema::import('http://localhost:1234/my_schema.json');
+```
+
+Or with boolean argument.
+```php
+$schema = Schema::import(true); // permissive schema, always validates
+$schema = Schema::import(false); // restrictive schema, always invalidates
+```
+
+### Understanding error cause
+
+With complex schemas it may be hard to find out what's wrong with your data. Exception message can look like:
+
+```
+No valid results for oneOf {
+ 0: Enum failed, enum: ["a"], data: "f" at #->properties:root->patternProperties[^[a-zA-Z0-9_]+$]:zoo->oneOf[0]
+ 1: Enum failed, enum: ["b"], data: "f" at #->properties:root->patternProperties[^[a-zA-Z0-9_]+$]:zoo->oneOf[1]
+ 2: No valid results for anyOf {
+   0: Enum failed, enum: ["c"], data: "f" at #->properties:root->patternProperties[^[a-zA-Z0-9_]+$]:zoo->oneOf[2]->$ref[#/cde]->anyOf[0]
+   1: Enum failed, enum: ["d"], data: "f" at #->properties:root->patternProperties[^[a-zA-Z0-9_]+$]:zoo->oneOf[2]->$ref[#/cde]->anyOf[1]
+   2: Enum failed, enum: ["e"], data: "f" at #->properties:root->patternProperties[^[a-zA-Z0-9_]+$]:zoo->oneOf[2]->$ref[#/cde]->anyOf[2]
+ } at #->properties:root->patternProperties[^[a-zA-Z0-9_]+$]:zoo->oneOf[2]->$ref[#/cde]
+} at #->properties:root->patternProperties[^[a-zA-Z0-9_]+$]:zoo
+```
+
+For ambiguous schemas defined with `oneOf`/`anyOf` message is indented multi-line string.
+
+Processing path is a combination of schema and data pointers. You can use `InvalidValue->getSchemaPointer()`
+and `InvalidValue->getDataPointer()` to extract schema/data pointer.
+
+You can receive `Schema` instance that failed validation with `InvalidValue->getFailedSubSchema`. 
+
+You can build error tree using `InvalidValue->inspect()`.
+
+### PHP structured classes with validation
+
+```php
+/**
+ * @property int $quantity PHPDoc defined dynamic properties will be validated on every set
+ */
+class User extends ClassStructure
+{
+    /* Native (public) properties will be validated only on import and export of structure data */
+
+    /** @var int */
+    public $id;
+    public $name;
+    /** @var Order[] */
+    public $orders;
+
+    /** @var UserInfo */
+    public $info;
+
+    /**
+     * @param Properties|static $properties
+     * @param Schema $ownerSchema
+     */
+    public static function setUpProperties($properties, Schema $ownerSchema)
+    {
+        // You can add custom meta to your schema
+        $dbTable = new DbTable;
+        $dbTable->tableName = 'users';
+        $ownerSchema->addMeta($dbTable);
+
+        // Setup property schemas
+        $properties->id = Schema::integer();
+        $properties->id->addMeta(new DbId($dbTable)); // You can add meta to property.
+
+        $properties->name = Schema::string();
+
+        // You can embed structures to main level with nested schemas
+        $properties->info = UserInfo::schema()->nested();
+
+        // You can set default value for property
+        $defaultOptions = new UserOptions();
+        $defaultOptions->autoLogin = true;
+        $defaultOptions->groupName = 'guest';
+        // UserOptions::schema() is safe to change as it is protected with lazy cloning
+        $properties->options = UserOptions::schema()->setDefault(UserOptions::export($defaultOptions));
+
+        // Dynamic (phpdoc-defined) properties can be used as well
+        $properties->quantity = Schema::integer();
+        $properties->quantity->minimum = 0;
+
+        // Property can be any complex structure
+        $properties->orders = Schema::create();
+        $properties->orders->items = Order::schema();
+
+        $ownerSchema->required = array(self::names()->id);
+    }
+}
+
+class UserInfo extends ClassStructure {
+    public $firstName;
+    public $lastName;
+    public $birthDay;
+
+    /**
+     * @param Properties|static $properties
+     * @param Schema $ownerSchema
+     */
+    public static function setUpProperties($properties, Schema $ownerSchema)
+    {
+        $properties->firstName = Schema::string();
+        $properties->lastName = Schema::string();
+        $properties->birthDay = Schema::string();
+    }
+}
+
+class UserOptions extends ClassStructure
+{
+    public $autoLogin;
+    public $groupName;
+
+    /**
+     * @param Properties|static $properties
+     * @param Schema $ownerSchema
+     */
+    public static function setUpProperties($properties, Schema $ownerSchema)
+    {
+        $properties->autoLogin = Schema::boolean();
+        $properties->groupName = Schema::string();
+    }
+}
+
+class Order implements ClassStructureContract
+{
+    use ClassStructureTrait; // You can use trait if you can't/don't want to extend ClassStructure
+
+    const FANCY_MAPPING = 'fAnCy'; // You can create additional mapping namespace
+
+    public $id;
+    public $userId;
+    public $dateTime;
+    public $price;
+
+    /**
+     * @param Properties|static $properties
+     * @param Schema $ownerSchema
+     */
+    public static function setUpProperties($properties, Schema $ownerSchema)
+    {
+        // Add some meta data to your schema
+        $dbMeta = new DbTable();
+        $dbMeta->tableName = 'orders';
+        $ownerSchema->addMeta($dbMeta);
+
+        // Define properties
+        $properties->id = Schema::integer();
+        $properties->userId = User::properties()->id; // referencing property of another schema keeps meta
+        $properties->dateTime = Schema::string();
+        $properties->dateTime->format = Format::DATE_TIME;
+        $properties->price = Schema::number();
+
+        $ownerSchema->required[] = self::names()->id;
+
+        // Define default mapping if any
+        $ownerSchema->addPropertyMapping('date_time', Order::names()->dateTime);
+
+        // Define additional mapping
+        $ownerSchema->addPropertyMapping('DaTe_TiMe', Order::names()->dateTime, self::FANCY_MAPPING);
+        $ownerSchema->addPropertyMapping('Id', Order::names()->id, self::FANCY_MAPPING);
+        $ownerSchema->addPropertyMapping('PrIcE', Order::names()->price, self::FANCY_MAPPING);
+    }
+}
+```
+
+Validation of dynamic properties is performed on set, 
+this can help to find source of invalid data at cost of 
+some performance drop
+```php
+$user = new User();
+$user->quantity = -1; // Exception: Value more than 0 expected, -1 received
+```
+
+Validation of native properties is performed only on import/export
+```php
+$user = new User();
+$user->quantity = 10;
+User::export($user); // Exception: Required property missing: id
+```
+
+Error messages provide a path to invalid data
+```php
+$user = new User();
+$user->id = 1;
+$user->name = 'John Doe';
+
+$order = new Order();
+$order->dateTime = (new \DateTime())->format(DATE_RFC3339);
+$user->orders[] = $order;
+
+User::export($user); // Exception: Required property missing: id at #->properties:orders->items[0]
+```
+
+#### Nested structures
+
+Nested structures allow you to make composition: flatten several objects in one and separate back.
+
+```php
+$user = new User();
+$user->id = 1;
+
+$info = new UserInfo();
+$info->firstName = 'John';
+$info->lastName = 'Doe';
+$info->birthDay = '1970-01-01';
+$user->info = $info;
+
+$json = <<<JSON
+{
+    "id": 1,
+    "firstName": "John",
+    "lastName": "Doe",
+    "birthDay": "1970-01-01"
+}
+JSON;
+$exported = User::export($user);
+$this->assertSame($json, json_encode($exported, JSON_PRETTY_PRINT));
+
+$imported = User::import(json_decode($json));
+$this->assertSame('John', $imported->info->firstName);
+$this->assertSame('Doe', $imported->info->lastName);
+```
+
+You can also use `\Swaggest\JsonSchema\Structure\Composition` to dynamically create schema compositions.
+This can be helpful to deal with results of database query on joined data.
+
+```php
+$schema = new Composition(UserInfo::schema(), Order::schema());
+$json = <<<JSON
+{
+    "id": 1,
+    "firstName": "John",
+    "lastName": "Doe",
+    "price": 2.66
+}
+JSON;
+$object = $schema->import(json_decode($json));
+
+// Get particular object with `pick` accessor
+$info = UserInfo::pick($object);
+$order = Order::pick($object);
+
+// Data is imported objects of according classes
+$this->assertTrue($order instanceof Order);
+$this->assertTrue($info instanceof UserInfo);
+
+$this->assertSame(1, $order->id);
+$this->assertSame('John', $info->firstName);
+$this->assertSame('Doe', $info->lastName);
+$this->assertSame(2.66, $order->price);
+```
+
+#### Keys mapping
+
+If property names of PHP objects should be different from raw data you 
+can call `->addPropertyMapping` on owner schema.
+
+```php
+// Define default mapping if any
+$ownerSchema->addPropertyMapping('date_time', Order::names()->dateTime);
+
+// Define additional mapping
+$ownerSchema->addPropertyMapping('DaTe_TiMe', Order::names()->dateTime, self::FANCY_MAPPING);
+$ownerSchema->addPropertyMapping('Id', Order::names()->id, self::FANCY_MAPPING);
+$ownerSchema->addPropertyMapping('PrIcE', Order::names()->price, self::FANCY_MAPPING);
+```
+
+It will affect data mapping:
+```php
+$order = new Order();
+$order->id = 1;
+$order->dateTime = '2015-10-28T07:28:00Z';
+$order->price = 2.2;
+$exported = Order::export($order);
+$json = <<<JSON
+{
+    "id": 1,
+    "date_time": "2015-10-28T07:28:00Z",
+    "price": 2.2
+}
+JSON;
+$this->assertSame($json, json_encode($exported, JSON_PRETTY_PRINT));
+
+$imported = Order::import(json_decode($json));
+$this->assertSame('2015-10-28T07:28:00Z', $imported->dateTime);
+```
+
+You can have multiple mapping namespaces, controlling with `mapping` property of `Context`
+```php
+$options = new Context();
+$options->mapping = Order::FANCY_MAPPING;
+
+$exported = Order::export($order, $options);
+$json = <<<JSON
+{
+    "Id": 1,
+    "DaTe_TiMe": "2015-10-28T07:28:00Z",
+    "PrIcE": 2.2
+}
+JSON;
+$this->assertSame($json, json_encode($exported, JSON_PRETTY_PRINT));
+
+$imported = Order::import(json_decode($json), $options);
+$this->assertSame('2015-10-28T07:28:00Z', $imported->dateTime);
+```
+
+You can create your own pre-processor implementing `Swaggest\JsonSchema\DataPreProcessor`.
+
+#### Meta
+
+`Meta` is a way to complement `Schema` with your own data. You can keep and retrieve it.
+
+You can store it.
+```php
+$dbMeta = new DbTable();
+$dbMeta->tableName = 'orders';
+$ownerSchema->addMeta($dbMeta);
+```
+
+And get back.
+```php
+// Retrieving meta
+$dbTable = DbTable::get(Order::schema());
+$this->assertSame('orders', $dbTable->tableName);
+```
+
+
+#### Mapping without validation
+
+If you want to tolerate invalid data or improve mapping performance you can specify `skipValidation` flag in processing `Context`
+
+```php
+$schema = Schema::object();
+$schema->setProperty('one', Schema::integer());
+$schema->properties->one->minimum = 5;
+
+$options = new Context();
+$options->skipValidation = true;
+
+$res = $schema->in(json_decode('{"one":4}'), $options);
+$this->assertSame(4, $res->one);
+```
+
+
+#### Overriding mapping classes
+
+If you want to map data to a different class you can register mapping at top level of your importer structure.
+
+```php
+class CustomSwaggerSchema extends SwaggerSchema
+{
+    public static function import($data, Context $options = null)
+    {
+        if ($options === null) {
+            $options = new Context();
+        }
+        $options->objectItemClassMapping[Schema::className()] = CustomSchema::className();
+        return parent::import($data, $options);
+    }
+}
+```
+
+Or specify it in processing context
+
+```php
+$context = new Context();
+$context->objectItemClassMapping[Schema::className()] = CustomSchema::className();
+$schema = SwaggerSchema::schema()->in(json_decode(
+    file_get_contents(__DIR__ . '/../../../../spec/petstore-swagger.json')
+), $context);
+$this->assertInstanceOf(CustomSchema::className(), $schema->definitions['User']);
+```
+
+## Code quality and test coverage
+
+Some code quality best practices are deliberately violated here
+(see [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/swaggest/php-json-schema/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/swaggest/php-json-schema/?branch=master)
+) to allow best performance at maintenance cost.
+
+Those violations are secured by comprehensive test coverage:
+ * draft-04, draft-06, draft-07 of [JSON-Schema-Test-Suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite)
+ * test cases (excluding `$data` and few tests) of [epoberezkin/ajv](https://github.com/epoberezkin/ajv/tree/master/spec) (a mature js implementation)
\ No newline at end of file
diff --git a/swaggest/json-schema/benchmarks/AjvSchemasBench.php b/swaggest/json-schema/benchmarks/AjvSchemasBench.php
new file mode 100644
index 0000000..3e69bbe
--- /dev/null
+++ b/swaggest/json-schema/benchmarks/AjvSchemasBench.php
@@ -0,0 +1,144 @@
+<?php
+
+
+use Swaggest\JsonSchema\Context;
+use Swaggest\JsonSchema\InvalidValue;
+use Swaggest\JsonSchema\RemoteRef\Preloaded;
+use Swaggest\JsonSchema\Schema;
+
+class AjvSchemasBench
+{
+    private static $cases;
+
+    public function provide()
+    {
+        foreach (self::$cases as $name => $tmp) {
+            yield $name => ['name' => $name];
+        }
+    }
+
+    /**
+     * @ParamProviders({"provide"})
+     */
+    public function benchSpec($params)
+    {
+        $case = self::$cases[$params['name']];
+
+        $actualValid = true;
+        try {
+            $options = $this->makeOptions(Schema::VERSION_DRAFT_07);
+            $options->schemasCache = self::$schemas;
+
+            $schema = Schema::import($case['schema'], $options);
+            // import with defaults applied
+            $schema->in($case['data'], $options);
+
+            // default is not required to pass validation, so result might be invalid
+            // for back-exporting defaults have to be disabled
+            $options->applyDefaults = false;
+            $imported = $schema->in($case['data'], $options);
+            $schema->out($imported);
+        } catch (InvalidValue $exception) {
+            $actualValid = false;
+        }
+
+        if ($actualValid !== $case['isValid']) {
+            throw new Exception('Assertion failed');
+        }
+    }
+
+    /** @var \SplObjectStorage */
+    private static $schemas;
+
+    protected function makeOptions($version)
+    {
+        $refProvider = static::getProvider();
+
+        $options = new Context();
+        $options->setRemoteRefProvider($refProvider);
+        $options->version = $version;
+        $options->strictBase64Validation = true;
+
+        return $options;
+    }
+
+    public static function getProvider()
+    {
+        static $refProvider = null;
+
+        if (null === $refProvider) {
+            $refProvider = new Preloaded();
+            $refProvider
+                ->setSchemaData(
+                    'http://localhost:1234/integer.json',
+                    json_decode(file_get_contents(__DIR__
+                        . '/../spec/JSON-Schema-Test-Suite/remotes/integer.json')))
+                ->setSchemaData(
+                    'http://localhost:1234/subSchemas.json',
+                    json_decode(file_get_contents(__DIR__
+                        . '/../spec/JSON-Schema-Test-Suite/remotes/subSchemas.json')))
+                ->setSchemaData(
+                    'http://localhost:1234/name.json',
+                    json_decode(file_get_contents(__DIR__
+                        . '/../spec/JSON-Schema-Test-Suite/remotes/name.json')))
+                ->setSchemaData(
+                    'http://localhost:1234/folder/folderInteger.json',
+                    json_decode(file_get_contents(__DIR__
+                        . '/../spec/JSON-Schema-Test-Suite/remotes/folder/folderInteger.json')));
+        }
+
+        return $refProvider;
+    }
+
+    private static function provider($path)
+    {
+        $testCases = array();
+
+        if ($handle = opendir($path)) {
+            while (false !== ($entry = readdir($handle))) {
+                if ($entry != "." && $entry != "..") {
+                    if ('.json' !== substr($entry, -5)) {
+                        continue;
+                    }
+                    $tests = json_decode(file_get_contents($path . '/' . $entry));
+
+                    foreach ($tests as $test) {
+                        foreach ($test->tests as $c => $case) {
+                            $name = $entry . ' ' . $test->description . ': ' . $case->description . ' [' . $c . ']';
+                            if (!isset($test->schema)) {
+                                if (isset($test->schemas)) {
+                                    foreach ($test->schemas as $i => $schema) {
+                                        $testCases[$name . '_' . $i] = array(
+                                            'schema' => $schema,
+                                            'data' => $case->data,
+                                            'isValid' => $case->valid,
+                                            'name' => $name,
+                                        );
+                                    }
+                                }
+                                continue;
+                            }
+                            $testCases[$name] = array(
+                                'schema' => $test->schema,
+                                'data' => $case->data,
+                                'isValid' => $case->valid,
+                                'name' => $name,
+                            );
+                        }
+                    }
+                }
+            }
+            closedir($handle);
+        }
+
+        return $testCases;
+    }
+
+    public static function init()
+    {
+        self::$cases = self::provider(__DIR__ . '/../spec/ajv/spec/tests/schemas');
+        self::$schemas = new \SplObjectStorage();
+    }
+}
+
+AjvSchemasBench::init();
\ No newline at end of file
diff --git a/swaggest/json-schema/benchmarks/Draft7Bench.php b/swaggest/json-schema/benchmarks/Draft7Bench.php
new file mode 100644
index 0000000..afc564b
--- /dev/null
+++ b/swaggest/json-schema/benchmarks/Draft7Bench.php
@@ -0,0 +1,144 @@
+<?php
+
+
+use Swaggest\JsonSchema\Context;
+use Swaggest\JsonSchema\InvalidValue;
+use Swaggest\JsonSchema\RemoteRef\Preloaded;
+use Swaggest\JsonSchema\Schema;
+
+class Draft7Bench
+{
+    private static $cases;
+
+    public function provide()
+    {
+        foreach (self::$cases as $name => $tmp) {
+            yield $name => ['name' => $name];
+        }
+    }
+
+    /**
+     * @ParamProviders({"provide"})
+     */
+    public function benchSpec($params)
+    {
+        $case = self::$cases[$params['name']];
+
+        $actualValid = true;
+        try {
+            $options = $this->makeOptions(Schema::VERSION_DRAFT_07);
+            $options->schemasCache = self::$schemas;
+
+            $schema = Schema::import($case['schema'], $options);
+            // import with defaults applied
+            $schema->in($case['data'], $options);
+
+            // default is not required to pass validation, so result might be invalid
+            // for back-exporting defaults have to be disabled
+            $options->applyDefaults = false;
+            $imported = $schema->in($case['data'], $options);
+            $schema->out($imported);
+        } catch (InvalidValue $exception) {
+            $actualValid = false;
+        }
+
+        if ($actualValid !== $case['isValid']) {
+            throw new Exception('Assertion failed');
+        }
+    }
+
+    /** @var \SplObjectStorage */
+    private static $schemas;
+
+    protected function makeOptions($version)
+    {
+        $refProvider = static::getProvider();
+
+        $options = new Context();
+        $options->setRemoteRefProvider($refProvider);
+        $options->version = $version;
+        $options->strictBase64Validation = true;
+
+        return $options;
+    }
+
+    public static function getProvider()
+    {
+        static $refProvider = null;
+
+        if (null === $refProvider) {
+            $refProvider = new Preloaded();
+            $refProvider
+                ->setSchemaData(
+                    'http://localhost:1234/integer.json',
+                    json_decode(file_get_contents(__DIR__
+                        . '/../spec/JSON-Schema-Test-Suite/remotes/integer.json')))
+                ->setSchemaData(
+                    'http://localhost:1234/subSchemas.json',
+                    json_decode(file_get_contents(__DIR__
+                        . '/../spec/JSON-Schema-Test-Suite/remotes/subSchemas.json')))
+                ->setSchemaData(
+                    'http://localhost:1234/name.json',
+                    json_decode(file_get_contents(__DIR__
+                        . '/../spec/JSON-Schema-Test-Suite/remotes/name.json')))
+                ->setSchemaData(
+                    'http://localhost:1234/folder/folderInteger.json',
+                    json_decode(file_get_contents(__DIR__
+                        . '/../spec/JSON-Schema-Test-Suite/remotes/folder/folderInteger.json')));
+        }
+
+        return $refProvider;
+    }
+
+    private static function provider($path)
+    {
+        $testCases = array();
+
+        if ($handle = opendir($path)) {
+            while (false !== ($entry = readdir($handle))) {
+                if ($entry != "." && $entry != "..") {
+                    if ('.json' !== substr($entry, -5)) {
+                        continue;
+                    }
+                    $tests = json_decode(file_get_contents($path . '/' . $entry));
+
+                    foreach ($tests as $test) {
+                        foreach ($test->tests as $c => $case) {
+                            $name = $entry . ' ' . $test->description . ': ' . $case->description . ' [' . $c . ']';
+                            if (!isset($test->schema)) {
+                                if (isset($test->schemas)) {
+                                    foreach ($test->schemas as $i => $schema) {
+                                        $testCases[$name . '_' . $i] = array(
+                                            'schema' => $schema,
+                                            'data' => $case->data,
+                                            'isValid' => $case->valid,
+                                            'name' => $name,
+                                        );
+                                    }
+                                }
+                                continue;
+                            }
+                            $testCases[$name] = array(
+                                'schema' => $test->schema,
+                                'data' => $case->data,
+                                'isValid' => $case->valid,
+                                'name' => $name,
+                            );
+                        }
+                    }
+                }
+            }
+            closedir($handle);
+        }
+
+        return $testCases;
+    }
+
+    public static function init()
+    {
+        self::$cases = self::provider(__DIR__ . '/../spec/JSON-Schema-Test-Suite/tests/draft7');
+        self::$schemas = new \SplObjectStorage();
+    }
+}
+
+Draft7Bench::init();
\ No newline at end of file
diff --git a/swaggest/json-schema/composer.json b/swaggest/json-schema/composer.json
new file mode 100644
index 0000000..a96e1e4
--- /dev/null
+++ b/swaggest/json-schema/composer.json
@@ -0,0 +1,42 @@
+{
+  "name": "swaggest/json-schema",
+  "description": "High definition PHP structures with JSON-schema based validation",
+  "type": "library",
+  "require": {
+    "php": ">=5.4",
+    "ext-json": "*",
+    "ext-mbstring": "*",
+    "phplang/scope-exit": "^1.0",
+    "swaggest/json-diff": "^3.5.1"
+  },
+  "require-dev": {
+    "phpunit/phpunit": "4.8.35",
+    "phpunit/php-code-coverage": "2.2.4"
+  },
+  "license": "MIT",
+  "authors": [
+    {
+      "name": "Viacheslav Poturaev",
+      "email": "vearutop@gmail.com"
+    }
+  ],
+  "support": {
+    "email": "vearutop@gmail.com"
+  },
+  "prefer-stable": true,
+  "autoload": {
+    "psr-4": {
+      "Swaggest\\JsonSchema\\": "src/"
+    }
+  },
+  "autoload-dev": {
+    "psr-4": {
+      "Swaggest\\JsonSchema\\Tests\\": "tests/src/"
+    }
+  },
+  "config": {
+    "platform": {
+      "php": "5.4.45"
+    }
+  }
+}
diff --git a/swaggest/json-schema/composer.lock b/swaggest/json-schema/composer.lock
new file mode 100644
index 0000000..0696c72
--- /dev/null
+++ b/swaggest/json-schema/composer.lock
@@ -0,0 +1,1132 @@
+{
+    "_readme": [
+        "This file locks the dependencies of your project to a known state",
+        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+        "This file is @generated automatically"
+    ],
+    "content-hash": "4f3a0a46f69c1095422bf99acd191102",
+    "packages": [
+        {
+            "name": "phplang/scope-exit",
+            "version": "1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phplang/scope-exit.git",
+                "reference": "239b73abe89f9414aa85a7ca075ec9445629192b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phplang/scope-exit/zipball/239b73abe89f9414aa85a7ca075ec9445629192b",
+                "reference": "239b73abe89f9414aa85a7ca075ec9445629192b",
+                "shasum": ""
+            },
+            "require-dev": {
+                "phpunit/phpunit": "*"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "PhpLang\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD"
+            ],
+            "authors": [
+                {
+                    "name": "Sara Golemon",
+                    "email": "pollita@php.net",
+                    "homepage": "https://twitter.com/SaraMG",
+                    "role": "Developer"
+                }
+            ],
+            "description": "Emulation of SCOPE_EXIT construct from C++",
+            "homepage": "https://github.com/phplang/scope-exit",
+            "keywords": [
+                "cleanup",
+                "exit",
+                "scope"
+            ],
+            "time": "2016-09-17T00:15:18+00:00"
+        },
+        {
+            "name": "swaggest/json-diff",
+            "version": "v3.7.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/swaggest/json-diff.git",
+                "reference": "fccbbd6ee31b5949ab52e9c53f7a1e1b06d84716"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/swaggest/json-diff/zipball/fccbbd6ee31b5949ab52e9c53f7a1e1b06d84716",
+                "reference": "fccbbd6ee31b5949ab52e9c53f7a1e1b06d84716",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.8.23"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Swaggest\\JsonDiff\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Viacheslav Poturaev",
+                    "email": "vearutop@gmail.com"
+                }
+            ],
+            "description": "JSON diff/rearrange/patch/pointer library for PHP",
+            "time": "2020-01-26T20:52:06+00:00"
+        }
+    ],
+    "packages-dev": [
+        {
+            "name": "doctrine/instantiator",
+            "version": "1.0.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/doctrine/instantiator.git",
+                "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d",
+                "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3,<8.0-DEV"
+            },
+            "require-dev": {
+                "athletic/athletic": "~0.1.8",
+                "ext-pdo": "*",
+                "ext-phar": "*",
+                "phpunit/phpunit": "~4.0",
+                "squizlabs/php_codesniffer": "~2.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Marco Pivetta",
+                    "email": "ocramius@gmail.com",
+                    "homepage": "http://ocramius.github.com/"
+                }
+            ],
+            "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
+            "homepage": "https://github.com/doctrine/instantiator",
+            "keywords": [
+                "constructor",
+                "instantiate"
+            ],
+            "time": "2015-06-14T21:17:01+00:00"
+        },
+        {
+            "name": "phpdocumentor/reflection-docblock",
+            "version": "2.0.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
+                "reference": "e6a969a640b00d8daa3c66518b0405fb41ae0c4b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/e6a969a640b00d8daa3c66518b0405fb41ae0c4b",
+                "reference": "e6a969a640b00d8daa3c66518b0405fb41ae0c4b",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.0"
+            },
+            "suggest": {
+                "dflydev/markdown": "~1.0",
+                "erusev/parsedown": "~1.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "phpDocumentor": [
+                        "src/"
+                    ]
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Mike van Riel",
+                    "email": "mike.vanriel@naenius.com"
+                }
+            ],
+            "time": "2016-01-25T08:17:30+00:00"
+        },
+        {
+            "name": "phpspec/prophecy",
+            "version": "v1.10.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpspec/prophecy.git",
+                "reference": "b4400efc9d206e83138e2bb97ed7f5b14b831cd9"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/b4400efc9d206e83138e2bb97ed7f5b14b831cd9",
+                "reference": "b4400efc9d206e83138e2bb97ed7f5b14b831cd9",
+                "shasum": ""
+            },
+            "require": {
+                "doctrine/instantiator": "^1.0.2",
+                "php": "^5.3|^7.0",
+                "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0",
+                "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0",
+                "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0"
+            },
+            "require-dev": {
+                "phpspec/phpspec": "^2.5 || ^3.2",
+                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.10.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Prophecy\\": "src/Prophecy"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Konstantin Kudryashov",
+                    "email": "ever.zet@gmail.com",
+                    "homepage": "http://everzet.com"
+                },
+                {
+                    "name": "Marcello Duarte",
+                    "email": "marcello.duarte@gmail.com"
+                }
+            ],
+            "description": "Highly opinionated mocking framework for PHP 5.3+",
+            "homepage": "https://github.com/phpspec/prophecy",
+            "keywords": [
+                "Double",
+                "Dummy",
+                "fake",
+                "mock",
+                "spy",
+                "stub"
+            ],
+            "time": "2020-01-20T15:57:02+00:00"
+        },
+        {
+            "name": "phpunit/php-code-coverage",
+            "version": "2.2.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+                "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979",
+                "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3",
+                "phpunit/php-file-iterator": "~1.3",
+                "phpunit/php-text-template": "~1.2",
+                "phpunit/php-token-stream": "~1.3",
+                "sebastian/environment": "^1.3.2",
+                "sebastian/version": "~1.0"
+            },
+            "require-dev": {
+                "ext-xdebug": ">=2.1.4",
+                "phpunit/phpunit": "~4"
+            },
+            "suggest": {
+                "ext-dom": "*",
+                "ext-xdebug": ">=2.2.1",
+                "ext-xmlwriter": "*"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.2.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "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": "2015-10-06T15:47:00+00:00"
+        },
+        {
+            "name": "phpunit/php-file-iterator",
+            "version": "1.4.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+                "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4",
+                "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.4.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "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": "https://github.com/sebastianbergmann/php-file-iterator/",
+            "keywords": [
+                "filesystem",
+                "iterator"
+            ],
+            "time": "2017-11-27T13:52:08+00:00"
+        },
+        {
+            "name": "phpunit/php-text-template",
+            "version": "1.2.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-text-template.git",
+                "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+                "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Simple template engine.",
+            "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+            "keywords": [
+                "template"
+            ],
+            "time": "2015-06-21T13:50:34+00:00"
+        },
+        {
+            "name": "phpunit/php-timer",
+            "version": "1.0.9",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-timer.git",
+                "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
+                "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.3.3 || ^7.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "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": "2017-02-26T11:10:40+00:00"
+        },
+        {
+            "name": "phpunit/php-token-stream",
+            "version": "1.4.12",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-token-stream.git",
+                "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1ce90ba27c42e4e44e6d8458241466380b51fa16",
+                "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16",
+                "shasum": ""
+            },
+            "require": {
+                "ext-tokenizer": "*",
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.2"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.4-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Wrapper around PHP's tokenizer extension.",
+            "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
+            "keywords": [
+                "tokenizer"
+            ],
+            "time": "2017-12-04T08:55:13+00:00"
+        },
+        {
+            "name": "phpunit/phpunit",
+            "version": "4.8.35",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/phpunit.git",
+                "reference": "791b1a67c25af50e230f841ee7a9c6eba507dc87"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/791b1a67c25af50e230f841ee7a9c6eba507dc87",
+                "reference": "791b1a67c25af50e230f841ee7a9c6eba507dc87",
+                "shasum": ""
+            },
+            "require": {
+                "ext-dom": "*",
+                "ext-json": "*",
+                "ext-pcre": "*",
+                "ext-reflection": "*",
+                "ext-spl": "*",
+                "php": ">=5.3.3",
+                "phpspec/prophecy": "^1.3.1",
+                "phpunit/php-code-coverage": "~2.1",
+                "phpunit/php-file-iterator": "~1.4",
+                "phpunit/php-text-template": "~1.2",
+                "phpunit/php-timer": "^1.0.6",
+                "phpunit/phpunit-mock-objects": "~2.3",
+                "sebastian/comparator": "~1.2.2",
+                "sebastian/diff": "~1.2",
+                "sebastian/environment": "~1.3",
+                "sebastian/exporter": "~1.2",
+                "sebastian/global-state": "~1.0",
+                "sebastian/version": "~1.0",
+                "symfony/yaml": "~2.1|~3.0"
+            },
+            "suggest": {
+                "phpunit/php-invoker": "~1.1"
+            },
+            "bin": [
+                "phpunit"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.8.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "The PHP Unit Testing framework.",
+            "homepage": "https://phpunit.de/",
+            "keywords": [
+                "phpunit",
+                "testing",
+                "xunit"
+            ],
+            "time": "2017-02-06T05:18:07+00:00"
+        },
+        {
+            "name": "phpunit/phpunit-mock-objects",
+            "version": "2.3.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
+                "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983",
+                "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983",
+                "shasum": ""
+            },
+            "require": {
+                "doctrine/instantiator": "^1.0.2",
+                "php": ">=5.3.3",
+                "phpunit/php-text-template": "~1.2",
+                "sebastian/exporter": "~1.2"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.4"
+            },
+            "suggest": {
+                "ext-soap": "*"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.3.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "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"
+            ],
+            "abandoned": true,
+            "time": "2015-10-02T06:51:40+00:00"
+        },
+        {
+            "name": "sebastian/comparator",
+            "version": "1.2.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/comparator.git",
+                "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be",
+                "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3",
+                "sebastian/diff": "~1.2",
+                "sebastian/exporter": "~1.2 || ~2.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.4"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.2.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Volker Dusch",
+                    "email": "github@wallbash.com"
+                },
+                {
+                    "name": "Bernhard Schussek",
+                    "email": "bschussek@2bepublished.at"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Provides the functionality to compare PHP values for equality",
+            "homepage": "http://www.github.com/sebastianbergmann/comparator",
+            "keywords": [
+                "comparator",
+                "compare",
+                "equality"
+            ],
+            "time": "2017-01-29T09:50:25+00:00"
+        },
+        {
+            "name": "sebastian/diff",
+            "version": "1.4.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/diff.git",
+                "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4",
+                "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.3.3 || ^7.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.4-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Kore Nordmann",
+                    "email": "mail@kore-nordmann.de"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Diff implementation",
+            "homepage": "https://github.com/sebastianbergmann/diff",
+            "keywords": [
+                "diff"
+            ],
+            "time": "2017-05-22T07:24:03+00:00"
+        },
+        {
+            "name": "sebastian/environment",
+            "version": "1.3.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/environment.git",
+                "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea",
+                "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.3.3 || ^7.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.8 || ^5.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.3.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Provides functionality to handle HHVM/PHP environments",
+            "homepage": "http://www.github.com/sebastianbergmann/environment",
+            "keywords": [
+                "Xdebug",
+                "environment",
+                "hhvm"
+            ],
+            "time": "2016-08-18T05:49:44+00:00"
+        },
+        {
+            "name": "sebastian/exporter",
+            "version": "1.2.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/exporter.git",
+                "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4",
+                "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3",
+                "sebastian/recursion-context": "~1.0"
+            },
+            "require-dev": {
+                "ext-mbstring": "*",
+                "phpunit/phpunit": "~4.4"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.3.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Volker Dusch",
+                    "email": "github@wallbash.com"
+                },
+                {
+                    "name": "Bernhard Schussek",
+                    "email": "bschussek@2bepublished.at"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                },
+                {
+                    "name": "Adam Harvey",
+                    "email": "aharvey@php.net"
+                }
+            ],
+            "description": "Provides the functionality to export PHP variables for visualization",
+            "homepage": "http://www.github.com/sebastianbergmann/exporter",
+            "keywords": [
+                "export",
+                "exporter"
+            ],
+            "time": "2016-06-17T09:04:28+00:00"
+        },
+        {
+            "name": "sebastian/global-state",
+            "version": "1.1.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/global-state.git",
+                "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4",
+                "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.2"
+            },
+            "suggest": {
+                "ext-uopz": "*"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Snapshotting of global state",
+            "homepage": "http://www.github.com/sebastianbergmann/global-state",
+            "keywords": [
+                "global state"
+            ],
+            "time": "2015-10-12T03:26:01+00:00"
+        },
+        {
+            "name": "sebastian/recursion-context",
+            "version": "1.0.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/recursion-context.git",
+                "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b19cc3298482a335a95f3016d2f8a6950f0fbcd7",
+                "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.4"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                },
+                {
+                    "name": "Adam Harvey",
+                    "email": "aharvey@php.net"
+                }
+            ],
+            "description": "Provides functionality to recursively process PHP variables",
+            "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
+            "time": "2016-10-03T07:41:43+00:00"
+        },
+        {
+            "name": "sebastian/version",
+            "version": "1.0.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/version.git",
+                "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
+                "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
+                "shasum": ""
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+            "homepage": "https://github.com/sebastianbergmann/version",
+            "time": "2015-06-21T13:59:46+00:00"
+        },
+        {
+            "name": "symfony/polyfill-ctype",
+            "version": "v1.13.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-ctype.git",
+                "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f8f0b461be3385e56d6de3dbb5a0df24c0c275e3",
+                "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "suggest": {
+                "ext-ctype": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.13-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Ctype\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Gert de Pagter",
+                    "email": "BackEndTea@gmail.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for ctype functions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "ctype",
+                "polyfill",
+                "portable"
+            ],
+            "time": "2019-11-27T13:56:44+00:00"
+        },
+        {
+            "name": "symfony/yaml",
+            "version": "v2.8.52",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/yaml.git",
+                "reference": "02c1859112aa779d9ab394ae4f3381911d84052b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/yaml/zipball/02c1859112aa779d9ab394ae4f3381911d84052b",
+                "reference": "02c1859112aa779d9ab394ae4f3381911d84052b",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.9",
+                "symfony/polyfill-ctype": "~1.8"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.8-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Yaml\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony Yaml Component",
+            "homepage": "https://symfony.com",
+            "time": "2018-11-11T11:18:13+00:00"
+        }
+    ],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": [],
+    "prefer-stable": true,
+    "prefer-lowest": false,
+    "platform": {
+        "php": ">=5.4",
+        "ext-json": "*",
+        "ext-mbstring": "*"
+    },
+    "platform-dev": [],
+    "platform-overrides": {
+        "php": "5.4.45"
+    }
+}
diff --git a/swaggest/json-schema/spec/json-schema-draft6.json b/swaggest/json-schema/spec/json-schema-draft6.json
new file mode 100644
index 0000000..5656240
--- /dev/null
+++ b/swaggest/json-schema/spec/json-schema-draft6.json
@@ -0,0 +1,154 @@
+{
+    "$schema": "http://json-schema.org/draft-06/schema#",
+    "$id": "http://json-schema.org/draft-06/schema#",
+    "title": "Core schema meta-schema",
+    "definitions": {
+        "schemaArray": {
+            "type": "array",
+            "minItems": 1,
+            "items": { "$ref": "#" }
+        },
+        "nonNegativeInteger": {
+            "type": "integer",
+            "minimum": 0
+        },
+        "nonNegativeIntegerDefault0": {
+            "allOf": [
+                { "$ref": "#/definitions/nonNegativeInteger" },
+                { "default": 0 }
+            ]
+        },
+        "simpleTypes": {
+            "enum": [
+                "array",
+                "boolean",
+                "integer",
+                "null",
+                "number",
+                "object",
+                "string"
+            ]
+        },
+        "stringArray": {
+            "type": "array",
+            "items": { "type": "string" },
+            "uniqueItems": true,
+            "default": []
+        }
+    },
+    "type": ["object", "boolean"],
+    "properties": {
+        "$id": {
+            "type": "string",
+            "format": "uri-reference"
+        },
+        "$schema": {
+            "type": "string",
+            "format": "uri"
+        },
+        "$ref": {
+            "type": "string",
+            "format": "uri-reference"
+        },
+        "title": {
+            "type": "string"
+        },
+        "description": {
+            "type": "string"
+        },
+        "default": {},
+        "examples": {
+            "type": "array",
+            "items": {}
+        },
+        "multipleOf": {
+            "type": "number",
+            "exclusiveMinimum": 0
+        },
+        "maximum": {
+            "type": "number"
+        },
+        "exclusiveMaximum": {
+            "type": "number"
+        },
+        "minimum": {
+            "type": "number"
+        },
+        "exclusiveMinimum": {
+            "type": "number"
+        },
+        "maxLength": { "$ref": "#/definitions/nonNegativeInteger" },
+        "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+        "pattern": {
+            "type": "string",
+            "format": "regex"
+        },
+        "additionalItems": { "$ref": "#" },
+        "items": {
+            "anyOf": [
+                { "$ref": "#" },
+                { "$ref": "#/definitions/schemaArray" }
+            ],
+            "default": {}
+        },
+        "maxItems": { "$ref": "#/definitions/nonNegativeInteger" },
+        "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+        "uniqueItems": {
+            "type": "boolean",
+            "default": false
+        },
+        "contains": { "$ref": "#" },
+        "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" },
+        "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+        "required": { "$ref": "#/definitions/stringArray" },
+        "additionalProperties": { "$ref": "#" },
+        "definitions": {
+            "type": "object",
+            "additionalProperties": { "$ref": "#" },
+            "default": {}
+        },
+        "properties": {
+            "type": "object",
+            "additionalProperties": { "$ref": "#" },
+            "default": {}
+        },
+        "patternProperties": {
+            "type": "object",
+            "additionalProperties": { "$ref": "#" },
+            "default": {}
+        },
+        "dependencies": {
+            "type": "object",
+            "additionalProperties": {
+                "anyOf": [
+                    { "$ref": "#" },
+                    { "$ref": "#/definitions/stringArray" }
+                ]
+            }
+        },
+        "propertyNames": { "$ref": "#" },
+        "const": {},
+        "enum": {
+            "type": "array",
+            "minItems": 1,
+            "uniqueItems": true
+        },
+        "type": {
+            "anyOf": [
+                { "$ref": "#/definitions/simpleTypes" },
+                {
+                    "type": "array",
+                    "items": { "$ref": "#/definitions/simpleTypes" },
+                    "minItems": 1,
+                    "uniqueItems": true
+                }
+            ]
+        },
+        "format": { "type": "string" },
+        "allOf": { "$ref": "#/definitions/schemaArray" },
+        "anyOf": { "$ref": "#/definitions/schemaArray" },
+        "oneOf": { "$ref": "#/definitions/schemaArray" },
+        "not": { "$ref": "#" }
+    },
+    "default": {}
+}
diff --git a/swaggest/json-schema/spec/json-schema-draft7.json b/swaggest/json-schema/spec/json-schema-draft7.json
new file mode 100644
index 0000000..5bee90e
--- /dev/null
+++ b/swaggest/json-schema/spec/json-schema-draft7.json
@@ -0,0 +1,168 @@
+{
+    "$schema": "http://json-schema.org/draft-07/schema#",
+    "$id": "http://json-schema.org/draft-07/schema#",
+    "title": "Core schema meta-schema",
+    "definitions": {
+        "schemaArray": {
+            "type": "array",
+            "minItems": 1,
+            "items": { "$ref": "#" }
+        },
+        "nonNegativeInteger": {
+            "type": "integer",
+            "minimum": 0
+        },
+        "nonNegativeIntegerDefault0": {
+            "allOf": [
+                { "$ref": "#/definitions/nonNegativeInteger" },
+                { "default": 0 }
+            ]
+        },
+        "simpleTypes": {
+            "enum": [
+                "array",
+                "boolean",
+                "integer",
+                "null",
+                "number",
+                "object",
+                "string"
+            ]
+        },
+        "stringArray": {
+            "type": "array",
+            "items": { "type": "string" },
+            "uniqueItems": true,
+            "default": []
+        }
+    },
+    "type": ["object", "boolean"],
+    "properties": {
+        "$id": {
+            "type": "string",
+            "format": "uri-reference"
+        },
+        "$schema": {
+            "type": "string",
+            "format": "uri"
+        },
+        "$ref": {
+            "type": "string",
+            "format": "uri-reference"
+        },
+        "$comment": {
+            "type": "string"
+        },
+        "title": {
+            "type": "string"
+        },
+        "description": {
+            "type": "string"
+        },
+        "default": true,
+        "readOnly": {
+            "type": "boolean",
+            "default": false
+        },
+        "examples": {
+            "type": "array",
+            "items": true
+        },
+        "multipleOf": {
+            "type": "number",
+            "exclusiveMinimum": 0
+        },
+        "maximum": {
+            "type": "number"
+        },
+        "exclusiveMaximum": {
+            "type": "number"
+        },
+        "minimum": {
+            "type": "number"
+        },
+        "exclusiveMinimum": {
+            "type": "number"
+        },
+        "maxLength": { "$ref": "#/definitions/nonNegativeInteger" },
+        "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+        "pattern": {
+            "type": "string",
+            "format": "regex"
+        },
+        "additionalItems": { "$ref": "#" },
+        "items": {
+            "anyOf": [
+                { "$ref": "#" },
+                { "$ref": "#/definitions/schemaArray" }
+            ],
+            "default": true
+        },
+        "maxItems": { "$ref": "#/definitions/nonNegativeInteger" },
+        "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+        "uniqueItems": {
+            "type": "boolean",
+            "default": false
+        },
+        "contains": { "$ref": "#" },
+        "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" },
+        "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+        "required": { "$ref": "#/definitions/stringArray" },
+        "additionalProperties": { "$ref": "#" },
+        "definitions": {
+            "type": "object",
+            "additionalProperties": { "$ref": "#" },
+            "default": {}
+        },
+        "properties": {
+            "type": "object",
+            "additionalProperties": { "$ref": "#" },
+            "default": {}
+        },
+        "patternProperties": {
+            "type": "object",
+            "additionalProperties": { "$ref": "#" },
+            "propertyNames": { "format": "regex" },
+            "default": {}
+        },
+        "dependencies": {
+            "type": "object",
+            "additionalProperties": {
+                "anyOf": [
+                    { "$ref": "#" },
+                    { "$ref": "#/definitions/stringArray" }
+                ]
+            }
+        },
+        "propertyNames": { "$ref": "#" },
+        "const": true,
+        "enum": {
+            "type": "array",
+            "items": true,
+            "minItems": 1,
+            "uniqueItems": true
+        },
+        "type": {
+            "anyOf": [
+                { "$ref": "#/definitions/simpleTypes" },
+                {
+                    "type": "array",
+                    "items": { "$ref": "#/definitions/simpleTypes" },
+                    "minItems": 1,
+                    "uniqueItems": true
+                }
+            ]
+        },
+        "format": { "type": "string" },
+        "contentMediaType": { "type": "string" },
+        "contentEncoding": { "type": "string" },
+        "if": {"$ref": "#"},
+        "then": {"$ref": "#"},
+        "else": {"$ref": "#"},
+        "allOf": { "$ref": "#/definitions/schemaArray" },
+        "anyOf": { "$ref": "#/definitions/schemaArray" },
+        "oneOf": { "$ref": "#/definitions/schemaArray" },
+        "not": { "$ref": "#" }
+    },
+    "default": true
+}
diff --git a/swaggest/json-schema/spec/json-schema.json b/swaggest/json-schema/spec/json-schema.json
new file mode 100644
index 0000000..3d81b55
--- /dev/null
+++ b/swaggest/json-schema/spec/json-schema.json
@@ -0,0 +1,152 @@
+{
+    "id": "http://json-schema.org/draft-04/schema#",
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "description": "Core schema meta-schema",
+    "definitions": {
+        "schemaArray": {
+            "type": "array",
+            "minItems": 1,
+            "items": { "$ref": "#" }
+        },
+        "positiveInteger": {
+            "type": "integer",
+            "minimum": 0
+        },
+        "positiveIntegerDefault0": {
+            "allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ]
+        },
+        "simpleTypes": {
+            "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
+        },
+        "stringArray": {
+            "type": "array",
+            "items": { "type": "string" },
+            "minItems": 1,
+            "uniqueItems": true
+        }
+    },
+    "type": "object",
+    "properties": {
+        "id": {
+            "type": "string",
+            "format": "uri"
+        },
+        "$schema": {
+            "type": "string",
+            "format": "uri"
+        },
+        "title": {
+            "type": "string"
+        },
+        "description": {
+            "type": "string"
+        },
+        "default": {},
+        "multipleOf": {
+            "type": "number",
+            "minimum": 0,
+            "exclusiveMinimum": true
+        },
+        "maximum": {
+            "type": "number"
+        },
+        "exclusiveMaximum": {
+            "type": "boolean",
+            "default": false
+        },
+        "minimum": {
+            "type": "number"
+        },
+        "exclusiveMinimum": {
+            "type": "boolean",
+            "default": false
+        },
+        "maxLength": { "$ref": "#/definitions/positiveInteger" },
+        "minLength": { "$ref": "#/definitions/positiveIntegerDefault0" },
+        "pattern": {
+            "type": "string",
+            "format": "regex"
+        },
+        "additionalItems": {
+            "anyOf": [
+                { "type": "boolean" },
+                { "$ref": "#" }
+            ],
+            "default": {}
+        },
+        "items": {
+            "anyOf": [
+                { "$ref": "#" },
+                { "$ref": "#/definitions/schemaArray" }
+            ],
+            "default": {}
+        },
+        "maxItems": { "$ref": "#/definitions/positiveInteger" },
+        "minItems": { "$ref": "#/definitions/positiveIntegerDefault0" },
+        "uniqueItems": {
+            "type": "boolean",
+            "default": false
+        },
+        "maxProperties": { "$ref": "#/definitions/positiveInteger" },
+        "minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" },
+        "required": { "$ref": "#/definitions/stringArray" },
+        "additionalProperties": {
+            "anyOf": [
+                { "type": "boolean" },
+                { "$ref": "#" }
+            ],
+            "default": {}
+        },
+        "definitions": {
+            "type": "object",
+            "additionalProperties": { "$ref": "#" },
+            "default": {}
+        },
+        "properties": {
+            "type": "object",
+            "additionalProperties": { "$ref": "#" },
+            "default": {}
+        },
+        "patternProperties": {
+            "type": "object",
+            "additionalProperties": { "$ref": "#" },
+            "default": {}
+        },
+        "dependencies": {
+            "type": "object",
+            "additionalProperties": {
+                "anyOf": [
+                    { "$ref": "#" },
+                    { "$ref": "#/definitions/stringArray" }
+                ]
+            }
+        },
+        "enum": {
+            "type": "array",
+            "minItems": 1,
+            "uniqueItems": true
+        },
+        "type": {
+            "anyOf": [
+                { "$ref": "#/definitions/simpleTypes" },
+                {
+                    "type": "array",
+                    "items": { "$ref": "#/definitions/simpleTypes" },
+                    "minItems": 1,
+                    "uniqueItems": true
+                }
+            ]
+        },
+        "format": { "type": "string" },
+        "$ref": {"type": "string"},
+        "allOf": { "$ref": "#/definitions/schemaArray" },
+        "anyOf": { "$ref": "#/definitions/schemaArray" },
+        "oneOf": { "$ref": "#/definitions/schemaArray" },
+        "not": { "$ref": "#" }
+    },
+    "dependencies": {
+        "exclusiveMaximum": [ "maximum" ],
+        "exclusiveMinimum": [ "minimum" ]
+    },
+    "default": {}
+}
diff --git a/swaggest/json-schema/spec/petstore-swagger.json b/swaggest/json-schema/spec/petstore-swagger.json
new file mode 100644
index 0000000..65f39d5
--- /dev/null
+++ b/swaggest/json-schema/spec/petstore-swagger.json
@@ -0,0 +1,1035 @@
+{
+  "swagger": "2.0",
+  "info": {
+    "description": "This is a sample server Petstore server.  You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/).  For this sample, you can use the api key `special-key` to test the authorization filters.",
+    "version": "1.0.0",
+    "title": "Swagger Petstore",
+    "termsOfService": "http://swagger.io/terms/",
+    "contact": {
+      "email": "apiteam@swagger.io"
+    },
+    "license": {
+      "name": "Apache 2.0",
+      "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
+    }
+  },
+  "host": "petstore.swagger.io",
+  "basePath": "/v2",
+  "tags": [
+    {
+      "name": "pet",
+      "description": "Everything about your Pets",
+      "externalDocs": {
+        "description": "Find out more",
+        "url": "http://swagger.io"
+      }
+    },
+    {
+      "name": "store",
+      "description": "Access to Petstore orders"
+    },
+    {
+      "name": "user",
+      "description": "Operations about user",
+      "externalDocs": {
+        "description": "Find out more about our store",
+        "url": "http://swagger.io"
+      }
+    }
+  ],
+  "schemes": [
+    "http"
+  ],
+  "paths": {
+    "/pet": {
+      "post": {
+        "tags": [
+          "pet"
+        ],
+        "summary": "Add a new pet to the store",
+        "description": "",
+        "operationId": "addPet",
+        "consumes": [
+          "application/json",
+          "application/xml"
+        ],
+        "produces": [
+          "application/xml",
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "in": "body",
+            "name": "body",
+            "description": "Pet object that needs to be added to the store",
+            "required": true,
+            "schema": {
+              "$ref": "#/definitions/Pet"
+            }
+          }
+        ],
+        "responses": {
+          "405": {
+            "description": "Invalid input"
+          }
+        },
+        "security": [
+          {
+            "petstore_auth": [
+              "write:pets",
+              "read:pets"
+            ]
+          }
+        ]
+      },
+      "put": {
+        "tags": [
+          "pet"
+        ],
+        "summary": "Update an existing pet",
+        "description": "",
+        "operationId": "updatePet",
+        "consumes": [
+          "application/json",
+          "application/xml"
+        ],
+        "produces": [
+          "application/xml",
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "in": "body",
+            "name": "body",
+            "description": "Pet object that needs to be added to the store",
+            "required": true,
+            "schema": {
+              "$ref": "#/definitions/Pet"
+            }
+          }
+        ],
+        "responses": {
+          "400": {
+            "description": "Invalid ID supplied"
+          },
+          "404": {
+            "description": "Pet not found"
+          },
+          "405": {
+            "description": "Validation exception"
+          }
+        },
+        "security": [
+          {
+            "petstore_auth": [
+              "write:pets",
+              "read:pets"
+            ]
+          }
+        ]
+      }
+    },
+    "/pet/findByStatus": {
+      "get": {
+        "tags": [
+          "pet"
+        ],
+        "summary": "Finds Pets by status",
+        "description": "Multiple status values can be provided with comma separated strings",
+        "operationId": "findPetsByStatus",
+        "produces": [
+          "application/xml",
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "name": "status",
+            "in": "query",
+            "description": "Status values that need to be considered for filter",
+            "required": true,
+            "type": "array",
+            "items": {
+              "type": "string",
+              "enum": [
+                "available",
+                "pending",
+                "sold"
+              ],
+              "default": "available"
+            },
+            "collectionFormat": "multi"
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "successful operation",
+            "schema": {
+              "type": "array",
+              "items": {
+                "$ref": "#/definitions/Pet"
+              }
+            }
+          },
+          "400": {
+            "description": "Invalid status value"
+          }
+        },
+        "security": [
+          {
+            "petstore_auth": [
+              "write:pets",
+              "read:pets"
+            ]
+          }
+        ]
+      }
+    },
+    "/pet/findByTags": {
+      "get": {
+        "tags": [
+          "pet"
+        ],
+        "summary": "Finds Pets by tags",
+        "description": "Muliple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.",
+        "operationId": "findPetsByTags",
+        "produces": [
+          "application/xml",
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "name": "tags",
+            "in": "query",
+            "description": "Tags to filter by",
+            "required": true,
+            "type": "array",
+            "items": {
+              "type": "string"
+            },
+            "collectionFormat": "multi"
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "successful operation",
+            "schema": {
+              "type": "array",
+              "items": {
+                "$ref": "#/definitions/Pet"
+              }
+            }
+          },
+          "400": {
+            "description": "Invalid tag value"
+          }
+        },
+        "security": [
+          {
+            "petstore_auth": [
+              "write:pets",
+              "read:pets"
+            ]
+          }
+        ],
+        "deprecated": true
+      }
+    },
+    "/pet/{petId}": {
+      "get": {
+        "tags": [
+          "pet"
+        ],
+        "summary": "Find pet by ID",
+        "description": "Returns a single pet",
+        "operationId": "getPetById",
+        "produces": [
+          "application/xml",
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "name": "petId",
+            "in": "path",
+            "description": "ID of pet to return",
+            "required": true,
+            "type": "integer",
+            "format": "int64"
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "successful operation",
+            "schema": {
+              "$ref": "#/definitions/Pet"
+            }
+          },
+          "400": {
+            "description": "Invalid ID supplied"
+          },
+          "404": {
+            "description": "Pet not found"
+          }
+        },
+        "security": [
+          {
+            "api_key": []
+          }
+        ]
+      },
+      "post": {
+        "tags": [
+          "pet"
+        ],
+        "summary": "Updates a pet in the store with form data",
+        "description": "",
+        "operationId": "updatePetWithForm",
+        "consumes": [
+          "application/x-www-form-urlencoded"
+        ],
+        "produces": [
+          "application/xml",
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "name": "petId",
+            "in": "path",
+            "description": "ID of pet that needs to be updated",
+            "required": true,
+            "type": "integer",
+            "format": "int64"
+          },
+          {
+            "name": "name",
+            "in": "formData",
+            "description": "Updated name of the pet",
+            "required": false,
+            "type": "string"
+          },
+          {
+            "name": "status",
+            "in": "formData",
+            "description": "Updated status of the pet",
+            "required": false,
+            "type": "string"
+          }
+        ],
+        "responses": {
+          "405": {
+            "description": "Invalid input"
+          }
+        },
+        "security": [
+          {
+            "petstore_auth": [
+              "write:pets",
+              "read:pets"
+            ]
+          }
+        ]
+      },
+      "delete": {
+        "tags": [
+          "pet"
+        ],
+        "summary": "Deletes a pet",
+        "description": "",
+        "operationId": "deletePet",
+        "produces": [
+          "application/xml",
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "name": "api_key",
+            "in": "header",
+            "required": false,
+            "type": "string"
+          },
+          {
+            "name": "petId",
+            "in": "path",
+            "description": "Pet id to delete",
+            "required": true,
+            "type": "integer",
+            "format": "int64"
+          }
+        ],
+        "responses": {
+          "400": {
+            "description": "Invalid ID supplied"
+          },
+          "404": {
+            "description": "Pet not found"
+          }
+        },
+        "security": [
+          {
+            "petstore_auth": [
+              "write:pets",
+              "read:pets"
+            ]
+          }
+        ]
+      }
+    },
+    "/pet/{petId}/uploadImage": {
+      "post": {
+        "tags": [
+          "pet"
+        ],
+        "summary": "uploads an image",
+        "description": "",
+        "operationId": "uploadFile",
+        "consumes": [
+          "multipart/form-data"
+        ],
+        "produces": [
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "name": "petId",
+            "in": "path",
+            "description": "ID of pet to update",
+            "required": true,
+            "type": "integer",
+            "format": "int64"
+          },
+          {
+            "name": "additionalMetadata",
+            "in": "formData",
+            "description": "Additional data to pass to server",
+            "required": false,
+            "type": "string"
+          },
+          {
+            "name": "file",
+            "in": "formData",
+            "description": "file to upload",
+            "required": false,
+            "type": "file"
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "successful operation",
+            "schema": {
+              "$ref": "#/definitions/ApiResponse"
+            }
+          }
+        },
+        "security": [
+          {
+            "petstore_auth": [
+              "write:pets",
+              "read:pets"
+            ]
+          }
+        ]
+      }
+    },
+    "/store/inventory": {
+      "get": {
+        "tags": [
+          "store"
+        ],
+        "summary": "Returns pet inventories by status",
+        "description": "Returns a map of status codes to quantities",
+        "operationId": "getInventory",
+        "produces": [
+          "application/json"
+        ],
+        "parameters": [],
+        "responses": {
+          "200": {
+            "description": "successful operation",
+            "schema": {
+              "type": "object",
+              "additionalProperties": {
+                "type": "integer",
+                "format": "int32"
+              }
+            }
+          }
+        },
+        "security": [
+          {
+            "api_key": []
+          }
+        ]
+      }
+    },
+    "/store/order": {
+      "post": {
+        "tags": [
+          "store"
+        ],
+        "summary": "Place an order for a pet",
+        "description": "",
+        "operationId": "placeOrder",
+        "produces": [
+          "application/xml",
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "in": "body",
+            "name": "body",
+            "description": "order placed for purchasing the pet",
+            "required": true,
+            "schema": {
+              "$ref": "#/definitions/Order"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "successful operation",
+            "schema": {
+              "$ref": "#/definitions/Order"
+            }
+          },
+          "400": {
+            "description": "Invalid Order"
+          }
+        }
+      }
+    },
+    "/store/order/{orderId}": {
+      "get": {
+        "tags": [
+          "store"
+        ],
+        "summary": "Find purchase order by ID",
+        "description": "For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions",
+        "operationId": "getOrderById",
+        "produces": [
+          "application/xml",
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "name": "orderId",
+            "in": "path",
+            "description": "ID of pet that needs to be fetched",
+            "required": true,
+            "type": "integer",
+            "maximum": 10.0,
+            "minimum": 1.0,
+            "format": "int64"
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "successful operation",
+            "schema": {
+              "$ref": "#/definitions/Order"
+            }
+          },
+          "400": {
+            "description": "Invalid ID supplied"
+          },
+          "404": {
+            "description": "Order not found"
+          }
+        }
+      },
+      "delete": {
+        "tags": [
+          "store"
+        ],
+        "summary": "Delete purchase order by ID",
+        "description": "For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors",
+        "operationId": "deleteOrder",
+        "produces": [
+          "application/xml",
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "name": "orderId",
+            "in": "path",
+            "description": "ID of the order that needs to be deleted",
+            "required": true,
+            "type": "integer",
+            "minimum": 1.0,
+            "format": "int64"
+          }
+        ],
+        "responses": {
+          "400": {
+            "description": "Invalid ID supplied"
+          },
+          "404": {
+            "description": "Order not found"
+          }
+        }
+      }
+    },
+    "/user": {
+      "post": {
+        "tags": [
+          "user"
+        ],
+        "summary": "Create user",
+        "description": "This can only be done by the logged in user.",
+        "operationId": "createUser",
+        "produces": [
+          "application/xml",
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "in": "body",
+            "name": "body",
+            "description": "Created user object",
+            "required": true,
+            "schema": {
+              "$ref": "#/definitions/User"
+            }
+          }
+        ],
+        "responses": {
+          "default": {
+            "description": "successful operation"
+          }
+        }
+      }
+    },
+    "/user/createWithArray": {
+      "post": {
+        "tags": [
+          "user"
+        ],
+        "summary": "Creates list of users with given input array",
+        "description": "",
+        "operationId": "createUsersWithArrayInput",
+        "produces": [
+          "application/xml",
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "in": "body",
+            "name": "body",
+            "description": "List of user object",
+            "required": true,
+            "schema": {
+              "type": "array",
+              "items": {
+                "$ref": "#/definitions/User"
+              }
+            }
+          }
+        ],
+        "responses": {
+          "default": {
+            "description": "successful operation"
+          }
+        }
+      }
+    },
+    "/user/createWithList": {
+      "post": {
+        "tags": [
+          "user"
+        ],
+        "summary": "Creates list of users with given input array",
+        "description": "",
+        "operationId": "createUsersWithListInput",
+        "produces": [
+          "application/xml",
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "in": "body",
+            "name": "body",
+            "description": "List of user object",
+            "required": true,
+            "schema": {
+              "type": "array",
+              "items": {
+                "$ref": "#/definitions/User"
+              }
+            }
+          }
+        ],
+        "responses": {
+          "default": {
+            "description": "successful operation"
+          }
+        }
+      }
+    },
+    "/user/login": {
+      "get": {
+        "tags": [
+          "user"
+        ],
+        "summary": "Logs user into the system",
+        "description": "",
+        "operationId": "loginUser",
+        "produces": [
+          "application/xml",
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "name": "username",
+            "in": "query",
+            "description": "The user name for login",
+            "required": true,
+            "type": "string"
+          },
+          {
+            "name": "password",
+            "in": "query",
+            "description": "The password for login in clear text",
+            "required": true,
+            "type": "string"
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "successful operation",
+            "schema": {
+              "type": "string"
+            },
+            "headers": {
+              "X-Rate-Limit": {
+                "type": "integer",
+                "format": "int32",
+                "description": "calls per hour allowed by the user"
+              },
+              "X-Expires-After": {
+                "type": "string",
+                "format": "date-time",
+                "description": "date in UTC when token expires"
+              }
+            }
+          },
+          "400": {
+            "description": "Invalid username/password supplied"
+          }
+        }
+      }
+    },
+    "/user/logout": {
+      "get": {
+        "tags": [
+          "user"
+        ],
+        "summary": "Logs out current logged in user session",
+        "description": "",
+        "operationId": "logoutUser",
+        "produces": [
+          "application/xml",
+          "application/json"
+        ],
+        "parameters": [],
+        "responses": {
+          "default": {
+            "description": "successful operation"
+          }
+        }
+      }
+    },
+    "/user/{username}": {
+      "get": {
+        "tags": [
+          "user"
+        ],
+        "summary": "Get user by user name",
+        "description": "",
+        "operationId": "getUserByName",
+        "produces": [
+          "application/xml",
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "name": "username",
+            "in": "path",
+            "description": "The name that needs to be fetched. Use user1 for testing. ",
+            "required": true,
+            "type": "string"
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "successful operation",
+            "schema": {
+              "$ref": "#/definitions/User"
+            }
+          },
+          "400": {
+            "description": "Invalid username supplied"
+          },
+          "404": {
+            "description": "User not found"
+          }
+        }
+      },
+      "put": {
+        "tags": [
+          "user"
+        ],
+        "summary": "Updated user",
+        "description": "This can only be done by the logged in user.",
+        "operationId": "updateUser",
+        "produces": [
+          "application/xml",
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "name": "username",
+            "in": "path",
+            "description": "name that need to be updated",
+            "required": true,
+            "type": "string"
+          },
+          {
+            "in": "body",
+            "name": "body",
+            "description": "Updated user object",
+            "required": true,
+            "schema": {
+              "$ref": "#/definitions/User"
+            }
+          }
+        ],
+        "responses": {
+          "400": {
+            "description": "Invalid user supplied"
+          },
+          "404": {
+            "description": "User not found"
+          }
+        }
+      },
+      "delete": {
+        "tags": [
+          "user"
+        ],
+        "summary": "Delete user",
+        "description": "This can only be done by the logged in user.",
+        "operationId": "deleteUser",
+        "produces": [
+          "application/xml",
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "name": "username",
+            "in": "path",
+            "description": "The name that needs to be deleted",
+            "required": true,
+            "type": "string"
+          }
+        ],
+        "responses": {
+          "400": {
+            "description": "Invalid username supplied"
+          },
+          "404": {
+            "description": "User not found"
+          }
+        }
+      }
+    }
+  },
+  "securityDefinitions": {
+    "petstore_auth": {
+      "type": "oauth2",
+      "authorizationUrl": "http://petstore.swagger.io/oauth/dialog",
+      "flow": "implicit",
+      "scopes": {
+        "write:pets": "modify pets in your account",
+        "read:pets": "read your pets"
+      }
+    },
+    "api_key": {
+      "type": "apiKey",
+      "name": "api_key",
+      "in": "header"
+    }
+  },
+  "definitions": {
+    "Order": {
+      "type": "object",
+      "properties": {
+        "id": {
+          "type": "integer",
+          "format": "int64"
+        },
+        "petId": {
+          "type": "integer",
+          "format": "int64"
+        },
+        "quantity": {
+          "type": "integer",
+          "format": "int32"
+        },
+        "shipDate": {
+          "type": "string",
+          "format": "date-time"
+        },
+        "status": {
+          "type": "string",
+          "description": "Order Status",
+          "enum": [
+            "placed",
+            "approved",
+            "delivered"
+          ]
+        },
+        "complete": {
+          "type": "boolean",
+          "default": false
+        }
+      },
+      "xml": {
+        "name": "Order"
+      }
+    },
+    "Category": {
+      "type": "object",
+      "properties": {
+        "id": {
+          "type": "integer",
+          "format": "int64"
+        },
+        "name": {
+          "type": "string"
+        }
+      },
+      "xml": {
+        "name": "Category"
+      }
+    },
+    "User": {
+      "type": "object",
+      "properties": {
+        "id": {
+          "type": "integer",
+          "format": "int64"
+        },
+        "username": {
+          "type": "string"
+        },
+        "firstName": {
+          "type": "string"
+        },
+        "lastName": {
+          "type": "string"
+        },
+        "email": {
+          "type": "string"
+        },
+        "password": {
+          "type": "string"
+        },
+        "phone": {
+          "type": "string"
+        },
+        "userStatus": {
+          "type": "integer",
+          "format": "int32",
+          "description": "User Status"
+        }
+      },
+      "xml": {
+        "name": "User"
+      }
+    },
+    "Tag": {
+      "type": "object",
+      "properties": {
+        "id": {
+          "type": "integer",
+          "format": "int64"
+        },
+        "name": {
+          "type": "string"
+        }
+      },
+      "xml": {
+        "name": "Tag"
+      }
+    },
+    "Pet": {
+      "type": "object",
+      "required": [
+        "name",
+        "photoUrls"
+      ],
+      "properties": {
+        "id": {
+          "type": "integer",
+          "format": "int64"
+        },
+        "category": {
+          "$ref": "#/definitions/Category"
+        },
+        "name": {
+          "type": "string",
+          "example": "doggie"
+        },
+        "photoUrls": {
+          "type": "array",
+          "xml": {
+            "name": "photoUrl",
+            "wrapped": true
+          },
+          "items": {
+            "type": "string"
+          }
+        },
+        "tags": {
+          "type": "array",
+          "xml": {
+            "name": "tag",
+            "wrapped": true
+          },
+          "items": {
+            "$ref": "#/definitions/Tag"
+          }
+        },
+        "status": {
+          "type": "string",
+          "description": "pet status in the store",
+          "enum": [
+            "available",
+            "pending",
+            "sold"
+          ]
+        }
+      },
+      "xml": {
+        "name": "Pet"
+      }
+    },
+    "ApiResponse": {
+      "type": "object",
+      "properties": {
+        "code": {
+          "type": "integer",
+          "format": "int32"
+        },
+        "type": {
+          "type": "string"
+        },
+        "message": {
+          "type": "string"
+        }
+      }
+    }
+  },
+  "externalDocs": {
+    "description": "Find out more about Swagger",
+    "url": "http://swagger.io"
+  }
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/spec/swagger-schema.json b/swaggest/json-schema/spec/swagger-schema.json
new file mode 100644
index 0000000..e34bfa0
--- /dev/null
+++ b/swaggest/json-schema/spec/swagger-schema.json
@@ -0,0 +1,1608 @@
+{
+  "title": "A JSON Schema for Swagger 2.0 API.",
+  "id": "http://swagger.io/v2/schema.json#",
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "type": "object",
+  "required": [
+    "swagger",
+    "info",
+    "paths"
+  ],
+  "additionalProperties": false,
+  "patternProperties": {
+    "^x-": {
+      "$ref": "#/definitions/vendorExtension"
+    }
+  },
+  "properties": {
+    "swagger": {
+      "type": "string",
+      "enum": [
+        "2.0"
+      ],
+      "description": "The Swagger version of this document."
+    },
+    "info": {
+      "$ref": "#/definitions/info"
+    },
+    "host": {
+      "type": "string",
+      "pattern": "^[^{}/ :\\\\]+(?::\\d+)?$",
+      "description": "The host (name or ip) of the API. Example: 'swagger.io'"
+    },
+    "basePath": {
+      "type": "string",
+      "pattern": "^/",
+      "description": "The base path to the API. Example: '/api'."
+    },
+    "schemes": {
+      "$ref": "#/definitions/schemesList"
+    },
+    "consumes": {
+      "description": "A list of MIME types accepted by the API.",
+      "allOf": [
+        {
+          "$ref": "#/definitions/mediaTypeList"
+        }
+      ]
+    },
+    "produces": {
+      "description": "A list of MIME types the API can produce.",
+      "allOf": [
+        {
+          "$ref": "#/definitions/mediaTypeList"
+        }
+      ]
+    },
+    "paths": {
+      "$ref": "#/definitions/paths"
+    },
+    "definitions": {
+      "$ref": "#/definitions/definitions"
+    },
+    "parameters": {
+      "$ref": "#/definitions/parameterDefinitions"
+    },
+    "responses": {
+      "$ref": "#/definitions/responseDefinitions"
+    },
+    "security": {
+      "$ref": "#/definitions/security"
+    },
+    "securityDefinitions": {
+      "$ref": "#/definitions/securityDefinitions"
+    },
+    "tags": {
+      "type": "array",
+      "items": {
+        "$ref": "#/definitions/tag"
+      },
+      "uniqueItems": true
+    },
+    "externalDocs": {
+      "$ref": "#/definitions/externalDocs"
+    }
+  },
+  "definitions": {
+    "info": {
+      "type": "object",
+      "description": "General information about the API.",
+      "required": [
+        "version",
+        "title"
+      ],
+      "additionalProperties": false,
+      "patternProperties": {
+        "^x-": {
+          "$ref": "#/definitions/vendorExtension"
+        }
+      },
+      "properties": {
+        "title": {
+          "type": "string",
+          "description": "A unique and precise title of the API."
+        },
+        "version": {
+          "type": "string",
+          "description": "A semantic version number of the API."
+        },
+        "description": {
+          "type": "string",
+          "description": "A longer description of the API. Should be different from the title.  GitHub Flavored Markdown is allowed."
+        },
+        "termsOfService": {
+          "type": "string",
+          "description": "The terms of service for the API."
+        },
+        "contact": {
+          "$ref": "#/definitions/contact"
+        },
+        "license": {
+          "$ref": "#/definitions/license"
+        }
+      }
+    },
+    "contact": {
+      "type": "object",
+      "description": "Contact information for the owners of the API.",
+      "additionalProperties": false,
+      "properties": {
+        "name": {
+          "type": "string",
+          "description": "The identifying name of the contact person/organization."
+        },
+        "url": {
+          "type": "string",
+          "description": "The URL pointing to the contact information.",
+          "format": "uri"
+        },
+        "email": {
+          "type": "string",
+          "description": "The email address of the contact person/organization.",
+          "format": "email"
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+          "$ref": "#/definitions/vendorExtension"
+        }
+      }
+    },
+    "license": {
+      "type": "object",
+      "required": [
+        "name"
+      ],
+      "additionalProperties": false,
+      "properties": {
+        "name": {
+          "type": "string",
+          "description": "The name of the license type. It's encouraged to use an OSI compatible license."
+        },
+        "url": {
+          "type": "string",
+          "description": "The URL pointing to the license.",
+          "format": "uri"
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+          "$ref": "#/definitions/vendorExtension"
+        }
+      }
+    },
+    "paths": {
+      "type": "object",
+      "description": "Relative paths to the individual endpoints. They must be relative to the 'basePath'.",
+      "patternProperties": {
+        "^x-": {
+          "$ref": "#/definitions/vendorExtension"
+        },
+        "^/": {
+          "$ref": "#/definitions/pathItem"
+        }
+      },
+      "additionalProperties": false
+    },
+    "definitions": {
+      "type": "object",
+      "additionalProperties": {
+        "$ref": "#/definitions/schema"
+      },
+      "description": "One or more JSON objects describing the schemas being consumed and produced by the API."
+    },
+    "parameterDefinitions": {
+      "type": "object",
+      "additionalProperties": {
+        "$ref": "#/definitions/parameter"
+      },
+      "description": "One or more JSON representations for parameters"
+    },
+    "responseDefinitions": {
+      "type": "object",
+      "additionalProperties": {
+        "$ref": "#/definitions/response"
+      },
+      "description": "One or more JSON representations for parameters"
+    },
+    "externalDocs": {
+      "type": "object",
+      "additionalProperties": false,
+      "description": "information about external documentation",
+      "required": [
+        "url"
+      ],
+      "properties": {
+        "description": {
+          "type": "string"
+        },
+        "url": {
+          "type": "string",
+          "format": "uri"
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+          "$ref": "#/definitions/vendorExtension"
+        }
+      }
+    },
+    "examples": {
+      "type": "object",
+      "additionalProperties": true
+    },
+    "mimeType": {
+      "type": "string",
+      "description": "The MIME type of the HTTP message."
+    },
+    "operation": {
+      "type": "object",
+      "required": [
+        "responses"
+      ],
+      "additionalProperties": false,
+      "patternProperties": {
+        "^x-": {
+          "$ref": "#/definitions/vendorExtension"
+        }
+      },
+      "properties": {
+        "tags": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "uniqueItems": true
+        },
+        "summary": {
+          "type": "string",
+          "description": "A brief summary of the operation."
+        },
+        "description": {
+          "type": "string",
+          "description": "A longer description of the operation, GitHub Flavored Markdown is allowed."
+        },
+        "externalDocs": {
+          "$ref": "#/definitions/externalDocs"
+        },
+        "operationId": {
+          "type": "string",
+          "description": "A unique identifier of the operation."
+        },
+        "produces": {
+          "description": "A list of MIME types the API can produce.",
+          "allOf": [
+            {
+              "$ref": "#/definitions/mediaTypeList"
+            }
+          ]
+        },
+        "consumes": {
+          "description": "A list of MIME types the API can consume.",
+          "allOf": [
+            {
+              "$ref": "#/definitions/mediaTypeList"
+            }
+          ]
+        },
+        "parameters": {
+          "$ref": "#/definitions/parametersList"
+        },
+        "responses": {
+          "$ref": "#/definitions/responses"
+        },
+        "schemes": {
+          "$ref": "#/definitions/schemesList"
+        },
+        "deprecated": {
+          "type": "boolean",
+          "default": false
+        },
+        "security": {
+          "$ref": "#/definitions/security"
+        }
+      }
+    },
+    "pathItem": {
+      "type": "object",
+      "additionalProperties": false,
+      "patternProperties": {
+        "^x-": {
+          "$ref": "#/definitions/vendorExtension"
+        }
+      },
+      "properties": {
+        "$ref": {
+          "type": "string"
+        },
+        "get": {
+          "$ref": "#/definitions/operation"
+        },
+        "put": {
+          "$ref": "#/definitions/operation"
+        },
+        "post": {
+          "$ref": "#/definitions/operation"
+        },
+        "delete": {
+          "$ref": "#/definitions/operation"
+        },
+        "options": {
+          "$ref": "#/definitions/operation"
+        },
+        "head": {
+          "$ref": "#/definitions/operation"
+        },
+        "patch": {
+          "$ref": "#/definitions/operation"
+        },
+        "parameters": {
+          "$ref": "#/definitions/parametersList"
+        }
+      }
+    },
+    "responses": {
+      "type": "object",
+      "description": "Response objects names can either be any valid HTTP status code or 'default'.",
+      "minProperties": 1,
+      "additionalProperties": false,
+      "patternProperties": {
+        "^([0-9]{3})$|^(default)$": {
+          "$ref": "#/definitions/responseValue"
+        },
+        "^x-": {
+          "$ref": "#/definitions/vendorExtension"
+        }
+      },
+      "not": {
+        "type": "object",
+        "additionalProperties": false,
+        "patternProperties": {
+          "^x-": {
+            "$ref": "#/definitions/vendorExtension"
+          }
+        }
+      }
+    },
+    "responseValue": {
+      "oneOf": [
+        {
+          "$ref": "#/definitions/response"
+        },
+        {
+          "$ref": "#/definitions/jsonReference"
+        }
+      ]
+    },
+    "response": {
+      "type": "object",
+      "required": [
+        "description"
+      ],
+      "properties": {
+        "description": {
+          "type": "string"
+        },
+        "schema": {
+          "oneOf": [
+            {
+              "$ref": "#/definitions/schema"
+            },
+            {
+              "$ref": "#/definitions/fileSchema"
+            }
+          ]
+        },
+        "headers": {
+          "$ref": "#/definitions/headers"
+        },
+        "examples": {
+          "$ref": "#/definitions/examples"
+        }
+      },
+      "additionalProperties": false,
+      "patternProperties": {
+        "^x-": {
+          "$ref": "#/definitions/vendorExtension"
+        }
+      }
+    },
+    "headers": {
+      "type": "object",
+      "additionalProperties": {
+        "$ref": "#/definitions/header"
+      }
+    },
+    "header": {
+      "type": "object",
+      "additionalProperties": false,
+      "required": [
+        "type"
+      ],
+      "properties": {
+        "type": {
+          "type": "string",
+          "enum": [
+            "string",
+            "number",
+            "integer",
+            "boolean",
+            "array"
+          ]
+        },
+        "format": {
+          "type": "string"
+        },
+        "items": {
+          "$ref": "#/definitions/primitivesItems"
+        },
+        "collectionFormat": {
+          "$ref": "#/definitions/collectionFormat"
+        },
+        "default": {
+          "$ref": "#/definitions/default"
+        },
+        "maximum": {
+          "$ref": "#/definitions/maximum"
+        },
+        "exclusiveMaximum": {
+          "$ref": "#/definitions/exclusiveMaximum"
+        },
+        "minimum": {
+          "$ref": "#/definitions/minimum"
+        },
+        "exclusiveMinimum": {
+          "$ref": "#/definitions/exclusiveMinimum"
+        },
+        "maxLength": {
+          "$ref": "#/definitions/maxLength"
+        },
+        "minLength": {
+          "$ref": "#/definitions/minLength"
+        },
+        "pattern": {
+          "$ref": "#/definitions/pattern"
+        },
+        "maxItems": {
+          "$ref": "#/definitions/maxItems"
+        },
+        "minItems": {
+          "$ref": "#/definitions/minItems"
+        },
+        "uniqueItems": {
+          "$ref": "#/definitions/uniqueItems"
+        },
+        "enum": {
+          "$ref": "#/definitions/enum"
+        },
+        "multipleOf": {
+          "$ref": "#/definitions/multipleOf"
+        },
+        "description": {
+          "type": "string"
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+          "$ref": "#/definitions/vendorExtension"
+        }
+      }
+    },
+    "vendorExtension": {
+      "description": "Any property starting with x- is valid.",
+      "additionalProperties": true,
+      "additionalItems": true
+    },
+    "bodyParameter": {
+      "type": "object",
+      "required": [
+        "name",
+        "in",
+        "schema"
+      ],
+      "patternProperties": {
+        "^x-": {
+          "$ref": "#/definitions/vendorExtension"
+        }
+      },
+      "properties": {
+        "description": {
+          "type": "string",
+          "description": "A brief description of the parameter. This could contain examples of use.  GitHub Flavored Markdown is allowed."
+        },
+        "name": {
+          "type": "string",
+          "description": "The name of the parameter."
+        },
+        "in": {
+          "type": "string",
+          "description": "Determines the location of the parameter.",
+          "enum": [
+            "body"
+          ]
+        },
+        "required": {
+          "type": "boolean",
+          "description": "Determines whether or not this parameter is required or optional.",
+          "default": false
+        },
+        "schema": {
+          "$ref": "#/definitions/schema"
+        }
+      },
+      "additionalProperties": false
+    },
+    "headerParameterSubSchema": {
+      "additionalProperties": false,
+      "patternProperties": {
+        "^x-": {
+          "$ref": "#/definitions/vendorExtension"
+        }
+      },
+      "properties": {
+        "required": {
+          "type": "boolean",
+          "description": "Determines whether or not this parameter is required or optional.",
+          "default": false
+        },
+        "in": {
+          "type": "string",
+          "description": "Determines the location of the parameter.",
+          "enum": [
+            "header"
+          ]
+        },
+        "description": {
+          "type": "string",
+          "description": "A brief description of the parameter. This could contain examples of use.  GitHub Flavored Markdown is allowed."
+        },
+        "name": {
+          "type": "string",
+          "description": "The name of the parameter."
+        },
+        "type": {
+          "type": "string",
+          "enum": [
+            "string",
+            "number",
+            "boolean",
+            "integer",
+            "array"
+          ]
+        },
+        "format": {
+          "type": "string"
+        },
+        "items": {
+          "$ref": "#/definitions/primitivesItems"
+        },
+        "collectionFormat": {
+          "$ref": "#/definitions/collectionFormat"
+        },
+        "default": {
+          "$ref": "#/definitions/default"
+        },
+        "maximum": {
+          "$ref": "#/definitions/maximum"
+        },
+        "exclusiveMaximum": {
+          "$ref": "#/definitions/exclusiveMaximum"
+        },
+        "minimum": {
+          "$ref": "#/definitions/minimum"
+        },
+        "exclusiveMinimum": {
+          "$ref": "#/definitions/exclusiveMinimum"
+        },
+        "maxLength": {
+          "$ref": "#/definitions/maxLength"
+        },
+        "minLength": {
+          "$ref": "#/definitions/minLength"
+        },
+        "pattern": {
+          "$ref": "#/definitions/pattern"
+        },
+        "maxItems": {
+          "$ref": "#/definitions/maxItems"
+        },
+        "minItems": {
+          "$ref": "#/definitions/minItems"
+        },
+        "uniqueItems": {
+          "$ref": "#/definitions/uniqueItems"
+        },
+        "enum": {
+          "$ref": "#/definitions/enum"
+        },
+        "multipleOf": {
+          "$ref": "#/definitions/multipleOf"
+        }
+      }
+    },
+    "queryParameterSubSchema": {
+      "additionalProperties": false,
+      "patternProperties": {
+        "^x-": {
+          "$ref": "#/definitions/vendorExtension"
+        }
+      },
+      "properties": {
+        "required": {
+          "type": "boolean",
+          "description": "Determines whether or not this parameter is required or optional.",
+          "default": false
+        },
+        "in": {
+          "type": "string",
+          "description": "Determines the location of the parameter.",
+          "enum": [
+            "query"
+          ]
+        },
+        "description": {
+          "type": "string",
+          "description": "A brief description of the parameter. This could contain examples of use.  GitHub Flavored Markdown is allowed."
+        },
+        "name": {
+          "type": "string",
+          "description": "The name of the parameter."
+        },
+        "allowEmptyValue": {
+          "type": "boolean",
+          "default": false,
+          "description": "allows sending a parameter by name only or with an empty value."
+        },
+        "type": {
+          "type": "string",
+          "enum": [
+            "string",
+            "number",
+            "boolean",
+            "integer",
+            "array"
+          ]
+        },
+        "format": {
+          "type": "string"
+        },
+        "items": {
+          "$ref": "#/definitions/primitivesItems"
+        },
+        "collectionFormat": {
+          "$ref": "#/definitions/collectionFormatWithMulti"
+        },
+        "default": {
+          "$ref": "#/definitions/default"
+        },
+        "maximum": {
+          "$ref": "#/definitions/maximum"
+        },
+        "exclusiveMaximum": {
+          "$ref": "#/definitions/exclusiveMaximum"
+        },
+        "minimum": {
+          "$ref": "#/definitions/minimum"
+        },
+        "exclusiveMinimum": {
+          "$ref": "#/definitions/exclusiveMinimum"
+        },
+        "maxLength": {
+          "$ref": "#/definitions/maxLength"
+        },
+        "minLength": {
+          "$ref": "#/definitions/minLength"
+        },
+        "pattern": {
+          "$ref": "#/definitions/pattern"
+        },
+        "maxItems": {
+          "$ref": "#/definitions/maxItems"
+        },
+        "minItems": {
+          "$ref": "#/definitions/minItems"
+        },
+        "uniqueItems": {
+          "$ref": "#/definitions/uniqueItems"
+        },
+        "enum": {
+          "$ref": "#/definitions/enum"
+        },
+        "multipleOf": {
+          "$ref": "#/definitions/multipleOf"
+        }
+      }
+    },
+    "formDataParameterSubSchema": {
+      "additionalProperties": false,
+      "patternProperties": {
+        "^x-": {
+          "$ref": "#/definitions/vendorExtension"
+        }
+      },
+      "properties": {
+        "required": {
+          "type": "boolean",
+          "description": "Determines whether or not this parameter is required or optional.",
+          "default": false
+        },
+        "in": {
+          "type": "string",
+          "description": "Determines the location of the parameter.",
+          "enum": [
+            "formData"
+          ]
+        },
+        "description": {
+          "type": "string",
+          "description": "A brief description of the parameter. This could contain examples of use.  GitHub Flavored Markdown is allowed."
+        },
+        "name": {
+          "type": "string",
+          "description": "The name of the parameter."
+        },
+        "allowEmptyValue": {
+          "type": "boolean",
+          "default": false,
+          "description": "allows sending a parameter by name only or with an empty value."
+        },
+        "type": {
+          "type": "string",
+          "enum": [
+            "string",
+            "number",
+            "boolean",
+            "integer",
+            "array",
+            "file"
+          ]
+        },
+        "format": {
+          "type": "string"
+        },
+        "items": {
+          "$ref": "#/definitions/primitivesItems"
+        },
+        "collectionFormat": {
+          "$ref": "#/definitions/collectionFormatWithMulti"
+        },
+        "default": {
+          "$ref": "#/definitions/default"
+        },
+        "maximum": {
+          "$ref": "#/definitions/maximum"
+        },
+        "exclusiveMaximum": {
+          "$ref": "#/definitions/exclusiveMaximum"
+        },
+        "minimum": {
+          "$ref": "#/definitions/minimum"
+        },
+        "exclusiveMinimum": {
+          "$ref": "#/definitions/exclusiveMinimum"
+        },
+        "maxLength": {
+          "$ref": "#/definitions/maxLength"
+        },
+        "minLength": {
+          "$ref": "#/definitions/minLength"
+        },
+        "pattern": {
+          "$ref": "#/definitions/pattern"
+        },
+        "maxItems": {
+          "$ref": "#/definitions/maxItems"
+        },
+        "minItems": {
+          "$ref": "#/definitions/minItems"
+        },
+        "uniqueItems": {
+          "$ref": "#/definitions/uniqueItems"
+        },
+        "enum": {
+          "$ref": "#/definitions/enum"
+        },
+        "multipleOf": {
+          "$ref": "#/definitions/multipleOf"
+        }
+      }
+    },
+    "pathParameterSubSchema": {
+      "additionalProperties": false,
+      "patternProperties": {
+        "^x-": {
+          "$ref": "#/definitions/vendorExtension"
+        }
+      },
+      "required": [
+        "required"
+      ],
+      "properties": {
+        "required": {
+          "type": "boolean",
+          "enum": [
+            true
+          ],
+          "description": "Determines whether or not this parameter is required or optional."
+        },
+        "in": {
+          "type": "string",
+          "description": "Determines the location of the parameter.",
+          "enum": [
+            "path"
+          ]
+        },
+        "description": {
+          "type": "string",
+          "description": "A brief description of the parameter. This could contain examples of use.  GitHub Flavored Markdown is allowed."
+        },
+        "name": {
+          "type": "string",
+          "description": "The name of the parameter."
+        },
+        "type": {
+          "type": "string",
+          "enum": [
+            "string",
+            "number",
+            "boolean",
+            "integer",
+            "array"
+          ]
+        },
+        "format": {
+          "type": "string"
+        },
+        "items": {
+          "$ref": "#/definitions/primitivesItems"
+        },
+        "collectionFormat": {
+          "$ref": "#/definitions/collectionFormat"
+        },
+        "default": {
+          "$ref": "#/definitions/default"
+        },
+        "maximum": {
+          "$ref": "#/definitions/maximum"
+        },
+        "exclusiveMaximum": {
+          "$ref": "#/definitions/exclusiveMaximum"
+        },
+        "minimum": {
+          "$ref": "#/definitions/minimum"
+        },
+        "exclusiveMinimum": {
+          "$ref": "#/definitions/exclusiveMinimum"
+        },
+        "maxLength": {
+          "$ref": "#/definitions/maxLength"
+        },
+        "minLength": {
+          "$ref": "#/definitions/minLength"
+        },
+        "pattern": {
+          "$ref": "#/definitions/pattern"
+        },
+        "maxItems": {
+          "$ref": "#/definitions/maxItems"
+        },
+        "minItems": {
+          "$ref": "#/definitions/minItems"
+        },
+        "uniqueItems": {
+          "$ref": "#/definitions/uniqueItems"
+        },
+        "enum": {
+          "$ref": "#/definitions/enum"
+        },
+        "multipleOf": {
+          "$ref": "#/definitions/multipleOf"
+        }
+      }
+    },
+    "nonBodyParameter": {
+      "type": "object",
+      "required": [
+        "name",
+        "in",
+        "type"
+      ],
+      "oneOf": [
+        {
+          "$ref": "#/definitions/headerParameterSubSchema"
+        },
+        {
+          "$ref": "#/definitions/formDataParameterSubSchema"
+        },
+        {
+          "$ref": "#/definitions/queryParameterSubSchema"
+        },
+        {
+          "$ref": "#/definitions/pathParameterSubSchema"
+        }
+      ]
+    },
+    "parameter": {
+      "oneOf": [
+        {
+          "$ref": "#/definitions/bodyParameter"
+        },
+        {
+          "$ref": "#/definitions/nonBodyParameter"
+        }
+      ]
+    },
+    "schema": {
+      "type": "object",
+      "description": "A deterministic version of a JSON Schema object.",
+      "patternProperties": {
+        "^x-": {
+          "$ref": "#/definitions/vendorExtension"
+        }
+      },
+      "properties": {
+        "$ref": {
+          "type": "string"
+        },
+        "format": {
+          "type": "string"
+        },
+        "title": {
+          "$ref": "http://json-schema.org/draft-04/schema#/properties/title"
+        },
+        "description": {
+          "$ref": "http://json-schema.org/draft-04/schema#/properties/description"
+        },
+        "default": {
+          "$ref": "http://json-schema.org/draft-04/schema#/properties/default"
+        },
+        "multipleOf": {
+          "$ref": "http://json-schema.org/draft-04/schema#/properties/multipleOf"
+        },
+        "maximum": {
+          "$ref": "http://json-schema.org/draft-04/schema#/properties/maximum"
+        },
+        "exclusiveMaximum": {
+          "$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMaximum"
+        },
+        "minimum": {
+          "$ref": "http://json-schema.org/draft-04/schema#/properties/minimum"
+        },
+        "exclusiveMinimum": {
+          "$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMinimum"
+        },
+        "maxLength": {
+          "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger"
+        },
+        "minLength": {
+          "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0"
+        },
+        "pattern": {
+          "$ref": "http://json-schema.org/draft-04/schema#/properties/pattern"
+        },
+        "maxItems": {
+          "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger"
+        },
+        "minItems": {
+          "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0"
+        },
+        "uniqueItems": {
+          "$ref": "http://json-schema.org/draft-04/schema#/properties/uniqueItems"
+        },
+        "maxProperties": {
+          "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger"
+        },
+        "minProperties": {
+          "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0"
+        },
+        "required": {
+          "$ref": "http://json-schema.org/draft-04/schema#/definitions/stringArray"
+        },
+        "enum": {
+          "$ref": "http://json-schema.org/draft-04/schema#/properties/enum"
+        },
+        "additionalProperties": {
+          "anyOf": [
+            {
+              "$ref": "#/definitions/schema"
+            },
+            {
+              "type": "boolean"
+            }
+          ],
+          "default": {}
+        },
+        "type": {
+          "$ref": "http://json-schema.org/draft-04/schema#/properties/type"
+        },
+        "items": {
+          "anyOf": [
+            {
+              "$ref": "#/definitions/schema"
+            },
+            {
+              "type": "array",
+              "minItems": 1,
+              "items": {
+                "$ref": "#/definitions/schema"
+              }
+            }
+          ],
+          "default": {}
+        },
+        "allOf": {
+          "type": "array",
+          "minItems": 1,
+          "items": {
+            "$ref": "#/definitions/schema"
+          }
+        },
+        "properties": {
+          "type": "object",
+          "additionalProperties": {
+            "$ref": "#/definitions/schema"
+          },
+          "default": {}
+        },
+        "discriminator": {
+          "type": "string"
+        },
+        "readOnly": {
+          "type": "boolean",
+          "default": false
+        },
+        "xml": {
+          "$ref": "#/definitions/xml"
+        },
+        "externalDocs": {
+          "$ref": "#/definitions/externalDocs"
+        },
+        "example": {}
+      },
+      "additionalProperties": false
+    },
+    "fileSchema": {
+      "type": "object",
+      "description": "A deterministic version of a JSON Schema object.",
+      "patternProperties": {
+        "^x-": {
+          "$ref": "#/definitions/vendorExtension"
+        }
+      },
+      "required": [
+        "type"
+      ],
+      "properties": {
+        "format": {
+          "type": "string"
+        },
+        "title": {
+          "$ref": "http://json-schema.org/draft-04/schema#/properties/title"
+        },
+        "description": {
+          "$ref": "http://json-schema.org/draft-04/schema#/properties/description"
+        },
+        "default": {
+          "$ref": "http://json-schema.org/draft-04/schema#/properties/default"
+        },
+        "required": {
+          "$ref": "http://json-schema.org/draft-04/schema#/definitions/stringArray"
+        },
+        "type": {
+          "type": "string",
+          "enum": [
+            "file"
+          ]
+        },
+        "readOnly": {
+          "type": "boolean",
+          "default": false
+        },
+        "externalDocs": {
+          "$ref": "#/definitions/externalDocs"
+        },
+        "example": {}
+      },
+      "additionalProperties": false
+    },
+    "primitivesItems": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "type": {
+          "type": "string",
+          "enum": [
+            "string",
+            "number",
+            "integer",
+            "boolean",
+            "array"
+          ]
+        },
+        "format": {
+          "type": "string"
+        },
+        "items": {
+          "$ref": "#/definitions/primitivesItems"
+        },
+        "collectionFormat": {
+          "$ref": "#/definitions/collectionFormat"
+        },
+        "default": {
+          "$ref": "#/definitions/default"
+        },
+        "maximum": {
+          "$ref": "#/definitions/maximum"
+        },
+        "exclusiveMaximum": {
+          "$ref": "#/definitions/exclusiveMaximum"
+        },
+        "minimum": {
+          "$ref": "#/definitions/minimum"
+        },
+        "exclusiveMinimum": {
+          "$ref": "#/definitions/exclusiveMinimum"
+        },
+        "maxLength": {
+          "$ref": "#/definitions/maxLength"
+        },
+        "minLength": {
+          "$ref": "#/definitions/minLength"
+        },
+        "pattern": {
+          "$ref": "#/definitions/pattern"
+        },
+        "maxItems": {
+          "$ref": "#/definitions/maxItems"
+        },
+        "minItems": {
+          "$ref": "#/definitions/minItems"
+        },
+        "uniqueItems": {
+          "$ref": "#/definitions/uniqueItems"
+        },
+        "enum": {
+          "$ref": "#/definitions/enum"
+        },
+        "multipleOf": {
+          "$ref": "#/definitions/multipleOf"
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+          "$ref": "#/definitions/vendorExtension"
+        }
+      }
+    },
+    "security": {
+      "type": "array",
+      "items": {
+        "$ref": "#/definitions/securityRequirement"
+      },
+      "uniqueItems": true
+    },
+    "securityRequirement": {
+      "type": "object",
+      "additionalProperties": {
+        "type": "array",
+        "items": {
+          "type": "string"
+        },
+        "uniqueItems": true
+      }
+    },
+    "xml": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "name": {
+          "type": "string"
+        },
+        "namespace": {
+          "type": "string"
+        },
+        "prefix": {
+          "type": "string"
+        },
+        "attribute": {
+          "type": "boolean",
+          "default": false
+        },
+        "wrapped": {
+          "type": "boolean",
+          "default": false
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+          "$ref": "#/definitions/vendorExtension"
+        }
+      }
+    },
+    "tag": {
+      "type": "object",
+      "additionalProperties": false,
+      "required": [
+        "name"
+      ],
+      "properties": {
+        "name": {
+          "type": "string"
+        },
+        "description": {
+          "type": "string"
+        },
+        "externalDocs": {
+          "$ref": "#/definitions/externalDocs"
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+          "$ref": "#/definitions/vendorExtension"
+        }
+      }
+    },
+    "securityDefinitions": {
+      "type": "object",
+      "additionalProperties": {
+        "oneOf": [
+          {
+            "$ref": "#/definitions/basicAuthenticationSecurity"
+          },
+          {
+            "$ref": "#/definitions/apiKeySecurity"
+          },
+          {
+            "$ref": "#/definitions/oauth2ImplicitSecurity"
+          },
+          {
+            "$ref": "#/definitions/oauth2PasswordSecurity"
+          },
+          {
+            "$ref": "#/definitions/oauth2ApplicationSecurity"
+          },
+          {
+            "$ref": "#/definitions/oauth2AccessCodeSecurity"
+          }
+        ]
+      }
+    },
+    "basicAuthenticationSecurity": {
+      "type": "object",
+      "additionalProperties": false,
+      "required": [
+        "type"
+      ],
+      "properties": {
+        "type": {
+          "type": "string",
+          "enum": [
+            "basic"
+          ]
+        },
+        "description": {
+          "type": "string"
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+          "$ref": "#/definitions/vendorExtension"
+        }
+      }
+    },
+    "apiKeySecurity": {
+      "type": "object",
+      "additionalProperties": false,
+      "required": [
+        "type",
+        "name",
+        "in"
+      ],
+      "properties": {
+        "type": {
+          "type": "string",
+          "enum": [
+            "apiKey"
+          ]
+        },
+        "name": {
+          "type": "string"
+        },
+        "in": {
+          "type": "string",
+          "enum": [
+            "header",
+            "query"
+          ]
+        },
+        "description": {
+          "type": "string"
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+          "$ref": "#/definitions/vendorExtension"
+        }
+      }
+    },
+    "oauth2ImplicitSecurity": {
+      "type": "object",
+      "additionalProperties": false,
+      "required": [
+        "type",
+        "flow",
+        "authorizationUrl"
+      ],
+      "properties": {
+        "type": {
+          "type": "string",
+          "enum": [
+            "oauth2"
+          ]
+        },
+        "flow": {
+          "type": "string",
+          "enum": [
+            "implicit"
+          ]
+        },
+        "scopes": {
+          "$ref": "#/definitions/oauth2Scopes"
+        },
+        "authorizationUrl": {
+          "type": "string",
+          "format": "uri"
+        },
+        "description": {
+          "type": "string"
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+          "$ref": "#/definitions/vendorExtension"
+        }
+      }
+    },
+    "oauth2PasswordSecurity": {
+      "type": "object",
+      "additionalProperties": false,
+      "required": [
+        "type",
+        "flow",
+        "tokenUrl"
+      ],
+      "properties": {
+        "type": {
+          "type": "string",
+          "enum": [
+            "oauth2"
+          ]
+        },
+        "flow": {
+          "type": "string",
+          "enum": [
+            "password"
+          ]
+        },
+        "scopes": {
+          "$ref": "#/definitions/oauth2Scopes"
+        },
+        "tokenUrl": {
+          "type": "string",
+          "format": "uri"
+        },
+        "description": {
+          "type": "string"
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+          "$ref": "#/definitions/vendorExtension"
+        }
+      }
+    },
+    "oauth2ApplicationSecurity": {
+      "type": "object",
+      "additionalProperties": false,
+      "required": [
+        "type",
+        "flow",
+        "tokenUrl"
+      ],
+      "properties": {
+        "type": {
+          "type": "string",
+          "enum": [
+            "oauth2"
+          ]
+        },
+        "flow": {
+          "type": "string",
+          "enum": [
+            "application"
+          ]
+        },
+        "scopes": {
+          "$ref": "#/definitions/oauth2Scopes"
+        },
+        "tokenUrl": {
+          "type": "string",
+          "format": "uri"
+        },
+        "description": {
+          "type": "string"
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+          "$ref": "#/definitions/vendorExtension"
+        }
+      }
+    },
+    "oauth2AccessCodeSecurity": {
+      "type": "object",
+      "additionalProperties": false,
+      "required": [
+        "type",
+        "flow",
+        "authorizationUrl",
+        "tokenUrl"
+      ],
+      "properties": {
+        "type": {
+          "type": "string",
+          "enum": [
+            "oauth2"
+          ]
+        },
+        "flow": {
+          "type": "string",
+          "enum": [
+            "accessCode"
+          ]
+        },
+        "scopes": {
+          "$ref": "#/definitions/oauth2Scopes"
+        },
+        "authorizationUrl": {
+          "type": "string",
+          "format": "uri"
+        },
+        "tokenUrl": {
+          "type": "string",
+          "format": "uri"
+        },
+        "description": {
+          "type": "string"
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+          "$ref": "#/definitions/vendorExtension"
+        }
+      }
+    },
+    "oauth2Scopes": {
+      "type": "object",
+      "additionalProperties": {
+        "type": "string"
+      }
+    },
+    "mediaTypeList": {
+      "type": "array",
+      "items": {
+        "$ref": "#/definitions/mimeType"
+      },
+      "uniqueItems": true
+    },
+    "parametersList": {
+      "type": "array",
+      "description": "The parameters needed to send a valid API call.",
+      "additionalItems": false,
+      "items": {
+        "oneOf": [
+          {
+            "$ref": "#/definitions/parameter"
+          },
+          {
+            "$ref": "#/definitions/jsonReference"
+          }
+        ]
+      },
+      "uniqueItems": true
+    },
+    "schemesList": {
+      "type": "array",
+      "description": "The transfer protocol of the API.",
+      "items": {
+        "type": "string",
+        "enum": [
+          "http",
+          "https",
+          "ws",
+          "wss"
+        ]
+      },
+      "uniqueItems": true
+    },
+    "collectionFormat": {
+      "type": "string",
+      "enum": [
+        "csv",
+        "ssv",
+        "tsv",
+        "pipes"
+      ],
+      "default": "csv"
+    },
+    "collectionFormatWithMulti": {
+      "type": "string",
+      "enum": [
+        "csv",
+        "ssv",
+        "tsv",
+        "pipes",
+        "multi"
+      ],
+      "default": "csv"
+    },
+    "title": {
+      "$ref": "http://json-schema.org/draft-04/schema#/properties/title"
+    },
+    "description": {
+      "$ref": "http://json-schema.org/draft-04/schema#/properties/description"
+    },
+    "default": {
+      "$ref": "http://json-schema.org/draft-04/schema#/properties/default"
+    },
+    "multipleOf": {
+      "$ref": "http://json-schema.org/draft-04/schema#/properties/multipleOf"
+    },
+    "maximum": {
+      "$ref": "http://json-schema.org/draft-04/schema#/properties/maximum"
+    },
+    "exclusiveMaximum": {
+      "$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMaximum"
+    },
+    "minimum": {
+      "$ref": "http://json-schema.org/draft-04/schema#/properties/minimum"
+    },
+    "exclusiveMinimum": {
+      "$ref": "http://json-schema.org/draft-04/schema#/properties/exclusiveMinimum"
+    },
+    "maxLength": {
+      "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger"
+    },
+    "minLength": {
+      "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0"
+    },
+    "pattern": {
+      "$ref": "http://json-schema.org/draft-04/schema#/properties/pattern"
+    },
+    "maxItems": {
+      "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveInteger"
+    },
+    "minItems": {
+      "$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0"
+    },
+    "uniqueItems": {
+      "$ref": "http://json-schema.org/draft-04/schema#/properties/uniqueItems"
+    },
+    "enum": {
+      "$ref": "http://json-schema.org/draft-04/schema#/properties/enum"
+    },
+    "jsonReference": {
+      "type": "object",
+      "required": [
+        "$ref"
+      ],
+      "additionalProperties": false,
+      "properties": {
+        "$ref": {
+          "type": "string",
+          "format": "uri-reference"
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/Constraint/Constraint.php b/swaggest/json-schema/src/Constraint/Constraint.php
new file mode 100644
index 0000000..da63e69
--- /dev/null
+++ b/swaggest/json-schema/src/Constraint/Constraint.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace Swaggest\JsonSchema\Constraint;
+
+interface Constraint
+{
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/Constraint/Content.php b/swaggest/json-schema/src/Constraint/Content.php
new file mode 100644
index 0000000..59d01f3
--- /dev/null
+++ b/swaggest/json-schema/src/Constraint/Content.php
@@ -0,0 +1,85 @@
+<?php
+
+namespace Swaggest\JsonSchema\Constraint;
+
+use Swaggest\JsonSchema\Context;
+use Swaggest\JsonSchema\Exception\ContentException;
+
+class Content
+{
+    const MEDIA_TYPE_APPLICATION_JSON = 'application/json';
+    const ENCODING_BASE64 = 'base64';
+
+    const BASE64_INVALID_REGEX = '_[^A-Za-z0-9+/=]+_';
+
+
+    /**
+     * @param Context $options
+     * @param string|null $encoding
+     * @param string|null $mediaType
+     * @param string $data
+     * @param bool $import
+     * @return bool|mixed|string
+     * @throws ContentException
+     */
+    public static function process(Context $options, $encoding, $mediaType, $data, $import = true)
+    {
+        if ($import) {
+            if ($encoding !== null) {
+                switch ($encoding) {
+                    case self::ENCODING_BASE64:
+                        if ($options->strictBase64Validation && preg_match(self::BASE64_INVALID_REGEX, $data)) {
+                            throw new ContentException('Invalid base64 string');
+                        }
+                        $data = base64_decode($data);
+                        if ($data === false && !$options->skipValidation) {
+                            throw new ContentException('Unable to decode base64');
+                        }
+                        break;
+                }
+            }
+
+            if ($mediaType !== null && $data !== false) {
+                switch ($mediaType) {
+                    case self::MEDIA_TYPE_APPLICATION_JSON:
+                        $data = json_decode($data);
+                        $lastErrorCode = json_last_error();
+                        if (($lastErrorCode !== JSON_ERROR_NONE) && !$options->skipValidation) {
+                            // TODO add readable error message
+                            throw new ContentException('Unable to decode json, err code: ' . $lastErrorCode);
+                        }
+                        break;
+
+                }
+            }
+
+            return $data;
+        } else {
+            // export
+
+            if ($mediaType !== null) {
+                switch ($mediaType) {
+                    case self::MEDIA_TYPE_APPLICATION_JSON:
+                        $data = json_encode($data);
+                        $lastErrorCode = json_last_error();
+                        if (($lastErrorCode !== JSON_ERROR_NONE) && !$options->skipValidation) {
+                            // TODO add readable error message
+                            throw new ContentException('Unable to encode json, err code: ' . $lastErrorCode);
+                        }
+                        break;
+                }
+            }
+
+            if ($encoding !== null && $data !== false) {
+                switch ($encoding) {
+                    case self::ENCODING_BASE64:
+                        $data = base64_encode($data);
+                        break;
+                }
+            }
+
+            return $data;
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/Constraint/Format.php b/swaggest/json-schema/src/Constraint/Format.php
new file mode 100644
index 0000000..7da4c2b
--- /dev/null
+++ b/swaggest/json-schema/src/Constraint/Format.php
@@ -0,0 +1,128 @@
+<?php
+
+namespace Swaggest\JsonSchema\Constraint;
+
+use Swaggest\JsonSchema\Constraint\Format\IdnHostname;
+use Swaggest\JsonSchema\Constraint\Format\Iri;
+use Swaggest\JsonSchema\Constraint\Format\Uri;
+
+class Format
+{
+    const DATE_TIME = 'date-time';
+    const DATE = 'date';
+    const TIME = 'time';
+    const URI = 'uri';
+    const IRI = 'iri';
+    const EMAIL = 'email';
+    const IDN_EMAIL = 'idn-email';
+    const IPV4 = 'ipv4';
+    const IPV6 = 'ipv6';
+    const HOSTNAME = 'hostname';
+    const IDN_HOSTNAME = 'idn-hostname';
+    const REGEX = 'regex';
+    const JSON_POINTER = 'json-pointer';
+    const RELATIVE_JSON_POINTER = 'relative-json-pointer';
+    const URI_REFERENCE = 'uri-reference';
+    const IRI_REFERENCE = 'iri-reference';
+    const URI_TEMPLATE = 'uri-template';
+
+    public static $strictDateTimeValidation = false;
+
+    private static $dateRegexPart = '(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])';
+    private static $timeRegexPart = '([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(Z|(\+|-)([01][0-9]|2[0-3]):?([0-5][0-9])?)?';
+    private static $jsonPointerRegex = '_^(?:/|(?:/[^/#]*)*)$_';
+    private static $jsonPointerRelativeRegex = '~^(0|[1-9][0-9]*)((?:/[^/#]*)*)(#?)$~';
+    private static $jsonPointerUnescapedTilde = '/~([^01]|$)/';
+
+    public static function validationError($format, $data)
+    {
+        switch ($format) {
+            case self::DATE_TIME:
+                return self::dateTimeError($data);
+            case self::DATE:
+                return preg_match('/^' . self::$dateRegexPart . '$/i', $data) ? null : 'Invalid date';
+            case self::TIME:
+                return preg_match('/^' . self::$timeRegexPart . '$/i', $data) ? null : 'Invalid time';
+            case self::URI:
+                return Uri::validationError($data, Uri::IS_SCHEME_REQUIRED);
+            case self::IRI:
+                return Iri::validationError($data);
+            case self::EMAIL:
+                return filter_var($data, FILTER_VALIDATE_EMAIL) ? null : 'Invalid email';
+            case self::IDN_EMAIL:
+                return count(explode('@', $data, 3)) === 2 ? null : 'Invalid email';
+            case self::IPV4:
+                return filter_var($data, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) ? null : 'Invalid ipv4';
+            case self::IPV6:
+                return filter_var($data, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) ? null : 'Invalid ipv6';
+            case self::HOSTNAME:
+                if (strlen(rtrim($data, '.')) >= 254) { // Not sure if it should be 254, higher number fails AJV suite.
+                    return 'Invalid hostname (too long)';
+                }
+                return preg_match(Uri::HOSTNAME_REGEX, $data) ? null : 'Invalid hostname';
+            case self::IDN_HOSTNAME:
+                return IdnHostname::validationError($data);
+            case self::REGEX:
+                return self::regexError($data);
+            case self::JSON_POINTER:
+                return self::jsonPointerError($data);
+            case self::RELATIVE_JSON_POINTER:
+                return self::jsonPointerError($data, true);
+            case self::URI_REFERENCE:
+                return Uri::validationError($data, Uri::IS_URI_REFERENCE);
+            case self::IRI_REFERENCE:
+                return Iri::validationError($data, Uri::IS_URI_REFERENCE);
+            case self::URI_TEMPLATE:
+                return Uri::validationError($data, Uri::IS_URI_TEMPLATE);
+        }
+        return null;
+    }
+
+    public static function dateTimeError($data)
+    {
+        if (!preg_match('/^' . self::$dateRegexPart . 'T' . self::$timeRegexPart . '$/i', $data)) {
+            return 'Invalid date-time format: ' . $data;
+        }
+
+        if (self::$strictDateTimeValidation) {
+            $dt = date_create($data);
+            if ($dt === false) {
+                return 'Failed to parse date-time: ' . $data;
+            }
+            $isLeapSecond = '6' === $data[17] && (
+                    0 === strpos(substr($data, 5, 5), '12-31') ||
+                    0 === strpos(substr($data, 5, 5), '06-30')
+                );
+            if (!$isLeapSecond &&
+                0 !== stripos($dt->format(DATE_RFC3339), substr($data, 0, 19))) {
+                return 'Invalid date-time value: ' . $data;
+            }
+        }
+
+        return null;
+    }
+
+    public static function regexError($data)
+    {
+        if (substr($data, -2) === '\Z') {
+            return 'Invalid regex: \Z is not supported';
+        }
+        if (substr($data, 0, 2) === '\A') {
+            return 'Invalid regex: \A is not supported';
+        }
+
+        return @preg_match('{' . $data . '}', '') === false ? 'Invalid regex: ' . $data : null;
+    }
+
+    public static function jsonPointerError($data, $isRelative = false)
+    {
+        if (preg_match(self::$jsonPointerUnescapedTilde, $data)) {
+            return 'Invalid json-pointer: unescaped ~';
+        }
+        if ($isRelative) {
+            return preg_match(self::$jsonPointerRelativeRegex, $data) ? null : 'Invalid relative json-pointer';
+        } else {
+            return preg_match(self::$jsonPointerRegex, $data) ? null : 'Invalid json-pointer';
+        }
+    }
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/Constraint/Format/IdnHostname.php b/swaggest/json-schema/src/Constraint/Format/IdnHostname.php
new file mode 100644
index 0000000..3c96975
--- /dev/null
+++ b/swaggest/json-schema/src/Constraint/Format/IdnHostname.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Swaggest\JsonSchema\Constraint\Format;
+
+class IdnHostname
+{
+    /**
+     * @see http://www.unicode.org/faq/idn.html
+     * @see https://gist.github.com/rxu/0660eef7a2f9e7992db6
+     * @param string $data
+     * @return null|string
+     */
+    public static function validationError($data)
+    {
+        $error = Iri::unicodeValidationError($data, $sanitized);
+        if ($error !== null) {
+            return $error;
+        }
+        return preg_match(Uri::HOSTNAME_REGEX, $sanitized) ? null : 'Invalid idn-hostname: ' . $data;
+    }
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/Constraint/Format/Iri.php b/swaggest/json-schema/src/Constraint/Format/Iri.php
new file mode 100644
index 0000000..646be1a
--- /dev/null
+++ b/swaggest/json-schema/src/Constraint/Format/Iri.php
@@ -0,0 +1,703 @@
+<?php
+
+namespace Swaggest\JsonSchema\Constraint\Format;
+
+class Iri
+{
+
+    /**
+     * @param string $data
+     * @param int $options
+     * @return null|string
+     */
+    public static function validationError($data, $options = Uri::IS_SCHEME_REQUIRED)
+    {
+        $error = Iri::unicodeValidationError($data, $sanitized);
+        if ($error !== null) {
+            return $error;
+        }
+
+        return Uri::validationError($sanitized, $options);
+    }
+
+
+    /**
+     * @see http://www.unicode.org/faq/idn.html
+     * @see https://gist.github.com/rxu/0660eef7a2f9e7992db6
+     * @param string $data
+     * @param string $sanitized
+     * @return null|string
+     */
+    public static function unicodeValidationError($data, &$sanitized = null)
+    {
+        static $excluded = array(
+            "\xD9\x80" => '\u0640',
+            "\xDF\xBA" => '\u07FA',
+            "\xE3\x80\xAE" => '\u302E',
+            "\xE3\x80\xAF" => '\u302F',
+            "\xE3\x80\xB1" => '\u3031',
+            "\xE3\x80\xB2" => '\u3032',
+            "\xE3\x80\xB3" => '\u3033',
+            "\xE3\x80\xB4" => '\u3034',
+            "\xE3\x80\xB5" => '\u3035',
+            "\xE3\x80\xBB" => '\u303B',
+
+            // Remove characters used for archaic Hangul (Korean) - \p{HST=L} and \p{HST=V}
+            // as per http://unicode.org/Public/UNIDATA/HangulSyllableType.txt
+            "\xE1\x84\x80" => '\u1100',
+            "\xE1\x84\x81" => '\u1101',
+            "\xE1\x84\x82" => '\u1102',
+            "\xE1\x84\x83" => '\u1103',
+            "\xE1\x84\x84" => '\u1104',
+            "\xE1\x84\x85" => '\u1105',
+            "\xE1\x84\x86" => '\u1106',
+            "\xE1\x84\x87" => '\u1107',
+            "\xE1\x84\x88" => '\u1108',
+            "\xE1\x84\x89" => '\u1109',
+            "\xE1\x84\x8A" => '\u110a',
+            "\xE1\x84\x8B" => '\u110b',
+            "\xE1\x84\x8C" => '\u110c',
+            "\xE1\x84\x8D" => '\u110d',
+            "\xE1\x84\x8E" => '\u110e',
+            "\xE1\x84\x8F" => '\u110f',
+            "\xE1\x84\x90" => '\u1110',
+            "\xE1\x84\x91" => '\u1111',
+            "\xE1\x84\x92" => '\u1112',
+            "\xE1\x84\x93" => '\u1113',
+            "\xE1\x84\x94" => '\u1114',
+            "\xE1\x84\x95" => '\u1115',
+            "\xE1\x84\x96" => '\u1116',
+            "\xE1\x84\x97" => '\u1117',
+            "\xE1\x84\x98" => '\u1118',
+            "\xE1\x84\x99" => '\u1119',
+            "\xE1\x84\x9A" => '\u111a',
+            "\xE1\x84\x9B" => '\u111b',
+            "\xE1\x84\x9C" => '\u111c',
+            "\xE1\x84\x9D" => '\u111d',
+            "\xE1\x84\x9E" => '\u111e',
+            "\xE1\x84\x9F" => '\u111f',
+            "\xE1\x84\xA0" => '\u1120',
+            "\xE1\x84\xA1" => '\u1121',
+            "\xE1\x84\xA2" => '\u1122',
+            "\xE1\x84\xA3" => '\u1123',
+            "\xE1\x84\xA4" => '\u1124',
+            "\xE1\x84\xA5" => '\u1125',
+            "\xE1\x84\xA6" => '\u1126',
+            "\xE1\x84\xA7" => '\u1127',
+            "\xE1\x84\xA8" => '\u1128',
+            "\xE1\x84\xA9" => '\u1129',
+            "\xE1\x84\xAA" => '\u112a',
+            "\xE1\x84\xAB" => '\u112b',
+            "\xE1\x84\xAC" => '\u112c',
+            "\xE1\x84\xAD" => '\u112d',
+            "\xE1\x84\xAE" => '\u112e',
+            "\xE1\x84\xAF" => '\u112f',
+            "\xE1\x84\xB0" => '\u1130',
+            "\xE1\x84\xB1" => '\u1131',
+            "\xE1\x84\xB2" => '\u1132',
+            "\xE1\x84\xB3" => '\u1133',
+            "\xE1\x84\xB4" => '\u1134',
+            "\xE1\x84\xB5" => '\u1135',
+            "\xE1\x84\xB6" => '\u1136',
+            "\xE1\x84\xB7" => '\u1137',
+            "\xE1\x84\xB8" => '\u1138',
+            "\xE1\x84\xB9" => '\u1139',
+            "\xE1\x84\xBA" => '\u113a',
+            "\xE1\x84\xBB" => '\u113b',
+            "\xE1\x84\xBC" => '\u113c',
+            "\xE1\x84\xBD" => '\u113d',
+            "\xE1\x84\xBE" => '\u113e',
+            "\xE1\x84\xBF" => '\u113f',
+            "\xE1\x85\x80" => '\u1140',
+            "\xE1\x85\x81" => '\u1141',
+            "\xE1\x85\x82" => '\u1142',
+            "\xE1\x85\x83" => '\u1143',
+            "\xE1\x85\x84" => '\u1144',
+            "\xE1\x85\x85" => '\u1145',
+            "\xE1\x85\x86" => '\u1146',
+            "\xE1\x85\x87" => '\u1147',
+            "\xE1\x85\x88" => '\u1148',
+            "\xE1\x85\x89" => '\u1149',
+            "\xE1\x85\x8A" => '\u114a',
+            "\xE1\x85\x8B" => '\u114b',
+            "\xE1\x85\x8C" => '\u114c',
+            "\xE1\x85\x8D" => '\u114d',
+            "\xE1\x85\x8E" => '\u114e',
+            "\xE1\x85\x8F" => '\u114f',
+            "\xE1\x85\x90" => '\u1150',
+            "\xE1\x85\x91" => '\u1151',
+            "\xE1\x85\x92" => '\u1152',
+            "\xE1\x85\x93" => '\u1153',
+            "\xE1\x85\x94" => '\u1154',
+            "\xE1\x85\x95" => '\u1155',
+            "\xE1\x85\x96" => '\u1156',
+            "\xE1\x85\x97" => '\u1157',
+            "\xE1\x85\x98" => '\u1158',
+            "\xE1\x85\x99" => '\u1159',
+            "\xE1\x85\x9A" => '\u115a',
+            "\xE1\x85\x9B" => '\u115b',
+            "\xE1\x85\x9C" => '\u115c',
+            "\xE1\x85\x9D" => '\u115d',
+            "\xE1\x85\x9E" => '\u115e',
+            "\xE1\x85\x9F" => '\u115f',
+            "\xEA\xA5\xA0" => '\ua960',
+            "\xEA\xA5\xA1" => '\ua961',
+            "\xEA\xA5\xA2" => '\ua962',
+            "\xEA\xA5\xA3" => '\ua963',
+            "\xEA\xA5\xA4" => '\ua964',
+            "\xEA\xA5\xA5" => '\ua965',
+            "\xEA\xA5\xA6" => '\ua966',
+            "\xEA\xA5\xA7" => '\ua967',
+            "\xEA\xA5\xA8" => '\ua968',
+            "\xEA\xA5\xA9" => '\ua969',
+            "\xEA\xA5\xAA" => '\ua96a',
+            "\xEA\xA5\xAB" => '\ua96b',
+            "\xEA\xA5\xAC" => '\ua96c',
+            "\xEA\xA5\xAD" => '\ua96d',
+            "\xEA\xA5\xAE" => '\ua96e',
+            "\xEA\xA5\xAF" => '\ua96f',
+            "\xEA\xA5\xB0" => '\ua970',
+            "\xEA\xA5\xB1" => '\ua971',
+            "\xEA\xA5\xB2" => '\ua972',
+            "\xEA\xA5\xB3" => '\ua973',
+            "\xEA\xA5\xB4" => '\ua974',
+            "\xEA\xA5\xB5" => '\ua975',
+            "\xEA\xA5\xB6" => '\ua976',
+            "\xEA\xA5\xB7" => '\ua977',
+            "\xEA\xA5\xB8" => '\ua978',
+            "\xEA\xA5\xB9" => '\ua979',
+            "\xEA\xA5\xBA" => '\ua97a',
+            "\xEA\xA5\xBB" => '\ua97b',
+            "\xEA\xA5\xBC" => '\ua97c',
+            "\xE1\x85\xA0" => '\u1160',
+            "\xE1\x85\xA1" => '\u1161',
+            "\xE1\x85\xA2" => '\u1162',
+            "\xE1\x85\xA3" => '\u1163',
+            "\xE1\x85\xA4" => '\u1164',
+            "\xE1\x85\xA5" => '\u1165',
+            "\xE1\x85\xA6" => '\u1166',
+            "\xE1\x85\xA7" => '\u1167',
+            "\xE1\x85\xA8" => '\u1168',
+            "\xE1\x85\xA9" => '\u1169',
+            "\xE1\x85\xAA" => '\u116a',
+            "\xE1\x85\xAB" => '\u116b',
+            "\xE1\x85\xAC" => '\u116c',
+            "\xE1\x85\xAD" => '\u116d',
+            "\xE1\x85\xAE" => '\u116e',
+            "\xE1\x85\xAF" => '\u116f',
+            "\xE1\x85\xB0" => '\u1170',
+            "\xE1\x85\xB1" => '\u1171',
+            "\xE1\x85\xB2" => '\u1172',
+            "\xE1\x85\xB3" => '\u1173',
+            "\xE1\x85\xB4" => '\u1174',
+            "\xE1\x85\xB5" => '\u1175',
+            "\xE1\x85\xB6" => '\u1176',
+            "\xE1\x85\xB7" => '\u1177',
+            "\xE1\x85\xB8" => '\u1178',
+            "\xE1\x85\xB9" => '\u1179',
+            "\xE1\x85\xBA" => '\u117a',
+            "\xE1\x85\xBB" => '\u117b',
+            "\xE1\x85\xBC" => '\u117c',
+            "\xE1\x85\xBD" => '\u117d',
+            "\xE1\x85\xBE" => '\u117e',
+            "\xE1\x85\xBF" => '\u117f',
+            "\xE1\x86\x80" => '\u1180',
+            "\xE1\x86\x81" => '\u1181',
+            "\xE1\x86\x82" => '\u1182',
+            "\xE1\x86\x83" => '\u1183',
+            "\xE1\x86\x84" => '\u1184',
+            "\xE1\x86\x85" => '\u1185',
+            "\xE1\x86\x86" => '\u1186',
+            "\xE1\x86\x87" => '\u1187',
+            "\xE1\x86\x88" => '\u1188',
+            "\xE1\x86\x89" => '\u1189',
+            "\xE1\x86\x8A" => '\u118a',
+            "\xE1\x86\x8B" => '\u118b',
+            "\xE1\x86\x8C" => '\u118c',
+            "\xE1\x86\x8D" => '\u118d',
+            "\xE1\x86\x8E" => '\u118e',
+            "\xE1\x86\x8F" => '\u118f',
+            "\xE1\x86\x90" => '\u1190',
+            "\xE1\x86\x91" => '\u1191',
+            "\xE1\x86\x92" => '\u1192',
+            "\xE1\x86\x93" => '\u1193',
+            "\xE1\x86\x94" => '\u1194',
+            "\xE1\x86\x95" => '\u1195',
+            "\xE1\x86\x96" => '\u1196',
+            "\xE1\x86\x97" => '\u1197',
+            "\xE1\x86\x98" => '\u1198',
+            "\xE1\x86\x99" => '\u1199',
+            "\xE1\x86\x9A" => '\u119a',
+            "\xE1\x86\x9B" => '\u119b',
+            "\xE1\x86\x9C" => '\u119c',
+            "\xE1\x86\x9D" => '\u119d',
+            "\xE1\x86\x9E" => '\u119e',
+            "\xE1\x86\x9F" => '\u119f',
+            "\xE1\x86\xA0" => '\u11a0',
+            "\xE1\x86\xA1" => '\u11a1',
+            "\xE1\x86\xA2" => '\u11a2',
+            "\xE1\x86\xA3" => '\u11a3',
+            "\xE1\x86\xA4" => '\u11a4',
+            "\xE1\x86\xA5" => '\u11a5',
+            "\xE1\x86\xA6" => '\u11a6',
+            "\xE1\x86\xA7" => '\u11a7',
+            "\xED\x9E\xB0" => '\ud7b0',
+            "\xED\x9E\xB1" => '\ud7b1',
+            "\xED\x9E\xB2" => '\ud7b2',
+            "\xED\x9E\xB3" => '\ud7b3',
+            "\xED\x9E\xB4" => '\ud7b4',
+            "\xED\x9E\xB5" => '\ud7b5',
+            "\xED\x9E\xB6" => '\ud7b6',
+            "\xED\x9E\xB7" => '\ud7b7',
+            "\xED\x9E\xB8" => '\ud7b8',
+            "\xED\x9E\xB9" => '\ud7b9',
+            "\xED\x9E\xBA" => '\ud7ba',
+            "\xED\x9E\xBB" => '\ud7bb',
+            "\xED\x9E\xBC" => '\ud7bc',
+            "\xED\x9E\xBD" => '\ud7bd',
+            "\xED\x9E\xBE" => '\ud7be',
+            "\xED\x9E\xBF" => '\ud7bf',
+            "\xED\x9F\x80" => '\ud7c0',
+            "\xED\x9F\x81" => '\ud7c1',
+            "\xED\x9F\x82" => '\ud7c2',
+            "\xED\x9F\x83" => '\ud7c3',
+            "\xED\x9F\x84" => '\ud7c4',
+            "\xED\x9F\x85" => '\ud7c5',
+            "\xED\x9F\x86" => '\ud7c6',
+
+            // Remove three blocks of technical or archaic symbols.
+            // \p{block=Combining_Diacritical_Marks_For_Symbols}
+            "\xE2\x83\x90" => '\u20d0',
+            "\xE2\x83\x91" => '\u20d1',
+            "\xE2\x83\x92" => '\u20d2',
+            "\xE2\x83\x93" => '\u20d3',
+            "\xE2\x83\x94" => '\u20d4',
+            "\xE2\x83\x95" => '\u20d5',
+            "\xE2\x83\x96" => '\u20d6',
+            "\xE2\x83\x97" => '\u20d7',
+            "\xE2\x83\x98" => '\u20d8',
+            "\xE2\x83\x99" => '\u20d9',
+            "\xE2\x83\x9A" => '\u20da',
+            "\xE2\x83\x9B" => '\u20db',
+            "\xE2\x83\x9C" => '\u20dc',
+            "\xE2\x83\x9D" => '\u20dd',
+            "\xE2\x83\x9E" => '\u20de',
+            "\xE2\x83\x9F" => '\u20df',
+            "\xE2\x83\xA0" => '\u20e0',
+            "\xE2\x83\xA1" => '\u20e1',
+            "\xE2\x83\xA2" => '\u20e2',
+            "\xE2\x83\xA3" => '\u20e3',
+            "\xE2\x83\xA4" => '\u20e4',
+            "\xE2\x83\xA5" => '\u20e5',
+            "\xE2\x83\xA6" => '\u20e6',
+            "\xE2\x83\xA7" => '\u20e7',
+            "\xE2\x83\xA8" => '\u20e8',
+            "\xE2\x83\xA9" => '\u20e9',
+            "\xE2\x83\xAA" => '\u20ea',
+            "\xE2\x83\xAB" => '\u20eb',
+            "\xE2\x83\xAC" => '\u20ec',
+            "\xE2\x83\xAD" => '\u20ed',
+            "\xE2\x83\xAE" => '\u20ee',
+            "\xE2\x83\xAF" => '\u20ef',
+            "\xE2\x83\xB0" => '\u20f0',
+            "\xE2\x83\xB1" => '\u20f1',
+            "\xE2\x83\xB2" => '\u20f2',
+            "\xE2\x83\xB3" => '\u20f3',
+            "\xE2\x83\xB4" => '\u20f4',
+            "\xE2\x83\xB5" => '\u20f5',
+            "\xE2\x83\xB6" => '\u20f6',
+            "\xE2\x83\xB7" => '\u20f7',
+            "\xE2\x83\xB8" => '\u20f8',
+            "\xE2\x83\xB9" => '\u20f9',
+            "\xE2\x83\xBA" => '\u20fa',
+            "\xE2\x83\xBB" => '\u20fb',
+            "\xE2\x83\xBC" => '\u20fc',
+            "\xE2\x83\xBD" => '\u20fd',
+            "\xE2\x83\xBE" => '\u20fe',
+            "\xE2\x83\xBF" => '\u20ff',
+
+            // \p{block=Musical_Symbols}
+            "\xE1\xB4\x90\x30" => '\u1d100',
+            "\xE1\xB4\x90\x31" => '\u1d101',
+            "\xE1\xB4\x90\x32" => '\u1d102',
+            "\xE1\xB4\x90\x33" => '\u1d103',
+            "\xE1\xB4\x90\x34" => '\u1d104',
+            "\xE1\xB4\x90\x35" => '\u1d105',
+            "\xE1\xB4\x90\x36" => '\u1d106',
+            "\xE1\xB4\x90\x37" => '\u1d107',
+            "\xE1\xB4\x90\x38" => '\u1d108',
+            "\xE1\xB4\x90\x39" => '\u1d109',
+            "\xE1\xB4\x90\x61" => '\u1d10a',
+            "\xE1\xB4\x90\x62" => '\u1d10b',
+            "\xE1\xB4\x90\x63" => '\u1d10c',
+            "\xE1\xB4\x90\x64" => '\u1d10d',
+            "\xE1\xB4\x90\x65" => '\u1d10e',
+            "\xE1\xB4\x90\x66" => '\u1d10f',
+            "\xE1\xB4\x91\x30" => '\u1d110',
+            "\xE1\xB4\x91\x31" => '\u1d111',
+            "\xE1\xB4\x91\x32" => '\u1d112',
+            "\xE1\xB4\x91\x33" => '\u1d113',
+            "\xE1\xB4\x91\x34" => '\u1d114',
+            "\xE1\xB4\x91\x35" => '\u1d115',
+            "\xE1\xB4\x91\x36" => '\u1d116',
+            "\xE1\xB4\x91\x37" => '\u1d117',
+            "\xE1\xB4\x91\x38" => '\u1d118',
+            "\xE1\xB4\x91\x39" => '\u1d119',
+            "\xE1\xB4\x91\x61" => '\u1d11a',
+            "\xE1\xB4\x91\x62" => '\u1d11b',
+            "\xE1\xB4\x91\x63" => '\u1d11c',
+            "\xE1\xB4\x91\x64" => '\u1d11d',
+            "\xE1\xB4\x91\x65" => '\u1d11e',
+            "\xE1\xB4\x91\x66" => '\u1d11f',
+            "\xE1\xB4\x92\x30" => '\u1d120',
+            "\xE1\xB4\x92\x31" => '\u1d121',
+            "\xE1\xB4\x92\x32" => '\u1d122',
+            "\xE1\xB4\x92\x33" => '\u1d123',
+            "\xE1\xB4\x92\x34" => '\u1d124',
+            "\xE1\xB4\x92\x35" => '\u1d125',
+            "\xE1\xB4\x92\x36" => '\u1d126',
+            "\xE1\xB4\x92\x37" => '\u1d127',
+            "\xE1\xB4\x92\x38" => '\u1d128',
+            "\xE1\xB4\x92\x39" => '\u1d129',
+            "\xE1\xB4\x92\x61" => '\u1d12a',
+            "\xE1\xB4\x92\x62" => '\u1d12b',
+            "\xE1\xB4\x92\x63" => '\u1d12c',
+            "\xE1\xB4\x92\x64" => '\u1d12d',
+            "\xE1\xB4\x92\x65" => '\u1d12e',
+            "\xE1\xB4\x92\x66" => '\u1d12f',
+            "\xE1\xB4\x93\x30" => '\u1d130',
+            "\xE1\xB4\x93\x31" => '\u1d131',
+            "\xE1\xB4\x93\x32" => '\u1d132',
+            "\xE1\xB4\x93\x33" => '\u1d133',
+            "\xE1\xB4\x93\x34" => '\u1d134',
+            "\xE1\xB4\x93\x35" => '\u1d135',
+            "\xE1\xB4\x93\x36" => '\u1d136',
+            "\xE1\xB4\x93\x37" => '\u1d137',
+            "\xE1\xB4\x93\x38" => '\u1d138',
+            "\xE1\xB4\x93\x39" => '\u1d139',
+            "\xE1\xB4\x93\x61" => '\u1d13a',
+            "\xE1\xB4\x93\x62" => '\u1d13b',
+            "\xE1\xB4\x93\x63" => '\u1d13c',
+            "\xE1\xB4\x93\x64" => '\u1d13d',
+            "\xE1\xB4\x93\x65" => '\u1d13e',
+            "\xE1\xB4\x93\x66" => '\u1d13f',
+            "\xE1\xB4\x94\x30" => '\u1d140',
+            "\xE1\xB4\x94\x31" => '\u1d141',
+            "\xE1\xB4\x94\x32" => '\u1d142',
+            "\xE1\xB4\x94\x33" => '\u1d143',
+            "\xE1\xB4\x94\x34" => '\u1d144',
+            "\xE1\xB4\x94\x35" => '\u1d145',
+            "\xE1\xB4\x94\x36" => '\u1d146',
+            "\xE1\xB4\x94\x37" => '\u1d147',
+            "\xE1\xB4\x94\x38" => '\u1d148',
+            "\xE1\xB4\x94\x39" => '\u1d149',
+            "\xE1\xB4\x94\x61" => '\u1d14a',
+            "\xE1\xB4\x94\x62" => '\u1d14b',
+            "\xE1\xB4\x94\x63" => '\u1d14c',
+            "\xE1\xB4\x94\x64" => '\u1d14d',
+            "\xE1\xB4\x94\x65" => '\u1d14e',
+            "\xE1\xB4\x94\x66" => '\u1d14f',
+            "\xE1\xB4\x95\x30" => '\u1d150',
+            "\xE1\xB4\x95\x31" => '\u1d151',
+            "\xE1\xB4\x95\x32" => '\u1d152',
+            "\xE1\xB4\x95\x33" => '\u1d153',
+            "\xE1\xB4\x95\x34" => '\u1d154',
+            "\xE1\xB4\x95\x35" => '\u1d155',
+            "\xE1\xB4\x95\x36" => '\u1d156',
+            "\xE1\xB4\x95\x37" => '\u1d157',
+            "\xE1\xB4\x95\x38" => '\u1d158',
+            "\xE1\xB4\x95\x39" => '\u1d159',
+            "\xE1\xB4\x95\x61" => '\u1d15a',
+            "\xE1\xB4\x95\x62" => '\u1d15b',
+            "\xE1\xB4\x95\x63" => '\u1d15c',
+            "\xE1\xB4\x95\x64" => '\u1d15d',
+            "\xE1\xB4\x95\x65" => '\u1d15e',
+            "\xE1\xB4\x95\x66" => '\u1d15f',
+            "\xE1\xB4\x96\x30" => '\u1d160',
+            "\xE1\xB4\x96\x31" => '\u1d161',
+            "\xE1\xB4\x96\x32" => '\u1d162',
+            "\xE1\xB4\x96\x33" => '\u1d163',
+            "\xE1\xB4\x96\x34" => '\u1d164',
+            "\xE1\xB4\x96\x35" => '\u1d165',
+            "\xE1\xB4\x96\x36" => '\u1d166',
+            "\xE1\xB4\x96\x37" => '\u1d167',
+            "\xE1\xB4\x96\x38" => '\u1d168',
+            "\xE1\xB4\x96\x39" => '\u1d169',
+            "\xE1\xB4\x96\x61" => '\u1d16a',
+            "\xE1\xB4\x96\x62" => '\u1d16b',
+            "\xE1\xB4\x96\x63" => '\u1d16c',
+            "\xE1\xB4\x96\x64" => '\u1d16d',
+            "\xE1\xB4\x96\x65" => '\u1d16e',
+            "\xE1\xB4\x96\x66" => '\u1d16f',
+            "\xE1\xB4\x97\x30" => '\u1d170',
+            "\xE1\xB4\x97\x31" => '\u1d171',
+            "\xE1\xB4\x97\x32" => '\u1d172',
+            "\xE1\xB4\x97\x33" => '\u1d173',
+            "\xE1\xB4\x97\x34" => '\u1d174',
+            "\xE1\xB4\x97\x35" => '\u1d175',
+            "\xE1\xB4\x97\x36" => '\u1d176',
+            "\xE1\xB4\x97\x37" => '\u1d177',
+            "\xE1\xB4\x97\x38" => '\u1d178',
+            "\xE1\xB4\x97\x39" => '\u1d179',
+            "\xE1\xB4\x97\x61" => '\u1d17a',
+            "\xE1\xB4\x97\x62" => '\u1d17b',
+            "\xE1\xB4\x97\x63" => '\u1d17c',
+            "\xE1\xB4\x97\x64" => '\u1d17d',
+            "\xE1\xB4\x97\x65" => '\u1d17e',
+            "\xE1\xB4\x97\x66" => '\u1d17f',
+            "\xE1\xB4\x98\x30" => '\u1d180',
+            "\xE1\xB4\x98\x31" => '\u1d181',
+            "\xE1\xB4\x98\x32" => '\u1d182',
+            "\xE1\xB4\x98\x33" => '\u1d183',
+            "\xE1\xB4\x98\x34" => '\u1d184',
+            "\xE1\xB4\x98\x35" => '\u1d185',
+            "\xE1\xB4\x98\x36" => '\u1d186',
+            "\xE1\xB4\x98\x37" => '\u1d187',
+            "\xE1\xB4\x98\x38" => '\u1d188',
+            "\xE1\xB4\x98\x39" => '\u1d189',
+            "\xE1\xB4\x98\x61" => '\u1d18a',
+            "\xE1\xB4\x98\x62" => '\u1d18b',
+            "\xE1\xB4\x98\x63" => '\u1d18c',
+            "\xE1\xB4\x98\x64" => '\u1d18d',
+            "\xE1\xB4\x98\x65" => '\u1d18e',
+            "\xE1\xB4\x98\x66" => '\u1d18f',
+            "\xE1\xB4\x99\x30" => '\u1d190',
+            "\xE1\xB4\x99\x31" => '\u1d191',
+            "\xE1\xB4\x99\x32" => '\u1d192',
+            "\xE1\xB4\x99\x33" => '\u1d193',
+            "\xE1\xB4\x99\x34" => '\u1d194',
+            "\xE1\xB4\x99\x35" => '\u1d195',
+            "\xE1\xB4\x99\x36" => '\u1d196',
+            "\xE1\xB4\x99\x37" => '\u1d197',
+            "\xE1\xB4\x99\x38" => '\u1d198',
+            "\xE1\xB4\x99\x39" => '\u1d199',
+            "\xE1\xB4\x99\x61" => '\u1d19a',
+            "\xE1\xB4\x99\x62" => '\u1d19b',
+            "\xE1\xB4\x99\x63" => '\u1d19c',
+            "\xE1\xB4\x99\x64" => '\u1d19d',
+            "\xE1\xB4\x99\x65" => '\u1d19e',
+            "\xE1\xB4\x99\x66" => '\u1d19f',
+            "\xE1\xB4\x9A\x30" => '\u1d1a0',
+            "\xE1\xB4\x9A\x31" => '\u1d1a1',
+            "\xE1\xB4\x9A\x32" => '\u1d1a2',
+            "\xE1\xB4\x9A\x33" => '\u1d1a3',
+            "\xE1\xB4\x9A\x34" => '\u1d1a4',
+            "\xE1\xB4\x9A\x35" => '\u1d1a5',
+            "\xE1\xB4\x9A\x36" => '\u1d1a6',
+            "\xE1\xB4\x9A\x37" => '\u1d1a7',
+            "\xE1\xB4\x9A\x38" => '\u1d1a8',
+            "\xE1\xB4\x9A\x39" => '\u1d1a9',
+            "\xE1\xB4\x9A\x61" => '\u1d1aa',
+            "\xE1\xB4\x9A\x62" => '\u1d1ab',
+            "\xE1\xB4\x9A\x63" => '\u1d1ac',
+            "\xE1\xB4\x9A\x64" => '\u1d1ad',
+            "\xE1\xB4\x9A\x65" => '\u1d1ae',
+            "\xE1\xB4\x9A\x66" => '\u1d1af',
+            "\xE1\xB4\x9B\x30" => '\u1d1b0',
+            "\xE1\xB4\x9B\x31" => '\u1d1b1',
+            "\xE1\xB4\x9B\x32" => '\u1d1b2',
+            "\xE1\xB4\x9B\x33" => '\u1d1b3',
+            "\xE1\xB4\x9B\x34" => '\u1d1b4',
+            "\xE1\xB4\x9B\x35" => '\u1d1b5',
+            "\xE1\xB4\x9B\x36" => '\u1d1b6',
+            "\xE1\xB4\x9B\x37" => '\u1d1b7',
+            "\xE1\xB4\x9B\x38" => '\u1d1b8',
+            "\xE1\xB4\x9B\x39" => '\u1d1b9',
+            "\xE1\xB4\x9B\x61" => '\u1d1ba',
+            "\xE1\xB4\x9B\x62" => '\u1d1bb',
+            "\xE1\xB4\x9B\x63" => '\u1d1bc',
+            "\xE1\xB4\x9B\x64" => '\u1d1bd',
+            "\xE1\xB4\x9B\x65" => '\u1d1be',
+            "\xE1\xB4\x9B\x66" => '\u1d1bf',
+            "\xE1\xB4\x9C\x30" => '\u1d1c0',
+            "\xE1\xB4\x9C\x31" => '\u1d1c1',
+            "\xE1\xB4\x9C\x32" => '\u1d1c2',
+            "\xE1\xB4\x9C\x33" => '\u1d1c3',
+            "\xE1\xB4\x9C\x34" => '\u1d1c4',
+            "\xE1\xB4\x9C\x35" => '\u1d1c5',
+            "\xE1\xB4\x9C\x36" => '\u1d1c6',
+            "\xE1\xB4\x9C\x37" => '\u1d1c7',
+            "\xE1\xB4\x9C\x38" => '\u1d1c8',
+            "\xE1\xB4\x9C\x39" => '\u1d1c9',
+            "\xE1\xB4\x9C\x61" => '\u1d1ca',
+            "\xE1\xB4\x9C\x62" => '\u1d1cb',
+            "\xE1\xB4\x9C\x63" => '\u1d1cc',
+            "\xE1\xB4\x9C\x64" => '\u1d1cd',
+            "\xE1\xB4\x9C\x65" => '\u1d1ce',
+            "\xE1\xB4\x9C\x66" => '\u1d1cf',
+            "\xE1\xB4\x9D\x30" => '\u1d1d0',
+            "\xE1\xB4\x9D\x31" => '\u1d1d1',
+            "\xE1\xB4\x9D\x32" => '\u1d1d2',
+            "\xE1\xB4\x9D\x33" => '\u1d1d3',
+            "\xE1\xB4\x9D\x34" => '\u1d1d4',
+            "\xE1\xB4\x9D\x35" => '\u1d1d5',
+            "\xE1\xB4\x9D\x36" => '\u1d1d6',
+            "\xE1\xB4\x9D\x37" => '\u1d1d7',
+            "\xE1\xB4\x9D\x38" => '\u1d1d8',
+            "\xE1\xB4\x9D\x39" => '\u1d1d9',
+            "\xE1\xB4\x9D\x61" => '\u1d1da',
+            "\xE1\xB4\x9D\x62" => '\u1d1db',
+            "\xE1\xB4\x9D\x63" => '\u1d1dc',
+            "\xE1\xB4\x9D\x64" => '\u1d1dd',
+            "\xE1\xB4\x9D\x65" => '\u1d1de',
+            "\xE1\xB4\x9D\x66" => '\u1d1df',
+            "\xE1\xB4\x9E\x30" => '\u1d1e0',
+            "\xE1\xB4\x9E\x31" => '\u1d1e1',
+            "\xE1\xB4\x9E\x32" => '\u1d1e2',
+            "\xE1\xB4\x9E\x33" => '\u1d1e3',
+            "\xE1\xB4\x9E\x34" => '\u1d1e4',
+            "\xE1\xB4\x9E\x35" => '\u1d1e5',
+            "\xE1\xB4\x9E\x36" => '\u1d1e6',
+            "\xE1\xB4\x9E\x37" => '\u1d1e7',
+            "\xE1\xB4\x9E\x38" => '\u1d1e8',
+            "\xE1\xB4\x9E\x39" => '\u1d1e9',
+            "\xE1\xB4\x9E\x61" => '\u1d1ea',
+            "\xE1\xB4\x9E\x62" => '\u1d1eb',
+            "\xE1\xB4\x9E\x63" => '\u1d1ec',
+            "\xE1\xB4\x9E\x64" => '\u1d1ed',
+            "\xE1\xB4\x9E\x65" => '\u1d1ee',
+            "\xE1\xB4\x9E\x66" => '\u1d1ef',
+            "\xE1\xB4\x9F\x30" => '\u1d1f0',
+            "\xE1\xB4\x9F\x31" => '\u1d1f1',
+            "\xE1\xB4\x9F\x32" => '\u1d1f2',
+            "\xE1\xB4\x9F\x33" => '\u1d1f3',
+            "\xE1\xB4\x9F\x34" => '\u1d1f4',
+            "\xE1\xB4\x9F\x35" => '\u1d1f5',
+            "\xE1\xB4\x9F\x36" => '\u1d1f6',
+            "\xE1\xB4\x9F\x37" => '\u1d1f7',
+            "\xE1\xB4\x9F\x38" => '\u1d1f8',
+            "\xE1\xB4\x9F\x39" => '\u1d1f9',
+            "\xE1\xB4\x9F\x61" => '\u1d1fa',
+            "\xE1\xB4\x9F\x62" => '\u1d1fb',
+            "\xE1\xB4\x9F\x63" => '\u1d1fc',
+            "\xE1\xB4\x9F\x64" => '\u1d1fd',
+            "\xE1\xB4\x9F\x65" => '\u1d1fe',
+            "\xE1\xB4\x9F\x66" => '\u1d1ff',
+
+            // \p{block=Ancient_Greek_Musical_Notation}
+            "\xE1\xB4\xA0\x30" => '\u1d200',
+            "\xE1\xB4\xA0\x31" => '\u1d201',
+            "\xE1\xB4\xA0\x32" => '\u1d202',
+            "\xE1\xB4\xA0\x33" => '\u1d203',
+            "\xE1\xB4\xA0\x34" => '\u1d204',
+            "\xE1\xB4\xA0\x35" => '\u1d205',
+            "\xE1\xB4\xA0\x36" => '\u1d206',
+            "\xE1\xB4\xA0\x37" => '\u1d207',
+            "\xE1\xB4\xA0\x38" => '\u1d208',
+            "\xE1\xB4\xA0\x39" => '\u1d209',
+            "\xE1\xB4\xA0\x61" => '\u1d20a',
+            "\xE1\xB4\xA0\x62" => '\u1d20b',
+            "\xE1\xB4\xA0\x63" => '\u1d20c',
+            "\xE1\xB4\xA0\x64" => '\u1d20d',
+            "\xE1\xB4\xA0\x65" => '\u1d20e',
+            "\xE1\xB4\xA0\x66" => '\u1d20f',
+            "\xE1\xB4\xA1\x30" => '\u1d210',
+            "\xE1\xB4\xA1\x31" => '\u1d211',
+            "\xE1\xB4\xA1\x32" => '\u1d212',
+            "\xE1\xB4\xA1\x33" => '\u1d213',
+            "\xE1\xB4\xA1\x34" => '\u1d214',
+            "\xE1\xB4\xA1\x35" => '\u1d215',
+            "\xE1\xB4\xA1\x36" => '\u1d216',
+            "\xE1\xB4\xA1\x37" => '\u1d217',
+            "\xE1\xB4\xA1\x38" => '\u1d218',
+            "\xE1\xB4\xA1\x39" => '\u1d219',
+            "\xE1\xB4\xA1\x61" => '\u1d21a',
+            "\xE1\xB4\xA1\x62" => '\u1d21b',
+            "\xE1\xB4\xA1\x63" => '\u1d21c',
+            "\xE1\xB4\xA1\x64" => '\u1d21d',
+            "\xE1\xB4\xA1\x65" => '\u1d21e',
+            "\xE1\xB4\xA1\x66" => '\u1d21f',
+            "\xE1\xB4\xA2\x30" => '\u1d220',
+            "\xE1\xB4\xA2\x31" => '\u1d221',
+            "\xE1\xB4\xA2\x32" => '\u1d222',
+            "\xE1\xB4\xA2\x33" => '\u1d223',
+            "\xE1\xB4\xA2\x34" => '\u1d224',
+            "\xE1\xB4\xA2\x35" => '\u1d225',
+            "\xE1\xB4\xA2\x36" => '\u1d226',
+            "\xE1\xB4\xA2\x37" => '\u1d227',
+            "\xE1\xB4\xA2\x38" => '\u1d228',
+            "\xE1\xB4\xA2\x39" => '\u1d229',
+            "\xE1\xB4\xA2\x61" => '\u1d22a',
+            "\xE1\xB4\xA2\x62" => '\u1d22b',
+            "\xE1\xB4\xA2\x63" => '\u1d22c',
+            "\xE1\xB4\xA2\x64" => '\u1d22d',
+            "\xE1\xB4\xA2\x65" => '\u1d22e',
+            "\xE1\xB4\xA2\x66" => '\u1d22f',
+            "\xE1\xB4\xA3\x30" => '\u1d230',
+            "\xE1\xB4\xA3\x31" => '\u1d231',
+            "\xE1\xB4\xA3\x32" => '\u1d232',
+            "\xE1\xB4\xA3\x33" => '\u1d233',
+            "\xE1\xB4\xA3\x34" => '\u1d234',
+            "\xE1\xB4\xA3\x35" => '\u1d235',
+            "\xE1\xB4\xA3\x36" => '\u1d236',
+            "\xE1\xB4\xA3\x37" => '\u1d237',
+            "\xE1\xB4\xA3\x38" => '\u1d238',
+            "\xE1\xB4\xA3\x39" => '\u1d239',
+            "\xE1\xB4\xA3\x61" => '\u1d23a',
+            "\xE1\xB4\xA3\x62" => '\u1d23b',
+            "\xE1\xB4\xA3\x63" => '\u1d23c',
+            "\xE1\xB4\xA3\x64" => '\u1d23d',
+            "\xE1\xB4\xA3\x65" => '\u1d23e',
+            "\xE1\xB4\xA3\x66" => '\u1d23f',
+            "\xE1\xB4\xA4\x30" => '\u1d240',
+            "\xE1\xB4\xA4\x31" => '\u1d241',
+            "\xE1\xB4\xA4\x32" => '\u1d242',
+            "\xE1\xB4\xA4\x33" => '\u1d243',
+            "\xE1\xB4\xA4\x34" => '\u1d244',
+            "\xE1\xB4\xA4\x35" => '\u1d245',
+            "\xE1\xB4\xA4\x36" => '\u1d246',
+            "\xE1\xB4\xA4\x37" => '\u1d247',
+            "\xE1\xB4\xA4\x38" => '\u1d248',
+            "\xE1\xB4\xA4\x39" => '\u1d249',
+            "\xE1\xB4\xA4\x61" => '\u1d24a',
+            "\xE1\xB4\xA4\x62" => '\u1d24b',
+            "\xE1\xB4\xA4\x63" => '\u1d24c',
+            "\xE1\xB4\xA4\x64" => '\u1d24d',
+            "\xE1\xB4\xA4\x65" => '\u1d24e',
+            "\xE1\xB4\xA4\x66" => '\u1d24f',
+
+        );
+
+        $sanitized = '';
+        $pointer = 0;
+        while (($char = self::unicodeNextChar($data, $pointer)) !== false) {
+            if (isset($excluded[$char])) {
+                return 'Invalid character: ' . $excluded[$char];
+            }
+            $sanitized .= strlen($char) === 1 ? $char : 'a';
+        }
+
+        return null;
+    }
+
+    /**
+     * @see https://stackoverflow.com/a/14366023/329463
+     * @param string $string
+     * @param int $pointer
+     * @return false|string
+     */
+    private static function unicodeNextChar($string, &$pointer)
+    {
+        if (!isset($string[$pointer])) return false;
+        $char = ord($string[$pointer]);
+        if ($char < 128) {
+            return $string[$pointer++];
+        } else {
+            if ($char < 224) {
+                $bytes = 2;
+            } elseif ($char < 240) {
+                $bytes = 3;
+            } elseif ($char < 248) {
+                $bytes = 4;
+            } elseif ($char == 252) {
+                $bytes = 5;
+            } else {
+                $bytes = 6;
+            }
+            $str = substr($string, $pointer, $bytes);
+            $pointer += $bytes;
+            return $str;
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/Constraint/Format/Uri.php b/swaggest/json-schema/src/Constraint/Format/Uri.php
new file mode 100644
index 0000000..d74f133
--- /dev/null
+++ b/swaggest/json-schema/src/Constraint/Format/Uri.php
@@ -0,0 +1,80 @@
+<?php
+
+namespace Swaggest\JsonSchema\Constraint\Format;
+
+class Uri
+{
+    /**
+     * @see http://stackoverflow.com/a/1420225
+     */
+    const HOSTNAME_REGEX = '/^
+      (?=.{1,255}$)
+      [0-9a-z]
+      (([0-9a-z]|-){0,61}[0-9a-z])?
+      (\.[0-9a-z](?:(?:[0-9a-z]|-){0,61}[0-9a-z])?)*
+      \.?
+    $/ix';
+
+    const IS_URI_REFERENCE = 1;
+    const IS_URI_TEMPLATE = 2;
+    const IS_SCHEME_REQUIRED = 8;
+
+    public static function validationError($data, $options = 0)
+    {
+        if ($options === Uri::IS_URI_TEMPLATE) {
+            $opened = false;
+            for ($i = 0; $i < strlen($data); ++$i) {
+                if ($data[$i] === '{') {
+                    if ($opened) {
+                        return 'Invalid uri-template: unexpected "{"';
+                    } else {
+                        $opened = true;
+                    }
+                } elseif ($data[$i] === '}') {
+                    if ($opened) {
+                        $opened = false;
+                    } else {
+                        return 'Invalid uri-template: unexpected "}"';
+                    }
+                }
+            }
+            if ($opened) {
+                return 'Invalid uri-template: unexpected end of string';
+            }
+        }
+
+        $uri = parse_url($data);
+        if (!$uri) {
+            return 'Malformed URI';
+        }
+        if (($options & self::IS_SCHEME_REQUIRED) && (!isset($uri['scheme']) || $uri['scheme'] === '')) {
+            return 'Missing scheme in URI';
+        }
+        if (isset($uri['host'])) {
+            $host = $uri['host'];
+            if (!preg_match(self::HOSTNAME_REGEX, $host)) {
+                // stripping [ ]
+                if ($host[0] === '[' && $host[strlen($host) - 1] === ']') {
+                    $host = substr($host, 1, -1);
+                }
+                if (!filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
+                    return 'Malformed host in URI: ' . $host;
+                }
+            }
+        }
+
+        if (isset($uri['path'])) {
+            if (strpos($uri['path'], '\\') !== false) {
+                return 'Invalid path: unescaped backslash';
+            }
+        }
+
+        if (isset($uri['fragment'])) {
+            if (strpos($uri['fragment'], '\\') !== false) {
+                return 'Invalid fragment: unescaped backslash';
+            }
+        }
+
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/Constraint/Properties.php b/swaggest/json-schema/src/Constraint/Properties.php
new file mode 100644
index 0000000..a99db73
--- /dev/null
+++ b/swaggest/json-schema/src/Constraint/Properties.php
@@ -0,0 +1,182 @@
+<?php
+
+namespace Swaggest\JsonSchema\Constraint;
+
+use Swaggest\JsonSchema\Exception;
+use Swaggest\JsonSchema\Schema;
+use Swaggest\JsonSchema\SchemaContract;
+use Swaggest\JsonSchema\Structure\Egg;
+use Swaggest\JsonSchema\Structure\Nested;
+use Swaggest\JsonSchema\Structure\ObjectItem;
+
+/**
+ * @method SchemaContract __get($key)
+ */
+class Properties extends ObjectItem implements Constraint
+{
+    private $__isReadOnly = false;
+
+    /** @var Schema[] */
+    protected $__arrayOfData = array();
+
+    /** @var Schema */
+    protected $__schema;
+
+    /**
+     * @var Schema[]
+     */
+    private $__mappedProperties;
+
+    /**
+     * @var array
+     */
+    private $__dataKeyMaps = array();
+
+    /**
+     * Data to property mapping, example ["$ref" => "ref"]
+     * @var array
+     */
+    public $__dataToProperty = array();
+
+    /**
+     * Property to data mapping, example ["ref" => "$ref"]
+     * @var array
+     */
+    public $__propertyToData = array();
+
+    /**
+     * Returns a map of properties by default data name
+     * @return Schema[]
+     */
+    public function &toArray()
+    {
+        if (!isset($this->__propertyToData[Schema::DEFAULT_MAPPING])) {
+            return $this->__arrayOfData;
+        }
+        if (null === $this->__mappedProperties) {
+            $properties = array();
+            foreach ($this->__arrayOfData as $propertyName => $property) {
+                if (isset($this->__propertyToData[Schema::DEFAULT_MAPPING][$propertyName])) {
+                    $propertyName = $this->__propertyToData[Schema::DEFAULT_MAPPING][$propertyName];
+                }
+                $properties[$propertyName] = $property;
+            }
+            $this->__mappedProperties = $properties;
+        }
+        return $this->__mappedProperties;
+    }
+
+    /**
+     * @param string $mapping
+     * @return string[] a map of propertyName to dataName
+     */
+    public function getDataKeyMap($mapping = Schema::DEFAULT_MAPPING)
+    {
+        if (!isset($this->__dataKeyMaps[$mapping])) {
+            $map = array();
+            foreach ($this->__arrayOfData as $propertyName => $property) {
+                if (isset($this->__propertyToData[$mapping][$propertyName])) {
+                    $map[$propertyName] = $this->__propertyToData[$mapping][$propertyName];
+                } else {
+                    $map[$propertyName] = $propertyName;
+                }
+            }
+            $this->__dataKeyMaps[$mapping] = $map;
+        }
+
+        return $this->__dataKeyMaps[$mapping];
+    }
+
+    public function lock()
+    {
+        $this->__isReadOnly = true;
+        return $this;
+    }
+
+    public function addPropertyMapping($dataName, $propertyName, $mapping = Schema::DEFAULT_MAPPING)
+    {
+        $this->__dataToProperty[$mapping][$dataName] = $propertyName;
+        $this->__propertyToData[$mapping][$propertyName] = $dataName;
+    }
+
+
+    /**
+     * @param string $name
+     * @param mixed $column
+     * @return $this|static
+     * @throws Exception
+     */
+    public function __set($name, $column)
+    {
+        if ($this->__isReadOnly) {
+            throw new Exception('Trying to modify read-only Properties');
+        }
+        if ($column instanceof Nested) {
+            $this->addNested($column->schema, $name);
+            return $this;
+        }
+        parent::__set($name, $column);
+        return $this;
+    }
+
+    public static function create()
+    {
+        return new static;
+    }
+
+    /** @var Egg[][] */
+    public $nestedProperties = array();
+
+    /** @var string[] */
+    public $nestedPropertyNames = array();
+
+    /**
+     * @param SchemaContract $nested
+     * @param string $name
+     * @return $this
+     * @throws Exception
+     */
+    protected function addNested(SchemaContract $nested, $name)
+    {
+        if ($this->__isReadOnly) {
+            throw new Exception('Trying to modify read-only Properties');
+        }
+        if (null === $nested->getProperties()) {
+            throw new Exception('Schema with properties required', Exception::PROPERTIES_REQUIRED);
+        }
+        $this->nestedPropertyNames[$name] = $name;
+        foreach ($nested->getProperties()->toArray() as $propertyName => $property) {
+            $this->nestedProperties[$propertyName][] = new Egg($nested, $name, $property);
+        }
+        return $this;
+    }
+
+    /**
+     * @return Egg[][]
+     */
+    public function getNestedProperties()
+    {
+        return $this->nestedProperties;
+    }
+
+    public function isEmpty()
+    {
+        return (count($this->__arrayOfData) + count($this->nestedProperties)) === 0;
+    }
+
+    public function jsonSerialize()
+    {
+        $result = $this->toArray();
+
+        if ($this->__nestedObjects) {
+            foreach ($this->__nestedObjects as $object) {
+                foreach ($object->toArray() as $key => $value) {
+                    $result[$key] = $value;
+                }
+            }
+        }
+
+        return (object)$result;
+    }
+
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/Constraint/Ref.php b/swaggest/json-schema/src/Constraint/Ref.php
new file mode 100644
index 0000000..35a9fb0
--- /dev/null
+++ b/swaggest/json-schema/src/Constraint/Ref.php
@@ -0,0 +1,71 @@
+<?php
+
+namespace Swaggest\JsonSchema\Constraint;
+
+class Ref implements Constraint
+{
+    /** @var string */
+    public $ref;
+    public function __construct($ref, $data = null)
+    {
+        $this->ref = $ref;
+        $this->data = $data;
+    }
+
+    /** @var mixed */
+    private $data;
+
+    /** @var mixed */
+    private $imported;
+    /** @var boolean */
+    private $isImported = false;
+
+    /**
+     * @return mixed
+     */
+    public function getImported()
+    {
+        return $this->imported;
+    }
+
+    /**
+     * @param mixed $imported
+     */
+    public function setImported($imported)
+    {
+        $this->isImported = true;
+        $this->imported = $imported;
+    }
+
+    /**
+     * @return boolean
+     */
+    public function isImported()
+    {
+        return $this->isImported;
+    }
+
+    public function unsetImported()
+    {
+        $this->isImported = false;
+        $this->imported = null;
+    }
+
+    /**
+     * @param mixed $data
+     * @return Ref
+     */
+    public function setData($data)
+    {
+        $this->data = $data;
+        return $this;
+    }
+
+    /**
+     * @return mixed
+     */
+    public function getData()
+    {
+        return $this->data;
+    }
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/Constraint/Type.php b/swaggest/json-schema/src/Constraint/Type.php
new file mode 100644
index 0000000..ea3c158
--- /dev/null
+++ b/swaggest/json-schema/src/Constraint/Type.php
@@ -0,0 +1,99 @@
+<?php
+
+namespace Swaggest\JsonSchema\Constraint;
+
+use Swaggest\JsonSchema\Schema;
+
+class Type implements Constraint
+{
+    // TODO deprecate in favour of JsonSchema::<TYPE> ?
+    const OBJECT = 'object';
+    const STRING = 'string';
+    const INTEGER = 'integer';
+    const NUMBER = 'number';
+    const ARR = 'array';
+    const BOOLEAN = 'boolean';
+    const NULL = 'null';
+
+    public static function readString($types, &$data)
+    {
+        if (!is_array($types)) {
+            $types = array($types);
+        }
+        $ok = false;
+        foreach ($types as $type) {
+            switch ($type) {
+                case self::OBJECT:
+                    break;
+                case self::ARR:
+                    break;
+                case self::STRING:
+                    $ok = true;
+                    break;
+                case self::NUMBER:
+                    $newData = filter_var($data, FILTER_VALIDATE_FLOAT, FILTER_NULL_ON_FAILURE);
+                    $ok = is_float($newData);
+                    break;
+                case self::INTEGER:
+                    $newData = filter_var($data, FILTER_VALIDATE_INT, FILTER_NULL_ON_FAILURE);
+                    $ok = is_int($newData);
+                    break;
+                case self::BOOLEAN:
+                    $newData = filter_var($data, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
+                    $ok = is_bool($newData);
+                    break;
+                case self::NULL:
+                    break;
+            }
+            if ($ok) {
+                if (isset($newData)) {
+                    $data = $newData;
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static function isValid($types, $data, $version)
+    {
+        if (!is_array($types)) {
+            $types = array($types);
+        }
+        $ok = false;
+        foreach ($types as $type) {
+            switch ($type) {
+                case self::OBJECT:
+                    $ok = is_object($data);
+                    break;
+                case self::ARR:
+                    $ok = is_array($data);
+                    break;
+                case self::STRING:
+                    $ok = is_string($data);
+                    break;
+                case self::INTEGER:
+                    $ok = is_int($data)
+                        || (is_float($data)
+                            && ((ceil($data) === $data && $version !== Schema::VERSION_DRAFT_04) // float without fraction is int
+                                || abs($data) > PHP_INT_MAX)); // big float accepted for int
+                    break;
+                case self::NUMBER:
+                    $ok = is_int($data) || is_float($data);
+                    break;
+                case self::BOOLEAN:
+                    $ok = is_bool($data);
+                    break;
+                case self::NULL:
+                    $ok = null === $data;
+                    break;
+            }
+            if ($ok) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/Constraint/UniqueItems.php b/swaggest/json-schema/src/Constraint/UniqueItems.php
new file mode 100644
index 0000000..c84bbb6
--- /dev/null
+++ b/swaggest/json-schema/src/Constraint/UniqueItems.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Swaggest\JsonSchema\Constraint;
+
+use Swaggest\JsonSchema\Structure\ObjectItemContract;
+
+class UniqueItems
+{
+    /**
+     * @param array $data
+     * @return bool
+     * @todo optimize a lot
+     */
+    public static function isValid(array $data)
+    {
+        $index = array();
+        foreach ($data as $value) {
+            if (is_array($value) || $value instanceof \stdClass) {
+                $value = json_encode($value);
+            } elseif (is_bool($value)) {
+                $value = '_B' . $value;
+            } elseif (is_string($value)) {
+                $value = '_S' . $value;
+            } elseif (is_int($value)) {
+                $value = '_I' . $value;
+            } elseif (is_float($value)) {
+                $value = '_F' . $value;
+            } elseif ($value instanceof ObjectItemContract) {
+                $value = json_encode($value);
+            }
+            $tmp = &$index[$value];
+            if ($tmp !== null) {
+                return false;
+            }
+            $tmp = true;
+        }
+        return true;
+    }
+
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/Context.php b/swaggest/json-schema/src/Context.php
new file mode 100644
index 0000000..c655dde
--- /dev/null
+++ b/swaggest/json-schema/src/Context.php
@@ -0,0 +1,137 @@
+<?php
+
+namespace Swaggest\JsonSchema;
+
+class Context extends MagicMap
+{
+    public $import = true;
+
+    /** @var DataPreProcessor */
+    public $dataPreProcessor;
+
+    /** @var RefResolver */
+    public $refResolver;
+
+    /** @var RemoteRefProvider|null */
+    public $remoteRefProvider;
+
+    /** @var bool Skip result mapping, only validate data */
+    public $validateOnly = false;
+
+    /** @var bool Apply default values */
+    public $applyDefaults = true;
+
+    /** @var \SplObjectStorage */
+    public $circularReferences;
+
+    /** @var bool */
+    public $skipValidation = false;
+
+    /** @var string[]|null map of from -> to class names */
+    public $objectItemClassMapping;
+
+    /** @var bool allow soft cast from to/strings */
+    public $tolerateStrings = false;
+
+    /** @var bool do not tolerate special symbols even if base64_decode accepts string */
+    public $strictBase64Validation = false;
+
+    /** @var bool pack/unpack application/json in string content */
+    public $unpackContentMediaType = true;
+
+    /** @var \SplObjectStorage optional schemas cache */
+    public $schemasCache;
+
+    /** @var string property mapping set name */
+    public $mapping = Schema::DEFAULT_MAPPING;
+
+    public $version = Schema::VERSION_AUTO;
+
+    public $exportedDefinitions = [];
+
+    public $isRef = false;
+
+    /**
+     * Dereference $ref unless there is a $ref property defined with format not equal to `uri-reference`.
+     * Default JSON Schema behavior is to dereference only if there is a $ref property defined with format
+     * equal to `uri-reference`.
+     *
+     * @var bool
+     */
+    public $dereference = false;
+
+    /**
+     * @param boolean $skipValidation
+     * @return Context
+     */
+    public function setSkipValidation($skipValidation = true)
+    {
+        $this->skipValidation = $skipValidation;
+        return $this;
+    }
+
+
+    /**
+     * ProcessingOptions constructor.
+     * @param RemoteRefProvider $remoteRefProvider
+     */
+    public function __construct(RemoteRefProvider $remoteRefProvider = null)
+    {
+        $this->remoteRefProvider = $remoteRefProvider;
+    }
+
+    /**
+     * @return DataPreProcessor
+     */
+    public function getDataPreProcessor()
+    {
+        return $this->dataPreProcessor;
+    }
+
+    /**
+     * @param DataPreProcessor $dataPreProcessor
+     * @return Context
+     */
+    public function setDataPreProcessor($dataPreProcessor)
+    {
+        $this->dataPreProcessor = $dataPreProcessor;
+        return $this;
+    }
+
+    /**
+     * @return RemoteRefProvider|null
+     */
+    public function getRemoteRefProvider()
+    {
+        return $this->remoteRefProvider;
+    }
+
+    /**
+     * @param RemoteRefProvider $remoteRefProvider
+     * @return Context
+     */
+    public function setRemoteRefProvider($remoteRefProvider)
+    {
+        $this->remoteRefProvider = $remoteRefProvider;
+        return $this;
+    }
+
+    /** @var self */
+    private $withDefault;
+
+    /**
+     * @return Context
+     */
+    public function withDefault()
+    {
+        if (null === $this->withDefault) {
+            $this->withDefault = clone $this;
+            $this->withDefault->skipValidation = true;
+            $this->withDefault->applyDefaults = false;
+        }
+
+        return $this->withDefault;
+    }
+
+
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/DataPreProcessor.php b/swaggest/json-schema/src/DataPreProcessor.php
new file mode 100644
index 0000000..b5915ca
--- /dev/null
+++ b/swaggest/json-schema/src/DataPreProcessor.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Swaggest\JsonSchema;
+
+
+interface DataPreProcessor
+{
+    /**
+     * @param mixed $data original data
+     * @param Schema $schema
+     * @param bool $import
+     * @return mixed processed data
+     */
+    public function process($data, Schema $schema, $import = true);
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/Exception.php b/swaggest/json-schema/src/Exception.php
new file mode 100644
index 0000000..d39d848
--- /dev/null
+++ b/swaggest/json-schema/src/Exception.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Swaggest\JsonSchema;
+
+
+class Exception extends \Exception
+{
+    const PROPERTIES_REQUIRED = 1;
+    const UNDEFINED_NESTED_NAME = 2;
+    const DEEP_NESTING = 3;
+    const RESOLVE_FAILED = 4;
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/Exception/ArrayException.php b/swaggest/json-schema/src/Exception/ArrayException.php
new file mode 100644
index 0000000..1be3fc0
--- /dev/null
+++ b/swaggest/json-schema/src/Exception/ArrayException.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace Swaggest\JsonSchema\Exception;
+
+
+use Swaggest\JsonSchema\InvalidValue;
+
+class ArrayException extends InvalidValue
+{
+
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/Exception/ConstException.php b/swaggest/json-schema/src/Exception/ConstException.php
new file mode 100644
index 0000000..e6dd886
--- /dev/null
+++ b/swaggest/json-schema/src/Exception/ConstException.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace Swaggest\JsonSchema\Exception;
+
+use Swaggest\JsonSchema\InvalidValue;
+
+class ConstException extends InvalidValue
+{
+
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/Exception/ContentException.php b/swaggest/json-schema/src/Exception/ContentException.php
new file mode 100644
index 0000000..ca716b8
--- /dev/null
+++ b/swaggest/json-schema/src/Exception/ContentException.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace Swaggest\JsonSchema\Exception;
+
+
+use Swaggest\JsonSchema\InvalidValue;
+
+class ContentException extends InvalidValue
+{
+
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/Exception/EnumException.php b/swaggest/json-schema/src/Exception/EnumException.php
new file mode 100644
index 0000000..58ffbef
--- /dev/null
+++ b/swaggest/json-schema/src/Exception/EnumException.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace Swaggest\JsonSchema\Exception;
+
+use Swaggest\JsonSchema\InvalidValue;
+
+class EnumException extends InvalidValue
+{
+
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/Exception/Error.php b/swaggest/json-schema/src/Exception/Error.php
new file mode 100644
index 0000000..49f1236
--- /dev/null
+++ b/swaggest/json-schema/src/Exception/Error.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace Swaggest\JsonSchema\Exception;
+
+use Swaggest\JsonSchema\InvalidValue;
+
+class Error
+{
+    /** @var string */
+    public $error;
+    /** @var string[] */
+    public $schemaPointers;
+    /** @var string */
+    public $dataPointer;
+    /** @var string */
+    public $processingPath;
+    /** @var Error[] */
+    public $subErrors;
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/Exception/LogicException.php b/swaggest/json-schema/src/Exception/LogicException.php
new file mode 100644
index 0000000..5604881
--- /dev/null
+++ b/swaggest/json-schema/src/Exception/LogicException.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace Swaggest\JsonSchema\Exception;
+
+use Swaggest\JsonSchema\InvalidValue;
+
+class LogicException extends InvalidValue
+{
+    /** @var InvalidValue[] */
+    public $subErrors;
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/Exception/NumericException.php b/swaggest/json-schema/src/Exception/NumericException.php
new file mode 100644
index 0000000..3bd6fcf
--- /dev/null
+++ b/swaggest/json-schema/src/Exception/NumericException.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Swaggest\JsonSchema\Exception;
+
+
+use Swaggest\JsonSchema\InvalidValue;
+
+class NumericException extends InvalidValue
+{
+    const MULTIPLE_OF = 1;
+    const MAXIMUM = 2;
+    const MINIMUM = 3;
+
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/Exception/ObjectException.php b/swaggest/json-schema/src/Exception/ObjectException.php
new file mode 100644
index 0000000..de157af
--- /dev/null
+++ b/swaggest/json-schema/src/Exception/ObjectException.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Swaggest\JsonSchema\Exception;
+
+use Swaggest\JsonSchema\InvalidValue;
+
+class ObjectException extends InvalidValue
+{
+    const REQUIRED = 1;
+    const TOO_MANY = 2;
+    const TOO_FEW = 3;
+    const DEPENDENCY_MISSING = 4;
+
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/Exception/StringException.php b/swaggest/json-schema/src/Exception/StringException.php
new file mode 100644
index 0000000..03cd07a
--- /dev/null
+++ b/swaggest/json-schema/src/Exception/StringException.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Swaggest\JsonSchema\Exception;
+
+
+use Swaggest\JsonSchema\InvalidValue;
+
+class StringException extends InvalidValue
+{
+    const TOO_SHORT = 1;
+    const TOO_LONG = 2;
+    const PATTERN_MISMATCH = 3;
+
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/Exception/TypeException.php b/swaggest/json-schema/src/Exception/TypeException.php
new file mode 100644
index 0000000..7a291b7
--- /dev/null
+++ b/swaggest/json-schema/src/Exception/TypeException.php
@@ -0,0 +1,11 @@
+<?php
+
+namespace Swaggest\JsonSchema\Exception;
+
+
+use Swaggest\JsonSchema\InvalidValue;
+
+class TypeException extends InvalidValue
+{
+
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/Helper.php b/swaggest/json-schema/src/Helper.php
new file mode 100644
index 0000000..6ed629a
--- /dev/null
+++ b/swaggest/json-schema/src/Helper.php
@@ -0,0 +1,72 @@
+<?php
+
+namespace Swaggest\JsonSchema;
+
+
+class Helper
+{
+    /**
+     * @param string $jsonPattern
+     * @return string
+     */
+    public static function toPregPattern($jsonPattern)
+    {
+        return '{' . $jsonPattern . '}u';
+    }
+
+    /**
+     * @param string $parent
+     * @param string $current
+     * @return string
+     * @todo getaway from zeroes
+     */
+    public static function resolveURI($parent, $current)
+    {
+        if ($current === '') {
+            return $parent;
+        }
+
+        $parentParts = explode('#', $parent, 2);
+        $currentParts = explode('#', $current, 2);
+
+        $resultParts = array($parentParts[0], '');
+        if (isset($currentParts[1])) {
+            $resultParts[1] = $currentParts[1];
+        }
+
+        if (isset($currentParts[0]) && $currentParts[0]) {
+            if (strpos($currentParts[0], '://')) {
+                $resultParts[0] = $currentParts[0];
+            } elseif ('/' === substr($currentParts[0], 0, 1)) {
+                $resultParts[0] = $currentParts[0];
+                if (($pos = strpos($parentParts[0], '://'))
+                    && ($len = strpos($parentParts[0], '/', $pos + 3))) {
+                    $resultParts[0] = substr($parentParts[0], 0, $len) . $resultParts[0];
+                }
+            } elseif (false !== $pos = strrpos($parentParts[0], '/')) {
+                $resultParts[0] = substr($parentParts[0], 0, $pos + 1) . $currentParts[0];
+            } else {
+                $resultParts[0] = $currentParts[0];
+            }
+        }
+
+        $result = $resultParts[0] . '#' . $resultParts[1];
+        return $result;
+    }
+
+
+    public static function padLines($with, $text, $skipFirst = true)
+    {
+        $lines = explode("\n", $text);
+        foreach ($lines as $index => $line) {
+            if ($skipFirst && !$index) {
+                continue;
+            }
+            if ($line) {
+                $lines[$index] = $with . $line;
+            }
+        }
+        return implode("\n", $lines);
+    }
+
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/InvalidValue.php b/swaggest/json-schema/src/InvalidValue.php
new file mode 100644
index 0000000..e6cb801
--- /dev/null
+++ b/swaggest/json-schema/src/InvalidValue.php
@@ -0,0 +1,84 @@
+<?php
+
+namespace Swaggest\JsonSchema;
+
+use Swaggest\JsonDiff\JsonPointer;
+use Swaggest\JsonSchema\Exception\Error;
+use Swaggest\JsonSchema\Exception\LogicException;
+use Swaggest\JsonSchema\Path\PointerUtil;
+use Swaggest\JsonSchema\Structure\ObjectItemContract;
+
+class InvalidValue extends Exception
+{
+    public $error;
+    public $path;
+
+    public function addPath($path)
+    {
+        if ($this->error === null) {
+            $this->error = $this->message;
+        }
+        $this->path = $path;
+        if ('#' !== $this->path) {
+            $this->message .= ' at ' . $path;
+        }
+    }
+
+    const INVALID_VALUE = 1;
+    const NOT_IMPLEMENTED = 2;
+
+
+    public function inspect()
+    {
+        $error = new Error();
+        $error->error = $this->error;
+        $error->processingPath = $this->path;
+        $error->dataPointer = PointerUtil::getDataPointer($error->processingPath);
+        $error->schemaPointers = PointerUtil::getSchemaPointers($error->processingPath);
+        if ($this instanceof LogicException) {
+            if ($this->subErrors !== null) {
+                foreach ($this->subErrors as $subError) {
+                    $error->subErrors[] = $subError->inspect();
+                }
+            }
+        }
+        return $error;
+    }
+
+    public function getSchemaPointer()
+    {
+        return PointerUtil::getSchemaPointer($this->path);
+    }
+
+    /**
+     * @param Schema $schema
+     * @return bool|Schema
+     * @throws \Swaggest\JsonDiff\Exception
+     */
+    public function getFailedSubSchema(Schema $schema)
+    {
+        $schemaPointer = $this->getSchemaPointer();
+        if ($schema instanceof ObjectItemContract) {
+            $refs = $schema->getFromRefs();
+            if ($refs !== null) {
+                foreach ($refs as $ref) {
+                    if (substr($schemaPointer, 0, strlen($ref)) === $ref) {
+                        $schemaPointer = substr($schemaPointer, strlen($ref));
+                    }
+                }
+            }
+        }
+        if (!(bool)$schemaPointer) {
+            return $schema;
+        }
+
+        return JsonPointer::getByPointer($schema, $this->getSchemaPointer());
+    }
+
+
+    public function getDataPointer()
+    {
+        return PointerUtil::getDataPointer($this->path);
+    }
+
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/JsonSchema.php b/swaggest/json-schema/src/JsonSchema.php
new file mode 100644
index 0000000..44b604f
--- /dev/null
+++ b/swaggest/json-schema/src/JsonSchema.php
@@ -0,0 +1,734 @@
+<?php
+/**
+ * @file ATTENTION!!! The code below was carefully crafted by a mean machine.
+ * Please consider to NOT put any emotional human-generated modifications as the splendid AI will throw them away with no mercy.
+ */
+
+namespace Swaggest\JsonSchema;
+
+use Swaggest\JsonSchema\Constraint\Format;
+use Swaggest\JsonSchema\Constraint\Properties;
+use Swaggest\JsonSchema\Constraint\Type;
+use Swaggest\JsonSchema\Schema as JsonBasicSchema;
+use Swaggest\JsonSchema\Structure\ClassStructure;
+
+
+/**
+ * Core schema meta-schema
+ *
+ * // draft6
+ * @property mixed $const
+ */
+class JsonSchema extends ClassStructure
+{
+    const _ARRAY = 'array';
+
+    const BOOLEAN = 'boolean';
+
+    const INTEGER = 'integer';
+
+    const NULL = 'null';
+
+    const NUMBER = 'number';
+
+    const OBJECT = 'object';
+
+    const STRING = 'string';
+
+    /** @var string */
+    public $id;
+
+    /** @var string */
+    public $schema;
+
+    /** @var string */
+    public $title;
+
+    /** @var string */
+    public $description;
+
+    public $default;
+
+    /** @var float|null */
+    public $multipleOf;
+
+    /** @var float|null */
+    public $maximum;
+
+    /** @var bool|float|null */
+    public $exclusiveMaximum;
+
+    /** @var float|null */
+    public $minimum;
+
+    /** @var bool|float */
+    public $exclusiveMinimum;
+
+    /** @var int|null */
+    public $maxLength;
+
+    /** @var int|null */
+    public $minLength;
+
+    /** @var string|null */
+    public $pattern;
+
+    /** @var bool|JsonSchema */
+    public $additionalItems;
+
+    /** @var JsonSchema|JsonSchema[]|array */
+    public $items;
+
+    /** @var int|null */
+    public $maxItems;
+
+    /** @var int|null */
+    public $minItems;
+
+    /** @var bool|null */
+    public $uniqueItems;
+
+    /** @var int */
+    public $maxProperties;
+
+    /** @var int */
+    public $minProperties;
+
+    /** @var string[]|array */
+    public $required;
+
+    /** @var bool|JsonSchema */
+    public $additionalProperties;
+
+    /** @var JsonSchema[] */
+    public $definitions;
+
+    /** @var JsonSchema[] */
+    public $properties;
+
+    /** @var JsonSchema[] */
+    public $patternProperties;
+
+    /** @var JsonSchema[]|string[][]|array[] */
+    public $dependencies;
+
+    /** @var array */
+    public $enum;
+
+    /** @var array|string */
+    public $type;
+
+    /** @var string|null */
+    public $format;
+
+    /** @var string */
+    public $ref;
+
+    /** @var JsonSchema[]|array */
+    public $allOf;
+
+    /** @var JsonSchema[]|array */
+    public $anyOf;
+
+    /** @var JsonSchema[]|array */
+    public $oneOf;
+
+    /** @var JsonSchema Core schema meta-schema */
+    public $not;
+
+
+    // draft6
+    /** @var JsonSchema */
+    public $contains;
+
+    /** @var JsonSchema */
+    public $propertyNames;
+
+    // draft7
+    /** @var JsonSchema */
+    public $if;
+
+    /** @var JsonSchema */
+    public $then;
+
+    /** @var JsonSchema */
+    public $else;
+
+    /** @var string */
+    public $contentMediaType;
+
+    /** @var string */
+    public $contentEncoding;
+
+    /**
+     * @param Properties|static $properties
+     * @param JsonBasicSchema $ownerSchema
+     */
+    public static function setUpProperties($properties, JsonBasicSchema $ownerSchema)
+    {
+        $properties->id = JsonBasicSchema::string();
+        $properties->id->format = 'uri';
+        $properties->schema = JsonBasicSchema::string();
+        $properties->schema->format = 'uri';
+        $ownerSchema->addPropertyMapping('$schema', self::names()->schema);
+        $properties->title = JsonBasicSchema::string();
+        $properties->description = JsonBasicSchema::string();
+        $properties->default = new JsonBasicSchema();
+        $properties->multipleOf = JsonBasicSchema::number();
+        $properties->multipleOf->minimum = 0;
+        $properties->multipleOf->exclusiveMinimum = true;
+        $properties->maximum = JsonBasicSchema::number();
+        $properties->exclusiveMaximum = new JsonBasicSchema(); // draft6
+        $properties->exclusiveMaximum->type = array(Type::BOOLEAN, Type::NUMBER); // draft6
+        //$properties->exclusiveMaximum = JsonBasicSchema::boolean(); // draft6
+        //$properties->exclusiveMaximum->default = false; // draft6
+        $properties->minimum = JsonBasicSchema::number();
+        $properties->exclusiveMinimum = new JsonBasicSchema(); // draft6
+        $properties->exclusiveMinimum->type = array(Type::BOOLEAN, Type::NUMBER); // draft6
+        //$properties->exclusiveMinimum = JsonBasicSchema::boolean(); // draft6
+        //$properties->exclusiveMinimum->default = false; // draft6
+        $properties->maxLength = JsonBasicSchema::integer();
+        $properties->maxLength->minimum = 0;
+        $properties->minLength = new JsonBasicSchema();
+        $properties->minLength->allOf[0] = JsonBasicSchema::integer();
+        $properties->minLength->allOf[0]->minimum = 0;
+        $properties->minLength->allOf[1] = new JsonBasicSchema();
+        $properties->minLength->allOf[1]->default = 0;
+        $properties->pattern = JsonBasicSchema::string();
+        $properties->pattern->format = 'regex';
+        $properties->additionalItems = new JsonBasicSchema();
+        $properties->additionalItems->anyOf[0] = JsonBasicSchema::boolean();
+        $properties->additionalItems->anyOf[1] = JsonBasicSchema::schema();
+        $properties->additionalItems->default = (object)array();
+        $properties->items = new JsonBasicSchema();
+        $properties->items->anyOf[0] = JsonBasicSchema::schema();
+        $properties->items->anyOf[1] = JsonBasicSchema::arr();
+        $properties->items->anyOf[1]->items = JsonBasicSchema::schema();
+        $properties->items->anyOf[1]->minItems = 1;
+        $properties->items->default = (object)array();
+        $properties->maxItems = JsonBasicSchema::integer();
+        $properties->maxItems->minimum = 0;
+        $properties->minItems = new JsonBasicSchema();
+        $properties->minItems->allOf[0] = JsonBasicSchema::integer();
+        $properties->minItems->allOf[0]->minimum = 0;
+        $properties->minItems->allOf[1] = new JsonBasicSchema();
+        $properties->minItems->allOf[1]->default = 0;
+        $properties->uniqueItems = JsonBasicSchema::boolean();
+        $properties->uniqueItems->default = false;
+        $properties->maxProperties = JsonBasicSchema::integer();
+        $properties->maxProperties->minimum = 0;
+        $properties->minProperties = new JsonBasicSchema();
+        $properties->minProperties->allOf[0] = JsonBasicSchema::integer();
+        $properties->minProperties->allOf[0]->minimum = 0;
+        $properties->minProperties->allOf[1] = new JsonBasicSchema();
+        $properties->minProperties->allOf[1]->default = 0;
+        $properties->required = JsonBasicSchema::arr();
+        $properties->required->items = JsonBasicSchema::string();
+        //$properties->required->minItems = 1; // disabled by draft6
+        $properties->required->uniqueItems = true;
+        $properties->additionalProperties = new JsonBasicSchema();
+        $properties->additionalProperties->anyOf[0] = JsonBasicSchema::boolean();
+        $properties->additionalProperties->anyOf[1] = JsonBasicSchema::schema();
+        $properties->additionalProperties->default = (object)array();
+        $properties->definitions = JsonBasicSchema::object();
+        $properties->definitions->additionalProperties = JsonBasicSchema::schema();
+        $properties->definitions->default = (object)array();
+        $properties->properties = JsonBasicSchema::object();
+        $properties->properties->additionalProperties = JsonBasicSchema::schema();
+        $properties->properties->default = (object)array();
+        $properties->patternProperties = JsonBasicSchema::object();
+        $properties->patternProperties->additionalProperties = JsonBasicSchema::schema();
+        $properties->patternProperties->default = (object)array();
+        $properties->dependencies = JsonBasicSchema::object();
+        $properties->dependencies->additionalProperties = new JsonBasicSchema();
+        $properties->dependencies->additionalProperties->anyOf[0] = JsonBasicSchema::schema();
+        $properties->dependencies->additionalProperties->anyOf[1] = JsonBasicSchema::arr();
+        $properties->dependencies->additionalProperties->anyOf[1]->items = JsonBasicSchema::string();
+        //$properties->dependencies->additionalProperties->anyOf[1]->minItems = 1; // disabled by draft6
+        $properties->dependencies->additionalProperties->anyOf[1]->uniqueItems = true;
+        $properties->enum = JsonBasicSchema::arr();
+        $properties->enum->minItems = 1;
+        $properties->enum->uniqueItems = true;
+        $properties->type = new JsonBasicSchema();
+        $anyOf0 = new JsonBasicSchema();
+        $anyOf0->enum = array(
+            self::_ARRAY,
+            self::BOOLEAN,
+            self::INTEGER,
+            self::NULL,
+            self::NUMBER,
+            self::OBJECT,
+            self::STRING,
+        );
+        $properties->type->anyOf[0] = $anyOf0;
+        $properties->type->anyOf[1] = JsonBasicSchema::arr();
+        $properties->type->anyOf[1]->items = new JsonBasicSchema();
+        $properties->type->anyOf[1]->items->enum = array(
+            self::_ARRAY,
+            self::BOOLEAN,
+            self::INTEGER,
+            self::NULL,
+            self::NUMBER,
+            self::OBJECT,
+            self::STRING,
+        );
+        $properties->type->anyOf[1]->minItems = 1;
+        $properties->type->anyOf[1]->uniqueItems = true;
+        $properties->format = JsonBasicSchema::string();
+        $properties->ref = JsonBasicSchema::string();
+        $properties->ref->format = Format::URI_REFERENCE;
+        $ownerSchema->addPropertyMapping('$ref', self::names()->ref);
+        $properties->allOf = JsonBasicSchema::arr();
+        $properties->allOf->items = JsonBasicSchema::schema();
+        $properties->allOf->minItems = 1;
+        $properties->anyOf = JsonBasicSchema::arr();
+        $properties->anyOf->items = JsonBasicSchema::schema();
+        $properties->anyOf->minItems = 1;
+        $properties->oneOf = JsonBasicSchema::arr();
+        $properties->oneOf->items = JsonBasicSchema::schema();
+        $properties->oneOf->minItems = 1;
+        $properties->not = JsonBasicSchema::schema();
+        $ownerSchema->type = array(self::OBJECT, self::BOOLEAN);
+        $ownerSchema->id = 'http://json-schema.org/draft-04/schema#';
+        $ownerSchema->schema = 'http://json-schema.org/draft-04/schema#';
+        $ownerSchema->description = 'Core schema meta-schema';
+        $ownerSchema->default = (object)array();
+        $ownerSchema->setFromRef(false);
+        // disabled by draft6
+        /*
+        $ownerSchema->dependencies = (object)array (
+          'exclusiveMaximum' =>
+          array (
+            0 => 'maximum',
+          ),
+          'exclusiveMinimum' =>
+          array (
+            0 => 'minimum',
+          ),
+        );
+        */
+
+
+        // draft6
+        $properties->const = (object)array();
+        $properties->contains = JsonBasicSchema::schema();
+        $properties->propertyNames = JsonBasicSchema::schema();
+
+        // draft7
+        $properties->if = JsonBasicSchema::schema();
+        $properties->then = JsonBasicSchema::schema();
+        $properties->else = JsonBasicSchema::schema();
+
+        $properties->contentEncoding = JsonBasicSchema::string();
+        $properties->contentMediaType = JsonBasicSchema::string();
+    }
+
+    /**
+     * @param string $id
+     * @return $this
+     * @codeCoverageIgnoreStart
+     */
+    public function setId($id)
+    {
+        $this->id = $id;
+        return $this;
+    }
+    /** @codeCoverageIgnoreEnd */
+
+    /**
+     * @param string $schema
+     * @return $this
+     * @codeCoverageIgnoreStart
+     */
+    public function setSchema($schema)
+    {
+        $this->schema = $schema;
+        return $this;
+    }
+    /** @codeCoverageIgnoreEnd */
+
+    /**
+     * @param string $title
+     * @return $this
+     * @codeCoverageIgnoreStart
+     */
+    public function setTitle($title)
+    {
+        $this->title = $title;
+        return $this;
+    }
+    /** @codeCoverageIgnoreEnd */
+
+    /**
+     * @param string $description
+     * @return $this
+     * @codeCoverageIgnoreStart
+     */
+    public function setDescription($description)
+    {
+        $this->description = $description;
+        return $this;
+    }
+    /** @codeCoverageIgnoreEnd */
+
+    /**
+     * @param mixed $default
+     * @return $this
+     * @codeCoverageIgnoreStart
+     */
+    public function setDefault($default)
+    {
+        $this->default = $default;
+        return $this;
+    }
+    /** @codeCoverageIgnoreEnd */
+
+    /**
+     * @param float $multipleOf
+     * @return $this
+     * @codeCoverageIgnoreStart
+     */
+    public function setMultipleOf($multipleOf)
+    {
+        $this->multipleOf = $multipleOf;
+        return $this;
+    }
+    /** @codeCoverageIgnoreEnd */
+
+    /**
+     * @param float $maximum
+     * @return $this
+     * @codeCoverageIgnoreStart
+     */
+    public function setMaximum($maximum)
+    {
+        $this->maximum = $maximum;
+        return $this;
+    }
+    /** @codeCoverageIgnoreEnd */
+
+    /**
+     * @param bool $exclusiveMaximum
+     * @return $this
+     * @codeCoverageIgnoreStart
+     */
+    public function setExclusiveMaximum($exclusiveMaximum)
+    {
+        $this->exclusiveMaximum = $exclusiveMaximum;
+        return $this;
+    }
+    /** @codeCoverageIgnoreEnd */
+
+    /**
+     * @param float $minimum
+     * @return $this
+     * @codeCoverageIgnoreStart
+     */
+    public function setMinimum($minimum)
+    {
+        $this->minimum = $minimum;
+        return $this;
+    }
+    /** @codeCoverageIgnoreEnd */
+
+    /**
+     * @param bool $exclusiveMinimum
+     * @return $this
+     * @codeCoverageIgnoreStart
+     */
+    public function setExclusiveMinimum($exclusiveMinimum)
+    {
+        $this->exclusiveMinimum = $exclusiveMinimum;
+        return $this;
+    }
+    /** @codeCoverageIgnoreEnd */
+
+    /**
+     * @param int $maxLength
+     * @return $this
+     * @codeCoverageIgnoreStart
+     */
+    public function setMaxLength($maxLength)
+    {
+        $this->maxLength = $maxLength;
+        return $this;
+    }
+    /** @codeCoverageIgnoreEnd */
+
+    /**
+     * @param int $minLength
+     * @return $this
+     * @codeCoverageIgnoreStart
+     */
+    public function setMinLength($minLength)
+    {
+        $this->minLength = $minLength;
+        return $this;
+    }
+    /** @codeCoverageIgnoreEnd */
+
+    /**
+     * @param string $pattern
+     * @return $this
+     * @codeCoverageIgnoreStart
+     */
+    public function setPattern($pattern)
+    {
+        $this->pattern = $pattern;
+        return $this;
+    }
+    /** @codeCoverageIgnoreEnd */
+
+    /**
+     * @param bool|JsonSchema $additionalItems
+     * @return $this
+     * @codeCoverageIgnoreStart
+     */
+    public function setAdditionalItems($additionalItems)
+    {
+        $this->additionalItems = $additionalItems;
+        return $this;
+    }
+    /** @codeCoverageIgnoreEnd */
+
+    /**
+     * @param JsonSchema|JsonSchema[]|array $items
+     * @return $this
+     * @codeCoverageIgnoreStart
+     */
+    public function setItems($items)
+    {
+        $this->items = $items;
+        return $this;
+    }
+    /** @codeCoverageIgnoreEnd */
+
+    /**
+     * @param int $maxItems
+     * @return $this
+     * @codeCoverageIgnoreStart
+     */
+    public function setMaxItems($maxItems)
+    {
+        $this->maxItems = $maxItems;
+        return $this;
+    }
+    /** @codeCoverageIgnoreEnd */
+
+    /**
+     * @param int $minItems
+     * @return $this
+     * @codeCoverageIgnoreStart
+     */
+    public function setMinItems($minItems)
+    {
+        $this->minItems = $minItems;
+        return $this;
+    }
+    /** @codeCoverageIgnoreEnd */
+
+    /**
+     * @param bool $uniqueItems
+     * @return $this
+     * @codeCoverageIgnoreStart
+     */
+    public function setUniqueItems($uniqueItems)
+    {
+        $this->uniqueItems = $uniqueItems;
+        return $this;
+    }
+    /** @codeCoverageIgnoreEnd */
+
+    /**
+     * @param int $maxProperties
+     * @return $this
+     * @codeCoverageIgnoreStart
+     */
+    public function setMaxProperties($maxProperties)
+    {
+        $this->maxProperties = $maxProperties;
+        return $this;
+    }
+    /** @codeCoverageIgnoreEnd */
+
+    /**
+     * @param int $minProperties
+     * @return $this
+     * @codeCoverageIgnoreStart
+     */
+    public function setMinProperties($minProperties)
+    {
+        $this->minProperties = $minProperties;
+        return $this;
+    }
+    /** @codeCoverageIgnoreEnd */
+
+    /**
+     * @param string[]|array $required
+     * @return $this
+     * @codeCoverageIgnoreStart
+     */
+    public function setRequired($required)
+    {
+        $this->required = $required;
+        return $this;
+    }
+    /** @codeCoverageIgnoreEnd */
+
+    /**
+     * @param bool|JsonSchema $additionalProperties
+     * @return $this
+     * @codeCoverageIgnoreStart
+     */
+    public function setAdditionalProperties($additionalProperties)
+    {
+        $this->additionalProperties = $additionalProperties;
+        return $this;
+    }
+    /** @codeCoverageIgnoreEnd */
+
+    /**
+     * @param JsonSchema[] $definitions
+     * @return $this
+     * @codeCoverageIgnoreStart
+     */
+    public function setDefinitions($definitions)
+    {
+        $this->definitions = $definitions;
+        return $this;
+    }
+    /** @codeCoverageIgnoreEnd */
+
+    /**
+     * @param JsonSchema[] $properties
+     * @return $this
+     * @codeCoverageIgnoreStart
+     */
+    public function setProperties($properties)
+    {
+        $this->properties = $properties;
+        return $this;
+    }
+    /** @codeCoverageIgnoreEnd */
+
+    /**
+     * @param JsonSchema[] $patternProperties
+     * @return $this
+     * @codeCoverageIgnoreStart
+     */
+    public function setPatternProperties($patternProperties)
+    {
+        $this->patternProperties = $patternProperties;
+        return $this;
+    }
+    /** @codeCoverageIgnoreEnd */
+
+    /**
+     * @param JsonSchema[]|string[][]|array[] $dependencies
+     * @return $this
+     * @codeCoverageIgnoreStart
+     */
+    public function setDependencies($dependencies)
+    {
+        $this->dependencies = $dependencies;
+        return $this;
+    }
+    /** @codeCoverageIgnoreEnd */
+
+    /**
+     * @param array $enum
+     * @return $this
+     * @codeCoverageIgnoreStart
+     */
+    public function setEnum($enum)
+    {
+        $this->enum = $enum;
+        return $this;
+    }
+    /** @codeCoverageIgnoreEnd */
+
+    /**
+     * @param array $type
+     * @return $this
+     * @codeCoverageIgnoreStart
+     */
+    public function setType($type)
+    {
+        $this->type = $type;
+        return $this;
+    }
+    /** @codeCoverageIgnoreEnd */
+
+    /**
+     * @param string $format
+     * @return $this
+     * @codeCoverageIgnoreStart
+     */
+    public function setFormat($format)
+    {
+        $this->format = $format;
+        return $this;
+    }
+    /** @codeCoverageIgnoreEnd */
+
+    /**
+     * @param string $ref
+     * @return $this
+     * @codeCoverageIgnoreStart
+     */
+    public function setRef($ref)
+    {
+        $this->ref = $ref;
+        return $this;
+    }
+    /** @codeCoverageIgnoreEnd */
+
+    /**
+     * @param JsonSchema[]|array $allOf
+     * @return $this
+     * @codeCoverageIgnoreStart
+     */
+    public function setAllOf($allOf)
+    {
+        $this->allOf = $allOf;
+        return $this;
+    }
+    /** @codeCoverageIgnoreEnd */
+
+    /**
+     * @param JsonSchema[]|array $anyOf
+     * @return $this
+     * @codeCoverageIgnoreStart
+     */
+    public function setAnyOf($anyOf)
+    {
+        $this->anyOf = $anyOf;
+        return $this;
+    }
+    /** @codeCoverageIgnoreEnd */
+
+    /**
+     * @param JsonSchema[]|array $oneOf
+     * @return $this
+     * @codeCoverageIgnoreStart
+     */
+    public function setOneOf($oneOf)
+    {
+        $this->oneOf = $oneOf;
+        return $this;
+    }
+    /** @codeCoverageIgnoreEnd */
+
+    /**
+     * @param JsonSchema $not Core schema meta-schema
+     * @return $this
+     * @codeCoverageIgnoreStart
+     */
+    public function setNot($not)
+    {
+        $this->not = $not;
+        return $this;
+    }
+    /** @codeCoverageIgnoreEnd */
+}
+
diff --git a/swaggest/json-schema/src/MagicMap.php b/swaggest/json-schema/src/MagicMap.php
new file mode 100644
index 0000000..332494a
--- /dev/null
+++ b/swaggest/json-schema/src/MagicMap.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace Swaggest\JsonSchema;
+
+class MagicMap implements \ArrayAccess, \JsonSerializable, \Iterator
+{
+    use MagicMapTrait;
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/MagicMapTrait.php b/swaggest/json-schema/src/MagicMapTrait.php
new file mode 100644
index 0000000..d2bec75
--- /dev/null
+++ b/swaggest/json-schema/src/MagicMapTrait.php
@@ -0,0 +1,133 @@
+<?php
+
+namespace Swaggest\JsonSchema;
+
+trait MagicMapTrait
+{
+    protected $__arrayOfData = array();
+
+    /**
+     * @param string $name
+     * @param mixed $column
+     * @return static
+     */
+    public function __set($name, $column)
+    {
+        $this->__arrayOfData[$name] = $column;
+        return $this;
+    }
+
+    public function &__get($name)
+    {
+        if (isset($this->__arrayOfData[$name])) {
+            return $this->__arrayOfData[$name];
+        } else {
+            $tmp = null;
+            return $tmp;
+        }
+    }
+
+    public function offsetExists($offset)
+    {
+        return array_key_exists($offset, $this->__arrayOfData);
+    }
+
+    public function &offsetGet($offset)
+    {
+        if (isset($this->__arrayOfData[$offset])) {
+            return $this->__arrayOfData[$offset];
+        } else {
+            $tmp = null;
+            return $tmp;
+        }
+    }
+
+    public function offsetSet($offset, $value)
+    {
+        $this->__set($offset, $value);
+    }
+
+    public function offsetUnset($offset)
+    {
+        unset($this->__arrayOfData[$offset]);
+    }
+
+    public function &toArray()
+    {
+        return $this->__arrayOfData;
+    }
+
+    public function jsonSerialize()
+    {
+        return (object)$this->__arrayOfData;
+    }
+
+
+    /** @var \ArrayIterator */
+    private $iterator;
+    /**
+     * Return the current element
+     * @link http://php.net/manual/en/iterator.current.php
+     * @return mixed Can return any type.
+     * @since 5.0.0
+     */
+    public function current()
+    {
+        return $this->iterator->current();
+    }
+
+    /**
+     * Move forward to next element
+     * @link http://php.net/manual/en/iterator.next.php
+     * @return void Any returned value is ignored.
+     * @since 5.0.0
+     */
+    public function next()
+    {
+        $this->iterator->next();
+    }
+
+    /**
+     * Return the key of the current element
+     * @link http://php.net/manual/en/iterator.key.php
+     * @return mixed scalar on success, or null on failure.
+     * @since 5.0.0
+     */
+    public function key()
+    {
+        return $this->iterator->key();
+    }
+
+    /**
+     * Checks if current position is valid
+     * @link http://php.net/manual/en/iterator.valid.php
+     * @return boolean The return value will be casted to boolean and then evaluated.
+     * Returns true on success or false on failure.
+     * @since 5.0.0
+     */
+    public function valid()
+    {
+        return $this->iterator->valid();
+    }
+
+    /**
+     * Rewind the Iterator to the first element
+     * @link http://php.net/manual/en/iterator.rewind.php
+     * @return void Any returned value is ignored.
+     * @since 5.0.0
+     */
+    public function rewind()
+    {
+        $this->iterator = new \ArrayIterator($this->__arrayOfData);
+    }
+
+
+    public function __isset($name)
+    {
+        if (isset($this->__arrayOfData[$name])) {
+            return true;
+        } else {
+            return isset($this->$name);
+        }
+    }
+}
diff --git a/swaggest/json-schema/src/Meta/AbstractMeta.php b/swaggest/json-schema/src/Meta/AbstractMeta.php
new file mode 100644
index 0000000..f1e4e2b
--- /dev/null
+++ b/swaggest/json-schema/src/Meta/AbstractMeta.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Swaggest\JsonSchema\Meta;
+
+
+abstract class AbstractMeta
+{
+    /**
+     * @param MetaHolder $schema
+     * @return static
+     */
+    public static function get(MetaHolder $schema)
+    {
+        return $schema->getMeta(get_called_class());
+    }
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/Meta/MetaHolder.php b/swaggest/json-schema/src/Meta/MetaHolder.php
new file mode 100644
index 0000000..4a2c4bf
--- /dev/null
+++ b/swaggest/json-schema/src/Meta/MetaHolder.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Swaggest\JsonSchema\Meta;
+
+
+interface MetaHolder
+{
+    public function addMeta($meta, $name = null);
+
+    public function getMeta($name);
+
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/NameMirror.php b/swaggest/json-schema/src/NameMirror.php
new file mode 100644
index 0000000..58c7f45
--- /dev/null
+++ b/swaggest/json-schema/src/NameMirror.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Swaggest\JsonSchema;
+
+class NameMirror
+{
+    public function __get($name)
+    {
+        return $name;
+    }
+
+    public function __set($name, $value)
+    {
+        throw new \Exception('Unexpected write to read-only structure');
+    }
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/Path/PointerUtil.php b/swaggest/json-schema/src/Path/PointerUtil.php
new file mode 100644
index 0000000..cf59777
--- /dev/null
+++ b/swaggest/json-schema/src/Path/PointerUtil.php
@@ -0,0 +1,103 @@
+<?php
+
+namespace Swaggest\JsonSchema\Path;
+
+use Swaggest\JsonDiff\JsonPointer;
+use Swaggest\JsonSchema\Schema;
+
+class PointerUtil
+{
+    /**
+     * Builds JSON pointer to schema from processing path
+     * Path example: #->properties:responses->additionalProperties:envvar->properties:schema
+     * @param string $path
+     * @param bool $isURIFragmentId
+     * @return string|null
+     */
+    public static function getSchemaPointer($path, $isURIFragmentId = false)
+    {
+        $result = self::getSchemaPointers($path, $isURIFragmentId);
+        return array_pop($result);
+    }
+
+    /**
+     * Builds JSON pointer to schema from processing path
+     * Path example: #->properties:responses->additionalProperties:envvar->properties:schema
+     * @param string $path
+     * @param bool $isURIFragmentId
+     * @return string[]
+     */
+    public static function getSchemaPointers($path, $isURIFragmentId = false)
+    {
+        $items = explode('->', $path);
+        unset($items[0]);
+        $result = array();
+        $pointer = $isURIFragmentId ? '#' : '';
+        foreach ($items as $item) {
+            $parts = explode(':', $item);
+            if (isset($parts[0])) {
+                $schemaPaths = explode('[', $parts[0], 2);
+                if (isset($schemaPaths[1])) {
+                    $schemaPaths[1] = substr(strtr($schemaPaths[1], array('~1' => '~', '~2' => ':')), 0, -1);
+                }
+
+                if ($schemaPaths[0] === Schema::PROP_REF) {
+                    $result[] = $pointer . '/' . JsonPointer::escapeSegment(Schema::PROP_REF, $isURIFragmentId);
+                    if ($schemaPaths[1][0] !== '#') { // Absolute URI.
+                        $pointer = $schemaPaths[1];
+                    } else {
+                        $pointer = self::rebuildPointer($schemaPaths[1], $isURIFragmentId);
+                    }
+                    continue;
+                }
+                $pointer .= '/' . JsonPointer::escapeSegment($schemaPaths[0], $isURIFragmentId);
+                if (isset($schemaPaths[1])) {
+                    $pointer .= '/' . JsonPointer::escapeSegment($schemaPaths[1], $isURIFragmentId);
+                } elseif (isset($parts[1])) {
+                    $pointer .= '/' . JsonPointer::escapeSegment($parts[1], $isURIFragmentId);
+                }
+            }
+        }
+        if ($pointer === '') {
+            $pointer = '/';
+        }
+        $result[] = $pointer;
+        return $result;
+    }
+
+
+    /**
+     * Builds JSON pointer to data from processing path
+     * Path example: #->properties:responses->additionalProperties:envvar->properties:schema
+     * @param string $path
+     * @param bool $isURIFragmentId
+     * @return string
+     */
+    public static function getDataPointer($path, $isURIFragmentId = false)
+    {
+        $items = explode('->', $path);
+        unset($items[0]);
+        $result = $isURIFragmentId ? '#' : '';
+        foreach ($items as $item) {
+            $parts = explode(':', $item);
+            if (isset($parts[1])) {
+                $result .= '/' . JsonPointer::escapeSegment($parts[1], $isURIFragmentId);
+            }
+        }
+        if ($result === '') {
+            return '/';
+        }
+        return $result;
+    }
+
+    private static function rebuildPointer($pointer, $isURIFragmentId = false)
+    {
+        $parts = JsonPointer::splitPath($pointer);
+        $result = $isURIFragmentId ? '#' : '';
+        foreach ($parts as $item) {
+            $result .= '/' . JsonPointer::escapeSegment($item, $isURIFragmentId);
+        }
+        return $result;
+    }
+
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/RefResolver.php b/swaggest/json-schema/src/RefResolver.php
new file mode 100644
index 0000000..819ce0a
--- /dev/null
+++ b/swaggest/json-schema/src/RefResolver.php
@@ -0,0 +1,267 @@
+<?php
+
+namespace Swaggest\JsonSchema;
+
+use PhpLang\ScopeExit;
+use Swaggest\JsonDiff\JsonPointer;
+use Swaggest\JsonSchema\Constraint\Ref;
+use Swaggest\JsonSchema\RemoteRef\BasicFetcher;
+
+class RefResolver
+{
+    public $resolutionScope = '';
+    public $url;
+    /** @var null|RefResolver */
+    private $rootResolver;
+
+    /**
+     * @param mixed $resolutionScope
+     * @return string previous value
+     */
+    public function setResolutionScope($resolutionScope)
+    {
+        $rootResolver = $this->rootResolver ? $this->rootResolver : $this;
+        if ($resolutionScope === $rootResolver->resolutionScope) {
+            return $resolutionScope;
+        }
+        $prev = $rootResolver->resolutionScope;
+        $rootResolver->resolutionScope = $resolutionScope;
+        return $prev;
+    }
+
+    /**
+     * @return string
+     */
+    public function getResolutionScope()
+    {
+        $rootResolver = $this->rootResolver ? $this->rootResolver : $this;
+        return $rootResolver->resolutionScope;
+    }
+
+
+    /**
+     * @param string $id
+     * @return string
+     */
+    public function updateResolutionScope($id)
+    {
+        $id = rtrim($id, '#'); // safe to trim because # in hashCode must be urlencoded to %23
+        $rootResolver = $this->rootResolver ? $this->rootResolver : $this;
+        if ((strpos($id, '://') !== false) || 'urn:' === substr($id, 0, 4)) {
+            $prev = $rootResolver->setResolutionScope($id);
+        } else {
+            $id = Helper::resolveURI($rootResolver->resolutionScope, $id);
+            $prev = $rootResolver->setResolutionScope($id);
+        }
+
+        return $prev;
+    }
+
+    public function setupResolutionScope($id, $data)
+    {
+        $rootResolver = $this->rootResolver ? $this->rootResolver : $this;
+
+        $prev = $rootResolver->updateResolutionScope($id);
+
+        $refParts = explode('#', $rootResolver->resolutionScope, 2);
+
+        if ($refParts[0]) { // external uri
+            $resolver = &$rootResolver->remoteRefResolvers[$refParts[0]];
+            if ($resolver === null) {
+                $resolver = new RefResolver();
+                $resolver->rootResolver = $rootResolver;
+                $resolver->url = $refParts[0];
+                $this->remoteRefResolvers[$refParts[0]] = $resolver;
+            }
+        } else { // local uri
+            $resolver = $this;
+        }
+
+        if (empty($refParts[1])) {
+            $resolver->rootData = $data;
+        } else {
+            $refPath = '#' . $refParts[1];
+            $resolver->refs[$refPath] = new Ref($refPath, $data);
+        }
+
+        return $prev;
+    }
+
+    private $rootData;
+
+    /** @var Ref[] */
+    private $refs = array();
+
+    /** @var RefResolver[]|null[] */
+    private $remoteRefResolvers = array();
+
+    /** @var RemoteRefProvider */
+    private $refProvider;
+
+    /**
+     * RefResolver constructor.
+     * @param JsonSchema $rootData
+     */
+    public function __construct($rootData = null)
+    {
+        $this->rootData = $rootData;
+    }
+
+    public function setRootData($rootData)
+    {
+        $this->rootData = $rootData;
+        return $this;
+    }
+
+
+    public function setRemoteRefProvider(RemoteRefProvider $provider)
+    {
+        $this->refProvider = $provider;
+        return $this;
+    }
+
+    private function getRefProvider()
+    {
+        if (null === $this->refProvider) {
+            $this->refProvider = new BasicFetcher();
+        }
+        return $this->refProvider;
+    }
+
+    /**
+     * @param string $referencePath
+     * @return Ref
+     * @throws Exception
+     */
+    public function resolveReference($referencePath)
+    {
+        if ($this->resolutionScope) {
+            $referencePath = Helper::resolveURI($this->resolutionScope, $referencePath);
+        }
+
+        $refParts = explode('#', $referencePath, 2);
+        $url = rtrim($refParts[0], '#');
+        $refLocalPath = isset($refParts[1]) ? '#' . $refParts[1] : '#';
+
+        if ($url === $this->url) {
+            $referencePath = $refLocalPath;
+        }
+
+        /** @var null|Ref $ref */
+        $ref = &$this->refs[$referencePath];
+
+        $refResolver = $this;
+
+        if (null === $ref) {
+            if ($referencePath[0] === '#') {
+                if ($referencePath === '#') {
+                    $ref = new Ref($referencePath, $refResolver->rootData);
+                } else {
+                    $ref = new Ref($referencePath);
+                    try {
+                        $path = JsonPointer::splitPath($referencePath);
+                    } catch (\Swaggest\JsonDiff\Exception $e) {
+                        throw new InvalidValue('Invalid reference: ' . $referencePath . ', ' . $e->getMessage());
+                    }
+
+                    /** @var JsonSchema|\stdClass $branch */
+                    $branch = &$refResolver->rootData;
+                    while (!empty($path)) {
+                        if (isset($branch->{Schema::PROP_ID_D4}) && is_string($branch->{Schema::PROP_ID_D4})) {
+                            $refResolver->updateResolutionScope($branch->{Schema::PROP_ID_D4});
+                        }
+                        if (isset($branch->{Schema::PROP_ID}) && is_string($branch->{Schema::PROP_ID})) {
+                            $refResolver->updateResolutionScope($branch->{Schema::PROP_ID});
+                        }
+
+                        $folder = array_shift($path);
+
+                        if ($branch instanceof \stdClass && isset($branch->$folder)) {
+                            $branch = &$branch->$folder;
+                        } elseif (is_array($branch) && isset($branch[$folder])) {
+                            $branch = &$branch[$folder];
+                        } else {
+                            throw new InvalidValue('Could not resolve ' . $referencePath . '@' . $this->getResolutionScope() . ': ' . $folder);
+                        }
+                    }
+                    $ref->setData($branch);
+                }
+            } else {
+                if ($url !== $this->url) {
+                    $rootResolver = $this->rootResolver ? $this->rootResolver : $this;
+                    /** @var null|RefResolver $refResolver */
+                    $refResolver = &$rootResolver->remoteRefResolvers[$url];
+                    $this->setResolutionScope($url);
+                    if (null === $refResolver) {
+                        $rootData = $rootResolver->getRefProvider()->getSchemaData($url);
+                        if ($rootData === null || $rootData === false) {
+                            throw new Exception("Failed to decode content from $url", Exception::RESOLVE_FAILED);
+                        }
+
+                        $refResolver = new RefResolver($rootData);
+                        $refResolver->rootResolver = $rootResolver;
+                        $refResolver->refProvider = $this->refProvider;
+                        $refResolver->url = $url;
+                        $rootResolver->setResolutionScope($url);
+                        $options = new Context(); // todo pass real ctx here, v0.13.0
+                        $rootResolver->preProcessReferences($rootData, $options);
+                    }
+                }
+
+                $ref = $refResolver->resolveReference($refLocalPath);
+            }
+        }
+
+        return $ref;
+    }
+
+
+    /**
+     * @param mixed $data
+     * @param Context $options
+     * @param int $nestingLevel
+     * @throws Exception
+     */
+    public function preProcessReferences($data, Context $options, $nestingLevel = 0)
+    {
+        if ($nestingLevel > 200) {
+            throw new Exception('Too deep nesting level', Exception::DEEP_NESTING);
+        }
+        if (is_array($data)) {
+            foreach ($data as $key => $item) {
+                $this->preProcessReferences($item, $options, $nestingLevel + 1);
+            }
+        } elseif ($data instanceof \stdClass) {
+            /** @var JsonSchema $data */
+            if (
+                isset($data->{Schema::PROP_ID_D4})
+                && is_string($data->{Schema::PROP_ID_D4})
+                && (($options->version === Schema::VERSION_AUTO) || $options->version === Schema::VERSION_DRAFT_04)
+            ) {
+                $prev = $this->setupResolutionScope($data->{Schema::PROP_ID_D4}, $data);
+                /** @noinspection PhpUnusedLocalVariableInspection */
+                $_ = new ScopeExit(function () use ($prev) {
+                    $this->setResolutionScope($prev);
+                });
+            }
+
+            if (isset($data->{Schema::PROP_ID})
+                && is_string($data->{Schema::PROP_ID})
+                && (($options->version === Schema::VERSION_AUTO) || $options->version >= Schema::VERSION_DRAFT_06)
+            ) {
+                $prev = $this->setupResolutionScope($data->{Schema::PROP_ID}, $data);
+                /** @noinspection PhpUnusedLocalVariableInspection */
+                $_ = new ScopeExit(function () use ($prev) {
+                    $this->setResolutionScope($prev);
+                });
+            }
+
+
+            foreach ((array)$data as $key => $value) {
+                $this->preProcessReferences($value, $options, $nestingLevel + 1);
+            }
+        }
+    }
+
+
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/RemoteRef/BasicFetcher.php b/swaggest/json-schema/src/RemoteRef/BasicFetcher.php
new file mode 100644
index 0000000..afcbde1
--- /dev/null
+++ b/swaggest/json-schema/src/RemoteRef/BasicFetcher.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Swaggest\JsonSchema\RemoteRef;
+
+use Swaggest\JsonSchema\RemoteRefProvider;
+
+class BasicFetcher implements RemoteRefProvider
+{
+    public function getSchemaData($url)
+    {
+        $arrContextOptions = [
+            "ssl" => [
+                "verify_peer" => false,
+                "verify_peer_name" => false,
+            ],
+        ];
+
+        if ($data = file_get_contents(rawurldecode($url), false, stream_context_create($arrContextOptions))) {
+            return json_decode($data);
+        }
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/RemoteRef/Preloaded.php b/swaggest/json-schema/src/RemoteRef/Preloaded.php
new file mode 100644
index 0000000..e5b4a38
--- /dev/null
+++ b/swaggest/json-schema/src/RemoteRef/Preloaded.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace Swaggest\JsonSchema\RemoteRef;
+
+use Swaggest\JsonSchema\Context;
+use Swaggest\JsonSchema\RefResolver;
+use Swaggest\JsonSchema\RemoteRefProvider;
+use Swaggest\JsonSchema\Schema;
+
+class Preloaded implements RemoteRefProvider
+{
+    private $storage;
+
+    private $schemaFiles;
+
+    public function __construct()
+    {
+        $this->schemaFiles = array(
+            'http://json-schema.org/draft-04/schema' => __DIR__ . '/../../spec/json-schema.json',
+            'http://json-schema.org/draft-06/schema' => __DIR__ . '/../../spec/json-schema-draft6.json',
+            'http://json-schema.org/draft-07/schema' => __DIR__ . '/../../spec/json-schema-draft7.json',
+        );
+    }
+
+    public function getSchemaData($url)
+    {
+        if (isset($this->storage[$url])) {
+            return $this->storage[$url];
+        } elseif (isset($this->schemaFiles[$url])) {
+            if ($data = file_get_contents($this->schemaFiles[$url])) {
+                $this->storage[$url] = json_decode($data);
+            } else {
+                return false;
+            }
+            return $this->storage[$url];
+        }
+        return false;
+    }
+
+    /**
+     * @param RefResolver $refResolver
+     * @param Context|null $options
+     * @throws \Swaggest\JsonSchema\Exception
+     */
+    public function populateSchemas(RefResolver $refResolver, Context $options = null)
+    {
+        if ($options === null) {
+            $options = new Context();
+            $options->refResolver = $refResolver;
+            $options->version = Schema::VERSION_AUTO;
+        }
+
+        $prev = $refResolver->getResolutionScope();
+        foreach ($this->storage as $url => $schemaData) {
+            $refResolver->setupResolutionScope($url, $schemaData);
+            $refResolver->setResolutionScope($url);
+            $refResolver->preProcessReferences($schemaData, $options);
+        }
+        $refResolver->setResolutionScope($prev);
+    }
+
+    public function setSchemaData($url, $schemaData)
+    {
+        $this->storage[rtrim($url, '#')] = $schemaData;
+        return $this;
+    }
+
+    public function setSchemaFile($url, $path)
+    {
+        $this->schemaFiles[rtrim($url, '#')] = $path;
+        return $this;
+    }
+
+
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/RemoteRefProvider.php b/swaggest/json-schema/src/RemoteRefProvider.php
new file mode 100644
index 0000000..645bec1
--- /dev/null
+++ b/swaggest/json-schema/src/RemoteRefProvider.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace Swaggest\JsonSchema;
+
+
+interface RemoteRefProvider
+{
+    /**
+     * @param string $url
+     * @return \stdClass|false json_decode of $url resource content
+     */
+    public function getSchemaData($url);
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/Schema.php b/swaggest/json-schema/src/Schema.php
new file mode 100644
index 0000000..e9fbab6
--- /dev/null
+++ b/swaggest/json-schema/src/Schema.php
@@ -0,0 +1,1445 @@
+<?php
+
+namespace Swaggest\JsonSchema;
+
+
+use PhpLang\ScopeExit;
+use Swaggest\JsonDiff\JsonDiff;
+use Swaggest\JsonDiff\JsonPointer;
+use Swaggest\JsonSchema\Constraint\Content;
+use Swaggest\JsonSchema\Constraint\Format;
+use Swaggest\JsonSchema\Constraint\Properties;
+use Swaggest\JsonSchema\Constraint\Type;
+use Swaggest\JsonSchema\Constraint\UniqueItems;
+use Swaggest\JsonSchema\Exception\ArrayException;
+use Swaggest\JsonSchema\Exception\ConstException;
+use Swaggest\JsonSchema\Exception\EnumException;
+use Swaggest\JsonSchema\Exception\LogicException;
+use Swaggest\JsonSchema\Exception\NumericException;
+use Swaggest\JsonSchema\Exception\ObjectException;
+use Swaggest\JsonSchema\Exception\StringException;
+use Swaggest\JsonSchema\Exception\TypeException;
+use Swaggest\JsonSchema\Meta\MetaHolder;
+use Swaggest\JsonSchema\Path\PointerUtil;
+use Swaggest\JsonSchema\Structure\ClassStructure;
+use Swaggest\JsonSchema\Structure\Egg;
+use Swaggest\JsonSchema\Structure\ObjectItem;
+use Swaggest\JsonSchema\Structure\ObjectItemContract;
+use Swaggest\JsonSchema\Structure\WithResolvedValue;
+
+/**
+ * Class Schema
+ * @package Swaggest\JsonSchema
+ */
+class Schema extends JsonSchema implements MetaHolder, SchemaContract
+{
+    const ENUM_NAMES_PROPERTY = 'x-enum-names';
+    const CONST_PROPERTY = 'const';
+
+    const DEFAULT_MAPPING = 'default';
+
+    const VERSION_AUTO = 'a';
+    const VERSION_DRAFT_04 = 4;
+    const VERSION_DRAFT_06 = 6;
+    const VERSION_DRAFT_07 = 7;
+
+    const PROP_REF = '$ref';
+    const PROP_ID = '$id';
+    const PROP_ID_D4 = 'id';
+
+    // Object
+    /** @var null|Properties */
+    public $properties;
+    /** @var SchemaContract|bool */
+    public $additionalProperties;
+    /** @var SchemaContract[]|Properties */
+    public $patternProperties;
+    /** @var string[][]|Schema[]|\stdClass */
+    public $dependencies;
+
+    // Array
+    /** @var null|SchemaContract|SchemaContract[] */
+    public $items;
+    /** @var null|SchemaContract|bool */
+    public $additionalItems;
+
+    /** @var SchemaContract[] */
+    public $allOf;
+    /** @var SchemaContract */
+    public $not;
+    /** @var SchemaContract[] */
+    public $anyOf;
+    /** @var SchemaContract[] */
+    public $oneOf;
+
+    /** @var SchemaContract */
+    public $if;
+    /** @var SchemaContract */
+    public $then;
+    /** @var SchemaContract */
+    public $else;
+
+
+    public $objectItemClass;
+
+    /**
+     * @todo check usages/deprecate
+     * @var bool
+     */
+    private $useObjectAsArray = false;
+
+    private $__booleanSchema;
+
+    public function addPropertyMapping($dataName, $propertyName, $mapping = self::DEFAULT_MAPPING)
+    {
+        if (null === $this->properties) {
+            $this->properties = new Properties();
+        }
+
+        $this->properties->addPropertyMapping($dataName, $propertyName, $mapping);
+        return $this;
+    }
+
+    /**
+     * @param mixed $data
+     * @param Context|null $options
+     * @return SchemaContract
+     * @throws Exception
+     * @throws InvalidValue
+     * @throws \Exception
+     */
+    public static function import($data, Context $options = null)
+    {
+        if (null === $options) {
+            $options = new Context();
+        }
+
+        $options->applyDefaults = false;
+
+        if (isset($options->schemasCache) && is_object($data)) {
+            if ($options->schemasCache->contains($data)) {
+                return $options->schemasCache->offsetGet($data);
+            } else {
+                $schema = parent::import($data, $options);
+                $options->schemasCache->attach($data, $schema);
+                return $schema;
+            }
+        }
+
+        // string $data is expected to be $ref uri
+        if (is_string($data)) {
+            $data = (object)array(self::PROP_REF => $data);
+        }
+
+        $data = self::unboolSchema($data);
+        if ($data instanceof SchemaContract) {
+            return $data;
+        }
+
+        return parent::import($data, $options);
+    }
+
+    /**
+     * @param mixed $data
+     * @param Context|null $options
+     * @return array|mixed|null|object|\stdClass
+     * @throws Exception
+     * @throws InvalidValue
+     * @throws \Exception
+     */
+    public function in($data, Context $options = null)
+    {
+        if (null !== $this->__booleanSchema) {
+            if ($this->__booleanSchema) {
+                return $data;
+            } elseif (empty($options->skipValidation)) {
+                $this->fail(new InvalidValue('Denied by false schema'), '#');
+            }
+        }
+
+        if ($options === null) {
+            $options = new Context();
+        }
+
+        $options->import = true;
+
+        if ($options->refResolver === null) {
+            $options->refResolver = new RefResolver($data);
+        } else {
+            $options->refResolver->setRootData($data);
+        }
+
+        if ($options->remoteRefProvider) {
+            $options->refResolver->setRemoteRefProvider($options->remoteRefProvider);
+        }
+
+        $options->refResolver->preProcessReferences($data, $options);
+
+        return $this->process($data, $options, '#');
+    }
+
+
+    /**
+     * @param mixed $data
+     * @param Context|null $options
+     * @return array|mixed|null|object|\stdClass
+     * @throws InvalidValue
+     * @throws \Exception
+     */
+    public function out($data, Context $options = null)
+    {
+        if ($options === null) {
+            $options = new Context();
+        }
+
+        $options->circularReferences = new \SplObjectStorage();
+        $options->import = false;
+        return $this->process($data, $options);
+    }
+
+    /**
+     * @param mixed $data
+     * @param Context $options
+     * @param string $path
+     * @throws InvalidValue
+     * @throws \Exception
+     */
+    private function processType(&$data, Context $options, $path = '#')
+    {
+        if ($options->tolerateStrings && is_string($data)) {
+            $valid = Type::readString($this->type, $data);
+        } else {
+            $valid = Type::isValid($this->type, $data, $options->version);
+        }
+        if (!$valid) {
+            $this->fail(new TypeException(ucfirst(
+                    implode(', ', is_array($this->type) ? $this->type : array($this->type))
+                    . ' expected, ' . json_encode($data) . ' received')
+            ), $path);
+        }
+    }
+
+    /**
+     * @param mixed $data
+     * @param string $path
+     * @throws InvalidValue
+     * @throws \Exception
+     */
+    private function processEnum($data, $path = '#')
+    {
+        $enumOk = false;
+        foreach ($this->enum as $item) {
+            if ($item === $data ||
+                ( // Int and float equality check.
+                    (is_int($item) || is_float($item)) &&
+                    (is_int($data) || is_float($data)) &&
+                    $item == $data
+                )
+            ) {
+                $enumOk = true;
+                break;
+            } else {
+                if (is_array($item) || is_object($item)) {
+                    $diff = new JsonDiff($item, $data, JsonDiff::STOP_ON_DIFF);
+                    if ($diff->getDiffCnt() === 0) {
+                        $enumOk = true;
+                        break;
+                    }
+                }
+            }
+        }
+        if (!$enumOk) {
+            $this->fail(new EnumException('Enum failed, enum: ' . json_encode($this->enum) . ', data: ' . json_encode($data)), $path);
+        }
+    }
+
+    /**
+     * @param mixed $data
+     * @param string $path
+     * @throws InvalidValue
+     * @throws \Swaggest\JsonDiff\Exception
+     */
+    private function processConst($data, $path)
+    {
+        if ($this->const !== $data &&
+            !( // Int and float equality.
+                (is_int($this->const) || is_float($this->const)) &&
+                (is_int($data) || is_float($data)) &&
+                $this->const == $data
+            )
+        ) {
+            if ((is_object($this->const) && is_object($data))
+                || (is_array($this->const) && is_array($data))) {
+                $diff = new JsonDiff($this->const, $data,
+                    JsonDiff::STOP_ON_DIFF);
+                if ($diff->getDiffCnt() != 0) {
+                    $this->fail(new ConstException('Const failed'), $path);
+                }
+            } else {
+                $this->fail(new ConstException('Const failed'), $path);
+            }
+        }
+    }
+
+    /**
+     * @param mixed $data
+     * @param Context $options
+     * @param string $path
+     * @throws InvalidValue
+     * @throws \Exception
+     * @throws \Swaggest\JsonDiff\Exception
+     */
+    private function processNot($data, Context $options, $path)
+    {
+        $exception = false;
+        try {
+            self::unboolSchema($this->not)->process($data, $options, $path . '->not');
+        } catch (InvalidValue $exception) {
+            // Expected exception
+        }
+        if ($exception === false) {
+            $this->fail(new LogicException('Not ' . json_encode($this->not) . ' expected, ' . json_encode($data) . ' received'), $path . '->not');
+        }
+    }
+
+    /**
+     * @param string $data
+     * @param string $path
+     * @throws InvalidValue
+     */
+    private function processString($data, $path)
+    {
+        if ($this->minLength !== null) {
+            if (mb_strlen($data, 'UTF-8') < $this->minLength) {
+                $this->fail(new StringException('String is too short', StringException::TOO_SHORT), $path);
+            }
+        }
+        if ($this->maxLength !== null) {
+            if (mb_strlen($data, 'UTF-8') > $this->maxLength) {
+                $this->fail(new StringException('String is too long', StringException::TOO_LONG), $path);
+            }
+        }
+        if ($this->pattern !== null) {
+            if (0 === preg_match(Helper::toPregPattern($this->pattern), $data)) {
+                $this->fail(new StringException(json_encode($data) . ' does not match to '
+                    . $this->pattern, StringException::PATTERN_MISMATCH), $path);
+            }
+        }
+        if ($this->format !== null) {
+            $validationError = Format::validationError($this->format, $data);
+            if ($validationError !== null) {
+                if (!($this->format === "uri" && substr($path, -3) === ':id')) {
+                    $this->fail(new StringException($validationError), $path);
+                }
+            }
+        }
+    }
+
+    /**
+     * @param float|int $data
+     * @param string $path
+     * @throws InvalidValue
+     */
+    private function processNumeric($data, $path)
+    {
+        if ($this->multipleOf !== null) {
+            $div = $data / $this->multipleOf;
+            if ($div != (int)$div) {
+                $this->fail(new NumericException($data . ' is not multiple of ' . $this->multipleOf, NumericException::MULTIPLE_OF), $path);
+            }
+        }
+
+        if ($this->exclusiveMaximum !== null && !is_bool($this->exclusiveMaximum)) {
+            if ($data >= $this->exclusiveMaximum) {
+                $this->fail(new NumericException(
+                    'Value less or equal than ' . $this->exclusiveMaximum . ' expected, ' . $data . ' received',
+                    NumericException::MAXIMUM), $path);
+            }
+        }
+
+        if ($this->exclusiveMinimum !== null && !is_bool($this->exclusiveMinimum)) {
+            if ($data <= $this->exclusiveMinimum) {
+                $this->fail(new NumericException(
+                    'Value more or equal than ' . $this->exclusiveMinimum . ' expected, ' . $data . ' received',
+                    NumericException::MINIMUM), $path);
+            }
+        }
+
+        if ($this->maximum !== null) {
+            if ($this->exclusiveMaximum === true) {
+                if ($data >= $this->maximum) {
+                    $this->fail(new NumericException(
+                        'Value less or equal than ' . $this->maximum . ' expected, ' . $data . ' received',
+                        NumericException::MAXIMUM), $path);
+                }
+            } else {
+                if ($data > $this->maximum) {
+                    $this->fail(new NumericException(
+                        'Value less than ' . $this->maximum . ' expected, ' . $data . ' received',
+                        NumericException::MAXIMUM), $path);
+                }
+            }
+        }
+
+        if ($this->minimum !== null) {
+            if ($this->exclusiveMinimum === true) {
+                if ($data <= $this->minimum) {
+                    $this->fail(new NumericException(
+                        'Value more or equal than ' . $this->minimum . ' expected, ' . $data . ' received',
+                        NumericException::MINIMUM), $path);
+                }
+            } else {
+                if ($data < $this->minimum) {
+                    $this->fail(new NumericException(
+                        'Value more than ' . $this->minimum . ' expected, ' . $data . ' received',
+                        NumericException::MINIMUM), $path);
+                }
+            }
+        }
+    }
+
+    /**
+     * @param mixed $data
+     * @param Context $options
+     * @param string $path
+     * @return array|mixed|null|object|\stdClass
+     * @throws InvalidValue
+     * @throws \Exception
+     * @throws \Swaggest\JsonDiff\Exception
+     */
+    private function processOneOf($data, Context $options, $path)
+    {
+        $successes = 0;
+        $failures = '';
+        $subErrors = [];
+        $skipValidation = false;
+        if ($options->skipValidation) {
+            $skipValidation = true;
+            $options->skipValidation = false;
+        }
+
+        $result = $data;
+        foreach ($this->oneOf as $index => $item) {
+            try {
+                $result = self::unboolSchema($item)->process($data, $options, $path . '->oneOf[' . $index . ']');
+                $successes++;
+                if ($successes > 1 || $options->skipValidation) {
+                    break;
+                }
+            } catch (InvalidValue $exception) {
+                $subErrors[$index] = $exception;
+                $failures .= ' ' . $index . ': ' . Helper::padLines(' ', $exception->getMessage()) . "\n";
+                // Expected exception
+            }
+        }
+        if ($skipValidation) {
+            $options->skipValidation = true;
+            if ($successes === 0) {
+                $result = self::unboolSchema($this->oneOf[0])->process($data, $options, $path . '->oneOf[0]');
+            }
+        }
+
+        if (!$options->skipValidation) {
+            if ($successes === 0) {
+                $exception = new LogicException('No valid results for oneOf {' . "\n" . substr($failures, 0, -1) . "\n}");
+                $exception->error = 'No valid results for oneOf';
+                $exception->subErrors = $subErrors;
+                $this->fail($exception, $path);
+            } elseif ($successes > 1) {
+                $exception = new LogicException('More than 1 valid result for oneOf: '
+                    . $successes . '/' . count($this->oneOf) . ' valid results for oneOf {'
+                    . "\n" . substr($failures, 0, -1) . "\n}");
+                $exception->error = 'More than 1 valid result for oneOf';
+                $exception->subErrors = $subErrors;
+                $this->fail($exception, $path);
+            }
+        }
+        return $result;
+    }
+
+    /**
+     * @param mixed $data
+     * @param Context $options
+     * @param string $path
+     * @return array|mixed|null|object|\stdClass
+     * @throws InvalidValue
+     * @throws \Exception
+     * @throws \Swaggest\JsonDiff\Exception
+     */
+    private function processAnyOf($data, Context $options, $path)
+    {
+        $successes = 0;
+        $failures = '';
+        $subErrors = [];
+        $result = $data;
+        foreach ($this->anyOf as $index => $item) {
+            try {
+                $result = self::unboolSchema($item)->process($data, $options, $path . '->anyOf[' . $index . ']');
+                $successes++;
+                if ($successes) {
+                    break;
+                }
+            } catch (InvalidValue $exception) {
+                $subErrors[$index] = $exception;
+                $failures .= ' ' . $index . ': ' . $exception->getMessage() . "\n";
+                // Expected exception
+            }
+        }
+        if (!$successes && !$options->skipValidation) {
+            $exception = new LogicException('No valid results for anyOf {' . "\n"
+                . substr(Helper::padLines(' ', $failures, false), 0, -1)
+                . "\n}");
+            $exception->error = 'No valid results for anyOf';
+            $exception->subErrors = $subErrors;
+            $this->fail($exception, $path);
+        }
+        return $result;
+    }
+
+    /**
+     * @param mixed $data
+     * @param Context $options
+     * @param string $path
+     * @return array|mixed|null|object|\stdClass
+     * @throws InvalidValue
+     * @throws \Exception
+     * @throws \Swaggest\JsonDiff\Exception
+     */
+    private function processAllOf($data, Context $options, $path)
+    {
+        $result = $data;
+        foreach ($this->allOf as $index => $item) {
+            $result = self::unboolSchema($item)->process($data, $options, $path . '->allOf[' . $index . ']');
+        }
+        return $result;
+    }
+
+    /**
+     * @param mixed $data
+     * @param Context $options
+     * @param string $path
+     * @return array|mixed|null|object|\stdClass
+     * @throws InvalidValue
+     * @throws \Exception
+     * @throws \Swaggest\JsonDiff\Exception
+     */
+    private function processIf($data, Context $options, $path)
+    {
+        $valid = true;
+        try {
+            self::unboolSchema($this->if)->process($data, $options, $path . '->if');
+        } catch (InvalidValue $exception) {
+            $valid = false;
+        }
+        if ($valid) {
+            if ($this->then !== null) {
+                return self::unboolSchema($this->then)->process($data, $options, $path . '->then');
+            }
+        } else {
+            if ($this->else !== null) {
+                return self::unboolSchema($this->else)->process($data, $options, $path . '->else');
+            }
+        }
+        return null;
+    }
+
+    /**
+     * @param array $array
+     * @param Context $options
+     * @param string $path
+     * @throws InvalidValue
+     */
+    private function processObjectRequired($array, Context $options, $path)
+    {
+        foreach ($this->required as $item) {
+            if (!array_key_exists($item, $array)) {
+                $this->fail(new ObjectException('Required property missing: ' . $item . ', data: ' . json_encode($array, JSON_UNESCAPED_SLASHES), ObjectException::REQUIRED), $path);
+            }
+        }
+    }
+
+    /**
+     * @param object $data
+     * @param Context $options
+     * @param string $path
+     * @param ObjectItemContract|null $result
+     * @return array|null|ClassStructure|ObjectItemContract|SchemaContract
+     * @throws InvalidValue
+     * @throws \Exception
+     * @throws \Swaggest\JsonDiff\Exception
+     */
+    private function processObject($data, Context $options, $path, $result = null)
+    {
+        $import = $options->import;
+
+        $hasMapping = $this->properties !== null && isset($this->properties->__dataToProperty[$options->mapping]);
+
+        $array = !$data instanceof \stdClass ? get_object_vars($data) : (array)$data;
+
+        // convert imported data to default mapping before validation
+        if ($import && $options->mapping !== self::DEFAULT_MAPPING) {
+            if ($this->properties !== null && isset($this->properties->__dataToProperty[$options->mapping])) {
+                foreach ($this->properties->__dataToProperty[$options->mapping] as $dataName => $propertyName) {
+                    if (!isset($array[$dataName])) {
+                        continue;
+                    }
+
+                    $propertyName = isset($this->properties->__propertyToData[self::DEFAULT_MAPPING][$propertyName])
+                        ? $this->properties->__propertyToData[self::DEFAULT_MAPPING][$propertyName]
+                        : $propertyName;
+                    if ($propertyName !== $dataName) {
+                        $array[$propertyName] = $array[$dataName];
+                        unset($array[$dataName]);
+                    }
+                }
+            }
+        }
+
+        if (!$options->skipValidation && $this->required !== null) {
+            $this->processObjectRequired($array, $options, $path);
+        }
+
+        // build result entity
+        if ($import) {
+            if (!$options->validateOnly) {
+
+                if ($this->useObjectAsArray) {
+                    $result = array();
+                } else {
+                    //* todo check performance impact
+                    if (null === $this->objectItemClass) {
+                        if (!$result instanceof ObjectItemContract) {
+                            $result = new ObjectItem();
+                            $result->setDocumentPath($path);
+                        }
+                    } else {
+                        $className = $this->objectItemClass;
+                        if ($options->objectItemClassMapping !== null) {
+                            if (isset($options->objectItemClassMapping[$className])) {
+                                $className = $options->objectItemClassMapping[$className];
+                            }
+                        }
+                        if (null === $result || get_class($result) !== $className) {
+                            $result = new $className;
+                            //* todo check performance impact
+                            if ($result instanceof ClassStructure) {
+                                $result->setDocumentPath($path);
+                                if ($result->__validateOnSet) {
+                                    $result->__validateOnSet = false;
+                                    /** @noinspection PhpUnusedLocalVariableInspection */
+                                    /* todo check performance impact
+                                    $validateOnSetHandler = new ScopeExit(function () use ($result) {
+                                        $result->__validateOnSet = true;
+                                    });
+                                    //*/
+                                }
+                            }
+                            //*/
+                        }
+                    }
+                    //*/
+                }
+            }
+        }
+
+        // @todo better check for schema id
+
+        if ($import
+            && isset($array[Schema::PROP_ID_D4])
+            && ($options->version === Schema::VERSION_DRAFT_04 || $options->version === Schema::VERSION_AUTO)
+            && is_string($array[Schema::PROP_ID_D4])) {
+            $id = $array[Schema::PROP_ID_D4];
+            $refResolver = $options->refResolver;
+            $parentScope = $refResolver->updateResolutionScope($id);
+            /** @noinspection PhpUnusedLocalVariableInspection */
+            $defer = new ScopeExit(function () use ($parentScope, $refResolver) {
+                $refResolver->setResolutionScope($parentScope);
+            });
+        }
+
+        if ($import
+            && isset($array[self::PROP_ID])
+            && ($options->version >= Schema::VERSION_DRAFT_06 || $options->version === Schema::VERSION_AUTO)
+            && is_string($array[self::PROP_ID])) {
+            $id = $array[self::PROP_ID];
+            $refResolver = $options->refResolver;
+            $parentScope = $refResolver->updateResolutionScope($id);
+            /** @noinspection PhpUnusedLocalVariableInspection */
+            $defer = new ScopeExit(function () use ($parentScope, $refResolver) {
+                $refResolver->setResolutionScope($parentScope);
+            });
+        }
+
+        // check $ref
+        if ($import) {
+            try {
+
+                $refProperty = null;
+                $dereference = $options->dereference;
+
+                if ($this->properties !== null && isset($array[self::PROP_REF])) {
+                    $refPropName = self::PROP_REF;
+                    if ($hasMapping) {
+                        if (isset($this->properties->__dataToProperty[$options->mapping][self::PROP_REF])) {
+                            $refPropName = $this->properties->__dataToProperty[$options->mapping][self::PROP_REF];
+                        }
+                    }
+
+                    $refProperty = $this->properties[$refPropName];
+
+                    if (isset($refProperty)) {
+                        $dereference = $refProperty->format === Format::URI_REFERENCE;
+                    }
+                }
+
+                if (
+                    isset($array[self::PROP_REF])
+                    && is_string($array[self::PROP_REF])
+                    && $dereference
+                ) {
+                    $refString = $array[self::PROP_REF];
+
+                    // todo check performance impact
+                    if ($refString === 'http://json-schema.org/draft-04/schema#'
+                        || $refString === 'http://json-schema.org/draft-06/schema#'
+                        || $refString === 'http://json-schema.org/draft-07/schema#') {
+                        return Schema::schema();
+                    }
+
+                    // TODO consider process # by reference here ?
+                    $refResolver = $options->refResolver;
+                    $preRefScope = $refResolver->getResolutionScope();
+                    /** @noinspection PhpUnusedLocalVariableInspection */
+                    $deferRefScope = new ScopeExit(function () use ($preRefScope, $refResolver) {
+                        $refResolver->setResolutionScope($preRefScope);
+                    });
+
+                    $ref = $refResolver->resolveReference($refString);
+                    $unresolvedData = $data;
+                    $data = self::unboolSchemaData($ref->getData());
+                    if (!$options->validateOnly) {
+                        if ($ref->isImported()) {
+                            $refResult = $ref->getImported();
+                            return $refResult;
+                        }
+                        $ref->setImported($result);
+                        try {
+                            // Best effort dereference delivery.
+                            $refResult = $this->process($data, $options, $path . '->$ref:' . $refString, $result);
+                            if ($refResult instanceof ObjectItemContract) {
+                                if ($refResult->getFromRefs()) {
+                                    $refResult = clone $refResult; // @todo check performance, consider option
+                                }
+                                $refResult->setFromRef($refString);
+                            }
+                            $ref->setImported($refResult);
+                            return $refResult;
+                        } catch (InvalidValue $exception) {
+                            $ref->unsetImported();
+                            $skipValidation = $options->skipValidation;
+                            $options->skipValidation = true;
+                            $refResult = $this->process($data, $options, $path . '->$ref:' . $refString);
+                            if ($refResult instanceof ObjectItemContract) {
+                                if ($refResult->getFromRefs()) {
+                                    $refResult = clone $refResult; // @todo check performance, consider option
+                                }
+                                $refResult->setFromRef($refString);
+                            }
+                            $options->skipValidation = $skipValidation;
+
+                            if ($result instanceof WithResolvedValue) {
+                                $result->setResolvedValue($refResult);
+                            }
+
+                            // Proceeding with unresolved data.
+                            $data = $unresolvedData;
+                        }
+                    } else {
+                        $this->process($data, $options, $path . '->$ref:' . $refString);
+                    }
+                }
+            } catch (InvalidValue $exception) {
+                $this->fail($exception, $path);
+            }
+        }
+
+        /** @var Schema[]|null $properties */
+        $properties = null;
+
+        $nestedProperties = null;
+        if ($this->properties !== null) {
+            $properties = $this->properties->toArray(); // todo call directly
+            $nestedProperties = $this->properties->nestedProperties;
+        }
+
+
+        if (!$options->skipValidation) {
+            if ($this->minProperties !== null && count($array) < $this->minProperties) {
+                $this->fail(new ObjectException("Not enough properties", ObjectException::TOO_FEW), $path);
+            }
+            if ($this->maxProperties !== null && count($array) > $this->maxProperties) {
+                $this->fail(new ObjectException("Too many properties", ObjectException::TOO_MANY), $path);
+            }
+            if ($this->propertyNames !== null) {
+                $propertyNames = self::unboolSchema($this->propertyNames);
+                foreach ($array as $key => $tmp) {
+                    $propertyNames->process($key, $options, $path . '->propertyNames:' . $key);
+                }
+            }
+        }
+
+        $defaultApplied = array();
+        if ($import
+            && !$options->validateOnly
+            && $options->applyDefaults
+            && $properties !== null
+        ) {
+            foreach ($properties as $key => $property) {
+                // todo check when property is \stdClass `{}` here (RefTest)
+                if ($property instanceof SchemaContract && null !== $default = $property->getDefault()) {
+                    if (!array_key_exists($key, $array)) {
+                        $defaultApplied[$key] = true;
+                        $array[$key] = $default;
+                    }
+                }
+            }
+        }
+
+        /**
+         * @var string $key
+         * @var mixed $value
+         */
+        foreach ($array as $key => $value) {
+            if ($key === '' && PHP_VERSION_ID < 70100) {
+                $this->fail(new InvalidValue('Empty property name'), $path);
+            }
+
+            $found = false;
+
+            if (!$options->skipValidation && !empty($this->dependencies)) {
+                $deps = $this->dependencies;
+                if (isset($deps->$key)) {
+                    $dependencies = $deps->$key;
+                    $dependencies = self::unboolSchema($dependencies);
+                    if ($dependencies instanceof SchemaContract) {
+                        $dependencies->process($data, $options, $path . '->dependencies:' . $key);
+                    } else {
+                        foreach ($dependencies as $item) {
+                            if (!array_key_exists($item, $array)) {
+                                $this->fail(new ObjectException('Dependency property missing: ' . $item,
+                                    ObjectException::DEPENDENCY_MISSING), $path);
+                            }
+                        }
+                    }
+                }
+            }
+
+            $propertyFound = false;
+            if (isset($properties[$key])) {
+                /** @var Schema[] $properties */
+                $prop = self::unboolSchema($properties[$key]);
+                $propertyFound = true;
+                $found = true;
+                if ($prop instanceof SchemaContract) {
+                    $value = $prop->process(
+                        $value,
+                        isset($defaultApplied[$key]) ? $options->withDefault() : $options,
+                        $path . '->properties:' . $key
+                    );
+                }
+            }
+
+            /** @var Egg[] $nestedEggs */
+            $nestedEggs = null;
+            if (isset($nestedProperties[$key])) {
+                $found = true;
+                $nestedEggs = $nestedProperties[$key];
+                // todo iterate all nested props?
+                $value = self::unboolSchema($nestedEggs[0]->propertySchema)->process($value, $options, $path . '->nestedProperties:' . $key);
+            }
+
+            if ($this->patternProperties !== null) {
+                foreach ($this->patternProperties as $pattern => $propertySchema) {
+                    if (preg_match(Helper::toPregPattern($pattern), $key)) {
+                        $found = true;
+                        $value = self::unboolSchema($propertySchema)->process($value, $options,
+                            $path . '->patternProperties[' . strtr($pattern, array('~' => '~1', ':' => '~2')) . ']:' . $key);
+                        if (!$options->validateOnly && $import) {
+                            $result->addPatternPropertyName($pattern, $key);
+                        }
+                        //break; // todo manage multiple import data properly (pattern accessor)
+                    }
+                }
+            }
+            if (!$found && $this->additionalProperties !== null) {
+                if (!$options->skipValidation && $this->additionalProperties === false) {
+                    $this->fail(new ObjectException('Additional properties not allowed: ' . $key), $path);
+                }
+
+                if ($this->additionalProperties instanceof SchemaContract) {
+                    $value = $this->additionalProperties->process($value, $options, $path . '->additionalProperties:' . $key);
+                }
+
+                if ($import && !$this->useObjectAsArray && !$options->validateOnly) {
+                    $result->addAdditionalPropertyName($key);
+                }
+            }
+
+            $propertyName = $key;
+
+            if ($hasMapping) {
+                if ($this->properties !== null && isset($this->properties->__dataToProperty[self::DEFAULT_MAPPING][$key])) {
+                    // todo check performance of local map access
+                    $propertyName = $this->properties->__dataToProperty[self::DEFAULT_MAPPING][$key];
+                }
+            }
+
+            if ($options->mapping !== self::DEFAULT_MAPPING) {
+                if (!$import) {
+                    if ($this->properties !== null && isset($this->properties->__propertyToData[$options->mapping][$propertyName])) {
+                        // todo check performance of local map access
+                        $propertyName = $this->properties->__propertyToData[$options->mapping][$propertyName];
+                    }
+                }
+            }
+
+            if (!$options->validateOnly && $nestedEggs && $import) {
+                foreach ($nestedEggs as $nestedEgg) {
+                    $result->setNestedProperty($key, $value, $nestedEgg);
+                }
+                if ($propertyFound) {
+                    $result->$propertyName = $value;
+                }
+            } else {
+                if (!$import && $hasMapping) {
+                    if ($this->properties !== null && isset($this->properties->__propertyToData[$options->mapping][$propertyName])) {
+                        $propertyName = $this->properties->__propertyToData[$options->mapping][$propertyName];
+                    }
+                }
+
+                if ($this->useObjectAsArray && $import) {
+                    $result[$propertyName] = $value;
+                } else {
+                    if ($found || !$import) {
+                        $result->$propertyName = $value;
+                    } elseif (!isset($result->$propertyName)) {
+                        if (self::PROP_REF !== $propertyName || empty($result->__fromRef)) {
+                            $result->$propertyName = $value;
+                        }
+                    }
+                }
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * @param array $data
+     * @param Context $options
+     * @param string $path
+     * @param array $result
+     * @return mixed
+     * @throws InvalidValue
+     * @throws \Exception
+     * @throws \Swaggest\JsonDiff\Exception
+     */
+    private function processArray($data, Context $options, $path, $result)
+    {
+        $count = count($data);
+        if (!$options->skipValidation) {
+            if ($this->minItems !== null && $count < $this->minItems) {
+                $this->fail(new ArrayException("Not enough items in array"), $path);
+            }
+
+            if ($this->maxItems !== null && $count > $this->maxItems) {
+                $this->fail(new ArrayException("Too many items in array"), $path);
+            }
+        }
+
+        $pathItems = 'items';
+        $this->items = self::unboolSchema($this->items);
+        if ($this->items instanceof SchemaContract) {
+            $items = array();
+            $additionalItems = $this->items;
+        } elseif ($this->items === null) { // items defaults to empty schema so everything is valid
+            $items = array();
+            $additionalItems = true;
+        } else { // listed items
+            $items = $this->items;
+            $additionalItems = $this->additionalItems;
+            $pathItems = 'additionalItems';
+        }
+
+        /**
+         * @var Schema|Schema[] $items
+         * @var null|bool|Schema $additionalItems
+         */
+        $itemsLen = is_array($items) ? count($items) : 0;
+        $index = 0;
+        foreach ($result as $key => $value) {
+            if ($index < $itemsLen) {
+                $itemSchema = self::unboolSchema($items[$index]);
+                $result[$key] = $itemSchema->process($value, $options, $path . '->items:' . $index);
+            } else {
+                if ($additionalItems instanceof SchemaContract) {
+                    $result[$key] = $additionalItems->process($value, $options, $path . '->' . $pathItems
+                        . '[' . $index . ']:' . $index);
+                } elseif (!$options->skipValidation && $additionalItems === false) {
+                    $this->fail(new ArrayException('Unexpected array item'), $path);
+                }
+            }
+            ++$index;
+        }
+
+        if (!$options->skipValidation && $this->uniqueItems) {
+            if (!UniqueItems::isValid($data)) {
+                $this->fail(new ArrayException('Array is not unique'), $path);
+            }
+        }
+
+        if (!$options->skipValidation && $this->contains !== null) {
+            /** @var Schema|bool $contains */
+            $contains = $this->contains;
+            if ($contains === false) {
+                $this->fail(new ArrayException('Contains is false'), $path);
+            }
+            if ($count === 0) {
+                $this->fail(new ArrayException('Empty array fails contains constraint'), $path);
+            }
+            if ($contains === true) {
+                $contains = self::unboolSchema($contains);
+            }
+            $containsOk = false;
+            foreach ($data as $key => $item) {
+                try {
+                    $contains->process($item, $options, $path . '->' . $key);
+                    $containsOk = true;
+                    break;
+                } catch (InvalidValue $exception) {
+                }
+            }
+            if (!$containsOk) {
+                $this->fail(new ArrayException('Array fails contains constraint'), $path);
+            }
+        }
+        return $result;
+    }
+
+    /**
+     * @param mixed|string $data
+     * @param Context $options
+     * @param string $path
+     * @return bool|mixed|string
+     * @throws InvalidValue
+     */
+    private function processContent($data, Context $options, $path)
+    {
+        try {
+            if ($options->unpackContentMediaType) {
+                return Content::process($options, $this->contentEncoding, $this->contentMediaType, $data, $options->import);
+            } else {
+                Content::process($options, $this->contentEncoding, $this->contentMediaType, $data, true);
+            }
+        } catch (InvalidValue $exception) {
+            $this->fail($exception, $path);
+        }
+        return $data;
+    }
+
+    /**
+     * @param mixed $data
+     * @param Context $options
+     * @param string $path
+     * @param mixed|null $result
+     * @return array|mixed|null|object|\stdClass
+     * @throws InvalidValue
+     * @throws \Exception
+     * @throws \Swaggest\JsonDiff\Exception
+     */
+    public function process($data, Context $options, $path = '#', $result = null)
+    {
+        $origData = $data;
+        $import = $options->import;
+
+        if (!$import && $data instanceof SchemaExporter) {
+            $data = $data->exportSchema(); // Used to export ClassStructure::schema()
+        }
+
+        if (!$import && $data instanceof ObjectItemContract) {
+            $result = new \stdClass();
+
+            if ('#' === $path) {
+                $injectDefinitions = new ScopeExit(function () use ($result, $options) {
+                    foreach ($options->exportedDefinitions as $ref => $data) {
+                        if ($data !== null && ($ref[0] === '#' || $ref[1] === '/')) {
+                            JsonPointer::add($result, JsonPointer::splitPath($ref), $data,
+                                /*JsonPointer::SKIP_IF_ISSET + */
+                                JsonPointer::RECURSIVE_KEY_CREATION);
+                        }
+                    }
+                });
+            }
+
+            if ($options->isRef) {
+                $options->isRef = false;
+            } else {
+                if ('#' !== $path && $refs = $data->getFromRefs()) {
+                    $ref = $refs[0];
+                    if (!array_key_exists($ref, $options->exportedDefinitions) && strpos($ref, '://') === false) {
+                        $exported = null;
+                        $options->exportedDefinitions[$ref] = &$exported;
+                        $options->isRef = true;
+                        $exported = $this->process($data, $options, $ref);
+                        unset($exported);
+                    }
+
+                    $countRefs = count($refs);
+                    for ($i = 1; $i < $countRefs; $i++) {
+                        $ref = $refs[$i];
+                        if (!array_key_exists($ref, $options->exportedDefinitions) && strpos($ref, '://') === false) {
+                            $exported = new \stdClass();
+                            $exported->{self::PROP_REF} = $refs[$i - 1];
+                            $options->exportedDefinitions[$ref] = $exported;
+                        }
+                    }
+
+                    $result->{self::PROP_REF} = $refs[$countRefs - 1];
+                    return $result;
+                }
+            }
+
+            if ($options->circularReferences->contains($data)) {
+                /** @noinspection PhpIllegalArrayKeyTypeInspection */
+                $path = $options->circularReferences[$data];
+                $result->{self::PROP_REF} = PointerUtil::getDataPointer($path, true);
+                return $result;
+            }
+            $options->circularReferences->attach($data, $path);
+
+            $data = $data->jsonSerialize();
+        }
+
+        $path .= $this->getFromRefPath();
+
+        if (!$import && is_array($data) && $this->useObjectAsArray) {
+            $data = (object)$data;
+        }
+
+        if (null !== $options->dataPreProcessor) {
+            $data = $options->dataPreProcessor->process($data, $this, $import);
+        }
+
+        if ($options->skipValidation) {
+            goto skipValidation;
+        }
+
+        if ($this->type !== null) {
+            $this->processType($data, $options, $path);
+        }
+
+        if ($this->enum !== null) {
+            $this->processEnum($data, $path);
+        }
+
+        if (array_key_exists(self::CONST_PROPERTY, $this->__arrayOfData)) {
+            $this->processConst($data, $path);
+        }
+
+        if ($this->not !== null) {
+            $this->processNot($data, $options, $path);
+        }
+
+        if (is_string($data)) {
+            $this->processString($data, $path);
+        }
+
+        if (is_int($data) || is_float($data)) {
+            $this->processNumeric($data, $path);
+        }
+
+        if ($this->if !== null) {
+            $result = $this->processIf($data, $options, $path);
+        }
+
+        skipValidation:
+
+        if ($result === null) {
+            $result = $data;
+        }
+
+        if ($this->oneOf !== null) {
+            $result = $this->processOneOf($data, $options, $path);
+        }
+
+        if ($this->anyOf !== null) {
+            $result = $this->processAnyOf($data, $options, $path);
+        }
+
+        if ($this->allOf !== null) {
+            $result = $this->processAllOf($data, $options, $path);
+        }
+
+        if (is_object($data)) {
+            $result = $this->processObject($data, $options, $path, $result);
+        }
+
+        if (is_array($data)) {
+            $result = $this->processArray($data, $options, $path, $result);
+        }
+
+        if ($this->contentEncoding !== null || $this->contentMediaType !== null) {
+            if ($import && !is_string($data)) {
+                return $result;
+            }
+            $result = $this->processContent($data, $options, $path);
+        }
+
+        return $result;
+    }
+
+    /**
+     * @param boolean $useObjectAsArray
+     * @return Schema
+     */
+    public function setUseObjectAsArray($useObjectAsArray)
+    {
+        $this->useObjectAsArray = $useObjectAsArray;
+        return $this;
+    }
+
+    /**
+     * @param InvalidValue $exception
+     * @param string $path
+     * @throws InvalidValue
+     */
+    private function fail(InvalidValue $exception, $path)
+    {
+        $exception->addPath($path);
+        throw $exception;
+    }
+
+    public static function integer()
+    {
+        $schema = new static();
+        $schema->type = Type::INTEGER;
+        return $schema;
+    }
+
+    public static function number()
+    {
+        $schema = new static();
+        $schema->type = Type::NUMBER;
+        return $schema;
+    }
+
+    public static function string()
+    {
+        $schema = new static();
+        $schema->type = Type::STRING;
+        return $schema;
+    }
+
+    public static function boolean()
+    {
+        $schema = new static();
+        $schema->type = Type::BOOLEAN;
+        return $schema;
+    }
+
+    public static function object()
+    {
+        $schema = new static();
+        $schema->type = Type::OBJECT;
+        return $schema;
+    }
+
+    public static function arr()
+    {
+        $schema = new static();
+        $schema->type = Type::ARR;
+        return $schema;
+    }
+
+    public static function null()
+    {
+        $schema = new static();
+        $schema->type = Type::NULL;
+        return $schema;
+    }
+
+
+    /**
+     * @param Properties $properties
+     * @return SchemaContract
+     */
+    public function setProperties($properties)
+    {
+        $this->properties = $properties;
+        return $this;
+    }
+
+    /**
+     * @param string $name
+     * @param Schema $schema
+     * @return $this
+     */
+    public function setProperty($name, $schema)
+    {
+        if (null === $this->properties) {
+            $this->properties = new Properties();
+        }
+        $this->properties->__set($name, $schema);
+        return $this;
+    }
+
+    /**
+     * @param string $name
+     * @param SchemaContract $schema
+     * @return $this
+     * @throws Exception
+     */
+    public function setPatternProperty($name, $schema)
+    {
+        if (null === $this->patternProperties) {
+            $this->patternProperties = new Properties();
+        }
+        $this->patternProperties->__set($name, $schema);
+        return $this;
+    }
+
+
+    /** @var mixed[] */
+    private $metaItems = array();
+
+    public function addMeta($meta, $name = null)
+    {
+        if ($name === null) {
+            $name = get_class($meta);
+        }
+        $this->metaItems[$name] = $meta;
+        return $this;
+    }
+
+    public function getMeta($name)
+    {
+        if (isset($this->metaItems[$name])) {
+            return $this->metaItems[$name];
+        }
+        return null;
+    }
+
+    /**
+     * @param Context $options
+     * @return ObjectItemContract
+     */
+    public function makeObjectItem(Context $options = null)
+    {
+        if (null === $this->objectItemClass) {
+            return new ObjectItem();
+        } else {
+            $className = $this->objectItemClass;
+            if ($options !== null) {
+                if (isset($options->objectItemClassMapping[$className])) {
+                    $className = $options->objectItemClassMapping[$className];
+                }
+            }
+            return new $className;
+        }
+    }
+
+    /**
+     * Resolves boolean schema into Schema instance.
+     *
+     * @param mixed $schema
+     * @return mixed|Schema
+     */
+    public static function unboolSchema($schema)
+    {
+        static $trueSchema;
+        static $falseSchema;
+
+        if (null === $trueSchema) {
+            $trueSchema = new Schema();
+            $trueSchema->__booleanSchema = true;
+            $falseSchema = new Schema();
+            $falseSchema->not = $trueSchema;
+            $falseSchema->__booleanSchema = false;
+        }
+
+        if ($schema === true) {
+            return $trueSchema;
+        } elseif ($schema === false) {
+            return $falseSchema;
+        } else {
+            return $schema;
+        }
+    }
+
+    /**
+     * Converts bool value into an object schema.
+     *
+     * @param mixed $data
+     * @return \stdClass
+     */
+    public static function unboolSchemaData($data)
+    {
+        static $trueSchema;
+        static $falseSchema;
+
+        if (null === $trueSchema) {
+            $trueSchema = new \stdClass();
+            $falseSchema = new \stdClass();
+            $falseSchema->not = $trueSchema;
+        }
+
+        if ($data === true) {
+            return $trueSchema;
+        } elseif ($data === false) {
+            return $falseSchema;
+        } else {
+            return $data;
+        }
+    }
+
+    public function getDefault()
+    {
+        return $this->default;
+    }
+
+    /**
+     * @nolint
+     */
+    public function getProperties()
+    {
+        return $this->properties;
+    }
+
+    public function getObjectItemClass()
+    {
+        return $this->objectItemClass;
+    }
+
+    /**
+     * @return string[]
+     */
+    public function getPropertyNames()
+    {
+        if (null === $this->properties) {
+            return array();
+        }
+        return array_keys($this->properties->toArray());
+    }
+
+    /**
+     * @return string[]
+     */
+    public function getNestedPropertyNames()
+    {
+        if (null === $this->properties) {
+            return array();
+        }
+        return $this->properties->nestedPropertyNames;
+    }
+
+}
diff --git a/swaggest/json-schema/src/SchemaContract.php b/swaggest/json-schema/src/SchemaContract.php
new file mode 100644
index 0000000..a051305
--- /dev/null
+++ b/swaggest/json-schema/src/SchemaContract.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Swaggest\JsonSchema;
+
+
+use Swaggest\JsonSchema\Constraint\Properties;
+
+interface SchemaContract
+{
+    /**
+     * @param mixed $data
+     * @param Context $options
+     * @param string $path
+     * @param mixed|null $result
+     * @return array|mixed|null|object|\stdClass
+     */
+    public function process($data, Context $options, $path = '#', $result = null);
+
+    /**
+     * @param mixed $data
+     * @param Context|null $options
+     * @throws InvalidValue
+     * @return array|mixed|null|object|\stdClass
+     */
+    public function in($data, Context $options = null);
+
+    /**
+     * @param mixed $data
+     * @param Context|null $options
+     * @throws InvalidValue
+     * @return array|mixed|null|object|\stdClass
+     */
+    public function out($data, Context $options = null);
+
+    /**
+     * @return mixed
+     */
+    public function getDefault();
+
+    /** @return null|Properties|Schema[] */
+    public function getProperties();
+
+    /**
+     * @param Context|null $options
+     * @return Structure\ObjectItemContract
+     */
+    public function makeObjectItem(Context $options = null);
+
+    /**
+     * @return string
+     */
+    public function getObjectItemClass();
+
+    /**
+     * @return string[]
+     */
+    public function getPropertyNames();
+
+    /**
+     * @return string[]
+     */
+    public function getNestedPropertyNames();
+
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/SchemaExporter.php b/swaggest/json-schema/src/SchemaExporter.php
new file mode 100644
index 0000000..00d9aef
--- /dev/null
+++ b/swaggest/json-schema/src/SchemaExporter.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Swaggest\JsonSchema;
+
+
+interface SchemaExporter
+{
+    /**
+     * @return Schema
+     */
+    public function exportSchema();
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/Structure/ClassStructure.php b/swaggest/json-schema/src/Structure/ClassStructure.php
new file mode 100644
index 0000000..fd08212
--- /dev/null
+++ b/swaggest/json-schema/src/Structure/ClassStructure.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace Swaggest\JsonSchema\Structure;
+
+abstract class ClassStructure implements ClassStructureContract, WithResolvedValue
+{
+    use ClassStructureTrait;
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/Structure/ClassStructureContract.php b/swaggest/json-schema/src/Structure/ClassStructureContract.php
new file mode 100644
index 0000000..9590ffe
--- /dev/null
+++ b/swaggest/json-schema/src/Structure/ClassStructureContract.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Swaggest\JsonSchema\Structure;
+
+use Swaggest\JsonSchema\Constraint\Properties;
+use Swaggest\JsonSchema\Schema;
+
+interface ClassStructureContract extends ObjectItemContract
+{
+    /**
+     * @param Properties|static $properties
+     * @param Schema $ownerSchema
+     */
+    public static function setUpProperties($properties, Schema $ownerSchema);
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/Structure/ClassStructureTrait.php b/swaggest/json-schema/src/Structure/ClassStructureTrait.php
new file mode 100644
index 0000000..1420ed4
--- /dev/null
+++ b/swaggest/json-schema/src/Structure/ClassStructureTrait.php
@@ -0,0 +1,182 @@
+<?php
+
+namespace Swaggest\JsonSchema\Structure;
+
+use Swaggest\JsonSchema\Constraint\Properties;
+use Swaggest\JsonSchema\Context;
+use Swaggest\JsonSchema\Schema;
+use Swaggest\JsonSchema\Wrapper;
+use Swaggest\JsonSchema\NameMirror;
+
+trait ClassStructureTrait
+{
+    use ObjectItemTrait;
+
+    /**
+     * @return Wrapper
+     */
+    public static function schema()
+    {
+        static $schemas = array();
+        $className = get_called_class();
+        $schemaWrapper = &$schemas[$className];
+
+        if (null === $schemaWrapper) {
+            $schema = new Schema();
+            $properties = new Properties();
+            $schema->properties = $properties;
+            $schema->objectItemClass = $className;
+            $schemaWrapper = new Wrapper($schema);
+            static::setUpProperties($properties, $schema);
+            if (null === $schema->getFromRefs()) {
+                $schema->setFromRef('#/definitions/' . $className);
+            }
+            if ($properties->isEmpty()) {
+                $schema->properties = null;
+            }
+            $properties->lock();
+        }
+
+        return $schemaWrapper;
+    }
+
+    /**
+     * @return Properties|static|null
+     */
+    public static function properties()
+    {
+        return static::schema()->getProperties();
+    }
+
+    /**
+     * @param mixed $data
+     * @param Context $options
+     * @return static|mixed
+     * @throws \Swaggest\JsonSchema\Exception
+     * @throws \Swaggest\JsonSchema\InvalidValue
+     */
+    public static function import($data, Context $options = null)
+    {
+        return static::schema()->in($data, $options);
+    }
+
+    /**
+     * @param mixed $data
+     * @param Context $options
+     * @return mixed
+     * @throws \Swaggest\JsonSchema\InvalidValue
+     * @throws \Exception
+     */
+    public static function export($data, Context $options = null)
+    {
+        return static::schema()->out($data, $options);
+    }
+
+    /**
+     * @param ObjectItemContract $objectItem
+     * @return static
+     */
+    public static function pick(ObjectItemContract $objectItem)
+    {
+        $className = get_called_class();
+        return $objectItem->getNestedObject($className);
+    }
+
+    /**
+     * @return static
+     */
+    public static function create()
+    {
+        return new static;
+    }
+
+    protected $__validateOnSet = true; // todo skip validation during import
+
+    /**
+     * @return \stdClass
+     */
+    public function jsonSerialize()
+    {
+        $result = new \stdClass();
+        $schema = static::schema();
+        $properties = $schema->getProperties();
+        $processed = array();
+        if (null !== $properties) {
+            foreach ($properties->getDataKeyMap() as $propertyName => $dataName) {
+                $value = $this->$propertyName;
+
+                // Value is exported if exists.
+                if (null !== $value || array_key_exists($propertyName, $this->__arrayOfData)) {
+                    $result->$dataName = $value;
+                    $processed[$propertyName] = true;
+                    continue;
+                }
+
+                // Non-existent value is only exported if belongs to nullable property (having 'null' in type array).
+                $property = $schema->getProperty($propertyName);
+                if ($property instanceof Schema) {
+                    $types = $property->type;
+                    if ($types === Schema::NULL || (is_array($types) && in_array(Schema::NULL, $types))) {
+                        $result->$dataName = $value;
+                    }
+                }
+            }
+        }
+        foreach ($schema->getNestedPropertyNames() as $name) {
+            /** @var ObjectItem $nested */
+            $nested = $this->$name;
+            if (null !== $nested) {
+                foreach ((array)$nested->jsonSerialize() as $key => $value) {
+                    $result->$key = $value;
+                }
+            }
+        }
+
+        if (!empty($this->__arrayOfData)) {
+            foreach ($this->__arrayOfData as $name => $value) {
+                if (!isset($processed[$name])) {
+                    $result->$name = $this->{$name};
+                }
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * @return static|NameMirror
+     */
+    public static function names()
+    {
+        static $nameflector = null;
+        if (null === $nameflector) {
+            $nameflector = new NameMirror();
+        }
+        return $nameflector;
+    }
+
+    public function __set($name, $column) // todo nested schemas
+    {
+        if ($this->__validateOnSet) {
+            if ($property = static::schema()->getProperty($name)) {
+                $property->out($column);
+            }
+        }
+        $this->__arrayOfData[$name] = $column;
+        return $this;
+    }
+
+    public static function className()
+    {
+        return get_called_class();
+    }
+
+    /**
+     * @throws \Exception
+     * @throws \Swaggest\JsonSchema\InvalidValue
+     */
+    public function validate()
+    {
+        static::schema()->out($this);
+    }
+}
diff --git a/swaggest/json-schema/src/Structure/Composition.php b/swaggest/json-schema/src/Structure/Composition.php
new file mode 100644
index 0000000..2cd231f
--- /dev/null
+++ b/swaggest/json-schema/src/Structure/Composition.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace Swaggest\JsonSchema\Structure;
+
+use Swaggest\JsonSchema\Constraint\Properties;
+use Swaggest\JsonSchema\Constraint\Type;
+use Swaggest\JsonSchema\Schema;
+use Swaggest\JsonSchema\SchemaContract;
+use Swaggest\JsonSchema\Wrapper;
+
+/**
+ * @todo think of anyOf, allOf, oneOf
+ */
+class Composition extends Schema
+{
+    /**
+     * @param SchemaContract... $schema
+     * @throws \Swaggest\JsonSchema\Exception
+     */
+    public function __construct()
+    {
+        $this->type = Type::OBJECT;
+        $properties = new Properties();
+        $this->properties = $properties;
+
+        foreach (func_get_args() as $arg) {
+            if ($arg instanceof Wrapper) {
+                $properties->__set($arg->objectItemClass, $arg->nested());
+            }
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/Structure/Egg.php b/swaggest/json-schema/src/Structure/Egg.php
new file mode 100644
index 0000000..32c961e
--- /dev/null
+++ b/swaggest/json-schema/src/Structure/Egg.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Swaggest\JsonSchema\Structure;
+
+use Swaggest\JsonSchema\Schema;
+use Swaggest\JsonSchema\SchemaContract;
+use Swaggest\JsonSchema\Wrapper;
+
+class Egg
+{
+
+
+    /** @var SchemaContract */
+    public $classSchema;
+    public $name;
+    /** @var SchemaContract */
+    public $propertySchema;
+
+    /**
+     * Egg constructor.
+     * @param SchemaContract $classSchema
+     * @param string $name
+     * @param SchemaContract $propertySchema
+     */
+    public function __construct(SchemaContract $classSchema, $name, SchemaContract $propertySchema)
+    {
+        $this->classSchema = $classSchema;
+        $this->name = $name;
+        $this->propertySchema = $propertySchema;
+    }
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/Structure/Nested.php b/swaggest/json-schema/src/Structure/Nested.php
new file mode 100644
index 0000000..efee646
--- /dev/null
+++ b/swaggest/json-schema/src/Structure/Nested.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Swaggest\JsonSchema\Structure;
+
+use Swaggest\JsonSchema\SchemaContract;
+
+class Nested
+{
+    /** @var SchemaContract */
+    public $schema;
+    public function __construct(SchemaContract $schema)
+    {
+        $this->schema = $schema;
+    }
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/Structure/ObjectItem.php b/swaggest/json-schema/src/Structure/ObjectItem.php
new file mode 100644
index 0000000..b03795f
--- /dev/null
+++ b/swaggest/json-schema/src/Structure/ObjectItem.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Swaggest\JsonSchema\Structure;
+
+/**
+ * @method getNestedObject($className);
+ * @method setNestedProperty($propertyName, $value, Egg $nestedEgg);
+ * @method addAdditionalPropertyName($name);
+ * @method setDocumentPath($path);
+ * @method setFromRef($ref);
+ * @method string|null getFromRef();
+ * @method string[]|null getFromRefs();
+ */
+class ObjectItem implements ObjectItemContract, WithResolvedValue
+{
+    use ObjectItemTrait;
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/Structure/ObjectItemContract.php b/swaggest/json-schema/src/Structure/ObjectItemContract.php
new file mode 100644
index 0000000..782612a
--- /dev/null
+++ b/swaggest/json-schema/src/Structure/ObjectItemContract.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace Swaggest\JsonSchema\Structure;
+
+
+interface ObjectItemContract extends \ArrayAccess, \JsonSerializable, \Iterator
+{
+    public function getNestedObject($className);
+    public function setNestedProperty($propertyName, $value, Egg $nestedEgg);
+    public function addAdditionalPropertyName($name);
+    public function setDocumentPath($path);
+    public function setFromRef($ref);
+    public function toArray();
+
+    /**
+     * @return string
+     * @deprecated
+     */
+    public function getFromRef();
+
+    /**
+     * @return string[]|null
+     */
+    public function getFromRefs();
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/Structure/ObjectItemTrait.php b/swaggest/json-schema/src/Structure/ObjectItemTrait.php
new file mode 100644
index 0000000..63e9dac
--- /dev/null
+++ b/swaggest/json-schema/src/Structure/ObjectItemTrait.php
@@ -0,0 +1,189 @@
+<?php
+
+namespace Swaggest\JsonSchema\Structure;
+
+use Swaggest\JsonSchema\MagicMapTrait;
+
+/**
+ * Trait ObjectItemTrait
+ * @package Swaggest\JsonSchema\Structure
+ * @see ObjectItemContract
+ */
+trait ObjectItemTrait
+{
+    use MagicMapTrait;
+
+    /** @var ObjectItemContract[] */
+    protected $__nestedObjects;
+    protected $__documentPath;
+    /** @var null|string[] */
+    protected $__fromRef;
+
+    protected $__hasResolvedValue = false;
+    protected $__resolvedValue;
+
+    public function setResolvedValue($value)
+    {
+        $this->__hasResolvedValue = true;
+        $this->__resolvedValue = $value;
+        return $this;
+    }
+
+    public function getResolvedValue()
+    {
+        return $this->__resolvedValue;
+    }
+
+    public function hasResolvedValue()
+    {
+        return $this->__hasResolvedValue;
+    }
+
+    public function getNestedObject($className)
+    {
+        if (isset($this->__nestedObjects[$className])) {
+            return $this->__nestedObjects[$className];
+        }
+        return null;
+    }
+
+    public function setNestedProperty($propertyName, $value, Egg $nestedEgg)
+    {
+        $nestedName = $nestedEgg->name;
+        /** @var null $nested */
+        $nested = &$this->__nestedObjects[$nestedName];
+        if (null === $nested) {
+            $nested = $nestedEgg->classSchema->makeObjectItem();
+            $this->__nestedObjects[$nestedName] = $nested;
+            if ($nestedName !== $nestedEgg->classSchema->getObjectItemClass()) {
+                $this->$nestedName = $nested;
+            }
+        }
+        $nested->$propertyName = $value;
+        $this->__arrayOfData[$propertyName] = &$nested->$propertyName;
+    }
+
+    protected $__additionalPropertyNames;
+
+    public function addAdditionalPropertyName($name)
+    {
+        $this->__additionalPropertyNames[$name] = true;
+    }
+
+    /**
+     * @return string[]
+     */
+    public function getAdditionalPropertyNames()
+    {
+        if (null === $this->__additionalPropertyNames) {
+            return [];
+        }
+        return array_keys($this->__additionalPropertyNames);
+    }
+
+    /**
+     * @var string[][]
+     */
+    protected $__patternPropertyNames;
+
+    public function addPatternPropertyName($pattern, $name)
+    {
+        $this->__patternPropertyNames[$pattern][$name] = true;
+    }
+
+    /**
+     * @param string $pattern
+     * @return null|string[]
+     */
+    public function getPatternPropertyNames($pattern)
+    {
+        if (isset($this->__patternPropertyNames[$pattern])) {
+            return array_keys($this->__patternPropertyNames[$pattern]);
+        }
+        return null;
+    }
+
+    public function jsonSerialize()
+    {
+        if ($this->__nestedObjects) {
+            $result = $this->__arrayOfData;
+            foreach ($this->__nestedObjects as $object) {
+                foreach ($object->toArray() as $key => $value) {
+                    $result[$key] = $value;
+                }
+            }
+            return (object)$result;
+        } else {
+            return (object)$this->__arrayOfData;
+        }
+    }
+
+    /**
+     * @return string
+     */
+    public function getDocumentPath()
+    {
+        return $this->__documentPath;
+    }
+
+    public function setDocumentPath($path)
+    {
+        $this->__documentPath = $path;
+        return $this;
+    }
+
+    /**
+     * @return string|null
+     * @deprecated use ObjectItemContract::getFromRefs
+     * @see ObjectItemContract::getFromRefs
+     * @todo remove
+     * @see ObjectItemContract::getFromRef
+     */
+    public function getFromRef()
+    {
+        return null === $this->__fromRef ? null : $this->__fromRef[0];
+    }
+
+    /**
+     * @return string[]|null
+     * @see ObjectItemContract::getFromRef
+     */
+    public function getFromRefs()
+    {
+        return $this->__fromRef;
+    }
+
+    /**
+     * @param false|string $ref
+     * @return $this
+     * @see ObjectItemContract::setFromRef
+     */
+    public function setFromRef($ref)
+    {
+        if (null === $this->__fromRef) {
+            $this->__fromRef = array($ref);
+        } else {
+            if (false !== $this->__fromRef[0]) {
+                $this->__fromRef[] = $ref;
+            }
+        }
+        return $this;
+    }
+
+    private $__refPath;
+
+    protected function getFromRefPath()
+    {
+        if ($this->__refPath === null) {
+            $this->__refPath = '';
+            if ($this->__fromRef) {
+                foreach ($this->__fromRef as $ref) {
+                    if ($ref) {
+                        $this->__refPath = '->$ref[' . strtr($ref, array('~' => '~1', ':' => '~2')) . ']' . $this->__refPath;
+                    }
+                }
+            }
+        }
+        return $this->__refPath;
+    }
+}
diff --git a/swaggest/json-schema/src/Structure/WithResolvedValue.php b/swaggest/json-schema/src/Structure/WithResolvedValue.php
new file mode 100644
index 0000000..c260a38
--- /dev/null
+++ b/swaggest/json-schema/src/Structure/WithResolvedValue.php
@@ -0,0 +1,15 @@
+<?php
+
+
+namespace Swaggest\JsonSchema\Structure;
+
+
+interface WithResolvedValue
+{
+    public function setResolvedValue($value);
+
+    public function getResolvedValue();
+
+    public function hasResolvedValue();
+
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/src/Wrapper.php b/swaggest/json-schema/src/Wrapper.php
new file mode 100644
index 0000000..19090cf
--- /dev/null
+++ b/swaggest/json-schema/src/Wrapper.php
@@ -0,0 +1,219 @@
+<?php
+
+namespace Swaggest\JsonSchema;
+
+use Swaggest\JsonSchema\Meta\MetaHolder;
+use Swaggest\JsonSchema\Structure\Nested;
+
+class Wrapper implements SchemaContract, MetaHolder, SchemaExporter, \JsonSerializable
+{
+    /** @var Schema */
+    private $schema;
+
+    /**
+     * Keeps reference to original instance for centralized Meta storage
+     * @var Schema
+     */
+    private $originalSchema;
+
+    public $objectItemClass;
+
+    private $cloned = false;
+
+    /**
+     * ImmutableSchema constructor.
+     * @param Schema $schema
+     */
+    public function __construct(Schema $schema)
+    {
+        $this->schema = $schema;
+        $this->originalSchema = $schema;
+        $this->objectItemClass = $schema->objectItemClass;
+    }
+
+    /**
+     * @param mixed $data
+     * @param Context $options
+     * @param string $path
+     * @param mixed|null $result
+     * @return array|mixed|null|object|\stdClass
+     * @throws InvalidValue
+     * @throws \Exception
+     * @throws \Swaggest\JsonDiff\Exception
+     */
+    public function process($data, Context $options, $path = '#', $result = null)
+    {
+        return $this->schema->process($data, $options, $path, $result);
+    }
+
+    /**
+     * @param mixed $data
+     * @param Context|null $options
+     * @return array|mixed|null|object|\stdClass
+     * @throws Exception
+     * @throws InvalidValue
+     */
+    public function in($data, Context $options = null)
+    {
+        return $this->schema->in($data, $options);
+    }
+
+    /**
+     * @param mixed $data
+     * @param Context|null $options
+     * @return array|mixed|null|object|\stdClass
+     * @throws InvalidValue
+     * @throws \Exception
+     */
+    public function out($data, Context $options = null)
+    {
+        return $this->schema->out($data, $options);
+    }
+
+    /**
+     * @return string[]
+     */
+    public function getPropertyNames()
+    {
+        return $this->schema->getPropertyNames();
+    }
+
+    /**
+     * @return string[]
+     */
+    public function getNestedPropertyNames()
+    {
+        return $this->schema->getNestedPropertyNames();
+    }
+
+    public function nested()
+    {
+        return new Nested($this);
+    }
+
+    /**
+     * @return null|Constraint\Properties
+     */
+    public function getProperties()
+    {
+        return $this->schema->properties;
+    }
+
+    /**
+     * @param string $name
+     * @return null|Schema|SchemaContract
+     */
+    public function getProperty($name)
+    {
+        return isset($this->schema->properties[$name]) ? $this->schema->properties[$name] : null;
+    }
+
+    /**
+     * @param string $name
+     * @param array $arguments
+     * @return mixed
+     * @throws Exception
+     */
+    public function __call($name, $arguments)
+    {
+        if (substr($name, 0, 3) === 'set') {
+            if (!$this->cloned) {
+                $this->schema = clone $this->schema;
+                $this->cloned = true;
+            }
+            $this->schema->$name($arguments[0]); // todo performance check direct set
+            return $this;
+        } else {
+            throw new Exception('Unknown method:' . $name);
+        }
+    }
+
+    public function getDefault()
+    {
+        return $this->schema->default;
+    }
+
+    /**
+     * @param mixed $default
+     * @return $this
+     */
+    public function setDefault($default)
+    {
+        if (!$this->cloned) {
+            $this->schema = clone $this->schema;
+            $this->cloned = true;
+        }
+
+        $this->schema->default = $default;
+        return $this;
+    }
+
+    /**
+     * @param string $name
+     * @throws Exception
+     */
+    public function __get($name)
+    {
+        throw new Exception('Unexpected get: ' . $name);
+    }
+
+    /**
+     * @param string $name
+     * @param mixed $value
+     * @return Wrapper
+     */
+    public function __set($name, $value)
+    {
+        if (!$this->cloned) {
+            $this->schema = clone $this->schema;
+            $this->cloned = true;
+        }
+        $this->schema->$name = $value;
+        return $this;
+    }
+
+    /**
+     * @param string $name
+     * @throws Exception
+     */
+    public function __isset($name)
+    {
+        throw new Exception('Unexpected isset: ' . $name);
+    }
+
+    public function addMeta($meta, $name = null)
+    {
+        $this->originalSchema->addMeta($meta, $name);
+        return $this;
+    }
+
+    public function getMeta($name)
+    {
+        return $this->originalSchema->getMeta($name);
+    }
+
+    /**
+     * @param Context|null $options
+     * @return Structure\ObjectItemContract
+     */
+    public function makeObjectItem(Context $options = null)
+    {
+        return $this->schema->makeObjectItem($options);
+    }
+
+    public function getObjectItemClass()
+    {
+        return $this->objectItemClass;
+    }
+
+    public function exportSchema()
+    {
+        return clone $this->schema;
+    }
+
+    public function jsonSerialize()
+    {
+        return $this->schema->jsonSerialize();
+    }
+
+}
-- 
2.20.1

$ date
Wed May  5 13:47:52 UTC 2021

$ git clone file:///srv/git/mediawiki-vendor.git repo --depth=1 -b master
Cloning into 'repo'...
Checking out files:  79% (4039/5057)   
Checking out files:  80% (4046/5057)   
Checking out files:  80% (4087/5057)   
Checking out files:  81% (4097/5057)   
Checking out files:  82% (4147/5057)   
Checking out files:  83% (4198/5057)   
Checking out files:  84% (4248/5057)   
Checking out files:  85% (4299/5057)   
Checking out files:  86% (4350/5057)   
Checking out files:  87% (4400/5057)   
Checking out files:  88% (4451/5057)   
Checking out files:  89% (4501/5057)   
Checking out files:  90% (4552/5057)   
Checking out files:  91% (4602/5057)   
Checking out files:  92% (4653/5057)   
Checking out files:  93% (4704/5057)   
Checking out files:  94% (4754/5057)   
Checking out files:  95% (4805/5057)   
Checking out files:  96% (4855/5057)   
Checking out files:  97% (4906/5057)   
Checking out files:  98% (4956/5057)   
Checking out files:  99% (5007/5057)   
Checking out files: 100% (5057/5057)   
Checking out files: 100% (5057/5057), done.

$ git config user.name libraryupgrader

$ git config user.email tools.libraryupgrader@tools.wmflabs.org

$ git submodule update --init

$ grr init
Installed commit-msg hook.

$ git show-ref refs/heads/master
137ce1975af6e0bfdc685cff4a7b6f4686bcb049 refs/heads/master

Upgrading c:php-parallel-lint/php-parallel-lint from 1.2.0 -> 1.3.0
$ composer update
Loading composer repositories with package information
                                                      Warning from https://repo.packagist.org: You are using an outdated version of Composer. Composer 2 is now available and you should upgrade. See https://getcomposer.org/2
Updating dependencies (including require-dev)
Package operations: 4 installs, 0 updates, 0 removals
  - Installing php-parallel-lint/php-parallel-lint (v1.3.0): Loading from cache
  - Installing swaggest/json-diff (v3.8.1): Loading from cache
  - Installing phplang/scope-exit (1.0.0): Loading from cache
  - Installing swaggest/json-schema (v0.12.29): Loading from cache
php-parallel-lint/php-parallel-lint suggests installing php-parallel-lint/php-console-highlighter (Highlight syntax in code snippet)
Package guzzlehttp/ringphp is abandoned, you should avoid using it. No replacement was suggested.
Package guzzlehttp/streams is abandoned, you should avoid using it. No replacement was suggested.
Writing lock file
Generating optimized autoload files
26 packages you are using are looking for funding.
Use the `composer fund` command to find out more!

$ composer install
Loading composer repositories with package information
Installing dependencies (including require-dev) from lock file
Nothing to install or update
Package guzzlehttp/ringphp is abandoned, you should avoid using it. No replacement was suggested.
Package guzzlehttp/streams is abandoned, you should avoid using it. No replacement was suggested.
Generating optimized autoload files
26 packages you are using are looking for funding.
Use the `composer fund` command to find out more!

$ composer test
> parallel-lint --exclude composer/autoload_static.php --exclude pear/net_smtp/tests --exclude pear/mail/tests .
PHP 7.3.27 | 10 parallel jobs
............................................................   60/3936 (1 %)
............................................................  120/3936 (3 %)
............................................................  180/3936 (4 %)
............................................................  240/3936 (6 %)
............................................................  300/3936 (7 %)
............................................................  360/3936 (9 %)
............................................................  420/3936 (10 %)
............................................................  480/3936 (12 %)
............................................................  540/3936 (13 %)
............................................................  600/3936 (15 %)
............................................................  660/3936 (16 %)
............................................................  720/3936 (18 %)
............................................................  780/3936 (19 %)
............................................................  840/3936 (21 %)
............................................................  900/3936 (22 %)
............................................................  960/3936 (24 %)
............................................................ 1020/3936 (25 %)
............................................................ 1080/3936 (27 %)
............................................................ 1140/3936 (28 %)
............................................................ 1200/3936 (30 %)
............................................................ 1260/3936 (32 %)
............................................................ 1320/3936 (33 %)
............................................................ 1380/3936 (35 %)
............................................................ 1440/3936 (36 %)
............................................................ 1500/3936 (38 %)
............................................................ 1560/3936 (39 %)
............................................................ 1620/3936 (41 %)
............................................................ 1680/3936 (42 %)
............................................................ 1740/3936 (44 %)
............................................................ 1800/3936 (45 %)
............................................................ 1860/3936 (47 %)
............................................................ 1920/3936 (48 %)
............................................................ 1980/3936 (50 %)
............................................................ 2040/3936 (51 %)
............................................................ 2100/3936 (53 %)
............................................................ 2160/3936 (54 %)
............................................................ 2220/3936 (56 %)
............................................................ 2280/3936 (57 %)
............................................................ 2340/3936 (59 %)
............................................................ 2400/3936 (60 %)
............................................................ 2460/3936 (62 %)
............................................................ 2520/3936 (64 %)
............................................................ 2580/3936 (65 %)
............................................................ 2640/3936 (67 %)
............................................................ 2700/3936 (68 %)
............................................................ 2760/3936 (70 %)
............................................................ 2820/3936 (71 %)
............................................................ 2880/3936 (73 %)
............................................................ 2940/3936 (74 %)
............................................................ 3000/3936 (76 %)
............................................................ 3060/3936 (77 %)
............................................................ 3120/3936 (79 %)
............................................................ 3180/3936 (80 %)
............................................................ 3240/3936 (82 %)
............................................................ 3300/3936 (83 %)
............................................................ 3360/3936 (85 %)
............................................................ 3420/3936 (86 %)
............................................................ 3480/3936 (88 %)
............................................................ 3540/3936 (89 %)
............................................................ 3600/3936 (91 %)
............................................................ 3660/3936 (92 %)
............................................................ 3720/3936 (94 %)
............................................................ 3780/3936 (96 %)
............................................................ 3840/3936 (97 %)
............................................................ 3900/3936 (99 %)
....................................                         3936/3936 (100 %)


Checked 3936 files in 13 seconds
No syntax error found

$ git add .

$ git commit -F /tmp/tmp2tnegmo6
[master 918c967] build: Updating php-parallel-lint/php-parallel-lint to 1.3.0
 121 files changed, 16164 insertions(+), 12 deletions(-)
 create mode 100644 php-parallel-lint/php-parallel-lint/CHANGELOG.md
 create mode 100644 php-parallel-lint/php-parallel-lint/LICENSE
 create mode 100644 php-parallel-lint/php-parallel-lint/README.md
 create mode 100644 php-parallel-lint/php-parallel-lint/bin/skip-linting.php
 create mode 100644 php-parallel-lint/php-parallel-lint/composer.json
 create mode 100755 php-parallel-lint/php-parallel-lint/parallel-lint
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Application.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Contracts/SyntaxErrorCallback.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Error.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/ErrorFormatter.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Manager.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Output.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/ParallelLint.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Process/GitBlameProcess.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Process/LintProcess.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Process/PhpExecutable.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Process/PhpProcess.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Process/Process.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Process/SkipLintProcess.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Result.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Settings.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/exceptions.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/polyfill.php
 create mode 100644 phplang/scope-exit/.gitignore
 create mode 100644 phplang/scope-exit/README.md
 create mode 100644 phplang/scope-exit/composer.json
 create mode 100644 phplang/scope-exit/src/ScopeExit.php
 create mode 100644 phplang/scope-exit/tests/BasicTest.php
 create mode 100644 swaggest/json-diff/.gitlab-ci.yml
 create mode 100644 swaggest/json-diff/CHANGELOG.md
 create mode 100644 swaggest/json-diff/LICENSE
 create mode 100644 swaggest/json-diff/README.md
 create mode 100644 swaggest/json-diff/composer.json
 create mode 100644 swaggest/json-diff/src/Exception.php
 create mode 100644 swaggest/json-diff/src/JsonDiff.php
 create mode 100644 swaggest/json-diff/src/JsonHash.php
 create mode 100644 swaggest/json-diff/src/JsonMergePatch.php
 create mode 100644 swaggest/json-diff/src/JsonPatch.php
 create mode 100644 swaggest/json-diff/src/JsonPatch/Add.php
 create mode 100644 swaggest/json-diff/src/JsonPatch/Copy.php
 create mode 100644 swaggest/json-diff/src/JsonPatch/Move.php
 create mode 100644 swaggest/json-diff/src/JsonPatch/OpPath.php
 create mode 100644 swaggest/json-diff/src/JsonPatch/OpPathFrom.php
 create mode 100644 swaggest/json-diff/src/JsonPatch/OpPathValue.php
 create mode 100644 swaggest/json-diff/src/JsonPatch/Remove.php
 create mode 100644 swaggest/json-diff/src/JsonPatch/Replace.php
 create mode 100644 swaggest/json-diff/src/JsonPatch/Test.php
 create mode 100644 swaggest/json-diff/src/JsonPointer.php
 create mode 100644 swaggest/json-diff/src/JsonValueReplace.php
 create mode 100644 swaggest/json-diff/src/ModifiedPathDiff.php
 create mode 100644 swaggest/json-schema/CHANGELOG.md
 create mode 100644 swaggest/json-schema/LICENSE
 create mode 100644 swaggest/json-schema/Makefile
 create mode 100644 swaggest/json-schema/README.md
 create mode 100644 swaggest/json-schema/benchmarks/AjvSchemasBench.php
 create mode 100644 swaggest/json-schema/benchmarks/Draft7Bench.php
 create mode 100644 swaggest/json-schema/composer.json
 create mode 100644 swaggest/json-schema/composer.lock
 create mode 100644 swaggest/json-schema/spec/json-schema-draft6.json
 create mode 100644 swaggest/json-schema/spec/json-schema-draft7.json
 create mode 100644 swaggest/json-schema/spec/json-schema.json
 create mode 100644 swaggest/json-schema/spec/petstore-swagger.json
 create mode 100644 swaggest/json-schema/spec/swagger-schema.json
 create mode 100644 swaggest/json-schema/src/Constraint/Constraint.php
 create mode 100644 swaggest/json-schema/src/Constraint/Content.php
 create mode 100644 swaggest/json-schema/src/Constraint/Format.php
 create mode 100644 swaggest/json-schema/src/Constraint/Format/IdnHostname.php
 create mode 100644 swaggest/json-schema/src/Constraint/Format/Iri.php
 create mode 100644 swaggest/json-schema/src/Constraint/Format/Uri.php
 create mode 100644 swaggest/json-schema/src/Constraint/Properties.php
 create mode 100644 swaggest/json-schema/src/Constraint/Ref.php
 create mode 100644 swaggest/json-schema/src/Constraint/Type.php
 create mode 100644 swaggest/json-schema/src/Constraint/UniqueItems.php
 create mode 100644 swaggest/json-schema/src/Context.php
 create mode 100644 swaggest/json-schema/src/DataPreProcessor.php
 create mode 100644 swaggest/json-schema/src/Exception.php
 create mode 100644 swaggest/json-schema/src/Exception/ArrayException.php
 create mode 100644 swaggest/json-schema/src/Exception/ConstException.php
 create mode 100644 swaggest/json-schema/src/Exception/ContentException.php
 create mode 100644 swaggest/json-schema/src/Exception/EnumException.php
 create mode 100644 swaggest/json-schema/src/Exception/Error.php
 create mode 100644 swaggest/json-schema/src/Exception/LogicException.php
 create mode 100644 swaggest/json-schema/src/Exception/NumericException.php
 create mode 100644 swaggest/json-schema/src/Exception/ObjectException.php
 create mode 100644 swaggest/json-schema/src/Exception/StringException.php
 create mode 100644 swaggest/json-schema/src/Exception/TypeException.php
 create mode 100644 swaggest/json-schema/src/Helper.php
 create mode 100644 swaggest/json-schema/src/InvalidValue.php
 create mode 100644 swaggest/json-schema/src/JsonSchema.php
 create mode 100644 swaggest/json-schema/src/MagicMap.php
 create mode 100644 swaggest/json-schema/src/MagicMapTrait.php
 create mode 100644 swaggest/json-schema/src/Meta/AbstractMeta.php
 create mode 100644 swaggest/json-schema/src/Meta/MetaHolder.php
 create mode 100644 swaggest/json-schema/src/NameMirror.php
 create mode 100644 swaggest/json-schema/src/Path/PointerUtil.php
 create mode 100644 swaggest/json-schema/src/RefResolver.php
 create mode 100644 swaggest/json-schema/src/RemoteRef/BasicFetcher.php
 create mode 100644 swaggest/json-schema/src/RemoteRef/Preloaded.php
 create mode 100644 swaggest/json-schema/src/RemoteRefProvider.php
 create mode 100644 swaggest/json-schema/src/Schema.php
 create mode 100644 swaggest/json-schema/src/SchemaContract.php
 create mode 100644 swaggest/json-schema/src/SchemaExporter.php
 create mode 100644 swaggest/json-schema/src/Structure/ClassStructure.php
 create mode 100644 swaggest/json-schema/src/Structure/ClassStructureContract.php
 create mode 100644 swaggest/json-schema/src/Structure/ClassStructureTrait.php
 create mode 100644 swaggest/json-schema/src/Structure/Composition.php
 create mode 100644 swaggest/json-schema/src/Structure/Egg.php
 create mode 100644 swaggest/json-schema/src/Structure/Nested.php
 create mode 100644 swaggest/json-schema/src/Structure/ObjectItem.php
 create mode 100644 swaggest/json-schema/src/Structure/ObjectItemContract.php
 create mode 100644 swaggest/json-schema/src/Structure/ObjectItemTrait.php
 create mode 100644 swaggest/json-schema/src/Structure/WithResolvedValue.php
 create mode 100644 swaggest/json-schema/src/Wrapper.php

$ git format-patch HEAD~1 --stdout
From 918c9678b05a759fb32fa76f3bf0b5642d3a9f73 Mon Sep 17 00:00:00 2001
From: libraryupgrader <tools.libraryupgrader@tools.wmflabs.org>
Date: Wed, 5 May 2021 13:48:31 +0000
Subject: [PATCH] build: Updating php-parallel-lint/php-parallel-lint to 1.3.0

Change-Id: I6405496ba1c3788e2bd1a6e9edbee571e10ae6c8
---
 composer.json                                 |    2 +-
 composer.lock                                 |   16 +-
 composer/ClassLoader.php                      |    2 +-
 composer/LICENSE                              |    2 -
 composer/autoload_classmap.php                |  103 ++
 composer/autoload_psr4.php                    |    3 +
 composer/autoload_static.php                  |  118 ++
 composer/installed.json                       |  188 ++
 .../php-parallel-lint/CHANGELOG.md            |   66 +
 php-parallel-lint/php-parallel-lint/LICENSE   |   26 +
 php-parallel-lint/php-parallel-lint/README.md |  111 ++
 .../php-parallel-lint/bin/skip-linting.php    |   19 +
 .../php-parallel-lint/composer.json           |   46 +
 .../php-parallel-lint/parallel-lint           |   67 +
 .../php-parallel-lint/src/Application.php     |  124 ++
 .../src/Contracts/SyntaxErrorCallback.php     |   13 +
 .../php-parallel-lint/src/Error.php           |  222 +++
 .../php-parallel-lint/src/ErrorFormatter.php  |  127 ++
 .../php-parallel-lint/src/Manager.php         |  287 +++
 .../php-parallel-lint/src/Output.php          |  513 ++++++
 .../php-parallel-lint/src/ParallelLint.php    |  286 +++
 .../src/Process/GitBlameProcess.php           |  147 ++
 .../src/Process/LintProcess.php               |  137 ++
 .../src/Process/PhpExecutable.php             |  128 ++
 .../src/Process/PhpProcess.php                |   35 +
 .../php-parallel-lint/src/Process/Process.php |  153 ++
 .../src/Process/SkipLintProcess.php           |   91 +
 .../php-parallel-lint/src/Result.php          |  168 ++
 .../php-parallel-lint/src/Settings.php        |  242 +++
 .../php-parallel-lint/src/exceptions.php      |   90 +
 .../php-parallel-lint/src/polyfill.php        |   15 +
 phplang/scope-exit/.gitignore                 |    2 +
 phplang/scope-exit/README.md                  |   18 +
 phplang/scope-exit/composer.json              |   28 +
 phplang/scope-exit/src/ScopeExit.php          |   40 +
 phplang/scope-exit/tests/BasicTest.php        |   29 +
 swaggest/json-diff/.gitlab-ci.yml             |   39 +
 swaggest/json-diff/CHANGELOG.md               |   66 +
 swaggest/json-diff/LICENSE                    |   21 +
 swaggest/json-diff/README.md                  |  285 +++
 swaggest/json-diff/composer.json              |   33 +
 swaggest/json-diff/src/Exception.php          |    9 +
 swaggest/json-diff/src/JsonDiff.php           |  542 ++++++
 swaggest/json-diff/src/JsonHash.php           |   76 +
 swaggest/json-diff/src/JsonMergePatch.php     |   31 +
 swaggest/json-diff/src/JsonPatch.php          |  187 ++
 swaggest/json-diff/src/JsonPatch/Add.php      |    8 +
 swaggest/json-diff/src/JsonPatch/Copy.php     |    8 +
 swaggest/json-diff/src/JsonPatch/Move.php     |    8 +
 swaggest/json-diff/src/JsonPatch/OpPath.php   |   18 +
 .../json-diff/src/JsonPatch/OpPathFrom.php    |   14 +
 .../json-diff/src/JsonPatch/OpPathValue.php   |   16 +
 swaggest/json-diff/src/JsonPatch/Remove.php   |    8 +
 swaggest/json-diff/src/JsonPatch/Replace.php  |    8 +
 swaggest/json-diff/src/JsonPatch/Test.php     |    8 +
 swaggest/json-diff/src/JsonPointer.php        |  305 ++++
 swaggest/json-diff/src/JsonValueReplace.php   |   79 +
 swaggest/json-diff/src/ModifiedPathDiff.php   |   30 +
 swaggest/json-schema/CHANGELOG.md             |   70 +
 swaggest/json-schema/LICENSE                  |   21 +
 swaggest/json-schema/Makefile                 |   32 +
 swaggest/json-schema/README.md                |  480 +++++
 .../benchmarks/AjvSchemasBench.php            |  144 ++
 .../json-schema/benchmarks/Draft7Bench.php    |  144 ++
 swaggest/json-schema/composer.json            |   42 +
 swaggest/json-schema/composer.lock            | 1132 ++++++++++++
 .../json-schema/spec/json-schema-draft6.json  |  154 ++
 .../json-schema/spec/json-schema-draft7.json  |  168 ++
 swaggest/json-schema/spec/json-schema.json    |  152 ++
 .../json-schema/spec/petstore-swagger.json    | 1035 +++++++++++
 swaggest/json-schema/spec/swagger-schema.json | 1608 +++++++++++++++++
 .../json-schema/src/Constraint/Constraint.php |    7 +
 .../json-schema/src/Constraint/Content.php    |   85 +
 .../json-schema/src/Constraint/Format.php     |  128 ++
 .../src/Constraint/Format/IdnHostname.php     |   21 +
 .../json-schema/src/Constraint/Format/Iri.php |  703 +++++++
 .../json-schema/src/Constraint/Format/Uri.php |   80 +
 .../json-schema/src/Constraint/Properties.php |  182 ++
 swaggest/json-schema/src/Constraint/Ref.php   |   71 +
 swaggest/json-schema/src/Constraint/Type.php  |   99 +
 .../src/Constraint/UniqueItems.php            |   40 +
 swaggest/json-schema/src/Context.php          |  137 ++
 swaggest/json-schema/src/DataPreProcessor.php |   15 +
 swaggest/json-schema/src/Exception.php        |   12 +
 .../src/Exception/ArrayException.php          |   11 +
 .../src/Exception/ConstException.php          |   10 +
 .../src/Exception/ContentException.php        |   11 +
 .../src/Exception/EnumException.php           |   10 +
 swaggest/json-schema/src/Exception/Error.php  |   19 +
 .../src/Exception/LogicException.php          |   11 +
 .../src/Exception/NumericException.php        |   14 +
 .../src/Exception/ObjectException.php         |   14 +
 .../src/Exception/StringException.php         |   14 +
 .../src/Exception/TypeException.php           |   11 +
 swaggest/json-schema/src/Helper.php           |   72 +
 swaggest/json-schema/src/InvalidValue.php     |   84 +
 swaggest/json-schema/src/JsonSchema.php       |  734 ++++++++
 swaggest/json-schema/src/MagicMap.php         |    8 +
 swaggest/json-schema/src/MagicMapTrait.php    |  133 ++
 .../json-schema/src/Meta/AbstractMeta.php     |   16 +
 swaggest/json-schema/src/Meta/MetaHolder.php  |   12 +
 swaggest/json-schema/src/NameMirror.php       |   16 +
 swaggest/json-schema/src/Path/PointerUtil.php |  103 ++
 swaggest/json-schema/src/RefResolver.php      |  267 +++
 .../src/RemoteRef/BasicFetcher.php            |   23 +
 .../json-schema/src/RemoteRef/Preloaded.php   |   75 +
 .../json-schema/src/RemoteRefProvider.php     |   13 +
 swaggest/json-schema/src/Schema.php           | 1445 +++++++++++++++
 swaggest/json-schema/src/SchemaContract.php   |   64 +
 swaggest/json-schema/src/SchemaExporter.php   |   12 +
 .../src/Structure/ClassStructure.php          |    8 +
 .../src/Structure/ClassStructureContract.php  |   15 +
 .../src/Structure/ClassStructureTrait.php     |  182 ++
 .../json-schema/src/Structure/Composition.php |   33 +
 swaggest/json-schema/src/Structure/Egg.php    |   31 +
 swaggest/json-schema/src/Structure/Nested.php |   15 +
 .../json-schema/src/Structure/ObjectItem.php  |   17 +
 .../src/Structure/ObjectItemContract.php      |   25 +
 .../src/Structure/ObjectItemTrait.php         |  189 ++
 .../src/Structure/WithResolvedValue.php       |   15 +
 swaggest/json-schema/src/Wrapper.php          |  219 +++
 121 files changed, 16164 insertions(+), 12 deletions(-)
 create mode 100644 php-parallel-lint/php-parallel-lint/CHANGELOG.md
 create mode 100644 php-parallel-lint/php-parallel-lint/LICENSE
 create mode 100644 php-parallel-lint/php-parallel-lint/README.md
 create mode 100644 php-parallel-lint/php-parallel-lint/bin/skip-linting.php
 create mode 100644 php-parallel-lint/php-parallel-lint/composer.json
 create mode 100755 php-parallel-lint/php-parallel-lint/parallel-lint
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Application.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Contracts/SyntaxErrorCallback.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Error.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/ErrorFormatter.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Manager.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Output.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/ParallelLint.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Process/GitBlameProcess.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Process/LintProcess.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Process/PhpExecutable.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Process/PhpProcess.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Process/Process.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Process/SkipLintProcess.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Result.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/Settings.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/exceptions.php
 create mode 100644 php-parallel-lint/php-parallel-lint/src/polyfill.php
 create mode 100644 phplang/scope-exit/.gitignore
 create mode 100644 phplang/scope-exit/README.md
 create mode 100644 phplang/scope-exit/composer.json
 create mode 100644 phplang/scope-exit/src/ScopeExit.php
 create mode 100644 phplang/scope-exit/tests/BasicTest.php
 create mode 100644 swaggest/json-diff/.gitlab-ci.yml
 create mode 100644 swaggest/json-diff/CHANGELOG.md
 create mode 100644 swaggest/json-diff/LICENSE
 create mode 100644 swaggest/json-diff/README.md
 create mode 100644 swaggest/json-diff/composer.json
 create mode 100644 swaggest/json-diff/src/Exception.php
 create mode 100644 swaggest/json-diff/src/JsonDiff.php
 create mode 100644 swaggest/json-diff/src/JsonHash.php
 create mode 100644 swaggest/json-diff/src/JsonMergePatch.php
 create mode 100644 swaggest/json-diff/src/JsonPatch.php
 create mode 100644 swaggest/json-diff/src/JsonPatch/Add.php
 create mode 100644 swaggest/json-diff/src/JsonPatch/Copy.php
 create mode 100644 swaggest/json-diff/src/JsonPatch/Move.php
 create mode 100644 swaggest/json-diff/src/JsonPatch/OpPath.php
 create mode 100644 swaggest/json-diff/src/JsonPatch/OpPathFrom.php
 create mode 100644 swaggest/json-diff/src/JsonPatch/OpPathValue.php
 create mode 100644 swaggest/json-diff/src/JsonPatch/Remove.php
 create mode 100644 swaggest/json-diff/src/JsonPatch/Replace.php
 create mode 100644 swaggest/json-diff/src/JsonPatch/Test.php
 create mode 100644 swaggest/json-diff/src/JsonPointer.php
 create mode 100644 swaggest/json-diff/src/JsonValueReplace.php
 create mode 100644 swaggest/json-diff/src/ModifiedPathDiff.php
 create mode 100644 swaggest/json-schema/CHANGELOG.md
 create mode 100644 swaggest/json-schema/LICENSE
 create mode 100644 swaggest/json-schema/Makefile
 create mode 100644 swaggest/json-schema/README.md
 create mode 100644 swaggest/json-schema/benchmarks/AjvSchemasBench.php
 create mode 100644 swaggest/json-schema/benchmarks/Draft7Bench.php
 create mode 100644 swaggest/json-schema/composer.json
 create mode 100644 swaggest/json-schema/composer.lock
 create mode 100644 swaggest/json-schema/spec/json-schema-draft6.json
 create mode 100644 swaggest/json-schema/spec/json-schema-draft7.json
 create mode 100644 swaggest/json-schema/spec/json-schema.json
 create mode 100644 swaggest/json-schema/spec/petstore-swagger.json
 create mode 100644 swaggest/json-schema/spec/swagger-schema.json
 create mode 100644 swaggest/json-schema/src/Constraint/Constraint.php
 create mode 100644 swaggest/json-schema/src/Constraint/Content.php
 create mode 100644 swaggest/json-schema/src/Constraint/Format.php
 create mode 100644 swaggest/json-schema/src/Constraint/Format/IdnHostname.php
 create mode 100644 swaggest/json-schema/src/Constraint/Format/Iri.php
 create mode 100644 swaggest/json-schema/src/Constraint/Format/Uri.php
 create mode 100644 swaggest/json-schema/src/Constraint/Properties.php
 create mode 100644 swaggest/json-schema/src/Constraint/Ref.php
 create mode 100644 swaggest/json-schema/src/Constraint/Type.php
 create mode 100644 swaggest/json-schema/src/Constraint/UniqueItems.php
 create mode 100644 swaggest/json-schema/src/Context.php
 create mode 100644 swaggest/json-schema/src/DataPreProcessor.php
 create mode 100644 swaggest/json-schema/src/Exception.php
 create mode 100644 swaggest/json-schema/src/Exception/ArrayException.php
 create mode 100644 swaggest/json-schema/src/Exception/ConstException.php
 create mode 100644 swaggest/json-schema/src/Exception/ContentException.php
 create mode 100644 swaggest/json-schema/src/Exception/EnumException.php
 create mode 100644 swaggest/json-schema/src/Exception/Error.php
 create mode 100644 swaggest/json-schema/src/Exception/LogicException.php
 create mode 100644 swaggest/json-schema/src/Exception/NumericException.php
 create mode 100644 swaggest/json-schema/src/Exception/ObjectException.php
 create mode 100644 swaggest/json-schema/src/Exception/StringException.php
 create mode 100644 swaggest/json-schema/src/Exception/TypeException.php
 create mode 100644 swaggest/json-schema/src/Helper.php
 create mode 100644 swaggest/json-schema/src/InvalidValue.php
 create mode 100644 swaggest/json-schema/src/JsonSchema.php
 create mode 100644 swaggest/json-schema/src/MagicMap.php
 create mode 100644 swaggest/json-schema/src/MagicMapTrait.php
 create mode 100644 swaggest/json-schema/src/Meta/AbstractMeta.php
 create mode 100644 swaggest/json-schema/src/Meta/MetaHolder.php
 create mode 100644 swaggest/json-schema/src/NameMirror.php
 create mode 100644 swaggest/json-schema/src/Path/PointerUtil.php
 create mode 100644 swaggest/json-schema/src/RefResolver.php
 create mode 100644 swaggest/json-schema/src/RemoteRef/BasicFetcher.php
 create mode 100644 swaggest/json-schema/src/RemoteRef/Preloaded.php
 create mode 100644 swaggest/json-schema/src/RemoteRefProvider.php
 create mode 100644 swaggest/json-schema/src/Schema.php
 create mode 100644 swaggest/json-schema/src/SchemaContract.php
 create mode 100644 swaggest/json-schema/src/SchemaExporter.php
 create mode 100644 swaggest/json-schema/src/Structure/ClassStructure.php
 create mode 100644 swaggest/json-schema/src/Structure/ClassStructureContract.php
 create mode 100644 swaggest/json-schema/src/Structure/ClassStructureTrait.php
 create mode 100644 swaggest/json-schema/src/Structure/Composition.php
 create mode 100644 swaggest/json-schema/src/Structure/Egg.php
 create mode 100644 swaggest/json-schema/src/Structure/Nested.php
 create mode 100644 swaggest/json-schema/src/Structure/ObjectItem.php
 create mode 100644 swaggest/json-schema/src/Structure/ObjectItemContract.php
 create mode 100644 swaggest/json-schema/src/Structure/ObjectItemTrait.php
 create mode 100644 swaggest/json-schema/src/Structure/WithResolvedValue.php
 create mode 100644 swaggest/json-schema/src/Wrapper.php

diff --git a/composer.json b/composer.json
index 2939036..da9751e 100644
--- a/composer.json
+++ b/composer.json
@@ -145,7 +145,7 @@
 		"symfony/polyfill-php72": "1.12.0"
 	},
 	"require-dev": {
-		"php-parallel-lint/php-parallel-lint": "1.2.0",
+		"php-parallel-lint/php-parallel-lint": "1.3.0",
 		"swaggest/json-schema": "0.12.29"
 	},
 	"scripts": {
diff --git a/composer.lock b/composer.lock
index 0e7780b..6d2dda7 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "c01c6df9e9a08d87cad0ee2c742bea44",
+    "content-hash": "4d2686ca2df540b72f6e1330a2995b4e",
     "packages": [
         {
             "name": "beberlei/assert",
@@ -7475,21 +7475,21 @@
     "packages-dev": [
         {
             "name": "php-parallel-lint/php-parallel-lint",
-            "version": "v1.2.0",
+            "version": "v1.3.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/php-parallel-lint/PHP-Parallel-Lint.git",
-                "reference": "474f18bc6cc6aca61ca40bfab55139de614e51ca"
+                "reference": "772a954e5f119f6f5871d015b23eabed8cbdadfb"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/php-parallel-lint/PHP-Parallel-Lint/zipball/474f18bc6cc6aca61ca40bfab55139de614e51ca",
-                "reference": "474f18bc6cc6aca61ca40bfab55139de614e51ca",
+                "url": "https://api.github.com/repos/php-parallel-lint/PHP-Parallel-Lint/zipball/772a954e5f119f6f5871d015b23eabed8cbdadfb",
+                "reference": "772a954e5f119f6f5871d015b23eabed8cbdadfb",
                 "shasum": ""
             },
             "require": {
                 "ext-json": "*",
-                "php": ">=5.4.0"
+                "php": ">=5.3.0"
             },
             "replace": {
                 "grogy/php-parallel-lint": "*",
@@ -7498,7 +7498,7 @@
             "require-dev": {
                 "nette/tester": "^1.3 || ^2.0",
                 "php-parallel-lint/php-console-highlighter": "~0.3",
-                "squizlabs/php_codesniffer": "~3.0"
+                "squizlabs/php_codesniffer": "^3.5"
             },
             "suggest": {
                 "php-parallel-lint/php-console-highlighter": "Highlight syntax in code snippet"
@@ -7524,7 +7524,7 @@
             ],
             "description": "This tool check syntax of PHP files about 20x faster than serial check.",
             "homepage": "https://github.com/php-parallel-lint/PHP-Parallel-Lint",
-            "time": "2020-04-04T12:18:32+00:00"
+            "time": "2021-04-07T14:42:48+00:00"
         },
         {
             "name": "phplang/scope-exit",
diff --git a/composer/ClassLoader.php b/composer/ClassLoader.php
index 03b9bb9..fce8549 100644
--- a/composer/ClassLoader.php
+++ b/composer/ClassLoader.php
@@ -60,7 +60,7 @@ class ClassLoader
     public function getPrefixes()
     {
         if (!empty($this->prefixesPsr0)) {
-            return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
+            return call_user_func_array('array_merge', $this->prefixesPsr0);
         }
 
         return array();
diff --git a/composer/LICENSE b/composer/LICENSE
index f27399a..62ecfd8 100644
--- a/composer/LICENSE
+++ b/composer/LICENSE
@@ -1,4 +1,3 @@
-
 Copyright (c) Nils Adermann, Jordi Boggiano
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -18,4 +17,3 @@ 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/composer/autoload_classmap.php b/composer/autoload_classmap.php
index 2b54c6e..8e93e74 100644
--- a/composer/autoload_classmap.php
+++ b/composer/autoload_classmap.php
@@ -867,6 +867,40 @@ return array(
     'GuzzleHttp\\Utils' => $vendorDir . '/guzzlehttp/guzzle/src/Utils.php',
     'HtmlFormatter\\HtmlFormatter' => $vendorDir . '/wikimedia/html-formatter/src/HtmlFormatter.php',
     'Image_XMP' => $vendorDir . '/james-heinrich/getid3/getid3/module.tag.xmp.php',
+    'JakubOnderka\\PhpParallelLint\\Application' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Application.php',
+    'JakubOnderka\\PhpParallelLint\\ArrayIterator' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Settings.php',
+    'JakubOnderka\\PhpParallelLint\\Blame' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Error.php',
+    'JakubOnderka\\PhpParallelLint\\CheckstyleOutput' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Output.php',
+    'JakubOnderka\\PhpParallelLint\\ConsoleWriter' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Output.php',
+    'JakubOnderka\\PhpParallelLint\\Contracts\\SyntaxErrorCallback' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Contracts/SyntaxErrorCallback.php',
+    'JakubOnderka\\PhpParallelLint\\Error' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Error.php',
+    'JakubOnderka\\PhpParallelLint\\ErrorFormatter' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/ErrorFormatter.php',
+    'JakubOnderka\\PhpParallelLint\\Exception' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/exceptions.php',
+    'JakubOnderka\\PhpParallelLint\\FileWriter' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Output.php',
+    'JakubOnderka\\PhpParallelLint\\IWriter' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Output.php',
+    'JakubOnderka\\PhpParallelLint\\InvalidArgumentException' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/exceptions.php',
+    'JakubOnderka\\PhpParallelLint\\JsonOutput' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Output.php',
+    'JakubOnderka\\PhpParallelLint\\Manager' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Manager.php',
+    'JakubOnderka\\PhpParallelLint\\MultipleWriter' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Output.php',
+    'JakubOnderka\\PhpParallelLint\\NotExistsClassException' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/exceptions.php',
+    'JakubOnderka\\PhpParallelLint\\NotExistsPathException' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/exceptions.php',
+    'JakubOnderka\\PhpParallelLint\\NotImplementCallbackException' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/exceptions.php',
+    'JakubOnderka\\PhpParallelLint\\NullWriter' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Output.php',
+    'JakubOnderka\\PhpParallelLint\\Output' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Output.php',
+    'JakubOnderka\\PhpParallelLint\\ParallelLint' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/ParallelLint.php',
+    'JakubOnderka\\PhpParallelLint\\Process\\GitBlameProcess' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Process/GitBlameProcess.php',
+    'JakubOnderka\\PhpParallelLint\\Process\\LintProcess' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Process/LintProcess.php',
+    'JakubOnderka\\PhpParallelLint\\Process\\PhpExecutable' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Process/PhpExecutable.php',
+    'JakubOnderka\\PhpParallelLint\\Process\\PhpProcess' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Process/PhpProcess.php',
+    'JakubOnderka\\PhpParallelLint\\Process\\Process' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Process/Process.php',
+    'JakubOnderka\\PhpParallelLint\\Process\\SkipLintProcess' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Process/SkipLintProcess.php',
+    'JakubOnderka\\PhpParallelLint\\RecursiveDirectoryFilterIterator' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Manager.php',
+    'JakubOnderka\\PhpParallelLint\\Result' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Result.php',
+    'JakubOnderka\\PhpParallelLint\\RunTimeException' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/exceptions.php',
+    'JakubOnderka\\PhpParallelLint\\Settings' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Settings.php',
+    'JakubOnderka\\PhpParallelLint\\SyntaxError' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Error.php',
+    'JakubOnderka\\PhpParallelLint\\TextOutput' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Output.php',
+    'JakubOnderka\\PhpParallelLint\\TextOutputColored' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/Output.php',
     'JsonException' => $vendorDir . '/symfony/polyfill-php73/Resources/stubs/JsonException.php',
     'JsonSchema\\Constraints\\BaseConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/BaseConstraint.php',
     'JsonSchema\\Constraints\\CollectionConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/CollectionConstraint.php',
@@ -911,6 +945,7 @@ return array(
     'JsonSchema\\Uri\\UriResolver' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/UriResolver.php',
     'JsonSchema\\Uri\\UriRetriever' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/UriRetriever.php',
     'JsonSchema\\Validator' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Validator.php',
+    'JsonSerializable' => $vendorDir . '/php-parallel-lint/php-parallel-lint/src/polyfill.php',
     'Kafka\\Client' => $vendorDir . '/nmred/kafka-php/src/Kafka/Client.php',
     'Kafka\\ClusterMetaData' => $vendorDir . '/nmred/kafka-php/src/Kafka/ClusterMetaData.php',
     'Kafka\\Consumer' => $vendorDir . '/nmred/kafka-php/src/Kafka/Consumer.php',
@@ -1437,6 +1472,7 @@ return array(
     'PEAR_Error' => $vendorDir . '/pear/pear-core-minimal/src/PEAR.php',
     'PEAR_ErrorStack' => $vendorDir . '/pear/pear-core-minimal/src/PEAR/ErrorStack.php',
     'PEAR_Exception' => $vendorDir . '/pear/pear_exception/PEAR/Exception.php',
+    'PhpLang\\ScopeExit' => $vendorDir . '/phplang/scope-exit/src/ScopeExit.php',
     'PhpParser\\Builder' => $vendorDir . '/nikic/php-parser/lib/PhpParser/Builder.php',
     'PhpParser\\BuilderFactory' => $vendorDir . '/nikic/php-parser/lib/PhpParser/BuilderFactory.php',
     'PhpParser\\BuilderHelpers' => $vendorDir . '/nikic/php-parser/lib/PhpParser/BuilderHelpers.php',
@@ -2247,6 +2283,73 @@ return array(
     'Shellbox\\ShellboxError' => $vendorDir . '/wikimedia/shellbox/src/ShellboxError.php',
     'Shellbox\\TempDirManager' => $vendorDir . '/wikimedia/shellbox/src/TempDirManager.php',
     'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
+    'Swaggest\\JsonDiff\\Exception' => $vendorDir . '/swaggest/json-diff/src/Exception.php',
+    'Swaggest\\JsonDiff\\JsonDiff' => $vendorDir . '/swaggest/json-diff/src/JsonDiff.php',
+    'Swaggest\\JsonDiff\\JsonHash' => $vendorDir . '/swaggest/json-diff/src/JsonHash.php',
+    'Swaggest\\JsonDiff\\JsonMergePatch' => $vendorDir . '/swaggest/json-diff/src/JsonMergePatch.php',
+    'Swaggest\\JsonDiff\\JsonPatch' => $vendorDir . '/swaggest/json-diff/src/JsonPatch.php',
+    'Swaggest\\JsonDiff\\JsonPatch\\Add' => $vendorDir . '/swaggest/json-diff/src/JsonPatch/Add.php',
+    'Swaggest\\JsonDiff\\JsonPatch\\Copy' => $vendorDir . '/swaggest/json-diff/src/JsonPatch/Copy.php',
+    'Swaggest\\JsonDiff\\JsonPatch\\Move' => $vendorDir . '/swaggest/json-diff/src/JsonPatch/Move.php',
+    'Swaggest\\JsonDiff\\JsonPatch\\OpPath' => $vendorDir . '/swaggest/json-diff/src/JsonPatch/OpPath.php',
+    'Swaggest\\JsonDiff\\JsonPatch\\OpPathFrom' => $vendorDir . '/swaggest/json-diff/src/JsonPatch/OpPathFrom.php',
+    'Swaggest\\JsonDiff\\JsonPatch\\OpPathValue' => $vendorDir . '/swaggest/json-diff/src/JsonPatch/OpPathValue.php',
+    'Swaggest\\JsonDiff\\JsonPatch\\Remove' => $vendorDir . '/swaggest/json-diff/src/JsonPatch/Remove.php',
+    'Swaggest\\JsonDiff\\JsonPatch\\Replace' => $vendorDir . '/swaggest/json-diff/src/JsonPatch/Replace.php',
+    'Swaggest\\JsonDiff\\JsonPatch\\Test' => $vendorDir . '/swaggest/json-diff/src/JsonPatch/Test.php',
+    'Swaggest\\JsonDiff\\JsonPointer' => $vendorDir . '/swaggest/json-diff/src/JsonPointer.php',
+    'Swaggest\\JsonDiff\\JsonValueReplace' => $vendorDir . '/swaggest/json-diff/src/JsonValueReplace.php',
+    'Swaggest\\JsonDiff\\ModifiedPathDiff' => $vendorDir . '/swaggest/json-diff/src/ModifiedPathDiff.php',
+    'Swaggest\\JsonSchema\\Constraint\\Constraint' => $vendorDir . '/swaggest/json-schema/src/Constraint/Constraint.php',
+    'Swaggest\\JsonSchema\\Constraint\\Content' => $vendorDir . '/swaggest/json-schema/src/Constraint/Content.php',
+    'Swaggest\\JsonSchema\\Constraint\\Format' => $vendorDir . '/swaggest/json-schema/src/Constraint/Format.php',
+    'Swaggest\\JsonSchema\\Constraint\\Format\\IdnHostname' => $vendorDir . '/swaggest/json-schema/src/Constraint/Format/IdnHostname.php',
+    'Swaggest\\JsonSchema\\Constraint\\Format\\Iri' => $vendorDir . '/swaggest/json-schema/src/Constraint/Format/Iri.php',
+    'Swaggest\\JsonSchema\\Constraint\\Format\\Uri' => $vendorDir . '/swaggest/json-schema/src/Constraint/Format/Uri.php',
+    'Swaggest\\JsonSchema\\Constraint\\Properties' => $vendorDir . '/swaggest/json-schema/src/Constraint/Properties.php',
+    'Swaggest\\JsonSchema\\Constraint\\Ref' => $vendorDir . '/swaggest/json-schema/src/Constraint/Ref.php',
+    'Swaggest\\JsonSchema\\Constraint\\Type' => $vendorDir . '/swaggest/json-schema/src/Constraint/Type.php',
+    'Swaggest\\JsonSchema\\Constraint\\UniqueItems' => $vendorDir . '/swaggest/json-schema/src/Constraint/UniqueItems.php',
+    'Swaggest\\JsonSchema\\Context' => $vendorDir . '/swaggest/json-schema/src/Context.php',
+    'Swaggest\\JsonSchema\\DataPreProcessor' => $vendorDir . '/swaggest/json-schema/src/DataPreProcessor.php',
+    'Swaggest\\JsonSchema\\Exception' => $vendorDir . '/swaggest/json-schema/src/Exception.php',
+    'Swaggest\\JsonSchema\\Exception\\ArrayException' => $vendorDir . '/swaggest/json-schema/src/Exception/ArrayException.php',
+    'Swaggest\\JsonSchema\\Exception\\ConstException' => $vendorDir . '/swaggest/json-schema/src/Exception/ConstException.php',
+    'Swaggest\\JsonSchema\\Exception\\ContentException' => $vendorDir . '/swaggest/json-schema/src/Exception/ContentException.php',
+    'Swaggest\\JsonSchema\\Exception\\EnumException' => $vendorDir . '/swaggest/json-schema/src/Exception/EnumException.php',
+    'Swaggest\\JsonSchema\\Exception\\Error' => $vendorDir . '/swaggest/json-schema/src/Exception/Error.php',
+    'Swaggest\\JsonSchema\\Exception\\LogicException' => $vendorDir . '/swaggest/json-schema/src/Exception/LogicException.php',
+    'Swaggest\\JsonSchema\\Exception\\NumericException' => $vendorDir . '/swaggest/json-schema/src/Exception/NumericException.php',
+    'Swaggest\\JsonSchema\\Exception\\ObjectException' => $vendorDir . '/swaggest/json-schema/src/Exception/ObjectException.php',
+    'Swaggest\\JsonSchema\\Exception\\StringException' => $vendorDir . '/swaggest/json-schema/src/Exception/StringException.php',
+    'Swaggest\\JsonSchema\\Exception\\TypeException' => $vendorDir . '/swaggest/json-schema/src/Exception/TypeException.php',
+    'Swaggest\\JsonSchema\\Helper' => $vendorDir . '/swaggest/json-schema/src/Helper.php',
+    'Swaggest\\JsonSchema\\InvalidValue' => $vendorDir . '/swaggest/json-schema/src/InvalidValue.php',
+    'Swaggest\\JsonSchema\\JsonSchema' => $vendorDir . '/swaggest/json-schema/src/JsonSchema.php',
+    'Swaggest\\JsonSchema\\MagicMap' => $vendorDir . '/swaggest/json-schema/src/MagicMap.php',
+    'Swaggest\\JsonSchema\\MagicMapTrait' => $vendorDir . '/swaggest/json-schema/src/MagicMapTrait.php',
+    'Swaggest\\JsonSchema\\Meta\\AbstractMeta' => $vendorDir . '/swaggest/json-schema/src/Meta/AbstractMeta.php',
+    'Swaggest\\JsonSchema\\Meta\\MetaHolder' => $vendorDir . '/swaggest/json-schema/src/Meta/MetaHolder.php',
+    'Swaggest\\JsonSchema\\NameMirror' => $vendorDir . '/swaggest/json-schema/src/NameMirror.php',
+    'Swaggest\\JsonSchema\\Path\\PointerUtil' => $vendorDir . '/swaggest/json-schema/src/Path/PointerUtil.php',
+    'Swaggest\\JsonSchema\\RefResolver' => $vendorDir . '/swaggest/json-schema/src/RefResolver.php',
+    'Swaggest\\JsonSchema\\RemoteRefProvider' => $vendorDir . '/swaggest/json-schema/src/RemoteRefProvider.php',
+    'Swaggest\\JsonSchema\\RemoteRef\\BasicFetcher' => $vendorDir . '/swaggest/json-schema/src/RemoteRef/BasicFetcher.php',
+    'Swaggest\\JsonSchema\\RemoteRef\\Preloaded' => $vendorDir . '/swaggest/json-schema/src/RemoteRef/Preloaded.php',
+    'Swaggest\\JsonSchema\\Schema' => $vendorDir . '/swaggest/json-schema/src/Schema.php',
+    'Swaggest\\JsonSchema\\SchemaContract' => $vendorDir . '/swaggest/json-schema/src/SchemaContract.php',
+    'Swaggest\\JsonSchema\\SchemaExporter' => $vendorDir . '/swaggest/json-schema/src/SchemaExporter.php',
+    'Swaggest\\JsonSchema\\Structure\\ClassStructure' => $vendorDir . '/swaggest/json-schema/src/Structure/ClassStructure.php',
+    'Swaggest\\JsonSchema\\Structure\\ClassStructureContract' => $vendorDir . '/swaggest/json-schema/src/Structure/ClassStructureContract.php',
+    'Swaggest\\JsonSchema\\Structure\\ClassStructureTrait' => $vendorDir . '/swaggest/json-schema/src/Structure/ClassStructureTrait.php',
+    'Swaggest\\JsonSchema\\Structure\\Composition' => $vendorDir . '/swaggest/json-schema/src/Structure/Composition.php',
+    'Swaggest\\JsonSchema\\Structure\\Egg' => $vendorDir . '/swaggest/json-schema/src/Structure/Egg.php',
+    'Swaggest\\JsonSchema\\Structure\\Nested' => $vendorDir . '/swaggest/json-schema/src/Structure/Nested.php',
+    'Swaggest\\JsonSchema\\Structure\\ObjectItem' => $vendorDir . '/swaggest/json-schema/src/Structure/ObjectItem.php',
+    'Swaggest\\JsonSchema\\Structure\\ObjectItemContract' => $vendorDir . '/swaggest/json-schema/src/Structure/ObjectItemContract.php',
+    'Swaggest\\JsonSchema\\Structure\\ObjectItemTrait' => $vendorDir . '/swaggest/json-schema/src/Structure/ObjectItemTrait.php',
+    'Swaggest\\JsonSchema\\Structure\\WithResolvedValue' => $vendorDir . '/swaggest/json-schema/src/Structure/WithResolvedValue.php',
+    'Swaggest\\JsonSchema\\Wrapper' => $vendorDir . '/swaggest/json-schema/src/Wrapper.php',
     'Symfony\\Component\\Console\\Application' => $vendorDir . '/symfony/console/Application.php',
     'Symfony\\Component\\Console\\Color' => $vendorDir . '/symfony/console/Color.php',
     'Symfony\\Component\\Console\\CommandLoader\\CommandLoaderInterface' => $vendorDir . '/symfony/console/CommandLoader/CommandLoaderInterface.php',
diff --git a/composer/autoload_psr4.php b/composer/autoload_psr4.php
index 0686773..63e30d1 100644
--- a/composer/autoload_psr4.php
+++ b/composer/autoload_psr4.php
@@ -47,6 +47,8 @@ return array(
     'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'),
     'Symfony\\Component\\Debug\\' => array($vendorDir . '/symfony/debug'),
     'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'),
+    'Swaggest\\JsonSchema\\' => array($vendorDir . '/swaggest/json-schema/src'),
+    'Swaggest\\JsonDiff\\' => array($vendorDir . '/swaggest/json-diff/src'),
     'Shellbox\\' => array($vendorDir . '/wikimedia/shellbox/src'),
     'Serializers\\' => array($vendorDir . '/serialization/serialization/src/Serializers'),
     'Safe\\' => array($vendorDir . '/thecodingmachine/safe/lib', $vendorDir . '/thecodingmachine/safe/deprecated', $vendorDir . '/thecodingmachine/safe/generated'),
@@ -62,6 +64,7 @@ return array(
     'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
     'Pleo\\BloomFilter\\' => array($vendorDir . '/pleonasm/bloom-filter/src'),
     'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'),
+    'PhpLang\\' => array($vendorDir . '/phplang/scope-exit/src'),
     'Onoi\\MessageReporter\\' => array($vendorDir . '/onoi/message-reporter/src'),
     'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'),
     'MaxMind\\WebService\\' => array($vendorDir . '/maxmind/web-service-common/src/WebService'),
diff --git a/composer/autoload_static.php b/composer/autoload_static.php
index a22d716..b3ec1f6 100644
--- a/composer/autoload_static.php
+++ b/composer/autoload_static.php
@@ -179,6 +179,8 @@ class ComposerStaticInit_mediawiki_vendor
             'Symfony\\Component\\Process\\' => 26,
             'Symfony\\Component\\Debug\\' => 24,
             'Symfony\\Component\\Console\\' => 26,
+            'Swaggest\\JsonSchema\\' => 20,
+            'Swaggest\\JsonDiff\\' => 18,
             'Shellbox\\' => 9,
             'Serializers\\' => 12,
             'Safe\\' => 5,
@@ -200,6 +202,7 @@ class ComposerStaticInit_mediawiki_vendor
             'Psr\\Container\\' => 14,
             'Pleo\\BloomFilter\\' => 17,
             'PhpParser\\' => 10,
+            'PhpLang\\' => 8,
         ),
         'O' => 
         array (
@@ -452,6 +455,14 @@ class ComposerStaticInit_mediawiki_vendor
         array (
             0 => __DIR__ . '/..' . '/symfony/console',
         ),
+        'Swaggest\\JsonSchema\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/swaggest/json-schema/src',
+        ),
+        'Swaggest\\JsonDiff\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/swaggest/json-diff/src',
+        ),
         'Shellbox\\' => 
         array (
             0 => __DIR__ . '/..' . '/wikimedia/shellbox/src',
@@ -515,6 +526,10 @@ class ComposerStaticInit_mediawiki_vendor
         array (
             0 => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser',
         ),
+        'PhpLang\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/phplang/scope-exit/src',
+        ),
         'Onoi\\MessageReporter\\' => 
         array (
             0 => __DIR__ . '/..' . '/onoi/message-reporter/src',
@@ -1626,6 +1641,40 @@ class ComposerStaticInit_mediawiki_vendor
         'GuzzleHttp\\Utils' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Utils.php',
         'HtmlFormatter\\HtmlFormatter' => __DIR__ . '/..' . '/wikimedia/html-formatter/src/HtmlFormatter.php',
         'Image_XMP' => __DIR__ . '/..' . '/james-heinrich/getid3/getid3/module.tag.xmp.php',
+        'JakubOnderka\\PhpParallelLint\\Application' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Application.php',
+        'JakubOnderka\\PhpParallelLint\\ArrayIterator' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Settings.php',
+        'JakubOnderka\\PhpParallelLint\\Blame' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Error.php',
+        'JakubOnderka\\PhpParallelLint\\CheckstyleOutput' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Output.php',
+        'JakubOnderka\\PhpParallelLint\\ConsoleWriter' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Output.php',
+        'JakubOnderka\\PhpParallelLint\\Contracts\\SyntaxErrorCallback' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Contracts/SyntaxErrorCallback.php',
+        'JakubOnderka\\PhpParallelLint\\Error' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Error.php',
+        'JakubOnderka\\PhpParallelLint\\ErrorFormatter' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/ErrorFormatter.php',
+        'JakubOnderka\\PhpParallelLint\\Exception' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/exceptions.php',
+        'JakubOnderka\\PhpParallelLint\\FileWriter' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Output.php',
+        'JakubOnderka\\PhpParallelLint\\IWriter' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Output.php',
+        'JakubOnderka\\PhpParallelLint\\InvalidArgumentException' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/exceptions.php',
+        'JakubOnderka\\PhpParallelLint\\JsonOutput' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Output.php',
+        'JakubOnderka\\PhpParallelLint\\Manager' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Manager.php',
+        'JakubOnderka\\PhpParallelLint\\MultipleWriter' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Output.php',
+        'JakubOnderka\\PhpParallelLint\\NotExistsClassException' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/exceptions.php',
+        'JakubOnderka\\PhpParallelLint\\NotExistsPathException' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/exceptions.php',
+        'JakubOnderka\\PhpParallelLint\\NotImplementCallbackException' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/exceptions.php',
+        'JakubOnderka\\PhpParallelLint\\NullWriter' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Output.php',
+        'JakubOnderka\\PhpParallelLint\\Output' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Output.php',
+        'JakubOnderka\\PhpParallelLint\\ParallelLint' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/ParallelLint.php',
+        'JakubOnderka\\PhpParallelLint\\Process\\GitBlameProcess' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Process/GitBlameProcess.php',
+        'JakubOnderka\\PhpParallelLint\\Process\\LintProcess' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Process/LintProcess.php',
+        'JakubOnderka\\PhpParallelLint\\Process\\PhpExecutable' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Process/PhpExecutable.php',
+        'JakubOnderka\\PhpParallelLint\\Process\\PhpProcess' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Process/PhpProcess.php',
+        'JakubOnderka\\PhpParallelLint\\Process\\Process' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Process/Process.php',
+        'JakubOnderka\\PhpParallelLint\\Process\\SkipLintProcess' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Process/SkipLintProcess.php',
+        'JakubOnderka\\PhpParallelLint\\RecursiveDirectoryFilterIterator' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Manager.php',
+        'JakubOnderka\\PhpParallelLint\\Result' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Result.php',
+        'JakubOnderka\\PhpParallelLint\\RunTimeException' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/exceptions.php',
+        'JakubOnderka\\PhpParallelLint\\Settings' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Settings.php',
+        'JakubOnderka\\PhpParallelLint\\SyntaxError' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Error.php',
+        'JakubOnderka\\PhpParallelLint\\TextOutput' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Output.php',
+        'JakubOnderka\\PhpParallelLint\\TextOutputColored' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/Output.php',
         'JsonException' => __DIR__ . '/..' . '/symfony/polyfill-php73/Resources/stubs/JsonException.php',
         'JsonSchema\\Constraints\\BaseConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/BaseConstraint.php',
         'JsonSchema\\Constraints\\CollectionConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/CollectionConstraint.php',
@@ -1670,6 +1719,7 @@ class ComposerStaticInit_mediawiki_vendor
         'JsonSchema\\Uri\\UriResolver' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Uri/UriResolver.php',
         'JsonSchema\\Uri\\UriRetriever' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Uri/UriRetriever.php',
         'JsonSchema\\Validator' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Validator.php',
+        'JsonSerializable' => __DIR__ . '/..' . '/php-parallel-lint/php-parallel-lint/src/polyfill.php',
         'Kafka\\Client' => __DIR__ . '/..' . '/nmred/kafka-php/src/Kafka/Client.php',
         'Kafka\\ClusterMetaData' => __DIR__ . '/..' . '/nmred/kafka-php/src/Kafka/ClusterMetaData.php',
         'Kafka\\Consumer' => __DIR__ . '/..' . '/nmred/kafka-php/src/Kafka/Consumer.php',
@@ -2196,6 +2246,7 @@ class ComposerStaticInit_mediawiki_vendor
         'PEAR_Error' => __DIR__ . '/..' . '/pear/pear-core-minimal/src/PEAR.php',
         'PEAR_ErrorStack' => __DIR__ . '/..' . '/pear/pear-core-minimal/src/PEAR/ErrorStack.php',
         'PEAR_Exception' => __DIR__ . '/..' . '/pear/pear_exception/PEAR/Exception.php',
+        'PhpLang\\ScopeExit' => __DIR__ . '/..' . '/phplang/scope-exit/src/ScopeExit.php',
         'PhpParser\\Builder' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/Builder.php',
         'PhpParser\\BuilderFactory' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/BuilderFactory.php',
         'PhpParser\\BuilderHelpers' => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser/BuilderHelpers.php',
@@ -3006,6 +3057,73 @@ class ComposerStaticInit_mediawiki_vendor
         'Shellbox\\ShellboxError' => __DIR__ . '/..' . '/wikimedia/shellbox/src/ShellboxError.php',
         'Shellbox\\TempDirManager' => __DIR__ . '/..' . '/wikimedia/shellbox/src/TempDirManager.php',
         'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
+        'Swaggest\\JsonDiff\\Exception' => __DIR__ . '/..' . '/swaggest/json-diff/src/Exception.php',
+        'Swaggest\\JsonDiff\\JsonDiff' => __DIR__ . '/..' . '/swaggest/json-diff/src/JsonDiff.php',
+        'Swaggest\\JsonDiff\\JsonHash' => __DIR__ . '/..' . '/swaggest/json-diff/src/JsonHash.php',
+        'Swaggest\\JsonDiff\\JsonMergePatch' => __DIR__ . '/..' . '/swaggest/json-diff/src/JsonMergePatch.php',
+        'Swaggest\\JsonDiff\\JsonPatch' => __DIR__ . '/..' . '/swaggest/json-diff/src/JsonPatch.php',
+        'Swaggest\\JsonDiff\\JsonPatch\\Add' => __DIR__ . '/..' . '/swaggest/json-diff/src/JsonPatch/Add.php',
+        'Swaggest\\JsonDiff\\JsonPatch\\Copy' => __DIR__ . '/..' . '/swaggest/json-diff/src/JsonPatch/Copy.php',
+        'Swaggest\\JsonDiff\\JsonPatch\\Move' => __DIR__ . '/..' . '/swaggest/json-diff/src/JsonPatch/Move.php',
+        'Swaggest\\JsonDiff\\JsonPatch\\OpPath' => __DIR__ . '/..' . '/swaggest/json-diff/src/JsonPatch/OpPath.php',
+        'Swaggest\\JsonDiff\\JsonPatch\\OpPathFrom' => __DIR__ . '/..' . '/swaggest/json-diff/src/JsonPatch/OpPathFrom.php',
+        'Swaggest\\JsonDiff\\JsonPatch\\OpPathValue' => __DIR__ . '/..' . '/swaggest/json-diff/src/JsonPatch/OpPathValue.php',
+        'Swaggest\\JsonDiff\\JsonPatch\\Remove' => __DIR__ . '/..' . '/swaggest/json-diff/src/JsonPatch/Remove.php',
+        'Swaggest\\JsonDiff\\JsonPatch\\Replace' => __DIR__ . '/..' . '/swaggest/json-diff/src/JsonPatch/Replace.php',
+        'Swaggest\\JsonDiff\\JsonPatch\\Test' => __DIR__ . '/..' . '/swaggest/json-diff/src/JsonPatch/Test.php',
+        'Swaggest\\JsonDiff\\JsonPointer' => __DIR__ . '/..' . '/swaggest/json-diff/src/JsonPointer.php',
+        'Swaggest\\JsonDiff\\JsonValueReplace' => __DIR__ . '/..' . '/swaggest/json-diff/src/JsonValueReplace.php',
+        'Swaggest\\JsonDiff\\ModifiedPathDiff' => __DIR__ . '/..' . '/swaggest/json-diff/src/ModifiedPathDiff.php',
+        'Swaggest\\JsonSchema\\Constraint\\Constraint' => __DIR__ . '/..' . '/swaggest/json-schema/src/Constraint/Constraint.php',
+        'Swaggest\\JsonSchema\\Constraint\\Content' => __DIR__ . '/..' . '/swaggest/json-schema/src/Constraint/Content.php',
+        'Swaggest\\JsonSchema\\Constraint\\Format' => __DIR__ . '/..' . '/swaggest/json-schema/src/Constraint/Format.php',
+        'Swaggest\\JsonSchema\\Constraint\\Format\\IdnHostname' => __DIR__ . '/..' . '/swaggest/json-schema/src/Constraint/Format/IdnHostname.php',
+        'Swaggest\\JsonSchema\\Constraint\\Format\\Iri' => __DIR__ . '/..' . '/swaggest/json-schema/src/Constraint/Format/Iri.php',
+        'Swaggest\\JsonSchema\\Constraint\\Format\\Uri' => __DIR__ . '/..' . '/swaggest/json-schema/src/Constraint/Format/Uri.php',
+        'Swaggest\\JsonSchema\\Constraint\\Properties' => __DIR__ . '/..' . '/swaggest/json-schema/src/Constraint/Properties.php',
+        'Swaggest\\JsonSchema\\Constraint\\Ref' => __DIR__ . '/..' . '/swaggest/json-schema/src/Constraint/Ref.php',
+        'Swaggest\\JsonSchema\\Constraint\\Type' => __DIR__ . '/..' . '/swaggest/json-schema/src/Constraint/Type.php',
+        'Swaggest\\JsonSchema\\Constraint\\UniqueItems' => __DIR__ . '/..' . '/swaggest/json-schema/src/Constraint/UniqueItems.php',
+        'Swaggest\\JsonSchema\\Context' => __DIR__ . '/..' . '/swaggest/json-schema/src/Context.php',
+        'Swaggest\\JsonSchema\\DataPreProcessor' => __DIR__ . '/..' . '/swaggest/json-schema/src/DataPreProcessor.php',
+        'Swaggest\\JsonSchema\\Exception' => __DIR__ . '/..' . '/swaggest/json-schema/src/Exception.php',
+        'Swaggest\\JsonSchema\\Exception\\ArrayException' => __DIR__ . '/..' . '/swaggest/json-schema/src/Exception/ArrayException.php',
+        'Swaggest\\JsonSchema\\Exception\\ConstException' => __DIR__ . '/..' . '/swaggest/json-schema/src/Exception/ConstException.php',
+        'Swaggest\\JsonSchema\\Exception\\ContentException' => __DIR__ . '/..' . '/swaggest/json-schema/src/Exception/ContentException.php',
+        'Swaggest\\JsonSchema\\Exception\\EnumException' => __DIR__ . '/..' . '/swaggest/json-schema/src/Exception/EnumException.php',
+        'Swaggest\\JsonSchema\\Exception\\Error' => __DIR__ . '/..' . '/swaggest/json-schema/src/Exception/Error.php',
+        'Swaggest\\JsonSchema\\Exception\\LogicException' => __DIR__ . '/..' . '/swaggest/json-schema/src/Exception/LogicException.php',
+        'Swaggest\\JsonSchema\\Exception\\NumericException' => __DIR__ . '/..' . '/swaggest/json-schema/src/Exception/NumericException.php',
+        'Swaggest\\JsonSchema\\Exception\\ObjectException' => __DIR__ . '/..' . '/swaggest/json-schema/src/Exception/ObjectException.php',
+        'Swaggest\\JsonSchema\\Exception\\StringException' => __DIR__ . '/..' . '/swaggest/json-schema/src/Exception/StringException.php',
+        'Swaggest\\JsonSchema\\Exception\\TypeException' => __DIR__ . '/..' . '/swaggest/json-schema/src/Exception/TypeException.php',
+        'Swaggest\\JsonSchema\\Helper' => __DIR__ . '/..' . '/swaggest/json-schema/src/Helper.php',
+        'Swaggest\\JsonSchema\\InvalidValue' => __DIR__ . '/..' . '/swaggest/json-schema/src/InvalidValue.php',
+        'Swaggest\\JsonSchema\\JsonSchema' => __DIR__ . '/..' . '/swaggest/json-schema/src/JsonSchema.php',
+        'Swaggest\\JsonSchema\\MagicMap' => __DIR__ . '/..' . '/swaggest/json-schema/src/MagicMap.php',
+        'Swaggest\\JsonSchema\\MagicMapTrait' => __DIR__ . '/..' . '/swaggest/json-schema/src/MagicMapTrait.php',
+        'Swaggest\\JsonSchema\\Meta\\AbstractMeta' => __DIR__ . '/..' . '/swaggest/json-schema/src/Meta/AbstractMeta.php',
+        'Swaggest\\JsonSchema\\Meta\\MetaHolder' => __DIR__ . '/..' . '/swaggest/json-schema/src/Meta/MetaHolder.php',
+        'Swaggest\\JsonSchema\\NameMirror' => __DIR__ . '/..' . '/swaggest/json-schema/src/NameMirror.php',
+        'Swaggest\\JsonSchema\\Path\\PointerUtil' => __DIR__ . '/..' . '/swaggest/json-schema/src/Path/PointerUtil.php',
+        'Swaggest\\JsonSchema\\RefResolver' => __DIR__ . '/..' . '/swaggest/json-schema/src/RefResolver.php',
+        'Swaggest\\JsonSchema\\RemoteRefProvider' => __DIR__ . '/..' . '/swaggest/json-schema/src/RemoteRefProvider.php',
+        'Swaggest\\JsonSchema\\RemoteRef\\BasicFetcher' => __DIR__ . '/..' . '/swaggest/json-schema/src/RemoteRef/BasicFetcher.php',
+        'Swaggest\\JsonSchema\\RemoteRef\\Preloaded' => __DIR__ . '/..' . '/swaggest/json-schema/src/RemoteRef/Preloaded.php',
+        'Swaggest\\JsonSchema\\Schema' => __DIR__ . '/..' . '/swaggest/json-schema/src/Schema.php',
+        'Swaggest\\JsonSchema\\SchemaContract' => __DIR__ . '/..' . '/swaggest/json-schema/src/SchemaContract.php',
+        'Swaggest\\JsonSchema\\SchemaExporter' => __DIR__ . '/..' . '/swaggest/json-schema/src/SchemaExporter.php',
+        'Swaggest\\JsonSchema\\Structure\\ClassStructure' => __DIR__ . '/..' . '/swaggest/json-schema/src/Structure/ClassStructure.php',
+        'Swaggest\\JsonSchema\\Structure\\ClassStructureContract' => __DIR__ . '/..' . '/swaggest/json-schema/src/Structure/ClassStructureContract.php',
+        'Swaggest\\JsonSchema\\Structure\\ClassStructureTrait' => __DIR__ . '/..' . '/swaggest/json-schema/src/Structure/ClassStructureTrait.php',
+        'Swaggest\\JsonSchema\\Structure\\Composition' => __DIR__ . '/..' . '/swaggest/json-schema/src/Structure/Composition.php',
+        'Swaggest\\JsonSchema\\Structure\\Egg' => __DIR__ . '/..' . '/swaggest/json-schema/src/Structure/Egg.php',
+        'Swaggest\\JsonSchema\\Structure\\Nested' => __DIR__ . '/..' . '/swaggest/json-schema/src/Structure/Nested.php',
+        'Swaggest\\JsonSchema\\Structure\\ObjectItem' => __DIR__ . '/..' . '/swaggest/json-schema/src/Structure/ObjectItem.php',
+        'Swaggest\\JsonSchema\\Structure\\ObjectItemContract' => __DIR__ . '/..' . '/swaggest/json-schema/src/Structure/ObjectItemContract.php',
+        'Swaggest\\JsonSchema\\Structure\\ObjectItemTrait' => __DIR__ . '/..' . '/swaggest/json-schema/src/Structure/ObjectItemTrait.php',
+        'Swaggest\\JsonSchema\\Structure\\WithResolvedValue' => __DIR__ . '/..' . '/swaggest/json-schema/src/Structure/WithResolvedValue.php',
+        'Swaggest\\JsonSchema\\Wrapper' => __DIR__ . '/..' . '/swaggest/json-schema/src/Wrapper.php',
         'Symfony\\Component\\Console\\Application' => __DIR__ . '/..' . '/symfony/console/Application.php',
         'Symfony\\Component\\Console\\Color' => __DIR__ . '/..' . '/symfony/console/Color.php',
         'Symfony\\Component\\Console\\CommandLoader\\CommandLoaderInterface' => __DIR__ . '/..' . '/symfony/console/CommandLoader/CommandLoaderInterface.php',
diff --git a/composer/installed.json b/composer/installed.json
index 6a22402..808c3c2 100644
--- a/composer/installed.json
+++ b/composer/installed.json
@@ -3154,6 +3154,107 @@
             "exception"
         ]
     },
+    {
+        "name": "php-parallel-lint/php-parallel-lint",
+        "version": "v1.3.0",
+        "version_normalized": "1.3.0.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/php-parallel-lint/PHP-Parallel-Lint.git",
+            "reference": "772a954e5f119f6f5871d015b23eabed8cbdadfb"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/php-parallel-lint/PHP-Parallel-Lint/zipball/772a954e5f119f6f5871d015b23eabed8cbdadfb",
+            "reference": "772a954e5f119f6f5871d015b23eabed8cbdadfb",
+            "shasum": ""
+        },
+        "require": {
+            "ext-json": "*",
+            "php": ">=5.3.0"
+        },
+        "replace": {
+            "grogy/php-parallel-lint": "*",
+            "jakub-onderka/php-parallel-lint": "*"
+        },
+        "require-dev": {
+            "nette/tester": "^1.3 || ^2.0",
+            "php-parallel-lint/php-console-highlighter": "~0.3",
+            "squizlabs/php_codesniffer": "^3.5"
+        },
+        "suggest": {
+            "php-parallel-lint/php-console-highlighter": "Highlight syntax in code snippet"
+        },
+        "time": "2021-04-07T14:42:48+00:00",
+        "bin": [
+            "parallel-lint"
+        ],
+        "type": "library",
+        "installation-source": "dist",
+        "autoload": {
+            "classmap": [
+                "./"
+            ]
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "BSD-2-Clause"
+        ],
+        "authors": [
+            {
+                "name": "Jakub Onderka",
+                "email": "ahoj@jakubonderka.cz"
+            }
+        ],
+        "description": "This tool check syntax of PHP files about 20x faster than serial check.",
+        "homepage": "https://github.com/php-parallel-lint/PHP-Parallel-Lint"
+    },
+    {
+        "name": "phplang/scope-exit",
+        "version": "1.0.0",
+        "version_normalized": "1.0.0.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/phplang/scope-exit.git",
+            "reference": "239b73abe89f9414aa85a7ca075ec9445629192b"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/phplang/scope-exit/zipball/239b73abe89f9414aa85a7ca075ec9445629192b",
+            "reference": "239b73abe89f9414aa85a7ca075ec9445629192b",
+            "shasum": ""
+        },
+        "require-dev": {
+            "phpunit/phpunit": "*"
+        },
+        "time": "2016-09-17T00:15:18+00:00",
+        "type": "library",
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "PhpLang\\": "src/"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "BSD"
+        ],
+        "authors": [
+            {
+                "name": "Sara Golemon",
+                "email": "pollita@php.net",
+                "homepage": "https://twitter.com/SaraMG",
+                "role": "Developer"
+            }
+        ],
+        "description": "Emulation of SCOPE_EXIT construct from C++",
+        "homepage": "https://github.com/phplang/scope-exit",
+        "keywords": [
+            "cleanup",
+            "exit",
+            "scope"
+        ]
+    },
     {
         "name": "pimple/pimple",
         "version": "v3.3.1",
@@ -4126,6 +4227,93 @@
             }
         ]
     },
+    {
+        "name": "swaggest/json-diff",
+        "version": "v3.8.1",
+        "version_normalized": "3.8.1.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/swaggest/json-diff.git",
+            "reference": "d2184358c5ef5ecaa1f6b4c2bce175fac2d25670"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/swaggest/json-diff/zipball/d2184358c5ef5ecaa1f6b4c2bce175fac2d25670",
+            "reference": "d2184358c5ef5ecaa1f6b4c2bce175fac2d25670",
+            "shasum": ""
+        },
+        "require": {
+            "ext-json": "*"
+        },
+        "require-dev": {
+            "phpunit/phpunit": "^4.8.23"
+        },
+        "time": "2020-09-25T17:47:07+00:00",
+        "type": "library",
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "Swaggest\\JsonDiff\\": "src/"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Viacheslav Poturaev",
+                "email": "vearutop@gmail.com"
+            }
+        ],
+        "description": "JSON diff/rearrange/patch/pointer library for PHP"
+    },
+    {
+        "name": "swaggest/json-schema",
+        "version": "v0.12.29",
+        "version_normalized": "0.12.29.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/swaggest/php-json-schema.git",
+            "reference": "7564d4a5fc8c068479698a30e5a7c589ea32a9c6"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/swaggest/php-json-schema/zipball/7564d4a5fc8c068479698a30e5a7c589ea32a9c6",
+            "reference": "7564d4a5fc8c068479698a30e5a7c589ea32a9c6",
+            "shasum": ""
+        },
+        "require": {
+            "ext-json": "*",
+            "ext-mbstring": "*",
+            "php": ">=5.4",
+            "phplang/scope-exit": "^1.0",
+            "swaggest/json-diff": "^3.5.1"
+        },
+        "require-dev": {
+            "phpunit/php-code-coverage": "2.2.4",
+            "phpunit/phpunit": "4.8.35"
+        },
+        "time": "2020-03-19T08:41:40+00:00",
+        "type": "library",
+        "installation-source": "dist",
+        "autoload": {
+            "psr-4": {
+                "Swaggest\\JsonSchema\\": "src/"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "Viacheslav Poturaev",
+                "email": "vearutop@gmail.com"
+            }
+        ],
+        "description": "High definition PHP structures with JSON-schema based validation"
+    },
     {
         "name": "symfony/console",
         "version": "v5.2.2",
diff --git a/php-parallel-lint/php-parallel-lint/CHANGELOG.md b/php-parallel-lint/php-parallel-lint/CHANGELOG.md
new file mode 100644
index 0000000..16c15e5
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/CHANGELOG.md
@@ -0,0 +1,66 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
+
+## [Unreleased]
+
+[Unreleased]: https://github.com/php-parallel-lint/PHP-Parallel-Lint/compare/v1.3.0...HEAD
+
+## [1.3.0] - 2021-04-07
+
+### Added
+
+- Allow for multi-part file extensions to be passed using -e (like `-e php,php.dist`) from [@jrfnl](https://github.com/jrfnl).
+- Added syntax error callback [#30](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/30) from [@arxeiss](https://github.com/arxeiss).
+- Ignore PHP startup errors [#34](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/34) from [@jrfnl](https://github.com/jrfnl).
+- Restore php 5.3 support [#51](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/51) from [@glensc](https://github.com/glensc).
+
+### Fixed
+
+- Determine skip lint process failure by status code instead of stderr content [#48](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/48) from [@jankonas](https://github.com/jankonas).
+
+### Changed
+
+- Improve wording in the readme [#52](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/52) from [@glensc](https://github.com/glensc).
+
+### Internal
+
+- Normalized composer.json from [@OndraM](https://github.com/OndraM).
+- Updated PHPCS dependency from [@jrfnl](https://github.com/jrfnl).
+- Cleaned coding style from [@jrfnl](https://github.com/jrfnl).
+- Provide one true way to run the test suite [#37](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/37) from [@mfn](https://github.com/mfn).
+- Travis: add build against PHP 8.0 and fix failing test [#41](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/41) from [@jrfnl](https://github.com/jrfnl).
+- GitHub Actions for testing, and automatic phar creation [#46](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/46) from [@roelofr](https://github.com/roelofr).
+- Add .github folder to .gitattributes export-ignore [#54](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/54) from [@glensc](https://github.com/glensc).
+- Suggest to curl composer install via HTTPS [#53](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/53) from [@reedy](https://github.com/reedy).
+- GH Actions: allow for manually triggering a workflow [#55](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/55) from [@jrfnl](https://github.com/jrfnl).
+- GH Actions: fix phar creation [#55](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/55) from [@jrfnl](https://github.com/jrfnl).
+- GH Actions: run the tests against all supported PHP versions [#55](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/55) from [@jrfnl](https://github.com/jrfnl).
+- GH Actions: report CS violations in the PR [#55](https://github.com/php-parallel-lint/PHP-Parallel-Lint/pull/55) from [@jrfnl](https://github.com/jrfnl).
+
+[1.3.0]: https://github.com/php-parallel-lint/PHP-Parallel-Lint/compare/v1.2.0...v1.3.0
+
+## [1.2.0] - 2020-04-04
+
+### Added
+
+- Added changelog.
+
+### Fixed
+
+- Fixed vendor location for running from other folder from [@Erkens](https://github.com/Erkens).
+
+### Internal
+
+- Added a .gitattributes file from [@jrfnl](https://github.com/jrfnl), thanks for issue to [@ondrejmirtes](https://github.com/ondrejmirtes).
+- Fixed incorrect unit tests from [@jrfnl](https://github.com/jrfnl).
+- Fixed minor grammatical errors from [@jrfnl](https://github.com/jrfnl).
+- Added Travis: test against nightly (= PHP 8) from [@jrfnl](https://github.com/jrfnl).
+- Travis: removed sudo from [@jrfnl](https://github.com/jrfnl).
+- Added info about installing like not a dependency.
+- Cleaned readme - new organization from previous package.
+- Added checklist for new version from [@szepeviktor](https://github.com/szepeviktor).
+
+[1.2.0]: https://github.com/php-parallel-lint/PHP-Parallel-Lint/compare/v1.1.0...v1.2.0
diff --git a/php-parallel-lint/php-parallel-lint/LICENSE b/php-parallel-lint/php-parallel-lint/LICENSE
new file mode 100644
index 0000000..09429bb
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/LICENSE
@@ -0,0 +1,26 @@
+Copyright (c) 2012, Jakub Onderka
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+The views and conclusions contained in the software and documentation are those
+of the authors and should not be interpreted as representing official policies,
+either expressed or implied, of the FreeBSD Project.
diff --git a/php-parallel-lint/php-parallel-lint/README.md b/php-parallel-lint/php-parallel-lint/README.md
new file mode 100644
index 0000000..e5c762f
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/README.md
@@ -0,0 +1,111 @@
+# PHP Parallel Lint
+
+[![Downloads this Month](https://img.shields.io/packagist/dm/php-parallel-lint/php-parallel-lint.svg)](https://packagist.org/packages/php-parallel-lint/php-parallel-lint)
+[![Build Status](https://github.com/php-parallel-lint/PHP-Parallel-Lint/actions/workflows/test.yml/badge.svg)](https://github.com/php-parallel-lint/PHP-Parallel-Lint/actions/workflows/test.yml)
+[![License](https://poser.pugx.org/php-parallel-lint/php-parallel-lint/license.svg)](https://packagist.org/packages/php-parallel-lint/php-parallel-lint)
+
+This application checks syntax of PHP files in parallel.
+It can output in plain text, colored text, json and checksyntax formats.
+Additionally `blame` can be used to show commits that introduced the breakage.
+
+Running parallel jobs in PHP is inspired by Nette framework tests.
+
+The application is officially supported for use with PHP 5.3 to 8.0.
+
+## Table of contents
+
+1. [Installation](#installation)
+2. [Example output](#example-output)
+3. [History](#history)
+4. [Command line options](#command-line-options)
+5. [Recommended excludes for Symfony framework](#recommended-excludes-for-symfony-framework)
+6. [Create Phar package](#create-phar-package)
+7. [How to upgrade](#how-to-upgrade)
+
+## Installation
+
+Install with `composer` as development dependency:
+
+    composer require --dev php-parallel-lint/php-parallel-lint
+
+Alternatively you can install as a standalone `composer` project:
+
+    composer create-project php-parallel-lint/php-parallel-lint /path/to/folder/php-parallel-lint
+    /path/to/folder/php-parallel-lint/parallel-lint # running tool
+
+For colored output, install the suggested package `php-parallel-lint/php-console-highlighter`:
+
+    composer require --dev php-parallel-lint/php-console-highlighter
+
+## Example output
+
+![Example use of tool with error](/tests/examples/example-images/use-error.png?raw=true "Example use of tool with error")
+
+
+## History
+
+This project was originally created by [@JakubOnderka] and released as
+[jakub-onderka/php-parallel-lint].
+
+Since then, Jakub has moved on to other interests and as of January 2020, the
+second most active maintainer [@grogy] has taken over maintenance of the project
+and given the project - and related dependencies - a new home in the PHP
+Parallel Lint organisation.
+
+It is strongly recommended for existing users of the (unmaintained)
+[jakub-onderka/php-parallel-lint] package to switch their dependency to
+[php-parallel-lint/php-parallel-lint], see [How to upgrade](#how-to-upgrade) below.
+
+[php-parallel-lint/php-parallel-lint]: https://github.com/php-parallel-lint/PHP-Parallel-Lint
+[grogy/php-parallel-lint]: https://github.com/grogy/PHP-Parallel-Lint
+[jakub-onderka/php-parallel-lint]: https://github.com/JakubOnderka/PHP-Parallel-Lint
+[@JakubOnderka]: https://github.com/JakubOnderka
+[@grogy]: https://github.com/grogy
+
+## Command line options
+
+- `-p <php>`        		Specify PHP-CGI executable to run (default: 'php').
+- `-s, --short`     		Set short_open_tag to On (default: Off).
+- `-a, --asp`       		Set asp_tags to On (default: Off).
+- `-e <ext>`        		Check only files with selected extensions separated by comma. (default: php,php3,php4,php5,phtml,phpt)
+- `--exclude`       		Exclude a file or directory. If you want exclude multiple items, use multiple exclude parameters.
+- `-j <num>`        		Run <num> jobs in parallel (default: 10).
+- `--colors`        		Force enable colors in console output.
+- `--no-colors`     		Disable colors in console output.
+- `--no-progress`   		Disable progress in console output.
+- `--checkstyle`    		Output results as Checkstyle XML.
+- `--json`          		Output results as JSON string (requires PHP 5.4).
+- `--blame`         		Try to show git blame for row with error.
+- `--git <git>`     		Path to Git executable to show blame message (default: 'git').
+- `--stdin`         		Load files and folder to test from standard input.
+- `--ignore-fails`  		Ignore failed tests.
+- `--syntax-error-callback` File with syntax error callback for ability to modify error, see more in [example](doc/syntax-error-callback.md)
+- `-h, --help`      		Print this help.
+- `-V, --version`   		Display this application version.
+
+
+## Recommended excludes for Symfony framework
+
+To run from the command line:
+
+    vendor/bin/parallel-lint --exclude app --exclude vendor .
+
+## Create Phar package
+
+PHP Parallel Lint supports [Box app](https://box-project.github.io/box2/) for creating Phar package. First, install box app:
+
+
+    curl -LSs https://box-project.github.io/box2/installer.php | php
+
+
+then run the build command in parallel lint folder, which creates `parallel-lint.phar` file.
+
+
+    box build
+
+## How to upgrade
+
+Are you using `jakub-onderka/php-parallel-lint` package? You can switch to `php-parallel-lint/php-parallel-lint` using:
+
+    composer remove --dev jakub-onderka/php-parallel-lint
+    composer require --dev php-parallel-lint/php-parallel-lint
diff --git a/php-parallel-lint/php-parallel-lint/bin/skip-linting.php b/php-parallel-lint/php-parallel-lint/bin/skip-linting.php
new file mode 100644
index 0000000..2c69554
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/bin/skip-linting.php
@@ -0,0 +1,19 @@
+<?php
+$stdin = fopen('php://stdin', 'r');
+$input = stream_get_contents($stdin);
+fclose($stdin);
+
+foreach (explode(PHP_EOL, $input) as $file) {
+    $skip = false;
+    $f = @fopen($file, 'r');
+    if ($f) {
+        $firstLine = fgets($f);
+        @fclose($f);
+
+        if (preg_match('~<?php\\s*\\/\\/\s*lint\s*([^\d\s]+)\s*([^\s]+)\s*~i', $firstLine, $m)) {
+            $skip = version_compare(PHP_VERSION, $m[2], $m[1]) === false;
+        }
+    }
+
+    echo $file . ';' . ($skip ? '1' : '0') . PHP_EOL;
+}
diff --git a/php-parallel-lint/php-parallel-lint/composer.json b/php-parallel-lint/php-parallel-lint/composer.json
new file mode 100644
index 0000000..ce7ed04
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/composer.json
@@ -0,0 +1,46 @@
+{
+    "name": "php-parallel-lint/php-parallel-lint",
+    "description": "This tool check syntax of PHP files about 20x faster than serial check.",
+    "homepage": "https://github.com/php-parallel-lint/PHP-Parallel-Lint",
+    "license": "BSD-2-Clause",
+    "authors": [
+        {
+            "name": "Jakub Onderka",
+            "email": "ahoj@jakubonderka.cz"
+        }
+    ],
+    "require": {
+        "php": ">=5.3.0",
+        "ext-json": "*"
+    },
+    "replace": {
+        "grogy/php-parallel-lint": "*",
+        "jakub-onderka/php-parallel-lint": "*"
+    },
+    "require-dev": {
+        "nette/tester": "^1.3 || ^2.0",
+        "php-parallel-lint/php-console-highlighter": "~0.3",
+        "squizlabs/php_codesniffer": "^3.5"
+    },
+    "suggest": {
+        "php-parallel-lint/php-console-highlighter": "Highlight syntax in code snippet"
+    },
+    "config": {
+        "sort-packages": true
+    },
+    "autoload": {
+        "classmap": [
+            "./"
+        ]
+    },
+    "bin": [
+        "parallel-lint"
+    ],
+    "scripts": {
+        "test": "@php vendor/bin/tester -C -p php tests",
+        "testphp5": "@php vendor/bin/tester -p php tests"
+    },
+    "scripts-descriptions": {
+        "test": "Run all tests!"
+    }
+}
diff --git a/php-parallel-lint/php-parallel-lint/parallel-lint b/php-parallel-lint/php-parallel-lint/parallel-lint
new file mode 100755
index 0000000..f44e110
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/parallel-lint
@@ -0,0 +1,67 @@
+#!/usr/bin/env php
+<?php
+
+/*
+Copyright (c) 2014, Jakub Onderka
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+The views and conclusions contained in the software and documentation are those
+of the authors and should not be interpreted as representing official policies,
+either expressed or implied, of the FreeBSD Project.
+ */
+
+if (!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 50300) {
+    fwrite(STDERR, "PHP Parallel Lint requires PHP 5.3.0 or newer." . PHP_EOL);
+    exit(254);
+}
+
+$autoloadLocations = array(
+    getcwd() . '/vendor/autoload.php',
+    getcwd() . '/../../autoload.php',
+    __DIR__ . '/vendor/autoload.php',
+    __DIR__ . '/../vendor/autoload.php',
+    __DIR__ . '/../../../autoload.php',
+    __DIR__ . '/../../autoload.php',
+);
+
+$loaded = false;
+foreach ($autoloadLocations as $autoload) {
+    if (is_file($autoload)) {
+        require_once($autoload);
+        $loaded = true;
+    }
+}
+
+if (!$loaded) {
+    fwrite(STDERR,
+        'You must set up the project dependencies, run the following commands:' . PHP_EOL .
+        'curl -s https://getcomposer.org/installer | php' . PHP_EOL .
+        'php composer.phar install' . PHP_EOL
+    );
+    exit(254);
+}
+
+require_once __DIR__ . '/src/polyfill.php';
+
+$app = new JakubOnderka\PhpParallelLint\Application();
+exit($app->run());
diff --git a/php-parallel-lint/php-parallel-lint/src/Application.php b/php-parallel-lint/php-parallel-lint/src/Application.php
new file mode 100644
index 0000000..d38e42d
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/src/Application.php
@@ -0,0 +1,124 @@
+<?php
+
+namespace JakubOnderka\PhpParallelLint;
+
+class Application
+{
+    const VERSION = '1.3.0';
+
+    // Return codes
+    const SUCCESS = 0,
+        WITH_ERRORS = 1,
+        FAILED = 254; // Error code 255 is reserved for PHP itself
+
+    /**
+     * Run the application
+     * @return int Return code
+     */
+    public function run()
+    {
+        if (in_array('proc_open', explode(',', ini_get('disable_functions')))) {
+            echo "Function 'proc_open' is required, but it is disabled by the 'disable_functions' ini setting.", PHP_EOL;
+            return self::FAILED;
+        }
+
+        if (in_array('-h', $_SERVER['argv']) || in_array('--help', $_SERVER['argv'])) {
+            $this->showUsage();
+            return self::SUCCESS;
+        }
+
+        if (in_array('-V', $_SERVER['argv']) || in_array('--version', $_SERVER['argv'])) {
+            $this->showVersion();
+            return self::SUCCESS;
+        }
+
+        try {
+            $settings = Settings::parseArguments($_SERVER['argv']);
+            if ($settings->stdin) {
+                $settings->addPaths(Settings::getPathsFromStdIn());
+            }
+            if (empty($settings->paths)) {
+                $this->showUsage();
+                return self::FAILED;
+            }
+            $manager = new Manager;
+            $result = $manager->run($settings);
+            if ($settings->ignoreFails) {
+                return $result->hasSyntaxError() ? self::WITH_ERRORS : self::SUCCESS;
+            } else {
+                return $result->hasError() ? self::WITH_ERRORS : self::SUCCESS;
+            }
+
+        } catch (InvalidArgumentException $e) {
+            echo "Invalid option {$e->getArgument()}", PHP_EOL, PHP_EOL;
+            $this->showOptions();
+            return self::FAILED;
+
+        } catch (Exception $e) {
+            if (isset($settings) && $settings->format === Settings::FORMAT_JSON) {
+                echo json_encode($e);
+            } else {
+                echo $e->getMessage(), PHP_EOL;
+            }
+            return self::FAILED;
+
+        } catch (\Exception $e) {
+            echo $e->getMessage(), PHP_EOL;
+            return self::FAILED;
+        }
+    }
+
+    /**
+     * Outputs the options
+     */
+    private function showOptions()
+    {
+        echo <<<HELP
+Options:
+    -p <php>                Specify PHP-CGI executable to run (default: 'php').
+    -s, --short             Set short_open_tag to On (default: Off).
+    -a, -asp                Set asp_tags to On (default: Off).
+    -e <ext>                Check only files with selected extensions separated by comma.
+                            (default: php,php3,php4,php5,phtml,phpt)
+    --exclude               Exclude a file or directory. If you want exclude multiple items,
+                            use multiple exclude parameters.
+    -j <num>                Run <num> jobs in parallel (default: 10).
+    --colors                Enable colors in console output. (disables auto detection of color support)
+    --no-colors             Disable colors in console output.
+    --no-progress           Disable progress in console output.
+    --json                  Output results as JSON string.
+    --checkstyle            Output results as Checkstyle XML.
+    --blame                 Try to show git blame for row with error.
+    --git <git>             Path to Git executable to show blame message (default: 'git').
+    --stdin                 Load files and folder to test from standard input.
+    --ignore-fails          Ignore failed tests.
+    --syntax-error-callback File with syntax error callback for ability to modify error
+    -h, --help              Print this help.
+    -V, --version           Display this application version
+
+HELP;
+    }
+
+    /**
+     * Outputs the current version
+     */
+    private function showVersion()
+    {
+        echo 'PHP Parallel Lint version ' . self::VERSION.PHP_EOL;
+    }
+
+    /**
+     * Shows usage
+     */
+    private function showUsage()
+    {
+        $this->showVersion();
+        echo <<<USAGE
+-------------------------------
+Usage:
+parallel-lint [sa] [-p php] [-e ext] [-j num] [--exclude dir] [files or directories]
+
+USAGE;
+        $this->showOptions();
+    }
+}
diff --git a/php-parallel-lint/php-parallel-lint/src/Contracts/SyntaxErrorCallback.php b/php-parallel-lint/php-parallel-lint/src/Contracts/SyntaxErrorCallback.php
new file mode 100644
index 0000000..0db055f
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/src/Contracts/SyntaxErrorCallback.php
@@ -0,0 +1,13 @@
+<?php
+namespace JakubOnderka\PhpParallelLint\Contracts;
+
+use JakubOnderka\PhpParallelLint\SyntaxError;
+
+interface SyntaxErrorCallback
+{
+    /**
+     * @param SyntaxError $error
+     * @return SyntaxError
+     */
+    public function errorFound(SyntaxError $error);
+}
diff --git a/php-parallel-lint/php-parallel-lint/src/Error.php b/php-parallel-lint/php-parallel-lint/src/Error.php
new file mode 100644
index 0000000..9041ab1
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/src/Error.php
@@ -0,0 +1,222 @@
+<?php
+namespace JakubOnderka\PhpParallelLint;
+
+class Error implements \JsonSerializable
+{
+    /** @var string */
+    protected $filePath;
+
+    /** @var string */
+    protected $message;
+
+    /**
+     * @param string $filePath
+     * @param string $message
+     */
+    public function __construct($filePath, $message)
+    {
+        $this->filePath = $filePath;
+        $this->message = rtrim($message);
+    }
+
+    /**
+     * @return string
+     */
+    public function getMessage()
+    {
+        return $this->message;
+    }
+
+    /**
+     * @return string
+     */
+    public function getFilePath()
+    {
+        return $this->filePath;
+    }
+
+    /**
+     * @return string
+     */
+    public function getShortFilePath()
+    {
+        $cwd = getcwd();
+
+        if ($cwd === '/') {
+            // For root directory in unix, do not modify path
+            return $this->filePath;
+        }
+
+        return preg_replace('/' . preg_quote($cwd, '/') . '/', '', $this->filePath, 1);
+    }
+
+    /**
+     * (PHP 5 &gt;= 5.4.0)<br/>
+     * Specify data which should be serialized to JSON
+     * @link http://php.net/manual/en/jsonserializable.jsonserialize.php
+     * @return mixed data which can be serialized by <b>json_encode</b>,
+     * which is a value of any type other than a resource.
+     */
+    public function jsonSerialize()
+    {
+        return array(
+            'type' => 'error',
+            'file' => $this->getFilePath(),
+            'message' => $this->getMessage(),
+        );
+    }
+}
+
+class Blame implements \JsonSerializable
+{
+    public $name;
+
+    public $email;
+
+    /** @var \DateTime */
+    public $datetime;
+
+    public $commitHash;
+
+    public $summary;
+
+    /**
+     * (PHP 5 &gt;= 5.4.0)<br/>
+     * Specify data which should be serialized to JSON
+     * @link http://php.net/manual/en/jsonserializable.jsonserialize.php
+     * @return mixed data which can be serialized by <b>json_encode</b>,
+     * which is a value of any type other than a resource.
+     */
+    function jsonSerialize()
+    {
+        return array(
+            'name' => $this->name,
+            'email' => $this->email,
+            'datetime' => $this->datetime,
+            'commitHash' => $this->commitHash,
+            'summary' => $this->summary,
+        );
+    }
+
+
+}
+
+class SyntaxError extends Error
+{
+    /** @var Blame */
+    private $blame;
+
+    /**
+     * @return int|null
+     */
+    public function getLine()
+    {
+        preg_match('~on line ([0-9]+)$~', $this->message, $matches);
+
+        if ($matches && isset($matches[1])) {
+            $onLine = (int) $matches[1];
+            return $onLine;
+        }
+
+        return null;
+    }
+
+    /**
+     * @param bool $translateTokens
+     * @return mixed|string
+     */
+    public function getNormalizedMessage($translateTokens = false)
+    {
+        $message = preg_replace('~^(Parse|Fatal) error: (syntax error, )?~', '', $this->message);
+        $message = preg_replace('~ in ' . preg_quote(basename($this->filePath)) . ' on line [0-9]+$~', '', $message);
+        $message = ucfirst($message);
+
+        if ($translateTokens) {
+            $message = $this->translateTokens($message);
+        }
+
+        return $message;
+    }
+
+    /**
+     * @param Blame $blame
+     */
+    public function setBlame(Blame $blame)
+    {
+        $this->blame = $blame;
+    }
+
+    /**
+     * @return Blame
+     */
+    public function getBlame()
+    {
+        return $this->blame;
+    }
+
+    /**
+     * @param string $message
+     * @return string
+     */
+    protected function translateTokens($message)
+    {
+        static $translateTokens = array(
+            'T_FILE' => '__FILE__',
+            'T_FUNC_C' => '__FUNCTION__',
+            'T_HALT_COMPILER' => '__halt_compiler()',
+            'T_INC' => '++',
+            'T_IS_EQUAL' => '==',
+            'T_IS_GREATER_OR_EQUAL' => '>=',
+            'T_IS_IDENTICAL' => '===',
+            'T_IS_NOT_IDENTICAL' => '!==',
+            'T_IS_SMALLER_OR_EQUAL' => '<=',
+            'T_LINE' => '__LINE__',
+            'T_METHOD_C' => '__METHOD__',
+            'T_MINUS_EQUAL' => '-=',
+            'T_MOD_EQUAL' => '%=',
+            'T_MUL_EQUAL' => '*=',
+            'T_NS_C' => '__NAMESPACE__',
+            'T_NS_SEPARATOR' => '\\',
+            'T_OBJECT_OPERATOR' => '->',
+            'T_OR_EQUAL' => '|=',
+            'T_PAAMAYIM_NEKUDOTAYIM' => '::',
+            'T_PLUS_EQUAL' => '+=',
+            'T_SL' => '<<',
+            'T_SL_EQUAL' => '<<=',
+            'T_SR' => '>>',
+            'T_SR_EQUAL' => '>>=',
+            'T_START_HEREDOC' => '<<<',
+            'T_XOR_EQUAL' => '^=',
+            'T_ECHO' => 'echo'
+        );
+
+        return preg_replace_callback('~T_([A-Z_]*)~', function ($matches) use ($translateTokens) {
+            list($tokenName) = $matches;
+            if (isset($translateTokens[$tokenName])) {
+                $operator = $translateTokens[$tokenName];
+                return "$operator ($tokenName)";
+            }
+
+            return $tokenName;
+        }, $message);
+    }
+
+    /**
+     * (PHP 5 &gt;= 5.4.0)<br/>
+     * Specify data which should be serialized to JSON
+     * @link http://php.net/manual/en/jsonserializable.jsonserialize.php
+     * @return mixed data which can be serialized by <b>json_encode</b>,
+     * which is a value of any type other than a resource.
+     */
+    public function jsonSerialize()
+    {
+        return array(
+            'type' => 'syntaxError',
+            'file' => $this->getFilePath(),
+            'line' => $this->getLine(),
+            'message' => $this->getMessage(),
+            'normalizeMessage' => $this->getNormalizedMessage(),
+            'blame' => $this->blame,
+        );
+    }
+}
\ No newline at end of file
diff --git a/php-parallel-lint/php-parallel-lint/src/ErrorFormatter.php b/php-parallel-lint/php-parallel-lint/src/ErrorFormatter.php
new file mode 100644
index 0000000..76b4466
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/src/ErrorFormatter.php
@@ -0,0 +1,127 @@
+<?php
+namespace JakubOnderka\PhpParallelLint;
+
+use JakubOnderka\PhpConsoleColor\ConsoleColor;
+use JakubOnderka\PhpConsoleHighlighter\Highlighter;
+
+class ErrorFormatter
+{
+    /** @var string */
+    private $useColors;
+
+    /** @var bool */
+    private $forceColors;
+
+    /** @var bool */
+    private $translateTokens;
+
+    public function __construct($useColors = Settings::AUTODETECT, $translateTokens = false, $forceColors = false)
+    {
+        $this->useColors = $useColors;
+        $this->forceColors = $forceColors;
+        $this->translateTokens = $translateTokens;
+    }
+
+    /**
+     * @param Error $error
+     * @return string
+     */
+    public function format(Error $error)
+    {
+        if ($error instanceof SyntaxError) {
+            return $this->formatSyntaxErrorMessage($error);
+        } else {
+            if ($error->getMessage()) {
+                return $error->getMessage();
+            } else {
+                return "Unknown error for file '{$error->getFilePath()}'.";
+            }
+        }
+    }
+
+    /**
+     * @param SyntaxError $error
+     * @param bool $withCodeSnipped
+     * @return string
+     */
+    public function formatSyntaxErrorMessage(SyntaxError $error, $withCodeSnipped = true)
+    {
+        $string = "Parse error: {$error->getShortFilePath()}";
+
+        if ($error->getLine()) {
+            $onLine = $error->getLine();
+            $string .= ":$onLine" . PHP_EOL;
+
+            if ($withCodeSnipped) {
+                if ($this->useColors !== Settings::DISABLED) {
+                    $string .= $this->getColoredCodeSnippet($error->getFilePath(), $onLine);
+                } else {
+                    $string .= $this->getCodeSnippet($error->getFilePath(), $onLine);
+                }
+            }
+        }
+
+        $string .= $error->getNormalizedMessage($this->translateTokens);
+
+        if ($error->getBlame()) {
+            $blame = $error->getBlame();
+            $shortCommitHash = substr($blame->commitHash, 0, 8);
+            $dateTime = $blame->datetime->format('c');
+            $string .= PHP_EOL . "Blame {$blame->name} <{$blame->email}>, commit '$shortCommitHash' from $dateTime";
+        }
+
+        return $string;
+    }
+
+    /**
+     * @param string $filePath
+     * @param int $lineNumber
+     * @param int $linesBefore
+     * @param int $linesAfter
+     * @return string
+     */
+    protected function getCodeSnippet($filePath, $lineNumber, $linesBefore = 2, $linesAfter = 2)
+    {
+        $lines = file($filePath);
+
+        $offset = $lineNumber - $linesBefore - 1;
+        $offset = max($offset, 0);
+        $length = $linesAfter + $linesBefore + 1;
+        $lines = array_slice($lines, $offset, $length, $preserveKeys = true);
+
+        end($lines);
+        $lineStrlen = strlen(key($lines) + 1);
+
+        $snippet = '';
+        foreach ($lines as $i => $line) {
+            $snippet .= ($lineNumber === $i + 1 ? '  > ' : '    ');
+            $snippet .= str_pad($i + 1, $lineStrlen, ' ', STR_PAD_LEFT) . '| ' . rtrim($line) . PHP_EOL;
+        }
+
+        return $snippet;
+    }
+
+    /**
+     * @param string $filePath
+     * @param int $lineNumber
+     * @param int $linesBefore
+     * @param int $linesAfter
+     * @return string
+     */
+    protected function getColoredCodeSnippet($filePath, $lineNumber, $linesBefore = 2, $linesAfter = 2)
+    {
+        if (
+            !class_exists('\JakubOnderka\PhpConsoleHighlighter\Highlighter') ||
+            !class_exists('\JakubOnderka\PhpConsoleColor\ConsoleColor')
+        ) {
+            return $this->getCodeSnippet($filePath, $lineNumber, $linesBefore, $linesAfter);
+        }
+
+        $colors = new ConsoleColor();
+        $colors->setForceStyle($this->forceColors);
+        $highlighter = new Highlighter($colors);
+
+        $fileContent = file_get_contents($filePath);
+        return $highlighter->getCodeSnippet($fileContent, $lineNumber, $linesBefore, $linesAfter);
+    }
+}
diff --git a/php-parallel-lint/php-parallel-lint/src/Manager.php b/php-parallel-lint/php-parallel-lint/src/Manager.php
new file mode 100644
index 0000000..63091b1
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/src/Manager.php
@@ -0,0 +1,287 @@
+<?php
+namespace JakubOnderka\PhpParallelLint;
+
+use JakubOnderka\PhpParallelLint\Contracts\SyntaxErrorCallback;
+use JakubOnderka\PhpParallelLint\Process\GitBlameProcess;
+use JakubOnderka\PhpParallelLint\Process\PhpExecutable;
+
+class Manager
+{
+    /** @var Output */
+    protected $output;
+
+    /**
+     * @param null|Settings $settings
+     * @return Result
+     * @throws Exception
+     * @throws \Exception
+     */
+    public function run(Settings $settings = null)
+    {
+        $settings = $settings ?: new Settings;
+        $output = $this->output ?: $this->getDefaultOutput($settings);
+
+        $phpExecutable = PhpExecutable::getPhpExecutable($settings->phpExecutable);
+        $olderThanPhp54 = $phpExecutable->getVersionId() < 50400; // From PHP version 5.4 are tokens translated by default
+        $translateTokens = $phpExecutable->isIsHhvmType() || $olderThanPhp54;
+
+        $output->writeHeader($phpExecutable->getVersionId(), $settings->parallelJobs, $phpExecutable->getHhvmVersion());
+
+        $files = $this->getFilesFromPaths($settings->paths, $settings->extensions, $settings->excluded);
+
+        if (empty($files)) {
+            throw new Exception('No file found to check.');
+        }
+
+        $output->setTotalFileCount(count($files));
+
+        $parallelLint = new ParallelLint($phpExecutable, $settings->parallelJobs);
+        $parallelLint->setAspTagsEnabled($settings->aspTags);
+        $parallelLint->setShortTagEnabled($settings->shortTag);
+        $parallelLint->setShowDeprecated($settings->showDeprecated);
+        $parallelLint->setSyntaxErrorCallback($this->createSyntaxErrorCallback($settings));
+
+        $parallelLint->setProcessCallback(function ($status, $file) use ($output) {
+            if ($status === ParallelLint::STATUS_OK) {
+                $output->ok();
+            } else if ($status === ParallelLint::STATUS_SKIP) {
+                $output->skip();
+            } else if ($status === ParallelLint::STATUS_ERROR) {
+                $output->error();
+            } else {
+                $output->fail();
+            }
+        });
+
+        $result = $parallelLint->lint($files);
+
+        if ($settings->blame) {
+            $this->gitBlame($result, $settings);
+        }
+
+        $output->writeResult($result, new ErrorFormatter($settings->colors, $translateTokens), $settings->ignoreFails);
+
+        return $result;
+    }
+
+    /**
+     * @param Output $output
+     */
+    public function setOutput(Output $output)
+    {
+        $this->output = $output;
+    }
+
+    /**
+     * @param Settings $settings
+     * @return Output
+     */
+    protected function getDefaultOutput(Settings $settings)
+    {
+        $writer = new ConsoleWriter;
+        switch ($settings->format) {
+            case Settings::FORMAT_JSON:
+                return new JsonOutput($writer);
+            case Settings::FORMAT_CHECKSTYLE:
+                return new CheckstyleOutput($writer);
+        }
+
+        if ($settings->colors === Settings::DISABLED) {
+            $output = new TextOutput($writer);
+        } else {
+            $output = new TextOutputColored($writer, $settings->colors);
+        }
+
+        $output->showProgress = $settings->showProgress;
+
+        return $output;
+    }
+
+    /**
+     * @param Result $result
+     * @param Settings $settings
+     * @throws Exception
+     */
+    protected function gitBlame(Result $result, Settings $settings)
+    {
+        if (!GitBlameProcess::gitExists($settings->gitExecutable)) {
+            return;
+        }
+
+        foreach ($result->getErrors() as $error) {
+            if ($error instanceof SyntaxError) {
+                $process = new GitBlameProcess($settings->gitExecutable, $error->getFilePath(), $error->getLine());
+                $process->waitForFinish();
+
+                if ($process->isSuccess()) {
+                    $blame = new Blame;
+                    $blame->name = $process->getAuthor();
+                    $blame->email = $process->getAuthorEmail();
+                    $blame->datetime = $process->getAuthorTime();
+                    $blame->commitHash = $process->getCommitHash();
+                    $blame->summary = $process->getSummary();
+
+                    $error->setBlame($blame);
+                }
+            }
+        }
+    }
+
+    /**
+     * @param array $paths
+     * @param array $extensions
+     * @param array $excluded
+     * @return array
+     * @throws NotExistsPathException
+     */
+    protected function getFilesFromPaths(array $paths, array $extensions, array $excluded = array())
+    {
+        $extensions = array_map('preg_quote', $extensions, array_fill(0, count($extensions), '`'));
+        $regex = '`\.(?:' . implode('|', $extensions) . ')$`iD';
+        $files = array();
+
+        foreach ($paths as $path) {
+            if (is_file($path)) {
+                $files[] = $path;
+            } else if (is_dir($path)) {
+                $iterator = new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS);
+                if (!empty($excluded)) {
+                    $iterator = new RecursiveDirectoryFilterIterator($iterator, $excluded);
+                }
+                $iterator = new \RecursiveIteratorIterator(
+                    $iterator,
+                    \RecursiveIteratorIterator::LEAVES_ONLY,
+                    \RecursiveIteratorIterator::CATCH_GET_CHILD
+                );
+
+                $iterator = new \RegexIterator($iterator, $regex);
+
+                /** @var \SplFileInfo[] $iterator */
+                foreach ($iterator as $directoryFile) {
+                    $files[] = (string) $directoryFile;
+                }
+            } else {
+                throw new NotExistsPathException($path);
+            }
+        }
+
+        $files = array_unique($files);
+
+        return $files;
+    }
+
+    protected function createSyntaxErrorCallback(Settings $settings)
+    {
+        if ($settings->syntaxErrorCallbackFile === null) {
+            return null;
+        }
+
+        $fullFilePath = realpath($settings->syntaxErrorCallbackFile);
+        if ($fullFilePath === false) {
+            throw new NotExistsPathException($settings->syntaxErrorCallbackFile);
+        }
+
+        require_once $fullFilePath;
+
+        $expectedClassName = basename($fullFilePath, '.php');
+        if (!class_exists($expectedClassName)) {
+            throw new NotExistsClassException($expectedClassName, $settings->syntaxErrorCallbackFile);
+        }
+
+        $callbackInstance = new $expectedClassName;
+
+        if (!($callbackInstance instanceof SyntaxErrorCallback)) {
+            throw new NotImplementCallbackException($expectedClassName);
+        }
+
+        return $callbackInstance;
+    }
+}
+
+class RecursiveDirectoryFilterIterator extends \RecursiveFilterIterator
+{
+    /** @var \RecursiveDirectoryIterator */
+    private $iterator;
+
+    /** @var array */
+    private $excluded = array();
+
+    /**
+     * @param \RecursiveDirectoryIterator $iterator
+     * @param array $excluded
+     */
+    public function __construct(\RecursiveDirectoryIterator $iterator, array $excluded)
+    {
+        parent::__construct($iterator);
+        $this->iterator = $iterator;
+        $this->excluded = array_map(array($this, 'getPathname'), $excluded);
+    }
+
+    /**
+     * (PHP 5 &gt;= 5.1.0)<br/>
+     * Check whether the current element of the iterator is acceptable
+     *
+     * @link http://php.net/manual/en/filteriterator.accept.php
+     * @return bool true if the current element is acceptable, otherwise false.
+     */
+    public function accept()
+    {
+        $current = $this->current()->getPathname();
+        $current = $this->normalizeDirectorySeparator($current);
+
+        if ('.' . DIRECTORY_SEPARATOR !== $current[0] . $current[1]) {
+            $current = '.' . DIRECTORY_SEPARATOR . $current;
+        }
+
+        return !in_array($current, $this->excluded);
+    }
+
+    /**
+     * (PHP 5 &gt;= 5.1.0)<br/>
+     * Check whether the inner iterator's current element has children
+     *
+     * @link http://php.net/manual/en/recursivefilteriterator.haschildren.php
+     * @return bool true if the inner iterator has children, otherwise false
+     */
+    public function hasChildren()
+    {
+        return $this->iterator->hasChildren();
+    }
+
+    /**
+     * (PHP 5 &gt;= 5.1.0)<br/>
+     * Return the inner iterator's children contained in a RecursiveFilterIterator
+     *
+     * @link http://php.net/manual/en/recursivefilteriterator.getchildren.php
+     * @return \RecursiveFilterIterator containing the inner iterator's children.
+     */
+    public function getChildren()
+    {
+        return new self($this->iterator->getChildren(), $this->excluded);
+    }
+
+    /**
+     * @param string $file
+     * @return string
+     */
+    private function getPathname($file)
+    {
+        $file = $this->normalizeDirectorySeparator($file);
+
+        if ('.' . DIRECTORY_SEPARATOR !== $file[0] . $file[1]) {
+            $file = '.' . DIRECTORY_SEPARATOR . $file;
+        }
+
+        $directoryFile = new \SplFileInfo($file);
+        return $directoryFile->getPathname();
+    }
+
+    /**
+     * @param string $file
+     * @return string
+     */
+    private function normalizeDirectorySeparator($file)
+    {
+        return str_replace(array('\\', '/'), DIRECTORY_SEPARATOR, $file);
+    }
+}
diff --git a/php-parallel-lint/php-parallel-lint/src/Output.php b/php-parallel-lint/php-parallel-lint/src/Output.php
new file mode 100644
index 0000000..c51d11b
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/src/Output.php
@@ -0,0 +1,513 @@
+<?php
+namespace JakubOnderka\PhpParallelLint;
+
+interface Output
+{
+    public function __construct(IWriter $writer);
+
+    public function ok();
+
+    public function skip();
+
+    public function error();
+
+    public function fail();
+
+    public function setTotalFileCount($count);
+
+    public function writeHeader($phpVersion, $parallelJobs, $hhvmVersion = null);
+
+    public function writeResult(Result $result, ErrorFormatter $errorFormatter, $ignoreFails);
+}
+
+class JsonOutput implements Output
+{
+    /** @var IWriter */
+    protected $writer;
+
+    /** @var int */
+    protected $phpVersion;
+
+    /** @var int */
+    protected $parallelJobs;
+
+    /** @var string */
+    protected $hhvmVersion;
+
+    /**
+     * @param IWriter $writer
+     */
+    public function __construct(IWriter $writer)
+    {
+        $this->writer = $writer;
+    }
+
+    public function ok()
+    {
+
+    }
+
+    public function skip()
+    {
+
+    }
+
+    public function error()
+    {
+
+    }
+
+    public function fail()
+    {
+
+    }
+
+    public function setTotalFileCount($count)
+    {
+
+    }
+
+    public function writeHeader($phpVersion, $parallelJobs, $hhvmVersion = null)
+    {
+        $this->phpVersion = $phpVersion;
+        $this->parallelJobs = $parallelJobs;
+        $this->hhvmVersion = $hhvmVersion;
+    }
+
+    public function writeResult(Result $result, ErrorFormatter $errorFormatter, $ignoreFails)
+    {
+        echo json_encode(array(
+            'phpVersion' => $this->phpVersion,
+            'hhvmVersion' => $this->hhvmVersion,
+            'parallelJobs' => $this->parallelJobs,
+            'results' => $result,
+        ));
+    }
+}
+
+class TextOutput implements Output
+{
+    const TYPE_DEFAULT = 'default',
+        TYPE_SKIP = 'skip',
+        TYPE_ERROR = 'error',
+        TYPE_FAIL = 'fail',
+        TYPE_OK = 'ok';
+
+    /** @var int */
+    public $filesPerLine = 60;
+
+    /** @var bool */
+    public $showProgress = true;
+
+    /** @var int */
+    protected $checkedFiles;
+
+    /** @var int */
+    protected $totalFileCount;
+
+    /** @var IWriter */
+    protected $writer;
+
+    /**
+     * @param IWriter $writer
+     */
+    public function __construct(IWriter $writer)
+    {
+        $this->writer = $writer;
+    }
+
+    public function ok()
+    {
+        $this->writeMark(self::TYPE_OK);
+    }
+
+    public function skip()
+    {
+        $this->writeMark(self::TYPE_SKIP);
+    }
+
+    public function error()
+    {
+        $this->writeMark(self::TYPE_ERROR);
+    }
+
+    public function fail()
+    {
+        $this->writeMark(self::TYPE_FAIL);
+    }
+
+    /**
+     * @param string $string
+     * @param string $type
+     */
+    public function write($string, $type = self::TYPE_DEFAULT)
+    {
+        $this->writer->write($string);
+    }
+
+    /**
+     * @param string|null $line
+     * @param string $type
+     */
+    public function writeLine($line = null, $type = self::TYPE_DEFAULT)
+    {
+        $this->write($line, $type);
+        $this->writeNewLine();
+    }
+
+    /**
+     * @param int $count
+     */
+    public function writeNewLine($count = 1)
+    {
+        $this->write(str_repeat(PHP_EOL, $count));
+    }
+
+    /**
+     * @param int $count
+     */
+    public function setTotalFileCount($count)
+    {
+        $this->totalFileCount = $count;
+    }
+
+    /**
+     * @param int $phpVersion
+     * @param int $parallelJobs
+     * @param string $hhvmVersion
+     */
+    public function writeHeader($phpVersion, $parallelJobs, $hhvmVersion = null)
+    {
+        $this->write("PHP {$this->phpVersionIdToString($phpVersion)} | ");
+
+        if ($hhvmVersion) {
+            $this->write("HHVM $hhvmVersion | ");
+        }
+
+        if ($parallelJobs === 1) {
+            $this->writeLine("1 job");
+        } else {
+            $this->writeLine("{$parallelJobs} parallel jobs");
+        }
+    }
+
+    /**
+     * @param Result $result
+     * @param ErrorFormatter $errorFormatter
+     * @param bool $ignoreFails
+     */
+    public function writeResult(Result $result, ErrorFormatter $errorFormatter, $ignoreFails)
+    {
+        if ($this->showProgress) {
+            if ($this->checkedFiles % $this->filesPerLine !== 0) {
+                $rest = $this->filesPerLine - ($this->checkedFiles % $this->filesPerLine);
+                $this->write(str_repeat(' ', $rest));
+                $this->writePercent();
+            }
+
+            $this->writeNewLine(2);
+        }
+
+        $testTime = round($result->getTestTime(), 1);
+        $message = "Checked {$result->getCheckedFilesCount()} files in $testTime ";
+        $message .= $testTime == 1 ? 'second' : 'seconds';
+
+        if ($result->getSkippedFilesCount() > 0) {
+            $message .= ", skipped {$result->getSkippedFilesCount()} ";
+            $message .= ($result->getSkippedFilesCount() === 1 ? 'file' : 'files');
+        }
+
+        $this->writeLine($message);
+
+        if (!$result->hasSyntaxError()) {
+            $message = "No syntax error found";
+        } else {
+            $message = "Syntax error found in {$result->getFilesWithSyntaxErrorCount()} ";
+            $message .= ($result->getFilesWithSyntaxErrorCount() === 1 ? 'file' : 'files');
+        }
+
+        if ($result->hasFilesWithFail()) {
+            $message .= ", failed to check {$result->getFilesWithFailCount()} ";
+            $message .= ($result->getFilesWithFailCount() === 1 ? 'file' : 'files');
+
+            if ($ignoreFails) {
+                $message .= ' (ignored)';
+            }
+        }
+
+        $hasError = $ignoreFails ? $result->hasSyntaxError() : $result->hasError();
+        $this->writeLine($message, $hasError ? self::TYPE_ERROR : self::TYPE_OK);
+
+        if ($result->hasError()) {
+            $this->writeNewLine();
+            foreach ($result->getErrors() as $error) {
+                $this->writeLine(str_repeat('-', 60));
+                $this->writeLine($errorFormatter->format($error));
+            }
+        }
+    }
+
+    protected function writeMark($type)
+    {
+        ++$this->checkedFiles;
+
+        if ($this->showProgress) {
+            if ($type === self::TYPE_OK) {
+                $this->writer->write('.');
+
+            } else if ($type === self::TYPE_SKIP) {
+                $this->write('S', self::TYPE_SKIP);
+
+            } else if ($type === self::TYPE_ERROR) {
+                $this->write('X', self::TYPE_ERROR);
+
+            } else if ($type === self::TYPE_FAIL) {
+                $this->writer->write('-');
+            }
+
+            if ($this->checkedFiles % $this->filesPerLine === 0) {
+                $this->writePercent();
+            }
+        }
+    }
+
+    protected function writePercent()
+    {
+        $percent = floor($this->checkedFiles / $this->totalFileCount * 100);
+        $current = $this->stringWidth($this->checkedFiles, strlen($this->totalFileCount));
+        $this->writeLine(" $current/$this->totalFileCount ($percent %)");
+    }
+
+    /**
+     * @param string $input
+     * @param int $width
+     * @return string
+     */
+    protected function stringWidth($input, $width = 3)
+    {
+        $multiplier = $width - strlen($input);
+        return str_repeat(' ', $multiplier > 0 ? $multiplier : 0) . $input;
+    }
+
+    /**
+     * @param int $phpVersionId
+     * @return string
+     */
+    protected function phpVersionIdToString($phpVersionId)
+    {
+        $releaseVersion = (int) substr($phpVersionId, -2, 2);
+        $minorVersion = (int) substr($phpVersionId, -4, 2);
+        $majorVersion = (int) substr($phpVersionId, 0, strlen($phpVersionId) - 4);
+
+        return "$majorVersion.$minorVersion.$releaseVersion";
+    }
+}
+
+class CheckstyleOutput implements Output
+{
+    private $writer;
+
+    public function __construct(IWriter $writer)
+    {
+        $this->writer = $writer;
+    }
+
+    public function ok()
+    {
+    }
+
+    public function skip()
+    {
+    }
+
+    public function error()
+    {
+    }
+
+    public function fail()
+    {
+    }
+
+    public function setTotalFileCount($count)
+    {
+    }
+
+    public function writeHeader($phpVersion, $parallelJobs, $hhvmVersion = null)
+    {
+        $this->writer->write('<?xml version="1.0" encoding="UTF-8"?>' . PHP_EOL);
+    }
+
+    public function writeResult(Result $result, ErrorFormatter $errorFormatter, $ignoreFails)
+    {
+        $this->writer->write('<checkstyle>' . PHP_EOL);
+        $errors = array();
+
+        foreach ($result->getErrors() as $error) {
+            $message = $error->getMessage();
+            if ($error instanceof SyntaxError) {
+                $line = $error->getLine();
+                $source = "Syntax Error";
+            } else {
+                $line = 1;
+                $source = "Linter Error";
+            }
+
+            $errors[$error->getShortFilePath()][] = array(
+                'message' => $message,
+                'line' => $line,
+                'source' => $source
+            );
+        }
+
+        foreach ($errors as $file => $fileErrors) {
+            $this->writer->write(sprintf('    <file name="%s">', $file) . PHP_EOL);
+            foreach ($fileErrors as $fileError) {
+                $this->writer->write(
+                    sprintf(
+                        '        <error line="%d" severity="ERROR" message="%s" source="%s" />',
+                        $fileError['line'],
+                        $fileError['message'],
+                        $fileError['source']
+                    ) .
+                    PHP_EOL
+                );
+            }
+            $this->writer->write('    </file>' . PHP_EOL);
+        }
+
+        $this->writer->write('</checkstyle>' . PHP_EOL);
+    }
+}
+
+class TextOutputColored extends TextOutput
+{
+    /** @var \JakubOnderka\PhpConsoleColor\ConsoleColor */
+    private $colors;
+
+    public function __construct(IWriter $writer, $colors = Settings::AUTODETECT)
+    {
+        parent::__construct($writer);
+
+        if (class_exists('\JakubOnderka\PhpConsoleColor\ConsoleColor')) {
+            $this->colors = new \JakubOnderka\PhpConsoleColor\ConsoleColor();
+            $this->colors->setForceStyle($colors === Settings::FORCED);
+        }
+    }
+
+    /**
+     * @param string $string
+     * @param string $type
+     * @throws \JakubOnderka\PhpConsoleColor\InvalidStyleException
+     */
+    public function write($string, $type = self::TYPE_DEFAULT)
+    {
+        if (!$this->colors instanceof \JakubOnderka\PhpConsoleColor\ConsoleColor) {
+            parent::write($string, $type);
+        } else {
+            switch ($type) {
+                case self::TYPE_OK:
+                    parent::write($this->colors->apply('bg_green', $string));
+                    break;
+
+                case self::TYPE_SKIP:
+                    parent::write($this->colors->apply('bg_yellow', $string));
+                    break;
+
+                case self::TYPE_ERROR:
+                    parent::write($this->colors->apply('bg_red', $string));
+                    break;
+
+                default:
+                    parent::write($string);
+            }
+        }
+    }
+}
+
+interface IWriter
+{
+    /**
+     * @param string $string
+     */
+    public function write($string);
+}
+
+class NullWriter implements IWriter
+{
+    /**
+     * @param string $string
+     */
+    public function write($string)
+    {
+
+    }
+}
+
+class ConsoleWriter implements IWriter
+{
+    /**
+     * @param string $string
+     */
+    public function write($string)
+    {
+        echo $string;
+    }
+}
+
+class FileWriter implements IWriter
+{
+    /** @var string */
+    protected $logFile;
+
+    /** @var string */
+    protected $buffer;
+
+    public function __construct($logFile)
+    {
+        $this->logFile = $logFile;
+    }
+
+    public function write($string)
+    {
+        $this->buffer .= $string;
+    }
+
+    public function __destruct()
+    {
+        file_put_contents($this->logFile, $this->buffer);
+    }
+}
+
+class MultipleWriter implements IWriter
+{
+    /** @var IWriter[] */
+    protected $writers;
+
+    /**
+     * @param IWriter[] $writers
+     */
+    public function __construct(array $writers)
+    {
+        foreach ($writers as $writer) {
+            $this->addWriter($writer);
+        }
+    }
+
+    /**
+     * @param IWriter $writer
+     */
+    public function addWriter(IWriter $writer)
+    {
+        $this->writers[] = $writer;
+    }
+
+    /**
+     * @param $string
+     */
+    public function write($string)
+    {
+        foreach ($this->writers as $writer) {
+            $writer->write($string);
+        }
+    }
+}
diff --git a/php-parallel-lint/php-parallel-lint/src/ParallelLint.php b/php-parallel-lint/php-parallel-lint/src/ParallelLint.php
new file mode 100644
index 0000000..b1825f3
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/src/ParallelLint.php
@@ -0,0 +1,286 @@
+<?php
+namespace JakubOnderka\PhpParallelLint;
+
+use JakubOnderka\PhpParallelLint\Contracts\SyntaxErrorCallback;
+use JakubOnderka\PhpParallelLint\Process\LintProcess;
+use JakubOnderka\PhpParallelLint\Process\PhpExecutable;
+use JakubOnderka\PhpParallelLint\Process\SkipLintProcess;
+
+class ParallelLint
+{
+    const STATUS_OK = 'ok',
+        STATUS_SKIP = 'skip',
+        STATUS_FAIL = 'fail',
+        STATUS_ERROR = 'error';
+
+    /** @var int */
+    private $parallelJobs;
+
+    /** @var PhpExecutable */
+    private $phpExecutable;
+
+    /** @var bool */
+    private $aspTagsEnabled = false;
+
+    /** @var bool */
+    private $shortTagEnabled = false;
+
+    /** @var callable */
+    private $processCallback;
+
+    /** @var bool */
+    private $showDeprecated = false;
+
+    /** @var SyntaxErrorCallback|null */
+    private $syntaxErrorCallback = null;
+
+    public function __construct(PhpExecutable $phpExecutable, $parallelJobs = 10)
+    {
+        $this->phpExecutable = $phpExecutable;
+        $this->parallelJobs = $parallelJobs;
+    }
+
+    /**
+     * @param array $files
+     * @return Result
+     * @throws \Exception
+     */
+    public function lint(array $files)
+    {
+        $startTime = microtime(true);
+
+        $skipLintProcess = new SkipLintProcess($this->phpExecutable, $files);
+
+        $processCallback = is_callable($this->processCallback) ? $this->processCallback : function () {
+        };
+
+        /**
+         * @var LintProcess[] $running
+         * @var LintProcess[] $waiting
+         */
+        $errors = $running = $waiting = array();
+        $skippedFiles = $checkedFiles = array();
+
+        while ($files || $running) {
+            for ($i = count($running); $files && $i < $this->parallelJobs; $i++) {
+                $file = array_shift($files);
+
+                if ($skipLintProcess->isSkipped($file) === true) {
+                    $skippedFiles[] = $file;
+                    $processCallback(self::STATUS_SKIP, $file);
+                } else {
+                    $running[$file] = new LintProcess(
+                        $this->phpExecutable,
+                        $file,
+                        $this->aspTagsEnabled,
+                        $this->shortTagEnabled,
+                        $this->showDeprecated
+                    );
+                }
+            }
+
+            $skipLintProcess->getChunk();
+            usleep(100);
+
+            foreach ($running as $file => $process) {
+                if ($process->isFinished()) {
+                    unset($running[$file]);
+
+                    $skipStatus = $skipLintProcess->isSkipped($file);
+                    if ($skipStatus === null) {
+                        $waiting[$file] = $process;
+
+                    } else if ($skipStatus === true) {
+                        $skippedFiles[] = $file;
+                        $processCallback(self::STATUS_SKIP, $file);
+
+                    } else if ($process->containsError()) {
+                        $checkedFiles[] = $file;
+                        $errors[] = $this->triggerSyntaxErrorCallback(new SyntaxError($file, $process->getSyntaxError()));
+                        $processCallback(self::STATUS_ERROR, $file);
+
+                    } else if ($process->isSuccess()) {
+                        $checkedFiles[] = $file;
+                        $processCallback(self::STATUS_OK, $file);
+
+
+                    } else {
+                        $errors[] = new Error($file, $process->getOutput());
+                        $processCallback(self::STATUS_FAIL, $file);
+                    }
+                }
+            }
+        }
+
+        if (!empty($waiting)) {
+            $skipLintProcess->waitForFinish();
+
+            if ($skipLintProcess->isFail()) {
+                $message = "Error in skip-linting.php process\nError output: {$skipLintProcess->getErrorOutput()}";
+                throw new \Exception($message);
+            }
+
+            foreach ($waiting as $file => $process) {
+                $skipStatus = $skipLintProcess->isSkipped($file);
+                if ($skipStatus === null) {
+                    throw new \Exception("File $file has empty skip status. Please contact the author of PHP Parallel Lint.");
+
+                } else if ($skipStatus === true) {
+                    $skippedFiles[] = $file;
+                    $processCallback(self::STATUS_SKIP, $file);
+
+                } else if ($process->isSuccess()) {
+                    $checkedFiles[] = $file;
+                    $processCallback(self::STATUS_OK, $file);
+
+                } else if ($process->containsError()) {
+                    $checkedFiles[] = $file;
+                    $errors[] = $this->triggerSyntaxErrorCallback(new SyntaxError($file, $process->getSyntaxError()));
+                    $processCallback(self::STATUS_ERROR, $file);
+
+                } else {
+                    $errors[] = new Error($file, $process->getOutput());
+                    $processCallback(self::STATUS_FAIL, $file);
+                }
+            }
+        }
+
+        $testTime = microtime(true) - $startTime;
+
+        return new Result($errors, $checkedFiles, $skippedFiles, $testTime);
+    }
+
+    /**
+     * @return int
+     */
+    public function getParallelJobs()
+    {
+        return $this->parallelJobs;
+    }
+
+    /**
+     * @param int $parallelJobs
+     * @return ParallelLint
+     */
+    public function setParallelJobs($parallelJobs)
+    {
+        $this->parallelJobs = $parallelJobs;
+
+        return $this;
+    }
+
+    /**
+     * @return string
+     */
+    public function getPhpExecutable()
+    {
+        return $this->phpExecutable;
+    }
+
+    /**
+     * @param string $phpExecutable
+     * @return ParallelLint
+     */
+    public function setPhpExecutable($phpExecutable)
+    {
+        $this->phpExecutable = $phpExecutable;
+
+        return $this;
+    }
+
+    /**
+     * @return callable
+     */
+    public function getProcessCallback()
+    {
+        return $this->processCallback;
+    }
+
+    /**
+     * @param callable $processCallback
+     * @return ParallelLint
+     */
+    public function setProcessCallback($processCallback)
+    {
+        $this->processCallback = $processCallback;
+
+        return $this;
+    }
+
+    /**
+     * @return boolean
+     */
+    public function isAspTagsEnabled()
+    {
+        return $this->aspTagsEnabled;
+    }
+
+    /**
+     * @param boolean $aspTagsEnabled
+     * @return ParallelLint
+     */
+    public function setAspTagsEnabled($aspTagsEnabled)
+    {
+        $this->aspTagsEnabled = $aspTagsEnabled;
+
+        return $this;
+    }
+
+    /**
+     * @return boolean
+     */
+    public function isShortTagEnabled()
+    {
+        return $this->shortTagEnabled;
+    }
+
+    /**
+     * @param boolean $shortTagEnabled
+     * @return ParallelLint
+     */
+    public function setShortTagEnabled($shortTagEnabled)
+    {
+        $this->shortTagEnabled = $shortTagEnabled;
+
+        return $this;
+    }
+
+    /**
+     * @return boolean
+     */
+    public function isShowDeprecated()
+    {
+        return $this->showDeprecated;
+    }
+
+    /**
+     * @param $showDeprecated
+     * @return ParallelLint
+     */
+    public function setShowDeprecated($showDeprecated)
+    {
+        $this->showDeprecated = $showDeprecated;
+
+        return $this;
+    }
+
+    public function triggerSyntaxErrorCallback($syntaxError)
+    {
+        if ($this->syntaxErrorCallback === null) {
+            return $syntaxError;
+        }
+
+        return $this->syntaxErrorCallback->errorFound($syntaxError);
+    }
+
+    /**
+     * @param SyntaxErrorCallback|null $syntaxErrorCallback
+     * @return ParallelLint
+     */
+    public function setSyntaxErrorCallback($syntaxErrorCallback)
+    {
+        $this->syntaxErrorCallback = $syntaxErrorCallback;
+
+        return $this;
+    }
+}
diff --git a/php-parallel-lint/php-parallel-lint/src/Process/GitBlameProcess.php b/php-parallel-lint/php-parallel-lint/src/Process/GitBlameProcess.php
new file mode 100644
index 0000000..2d675b8
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/src/Process/GitBlameProcess.php
@@ -0,0 +1,147 @@
+<?php
+namespace JakubOnderka\PhpParallelLint\Process;
+
+use JakubOnderka\PhpParallelLint\RunTimeException;
+
+class GitBlameProcess extends Process
+{
+    /**
+     * @param string $gitExecutable
+     * @param string $file
+     * @param int $line
+     * @throws RunTimeException
+     */
+    public function __construct($gitExecutable, $file, $line)
+    {
+        $arguments = array('blame', '-p', '-L', "$line,+1", $file);
+        parent::__construct($gitExecutable, $arguments);
+    }
+
+    /**
+     * @return bool
+     * @throws RunTimeException
+     */
+    public function isSuccess()
+    {
+        return $this->getStatusCode() === 0;
+    }
+
+    /**
+     * @return string
+     * @throws RunTimeException
+     */
+    public function getAuthor()
+    {
+        if (!$this->isSuccess()) {
+            throw new RunTimeException("Author can only be retrieved for successful process output.");
+        }
+
+        $output = $this->getOutput();
+        preg_match('~^author (.*)~m', $output, $matches);
+        return $matches[1];
+    }
+
+    /**
+     * @return string
+     * @throws RunTimeException
+     */
+    public function getAuthorEmail()
+    {
+        if (!$this->isSuccess()) {
+            throw new RunTimeException("Author e-mail can only be retrieved for successful process output.");
+        }
+
+        $output = $this->getOutput();
+        preg_match('~^author-mail <(.*)>~m', $output, $matches);
+        return $matches[1];
+    }
+
+    /**
+     * @return \DateTime
+     * @throws RunTimeException
+     */
+    public function getAuthorTime()
+    {
+        if (!$this->isSuccess()) {
+            throw new RunTimeException("Author time can only be retrieved for successful process output.");
+        }
+
+        $output = $this->getOutput();
+
+        preg_match('~^author-time (.*)~m', $output, $matches);
+        $time = $matches[1];
+
+        preg_match('~^author-tz (.*)~m', $output, $matches);
+        $zone = $matches[1];
+
+        return $this->getDateTime($time, $zone);
+    }
+
+    /**
+     * @return string
+     * @throws RunTimeException
+     */
+    public function getCommitHash()
+    {
+        if (!$this->isSuccess()) {
+            throw new RunTimeException("Commit hash can only be retrieved for successful process output.");
+        }
+
+        return substr($this->getOutput(), 0, strpos($this->getOutput(), ' '));
+    }
+
+    /**
+     * @return string
+     * @throws RunTimeException
+     */
+    public function getSummary()
+    {
+        if (!$this->isSuccess()) {
+            throw new RunTimeException("Commit summary can only be retrieved for successful process output.");
+        }
+
+        $output = $this->getOutput();
+        preg_match('~^summary (.*)~m', $output, $matches);
+        return $matches[1];
+    }
+
+    /**
+     * @param string $gitExecutable
+     * @return bool
+     * @throws RunTimeException
+     */
+    public static function gitExists($gitExecutable)
+    {
+        $process = new Process($gitExecutable, array('--version'));
+        $process->waitForFinish();
+        return $process->getStatusCode() === 0;
+    }
+
+    /**
+     * This harakiri method is required to correct support time zone in PHP 5.4
+     *
+     * @param int $time
+     * @param string $zone
+     * @return \DateTime
+     * @throws \Exception
+     */
+    protected function getDateTime($time, $zone)
+    {
+        $utcTimeZone = new \DateTimeZone('UTC');
+        $datetime = \DateTime::createFromFormat('U', $time, $utcTimeZone);
+
+        $way = substr($zone, 0, 1);
+        $hours = (int) substr($zone, 1, 2);
+        $minutes = (int) substr($zone, 3, 2);
+
+        $interval = new \DateInterval("PT{$hours}H{$minutes}M");
+
+        if ($way === '+') {
+            $datetime->add($interval);
+        } else {
+            $datetime->sub($interval);
+        }
+
+        return new \DateTime($datetime->format('Y-m-d\TH:i:s') . $zone, $utcTimeZone);
+    }
+}
diff --git a/php-parallel-lint/php-parallel-lint/src/Process/LintProcess.php b/php-parallel-lint/php-parallel-lint/src/Process/LintProcess.php
new file mode 100644
index 0000000..e2e6b2d
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/src/Process/LintProcess.php
@@ -0,0 +1,137 @@
+<?php
+namespace JakubOnderka\PhpParallelLint\Process;
+
+use JakubOnderka\PhpParallelLint\RunTimeException;
+
+class LintProcess extends PhpProcess
+{
+    const FATAL_ERROR = 'Fatal error';
+    const PARSE_ERROR = 'Parse error';
+    const DEPRECATED_ERROR = 'Deprecated:';
+
+    /**
+     * @var bool
+     */
+    private $showDeprecatedErrors;
+
+    /**
+     * @param PhpExecutable $phpExecutable
+     * @param string $fileToCheck Path to file to check
+     * @param bool $aspTags
+     * @param bool $shortTag
+     * @param bool $deprecated
+     * @throws RunTimeException
+     */
+    public function __construct(PhpExecutable $phpExecutable, $fileToCheck, $aspTags = false, $shortTag = false, $deprecated = false)
+    {
+        if (empty($fileToCheck)) {
+            throw new \InvalidArgumentException("File to check must be set.");
+        }
+
+        $parameters = array(
+            '-d asp_tags=' . ($aspTags ? 'On' : 'Off'),
+            '-d short_open_tag=' . ($shortTag ? 'On' : 'Off'),
+            '-d error_reporting=E_ALL',
+            '-n',
+            '-l',
+            $fileToCheck,
+        );
+
+        $this->showDeprecatedErrors = $deprecated;
+        parent::__construct($phpExecutable, $parameters);
+    }
+
+    /**
+     * @return bool
+     * @throws
+     */
+    public function containsError()
+    {
+        return $this->containsParserError($this->getOutput()) ||
+            $this->containsFatalError($this->getOutput()) ||
+            $this->containsDeprecatedError($this->getOutput());
+    }
+
+    /**
+     * @return string
+     * @throws RunTimeException
+     */
+    public function getSyntaxError()
+    {
+        if ($this->containsError()) {
+            // Look for fatal errors first
+            foreach (explode("\n", $this->getOutput()) as $line) {
+                if ($this->containsFatalError($line)) {
+                    return $line;
+                }
+            }
+
+            // Look for parser errors second
+            foreach (explode("\n", $this->getOutput()) as $line) {
+                if ($this->containsParserError($line)) {
+                    return $line;
+                }
+            }
+
+            // Look for deprecated errors third
+            foreach (explode("\n", $this->getOutput()) as $line) {
+                if ($this->containsDeprecatedError($line)) {
+                    return $line;
+                }
+            }
+
+            throw new RunTimeException("The output '{$this->getOutput()}' does not contain Parse or Syntax errors");
+        }
+
+        return false;
+    }
+
+    /**
+     * @return bool
+     * @throws RunTimeException
+     */
+    public function isFail()
+    {
+        return defined('PHP_WINDOWS_VERSION_MAJOR') ? $this->getStatusCode() === 1 : parent::isFail();
+    }
+
+    /**
+     * @return bool
+     * @throws RunTimeException
+     */
+    public function isSuccess()
+    {
+        return $this->getStatusCode() === 0;
+    }
+
+    /**
+     * @param string $string
+     * @return bool
+     */
+    private function containsParserError($string)
+    {
+        return strpos($string, self::PARSE_ERROR) !== false;
+    }
+
+    /**
+     * @param string $string
+     * @return bool
+     */
+    private function containsFatalError($string)
+    {
+        return strpos($string, self::FATAL_ERROR) !== false;
+    }
+
+    /**
+     * @param string $string
+     * @return bool
+     */
+    private function containsDeprecatedError($string)
+    {
+        if ($this->showDeprecatedErrors === false) {
+            return false;
+        }
+
+        return strpos($string, self::DEPRECATED_ERROR) !== false;
+    }
+}
diff --git a/php-parallel-lint/php-parallel-lint/src/Process/PhpExecutable.php b/php-parallel-lint/php-parallel-lint/src/Process/PhpExecutable.php
new file mode 100644
index 0000000..001b1e9
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/src/Process/PhpExecutable.php
@@ -0,0 +1,128 @@
+<?php
+
+namespace JakubOnderka\PhpParallelLint\Process;
+
+use JakubOnderka\PhpParallelLint\RunTimeException;
+
+class PhpExecutable
+{
+    /** @var string */
+    private $path;
+
+    /**
+     * Version as PHP_VERSION_ID constant
+     * @var int
+     */
+    private $versionId;
+
+    /** @var string */
+    private $hhvmVersion;
+
+    /** @var bool */
+    private $isHhvmType;
+
+    /**
+     * @param string $path
+     * @param int $versionId
+     * @param string $hhvmVersion
+     * @param bool $isHhvmType
+     */
+    public function __construct($path, $versionId, $hhvmVersion, $isHhvmType)
+    {
+        $this->path = $path;
+        $this->versionId = $versionId;
+        $this->hhvmVersion = $hhvmVersion;
+        $this->isHhvmType = $isHhvmType;
+    }
+
+    /**
+     * @return string
+     */
+    public function getHhvmVersion()
+    {
+        return $this->hhvmVersion;
+    }
+
+    /**
+     * @return boolean
+     */
+    public function isIsHhvmType()
+    {
+        return $this->isHhvmType;
+    }
+
+    /**
+     * @return string
+     */
+    public function getPath()
+    {
+        return $this->path;
+    }
+
+    /**
+     * @return int
+     */
+    public function getVersionId()
+    {
+        return $this->versionId;
+    }
+
+    /**
+     * @param string $phpExecutable
+     * @return PhpExecutable
+     * @throws \Exception
+     */
+    public static function getPhpExecutable($phpExecutable)
+    {
+        $codeToExecute = <<<PHP
+echo 'PHP;', PHP_VERSION_ID, ';', defined('HPHP_VERSION') ? HPHP_VERSION : null;
+PHP;
+
+        $process = new Process($phpExecutable, array('-n', '-r', $codeToExecute));
+        $process->waitForFinish();
+
+        try {
+            if ($process->getStatusCode() !== 0 && $process->getStatusCode() !== 255) {
+                throw new RunTimeException("Unable to execute '{$phpExecutable}'.");
+            }
+
+            return self::getPhpExecutableFromOutput($phpExecutable, $process->getOutput());
+
+        } catch (RunTimeException $e) {
+            // Try HHVM type
+            $process = new Process($phpExecutable, array('--php', '-r', $codeToExecute));
+            $process->waitForFinish();
+
+            if ($process->getStatusCode() !== 0 && $process->getStatusCode() !== 255) {
+                throw new RunTimeException("Unable to execute '{$phpExecutable}'.");
+            }
+
+            return self::getPhpExecutableFromOutput($phpExecutable, $process->getOutput(), $isHhvmType = true);
+        }
+    }
+
+    /**
+     * @param string $phpExecutable
+     * @param string $output
+     * @param bool $isHhvmType
+     * @return PhpExecutable
+     * @throws RunTimeException
+     */
+    private static function getPhpExecutableFromOutput($phpExecutable, $output, $isHhvmType = false)
+    {
+        $parts = explode(';', $output);
+
+        if ($parts[0] !== 'PHP' || !preg_match('~([0-9]+)~', $parts[1], $matches)) {
+            throw new RunTimeException("'{$phpExecutable}' is not valid PHP binary.");
+        }
+
+        $hhvmVersion = isset($parts[2]) ? $parts[2] : false;
+
+        return new PhpExecutable(
+            $phpExecutable,
+            intval($matches[1]),
+            $hhvmVersion,
+            $isHhvmType
+        );
+    }
+}
diff --git a/php-parallel-lint/php-parallel-lint/src/Process/PhpProcess.php b/php-parallel-lint/php-parallel-lint/src/Process/PhpProcess.php
new file mode 100644
index 0000000..0df293a
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/src/Process/PhpProcess.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace JakubOnderka\PhpParallelLint\Process;
+
+class PhpProcess extends Process
+{
+    /**
+     * @param PhpExecutable $phpExecutable
+     * @param array $parameters
+     * @param string|null $stdIn
+     * @throws \JakubOnderka\PhpParallelLint\RunTimeException
+     */
+    public function __construct(PhpExecutable $phpExecutable, array $parameters = array(), $stdIn = null)
+    {
+        $constructedParameters = $this->constructParameters($parameters, $phpExecutable->isIsHhvmType());
+        parent::__construct($phpExecutable->getPath(), $constructedParameters, $stdIn);
+    }
+
+    /**
+     * @param array $parameters
+     * @param bool $isHhvm
+     * @return array
+     */
+    private function constructParameters(array $parameters, $isHhvm)
+    {
+        // Always ignore PHP startup errors ("Unable to load library...") in sub-processes.
+        array_unshift($parameters, '-d display_startup_errors=0');
+
+        if ($isHhvm) {
+            array_unshift($parameters, '-php');
+        }
+
+        return $parameters;
+    }
+}
diff --git a/php-parallel-lint/php-parallel-lint/src/Process/Process.php b/php-parallel-lint/php-parallel-lint/src/Process/Process.php
new file mode 100644
index 0000000..190511e
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/src/Process/Process.php
@@ -0,0 +1,153 @@
+<?php
+
+namespace JakubOnderka\PhpParallelLint\Process;
+
+use JakubOnderka\PhpParallelLint\RunTimeException;
+
+class Process
+{
+    const STDIN = 0,
+        STDOUT = 1,
+        STDERR = 2;
+
+    const READ = 'r',
+        WRITE = 'w';
+
+    /** @var resource */
+    protected $process;
+
+    /** @var resource */
+    protected $stdout;
+
+    /** @var resource */
+    protected $stderr;
+
+    /** @var string */
+    private $output;
+
+    /** @var string */
+    private $errorOutput;
+
+    /** @var int */
+    private $statusCode;
+
+    /**
+     * @param string $executable
+     * @param string[] $arguments
+     * @param string $stdInInput
+     * @throws RunTimeException
+     */
+    public function __construct($executable, array $arguments = array(), $stdInInput = null)
+    {
+        $descriptors = array(
+            self::STDIN  => array('pipe', self::READ),
+            self::STDOUT => array('pipe', self::WRITE),
+            self::STDERR => array('pipe', self::WRITE),
+        );
+
+        $cmdLine = $executable . ' ' . implode(' ', array_map('escapeshellarg', $arguments));
+        $this->process = proc_open($cmdLine, $descriptors, $pipes, null, null, array('bypass_shell' => true));
+
+        if ($this->process === false || $this->process === null) {
+            throw new RunTimeException("Cannot create new process $cmdLine");
+        }
+
+        list($stdin, $this->stdout, $this->stderr) = $pipes;
+
+        if ($stdInInput) {
+            fwrite($stdin, $stdInInput);
+        }
+
+        fclose($stdin);
+    }
+
+    /**
+     * @return bool
+     */
+    public function isFinished()
+    {
+        if ($this->statusCode !== null) {
+            return true;
+        }
+
+        $status = proc_get_status($this->process);
+
+        if ($status['running']) {
+            return false;
+        } else if ($this->statusCode === null) {
+            $this->statusCode = (int) $status['exitcode'];
+        }
+
+        // Process outputs
+        $this->output = stream_get_contents($this->stdout);
+        fclose($this->stdout);
+
+        $this->errorOutput = stream_get_contents($this->stderr);
+        fclose($this->stderr);
+
+        $statusCode = proc_close($this->process);
+
+        if ($this->statusCode === null) {
+            $this->statusCode = $statusCode;
+        }
+
+        $this->process = null;
+
+        return true;
+    }
+
+    public function waitForFinish()
+    {
+        while (!$this->isFinished()) {
+            usleep(100);
+        }
+    }
+
+    /**
+     * @return string
+     * @throws RunTimeException
+     */
+    public function getOutput()
+    {
+        if (!$this->isFinished()) {
+            throw new RunTimeException("Cannot get output for running process");
+        }
+
+        return $this->output;
+    }
+
+    /**
+     * @return string
+     * @throws RunTimeException
+     */
+    public function getErrorOutput()
+    {
+        if (!$this->isFinished()) {
+            throw new RunTimeException("Cannot get error output for running process");
+        }
+
+        return $this->errorOutput;
+    }
+
+    /**
+     * @return int
+     * @throws RunTimeException
+     */
+    public function getStatusCode()
+    {
+        if (!$this->isFinished()) {
+            throw new RunTimeException("Cannot get status code for running process");
+        }
+
+        return $this->statusCode;
+    }
+
+    /**
+     * @return bool
+     * @throws RunTimeException
+     */
+    public function isFail()
+    {
+        return $this->getStatusCode() === 1;
+    }
+}
diff --git a/php-parallel-lint/php-parallel-lint/src/Process/SkipLintProcess.php b/php-parallel-lint/php-parallel-lint/src/Process/SkipLintProcess.php
new file mode 100644
index 0000000..52f4e76
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/src/Process/SkipLintProcess.php
@@ -0,0 +1,91 @@
+<?php
+namespace JakubOnderka\PhpParallelLint\Process;
+
+use JakubOnderka\PhpParallelLint\RunTimeException;
+
+class SkipLintProcess extends PhpProcess
+{
+    /** @var array */
+    private $skipped = array();
+
+    /** @var bool */
+    private $done = false;
+
+    /** @var string */
+    private $endLastChunk = '';
+
+    /**
+     * @param PhpExecutable $phpExecutable
+     * @param array $filesToCheck
+     * @throws RunTimeException
+     */
+    public function __construct(PhpExecutable $phpExecutable, array $filesToCheck)
+    {
+        $scriptPath = __DIR__ . '/../../bin/skip-linting.php';
+        $script = file_get_contents($scriptPath);
+
+        if (!$script) {
+            throw new RunTimeException("skip-linting.php script not found in '$scriptPath'.");
+        }
+
+        $script = str_replace('<?php', '', $script);
+
+        $parameters = array('-d', 'display_errors=stderr', '-r', $script);
+        parent::__construct($phpExecutable, $parameters, implode(PHP_EOL, $filesToCheck));
+    }
+
+    /**
+     * @throws RunTimeException
+     */
+    public function getChunk()
+    {
+        if (!$this->isFinished()) {
+            $this->processLines(fread($this->stdout, 8192));
+        }
+    }
+
+    /**
+     * @return bool
+     * @throws \JakubOnderka\PhpParallelLint\RunTimeException
+     */
+    public function isFinished()
+    {
+        $isFinished = parent::isFinished();
+        if ($isFinished && !$this->done) {
+            $this->done = true;
+            $output = $this->getOutput();
+            $this->processLines($output);
+        }
+
+        return $isFinished;
+    }
+
+    /**
+     * @param string $file
+     * @return bool|null
+     */
+    public function isSkipped($file)
+    {
+        if (isset($this->skipped[$file])) {
+            return $this->skipped[$file];
+        }
+
+        return null;
+    }
+
+    /**
+     * @param string $content
+     */
+    private function processLines($content)
+    {
+        if (!empty($content)) {
+            $lines = explode(PHP_EOL, $this->endLastChunk . $content);
+            $this->endLastChunk = array_pop($lines);
+            foreach ($lines as $line) {
+                $parts = explode(';', $line);
+                list($file, $status) = $parts;
+                $this->skipped[$file] = $status === '1' ? true : false;
+            }
+        }
+    }
+}
diff --git a/php-parallel-lint/php-parallel-lint/src/Result.php b/php-parallel-lint/php-parallel-lint/src/Result.php
new file mode 100644
index 0000000..6004374
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/src/Result.php
@@ -0,0 +1,168 @@
+<?php
+namespace JakubOnderka\PhpParallelLint;
+
+class Result implements \JsonSerializable
+{
+    /** @var Error[] */
+    private $errors;
+
+    /** @var array */
+    private $checkedFiles;
+
+    /** @var array */
+    private $skippedFiles;
+
+    /** @var float */
+    private $testTime;
+
+    /**
+     * @param Error[] $errors
+     * @param array $checkedFiles
+     * @param array $skippedFiles
+     * @param float $testTime
+     */
+    public function __construct(array $errors, array $checkedFiles, array $skippedFiles, $testTime)
+    {
+        $this->errors = $errors;
+        $this->checkedFiles = $checkedFiles;
+        $this->skippedFiles = $skippedFiles;
+        $this->testTime = $testTime;
+    }
+
+    /**
+     * @return Error[]
+     */
+    public function getErrors()
+    {
+        return $this->errors;
+    }
+
+    /**
+     * @return bool
+     */
+    public function hasError()
+    {
+        return !empty($this->errors);
+    }
+
+    /**
+     * @return array
+     */
+    public function getFilesWithFail()
+    {
+        $filesWithFail = array();
+        foreach ($this->errors as $error) {
+            if (!$error instanceof SyntaxError) {
+                $filesWithFail[] = $error->getFilePath();
+            }
+        }
+
+        return $filesWithFail;
+    }
+
+    /**
+     * @return int
+     */
+    public function getFilesWithFailCount()
+    {
+        return count($this->getFilesWithFail());
+    }
+
+    /**
+     * @return bool
+     */
+    public function hasFilesWithFail()
+    {
+        return $this->getFilesWithFailCount() !== 0;
+    }
+
+    /**
+     * @return array
+     */
+    public function getCheckedFiles()
+    {
+        return $this->checkedFiles;
+    }
+
+    /**
+     * @return int
+     */
+    public function getCheckedFilesCount()
+    {
+        return count($this->checkedFiles);
+    }
+
+    /**
+     * @return array
+     */
+    public function getSkippedFiles()
+    {
+        return $this->skippedFiles;
+    }
+
+    /**
+     * @return int
+     */
+    public function getSkippedFilesCount()
+    {
+        return count($this->skippedFiles);
+    }
+
+    /**
+     * @return array
+     */
+    public function getFilesWithSyntaxError()
+    {
+        $filesWithSyntaxError = array();
+        foreach ($this->errors as $error) {
+            if ($error instanceof SyntaxError) {
+                $filesWithSyntaxError[] = $error->getFilePath();
+            }
+        }
+
+        return $filesWithSyntaxError;
+    }
+
+    /**
+     * @return int
+     */
+    public function getFilesWithSyntaxErrorCount()
+    {
+        return count($this->getFilesWithSyntaxError());
+    }
+
+    /**
+     * @return bool
+     */
+    public function hasSyntaxError()
+    {
+        return $this->getFilesWithSyntaxErrorCount() !== 0;
+    }
+
+    /**
+     * @return float
+     */
+    public function getTestTime()
+    {
+        return $this->testTime;
+    }
+
+    /**
+     * (PHP 5 &gt;= 5.4.0)<br/>
+     * Specify data which should be serialized to JSON
+     * @link http://php.net/manual/en/jsonserializable.jsonserialize.php
+     * @return mixed data which can be serialized by <b>json_encode</b>,
+     * which is a value of any type other than a resource.
+     */
+    function jsonSerialize()
+    {
+        return array(
+            'checkedFiles' => $this->getCheckedFiles(),
+            'filesWithSyntaxError' => $this->getFilesWithSyntaxError(),
+            'skippedFiles' => $this->getSkippedFiles(),
+            'errors' => $this->getErrors(),
+        );
+    }
+
+
+}
\ No newline at end of file
diff --git a/php-parallel-lint/php-parallel-lint/src/Settings.php b/php-parallel-lint/php-parallel-lint/src/Settings.php
new file mode 100644
index 0000000..3e03a71
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/src/Settings.php
@@ -0,0 +1,242 @@
+<?php
+namespace JakubOnderka\PhpParallelLint;
+
+class Settings
+{
+
+    /**
+     * constants for enum settings
+     */
+    const FORCED = 'FORCED';
+    const DISABLED = 'DISABLED';
+    const AUTODETECT = 'AUTODETECT';
+
+    const FORMAT_TEXT = 'text';
+    const FORMAT_JSON = 'json';
+    const FORMAT_CHECKSTYLE = 'checkstyle';
+
+    /**
+     * Path to PHP executable
+     * @var string
+     */
+    public $phpExecutable = 'php';
+
+    /**
+     * Check code inside PHP opening short tag <? or <?= in PHP 5.3
+     * @var bool
+     */
+    public $shortTag = false;
+
+    /**
+     * Check PHP code inside ASP-style <% %> tags.
+     * @var bool
+     */
+    public $aspTags = false;
+
+    /**
+     * Number of jobs running in same time
+     * @var int
+     */
+    public $parallelJobs = 10;
+
+    /**
+     * If path contains directory, only file with these extensions are checked
+     * @var array
+     */
+    public $extensions = array('php', 'phtml', 'php3', 'php4', 'php5', 'phpt');
+
+    /**
+     * Array of file or directories to check
+     * @var array
+     */
+    public $paths = array();
+
+    /**
+     * Don't check files or directories
+     * @var array
+     */
+    public $excluded = array();
+
+    /**
+     * Mode for color detection. Possible values: self::FORCED, self::DISABLED and self::AUTODETECT
+     * @var string
+     */
+    public $colors = self::AUTODETECT;
+
+    /**
+     * Show progress in text output
+     * @var bool
+     */
+    public $showProgress = true;
+
+    /**
+     * Output format (see FORMAT_* constants)
+     * @var string
+     */
+    public $format = self::FORMAT_TEXT;
+
+    /**
+     * Read files and folder to tests from standard input (blocking)
+     * @var bool
+     */
+    public $stdin = false;
+
+    /**
+     * Try to show git blame for row with error
+     * @var bool
+     */
+    public $blame = false;
+
+    /**
+     * Path to git executable for blame
+     * @var string
+     */
+    public $gitExecutable = 'git';
+
+    /**
+     * @var bool
+     */
+    public $ignoreFails = false;
+
+    /**
+     * @var bool
+     */
+    public $showDeprecated = false;
+
+    /**
+     * Path to a file with syntax error callback
+     * @var string|null
+     */
+    public $syntaxErrorCallbackFile = null;
+
+    /**
+     * @param array $paths
+     */
+    public function addPaths(array $paths)
+    {
+        $this->paths = array_merge($this->paths, $paths);
+    }
+
+    /**
+     * @param array $arguments
+     * @return Settings
+     * @throws InvalidArgumentException
+     */
+    public static function parseArguments(array $arguments)
+    {
+        $arguments = new ArrayIterator(array_slice($arguments, 1));
+        $settings = new self;
+
+        // Use the currently invoked php as the default if possible
+        if (defined('PHP_BINARY')) {
+            $settings->phpExecutable = PHP_BINARY;
+        }
+
+        foreach ($arguments as $argument) {
+            if ($argument[0] !== '-') {
+                $settings->paths[] = $argument;
+            } else {
+                switch ($argument) {
+                    case '-p':
+                        $settings->phpExecutable = $arguments->getNext();
+                        break;
+
+                    case '-s':
+                    case '--short':
+                        $settings->shortTag = true;
+                        break;
+
+                    case '-a':
+                    case '--asp':
+                        $settings->aspTags = true;
+                        break;
+
+                    case '--exclude':
+                        $settings->excluded[] = $arguments->getNext();
+                        break;
+
+                    case '-e':
+                        $settings->extensions = array_map('trim', explode(',', $arguments->getNext()));
+                        break;
+
+                    case '-j':
+                        $settings->parallelJobs = max((int) $arguments->getNext(), 1);
+                        break;
+
+                    case '--colors':
+                        $settings->colors = self::FORCED;
+                        break;
+
+                    case '--no-colors':
+                        $settings->colors = self::DISABLED;
+                        break;
+
+                    case '--no-progress':
+                        $settings->showProgress = false;
+                        break;
+
+                    case '--json':
+                        $settings->format = self::FORMAT_JSON;
+                        break;
+
+                    case '--checkstyle':
+                        $settings->format = self::FORMAT_CHECKSTYLE;
+                        break;
+
+                    case '--git':
+                        $settings->gitExecutable = $arguments->getNext();
+                        break;
+
+                    case '--stdin':
+                        $settings->stdin = true;
+                        break;
+
+                    case '--blame':
+                        $settings->blame = true;
+                        break;
+
+                    case '--ignore-fails':
+                        $settings->ignoreFails = true;
+                        break;
+
+                    case '--show-deprecated':
+                        $settings->showDeprecated = true;
+                        break;
+
+                    case '--syntax-error-callback':
+                        $settings->syntaxErrorCallbackFile = $arguments->getNext();
+                        break;
+
+                    default:
+                        throw new InvalidArgumentException($argument);
+                }
+            }
+        }
+
+        return $settings;
+    }
+
+    /**
+     * @return array
+     */
+    public static function getPathsFromStdIn()
+    {
+        $content = stream_get_contents(STDIN);
+
+        if (empty($content)) {
+            return array();
+        }
+
+        $lines = explode("\n", rtrim($content));
+        return array_map('rtrim', $lines);
+    }
+}
+
+class ArrayIterator extends \ArrayIterator
+{
+    public function getNext()
+    {
+        $this->next();
+        return $this->current();
+    }
+}
diff --git a/php-parallel-lint/php-parallel-lint/src/exceptions.php b/php-parallel-lint/php-parallel-lint/src/exceptions.php
new file mode 100644
index 0000000..3893b83
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/src/exceptions.php
@@ -0,0 +1,90 @@
+<?php
+namespace JakubOnderka\PhpParallelLint;
+
+class Exception extends \Exception implements \JsonSerializable
+{
+    public function jsonSerialize()
+    {
+        return array(
+            'type' => get_class($this),
+            'message' => $this->getMessage(),
+            'code' => $this->getCode(),
+        );
+    }
+}
+
+class RunTimeException extends Exception
+{
+
+}
+
+class InvalidArgumentException extends Exception
+{
+    protected $argument;
+
+    public function __construct($argument)
+    {
+        $this->argument = $argument;
+        $this->message = "Invalid argument $argument";
+    }
+
+    public function getArgument()
+    {
+        return $this->argument;
+    }
+}
+
+class NotExistsPathException extends Exception
+{
+    protected $path;
+
+    public function __construct($path)
+    {
+        $this->path = $path;
+        $this->message = "Path '$path' not found";
+    }
+
+    public function getPath()
+    {
+        return $this->path;
+    }
+}
+
+class NotExistsClassException extends Exception
+{
+    protected $className;
+    protected $fileName;
+
+    public function __construct($className, $fileName)
+    {
+        $this->className = $className;
+        $this->fileName = $fileName;
+        $this->message = "Class with name '$className' does not exists in file '$fileName'";
+    }
+
+    public function getClassName()
+    {
+        return $this->className;
+    }
+
+    public function getFileName()
+    {
+        return $this->fileName;
+    }
+}
+
+class NotImplementCallbackException extends Exception
+{
+    protected $className;
+
+    public function __construct($className)
+    {
+        $this->className = $className;
+        $this->message = "Class '$className' does not implement SyntaxErrorCallback interface.";
+    }
+
+    public function getClassName()
+    {
+        return $this->className;
+    }
+}
diff --git a/php-parallel-lint/php-parallel-lint/src/polyfill.php b/php-parallel-lint/php-parallel-lint/src/polyfill.php
new file mode 100644
index 0000000..79b3049
--- /dev/null
+++ b/php-parallel-lint/php-parallel-lint/src/polyfill.php
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * Polyfill for PHP < 5.4
+ */
+if (!interface_exists('JsonSerializable', false)) {
+    interface JsonSerializable
+    {
+        /**
+         * @param void
+         * @return mixed
+         */
+        function jsonSerialize();
+    }
+}
diff --git a/phplang/scope-exit/.gitignore b/phplang/scope-exit/.gitignore
new file mode 100644
index 0000000..d8a7996
--- /dev/null
+++ b/phplang/scope-exit/.gitignore
@@ -0,0 +1,2 @@
+composer.lock
+vendor/
diff --git a/phplang/scope-exit/README.md b/phplang/scope-exit/README.md
new file mode 100644
index 0000000..5d495c2
--- /dev/null
+++ b/phplang/scope-exit/README.md
@@ -0,0 +1,18 @@
+# ScopeExit
+
+This simple class provides an implementation of C++'s `SCOPE_EXIT`, or GoLang's `defer`.
+
+To use, assign an instance of this object to a local variable.  When that variable falls out of scope (or is explicitly `unset`, the callback passed to the constructor will be invoked.  This is useful, for example, to aid cleanup at the end of a funciton.
+
+```
+function f(&$x) {
+  $x = 1;
+  $_ = new \PhpLang\ScopeExit(function() use (&$x) { $x = 2; });
+  // $x is still 1 at this point.
+  return 42;
+  // After the return, the local scope is cleaned up, the closure is invoked, and it's set to 2
+}
+
+f($a);
+var_dump($a); // int(2)
+```
diff --git a/phplang/scope-exit/composer.json b/phplang/scope-exit/composer.json
new file mode 100644
index 0000000..3b3e223
--- /dev/null
+++ b/phplang/scope-exit/composer.json
@@ -0,0 +1,28 @@
+{
+  "name": "phplang/scope-exit",
+  "description": "Emulation of SCOPE_EXIT construct from C++",
+  "type": "library",
+  "keywords": [
+    "cleanup",
+    "scope",
+    "exit"
+  ],
+  "homepage": "https://github.com/phplang/scope-exit",
+  "license": "BSD",
+  "authors": [
+    {
+      "name": "Sara Golemon",
+      "email": "pollita@php.net",
+      "homepage": "https://twitter.com/SaraMG",
+      "role": "Developer"
+    }
+  ],
+  "autoload": {
+    "psr-4": {
+      "PhpLang\\": "src/"
+    }
+  },
+  "require-dev": {
+    "phpunit/phpunit": "*"
+  }
+}
diff --git a/phplang/scope-exit/src/ScopeExit.php b/phplang/scope-exit/src/ScopeExit.php
new file mode 100644
index 0000000..e776aba
--- /dev/null
+++ b/phplang/scope-exit/src/ScopeExit.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace PhpLang;
+
+/**
+ * Emulate C++'s SCOPE_EXIT { expr; }; construct by means of a
+ * short-lived object which takes a callable as its ctor arg
+ * and invokes it on destruction (i.e. when it drops out of scope).
+ *
+ * Note that if you retain a copy of this object, such as in
+ * another object's property.  It's scope will not fully exit
+ * until that object dies.
+ *
+ * This allows ScopeExit to apply to more than just lexical scopes.
+ */
+class ScopeExit {
+    /** @var Callable Method to invoke on object destruction */
+    private $onexit = null;
+
+    /**
+     * Initialize object from Callable
+     *
+     * When this object drops out of scope,
+     * either by leaving the scope, or by explicit unset()
+     * The $onexit property is invoked.
+     *
+     * @param Callable $onexit - Invocable callback with zero arg
+     */
+    public function __construct(Callable $onexit) {
+        $this->onexit = $onexit;
+    }
+
+    /**
+     * Invoke the callback since the object is falling out of scope
+     */
+    public function __destruct() {
+        $cb = $this->onexit;
+        $cb();
+    }
+}
diff --git a/phplang/scope-exit/tests/BasicTest.php b/phplang/scope-exit/tests/BasicTest.php
new file mode 100644
index 0000000..b0f320b
--- /dev/null
+++ b/phplang/scope-exit/tests/BasicTest.php
@@ -0,0 +1,29 @@
+<?php
+
+require __DIR__ . '/../vendor/autoload.php';
+
+use PhpLang\ScopeExit;
+
+class ScopeExitBasicTest extends PHPUnit_Framework_TestCase {
+
+    private function fCall(&$arg) {
+        $arg = 1;
+        $_ = new ScopeExit(function() use (&$arg) { $arg = 2; });
+        $this->assertEquals(1, $arg);
+        return;
+    }
+
+    public function testFCall() {
+        $x = 0;
+        $this->fCall($x);
+        $this->assertEquals(2, $x);
+    }
+
+    public function testUnset() {
+      $x = 0;
+      $_ = new ScopeExit(function() use (&$x) { $x = 123; });
+      $this->assertEquals(0, $x);
+      unset($_);
+      $this->assertEquals(123, $x);
+    }
+}
diff --git a/swaggest/json-diff/.gitlab-ci.yml b/swaggest/json-diff/.gitlab-ci.yml
new file mode 100644
index 0000000..35b70e3
--- /dev/null
+++ b/swaggest/json-diff/.gitlab-ci.yml
@@ -0,0 +1,39 @@
+before_script:
+- apt-get update -yqq
+- apt-get install git unzip -yqq
+- curl https://composer.github.io/installer.sig | tr -d '\n' > installer.sig
+- php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
+- php -r "if (hash_file('SHA384', 'composer-setup.php') === file_get_contents('installer.sig')) { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
+- php composer-setup.php
+- php -r "unlink('composer-setup.php'); unlink('installer.sig');"
+- php composer.phar install --prefer-dist --no-ansi --no-interaction --no-progress
+
+test:5.6:
+  image: php:5.6
+  script:
+  - pecl install xdebug-2.5.5
+  - docker-php-ext-enable xdebug
+  - vendor/bin/phpunit --configuration phpunit.xml -v --coverage-text --colors=never --stderr
+
+test:7.0:
+  image: php:7.0
+  script:
+  - pecl install xdebug
+  - docker-php-ext-enable xdebug
+  - vendor/bin/phpunit --configuration phpunit.xml -v --coverage-text --colors=never --stderr
+
+test:7.1:
+  image: php:7.1
+  script:
+  - pecl install xdebug
+  - docker-php-ext-enable xdebug
+  - vendor/bin/phpunit --configuration phpunit.xml -v --coverage-text --colors=never --stderr
+
+test:7.2:
+  image: php:7.2
+  script:
+  - pecl install xdebug
+  - docker-php-ext-enable xdebug
+  - vendor/bin/phpunit --configuration phpunit.xml -v --coverage-text --colors=never --stderr
+  - curl https://github.com/phpstan/phpstan/releases/download/0.9.2/phpstan.phar -sLo ./phpstan.phar
+  - php phpstan.phar analyze -l 7 ./src
diff --git a/swaggest/json-diff/CHANGELOG.md b/swaggest/json-diff/CHANGELOG.md
new file mode 100644
index 0000000..a873ae8
--- /dev/null
+++ b/swaggest/json-diff/CHANGELOG.md
@@ -0,0 +1,66 @@
+# Changelog
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [3.8.1] - 2020-09-25
+
+### Fixed
+- Rearrangement of equal array items is corrupting data by redundant replaces.
+
+## [3.8.0] - 2020-09-25
+
+### Added
+- Rearrangement of equal items for non-homogeneous arrays with `JsonDiff::REARRANGE_ARRAYS` option.
+
+## [3.7.5] - 2020-05-26
+
+### Fixed
+- Accidental array to associative array conversion ([#31](https://github.com/swaggest/json-diff/issues/31)).
+
+## [3.7.4] - 2020-01-26
+
+### Fixed
+- PHP version check for empty property name support.
+
+## [3.7.3] - 2020-01-24
+
+### Fixed
+- Merge patch was not replacing partially different arrays.
+
+## [3.7.2] - 2019-10-23
+
+### Added
+- Change log.
+
+### Fixed
+- Few irrelevant files not mentioned in `.gitattributes`.
+
+## [3.7.1] - 2019-09-26
+
+### Added
+- Benchmarks to CI.
+
+### Fixed
+- Unstable array rearrange order.
+
+## [3.7.0] - 2019-04-25
+
+### Added
+- `getModifiedDiff()` and `COLLECT_MODIFIED_DIFF` option to return paths with original and new values.
+
+## [3.6.0] - 2019-04-24
+
+### Added
+- Compatibility option to `TOLERATE_ASSOCIATIVE_ARRAYS` that mimic JSON objects.
+
+[3.8.1]: https://github.com/swaggest/json-diff/compare/v3.8.0...v3.8.1
+[3.8.0]: https://github.com/swaggest/json-diff/compare/v3.7.5...v3.8.0
+[3.7.5]: https://github.com/swaggest/json-diff/compare/v3.7.4...v3.7.5
+[3.7.4]: https://github.com/swaggest/json-diff/compare/v3.7.3...v3.7.4
+[3.7.3]: https://github.com/swaggest/json-diff/compare/v3.7.2...v3.7.3
+[3.7.2]: https://github.com/swaggest/json-diff/compare/v3.7.1...v3.7.2
+[3.7.1]: https://github.com/swaggest/json-diff/compare/v3.7.0...v3.7.1
+[3.7.0]: https://github.com/swaggest/json-diff/compare/v3.6.0...v3.7.0
+[3.6.0]: https://github.com/swaggest/json-diff/compare/v3.5.1...v3.6.0
diff --git a/swaggest/json-diff/LICENSE b/swaggest/json-diff/LICENSE
new file mode 100644
index 0000000..f23a483
--- /dev/null
+++ b/swaggest/json-diff/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 swaggest
+
+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/swaggest/json-diff/README.md b/swaggest/json-diff/README.md
new file mode 100644
index 0000000..2111c69
--- /dev/null
+++ b/swaggest/json-diff/README.md
@@ -0,0 +1,285 @@
+# JSON diff/rearrange/patch/pointer library for PHP
+
+A PHP implementation for finding unordered diff between two `JSON` documents.
+
+[![Build Status](https://travis-ci.org/swaggest/json-diff.svg?branch=master)](https://travis-ci.org/swaggest/json-diff)
+[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/swaggest/json-diff/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/swaggest/json-diff/?branch=master)
+[![Code Climate](https://codeclimate.com/github/swaggest/json-diff/badges/gpa.svg)](https://codeclimate.com/github/swaggest/json-diff)
+[![Code Coverage](https://scrutinizer-ci.com/g/swaggest/json-diff/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/swaggest/json-diff/code-structure/master/code-coverage)
+
+## Purpose
+
+ * To simplify changes review between two `JSON` files you can use a standard `diff` tool on rearranged pretty-printed `JSON`.
+ * To detect breaking changes by analyzing removals and changes from original `JSON`.
+ * To keep original order of object sets (for example `swagger.json` [parameters](https://swagger.io/docs/specification/describing-parameters/) list).
+ * To [make](#getpatch) and [apply](#jsonpatch) JSON Patches, specified in [RFC 6902](http://tools.ietf.org/html/rfc6902) from the IETF.
+ * To [make](#getmergepatch) and [apply](#jsonmergepatch) JSON Merge Patches, specified in [RFC 7386](https://tools.ietf.org/html/rfc7386) from the IETF.
+ * To retrieve and modify data by [JSON Pointer](http://tools.ietf.org/html/rfc6901).
+ * To recursively replace by JSON value.
+
+## Installation
+
+### Library
+
+```bash
+git clone https://github.com/swaggest/json-diff.git
+```
+
+### Composer
+
+[Install PHP Composer](https://getcomposer.org/doc/00-intro.md)
+
+```bash
+composer require swaggest/json-diff
+```
+
+## Library usage
+
+### `JsonDiff`
+
+Create `JsonDiff` object from two values (`original` and `new`).
+
+```php
+$r = new JsonDiff(json_decode($originalJson), json_decode($newJson));
+```
+
+On construction `JsonDiff` will build `rearranged` value of `new` recursively keeping `original` keys order where possible. 
+Keys that are missing in `original` will be appended to the end of `rearranged` value in same order they had in `new` value.
+
+If two values are arrays of objects, `JsonDiff` will try to find a common unique field in those objects and use it as criteria for rearranging. 
+You can enable this behaviour with `JsonDiff::REARRANGE_ARRAYS` option:
+```php
+$r = new JsonDiff(
+    json_decode($originalJson), 
+    json_decode($newJson),
+    JsonDiff::REARRANGE_ARRAYS
+);
+```
+
+Available options:
+ * `REARRANGE_ARRAYS` is an option to enable [arrays rearrangement](#arrays-rearrangement) to minimize the difference.
+ * `STOP_ON_DIFF` is an option to improve performance by stopping comparison when a difference is found.
+ * `JSON_URI_FRAGMENT_ID` is an option to use URI Fragment Identifier Representation (example: "#/c%25d"). If not set default JSON String Representation (example: "/c%d").
+ * `SKIP_JSON_PATCH` is an option to improve performance by not building JsonPatch for this diff.
+ * `SKIP_JSON_MERGE_PATCH` is an option to improve performance by not building JSON Merge Patch value for this diff.
+ * `TOLERATE_ASSOCIATIVE_ARRAYS` is an option to allow associative arrays to mimic JSON objects (not recommended).
+ * `COLLECT_MODIFIED_DIFF` is an option to enable [getModifiedDiff](#getmodifieddiff).
+
+Options can be combined, e.g. `JsonDiff::REARRANGE_ARRAYS + JsonDiff::STOP_ON_DIFF`.
+
+#### `getDiffCnt`
+Returns total number of differences
+
+#### `getPatch`
+Returns [`JsonPatch`](#jsonpatch) of difference
+
+#### `getMergePatch`
+Returns [JSON Merge Patch](https://tools.ietf.org/html/rfc7386) value of difference
+
+#### `getRearranged`
+Returns new value, rearranged with original order.
+
+#### `getRemoved`
+Returns removals as partial value of original.
+
+#### `getRemovedPaths`
+Returns list of `JSON` paths that were removed from original.
+
+#### `getRemovedCnt`
+Returns number of removals.
+
+#### `getAdded`
+Returns additions as partial value of new.
+
+#### `getAddedPaths`
+Returns list of `JSON` paths that were added to new.
+
+#### `getAddedCnt`
+Returns number of additions.
+
+#### `getModifiedOriginal`
+Returns modifications as partial value of original.
+
+#### `getModifiedNew`
+Returns modifications as partial value of new.
+
+#### `getModifiedDiff`
+Returns list of [`ModifiedPathDiff`](src/ModifiedPathDiff.php) containing paths with original and new values.
+
+Not collected by default, requires `JsonDiff::COLLECT_MODIFIED_DIFF` option.
+
+#### `getModifiedPaths`
+Returns list of `JSON` paths that were modified from original to new.
+
+#### `getModifiedCnt`
+Returns number of modifications.
+
+### `JsonPatch`
+
+#### `import`
+Creates `JsonPatch` instance from `JSON`-decoded data.
+
+#### `export`
+Creates patch data from `JsonPatch` object.
+
+#### `op`
+Adds operation to `JsonPatch`.
+
+#### `apply`
+Applies patch to `JSON`-decoded data.
+
+#### `setFlags`
+Alters default behavior.
+
+Available flags:
+
+* `JsonPatch::STRICT_MODE` Disallow converting empty array to object for key creation.
+* `JsonPatch::TOLERATE_ASSOCIATIVE_ARRAYS` Allow associative arrays to mimic JSON objects (not recommended).
+
+### `JsonPointer`
+
+#### `escapeSegment`
+Escapes path segment.
+
+#### `splitPath`
+Creates array of unescaped segments from `JSON Pointer` string.
+
+#### `buildPath`
+Creates `JSON Pointer` string from array of unescaped segments.
+
+#### `add`
+Adds value to data at path specified by segments.
+
+#### `get`
+Gets value from data at path specified by segments.
+
+#### `getByPointer`
+Gets value from data at path specified `JSON Pointer` string.
+
+#### `remove`
+Removes value from data at path specified by segments.
+
+### `JsonMergePatch`
+
+#### `apply`
+Applies patch to `JSON`-decoded data.
+
+### `JsonValueReplace`
+
+#### `process`
+Recursively replaces all nodes equal to `search` value with `replace` value.
+
+## Example
+
+```php
+$originalJson = <<<'JSON'
+{
+    "key1": [4, 1, 2, 3],
+    "key2": 2,
+    "key3": {
+        "sub0": 0,
+        "sub1": "a",
+        "sub2": "b"
+    },
+    "key4": [
+        {"a":1, "b":true, "subs": [{"s":1}, {"s":2}, {"s":3}]}, {"a":2, "b":false}, {"a":3}
+    ]
+}
+JSON;
+
+$newJson = <<<'JSON'
+{
+    "key5": "wat",
+    "key1": [5, 1, 2, 3],
+    "key4": [
+        {"c":false, "a":2}, {"a":1, "b":true, "subs": [{"s":3, "add": true}, {"s":2}, {"s":1}]}, {"c":1, "a":3}
+    ],
+    "key3": {
+        "sub3": 0,
+        "sub2": false,
+        "sub1": "c"
+    }
+}
+JSON;
+
+$patchJson = <<<'JSON'
+[
+    {"value":4,"op":"test","path":"/key1/0"},
+    {"value":5,"op":"replace","path":"/key1/0"},
+    
+    {"op":"remove","path":"/key2"},
+    
+    {"op":"remove","path":"/key3/sub0"},
+    
+    {"value":"a","op":"test","path":"/key3/sub1"},
+    {"value":"c","op":"replace","path":"/key3/sub1"},
+    
+    {"value":"b","op":"test","path":"/key3/sub2"},
+    {"value":false,"op":"replace","path":"/key3/sub2"},
+    
+    {"value":0,"op":"add","path":"/key3/sub3"},
+
+    {"value":true,"op":"add","path":"/key4/0/subs/2/add"},
+    
+    {"op":"remove","path":"/key4/1/b"},
+    
+    {"value":false,"op":"add","path":"/key4/1/c"},
+    
+    {"value":1,"op":"add","path":"/key4/2/c"},
+    
+    {"value":"wat","op":"add","path":"/key5"}
+]
+JSON;
+
+$diff = new JsonDiff(json_decode($originalJson), json_decode($newJson), JsonDiff::REARRANGE_ARRAYS);
+$this->assertEquals(json_decode($patchJson), $diff->getPatch()->jsonSerialize());
+
+$original = json_decode($originalJson);
+$patch = JsonPatch::import(json_decode($patchJson));
+$patch->apply($original);
+$this->assertEquals($diff->getRearranged(), $original);
+```
+
+## PHP Classes as JSON objects
+
+Due to magical methods and other restrictions PHP classes can not be reliably mapped to/from JSON objects.
+There is support for objects of PHP classes in `JsonPointer` with limitations:
+* `null` is equal to non-existent
+
+## Arrays Rearrangement
+
+When `JsonDiff::REARRANGE_ARRAYS` option is enabled, array items are ordered to match the original array.
+
+If arrays contain homogenous objects, and those objects have a common property with unique values, array is
+ordered to match placement of items with same value of such property in the original array.
+
+Example:
+original
+```json
+[{"name": "Alex", "height": 180},{"name": "Joe", "height": 179},{"name": "Jane", "height": 165}]
+```
+vs new
+```json
+[{"name": "Joe", "height": 179},{"name": "Jane", "height": 168},{"name": "Alex", "height": 180}]
+```
+would produce a patch:
+```json
+[{"value":165,"op":"test","path":"/2/height"},{"value":168,"op":"replace","path":"/2/height"}]
+```
+
+If qualifying indexing property is not found, rearrangement is done based on items equality.
+
+Example:
+original
+```json
+{"data": [{"A": 1, "C": [1, 2, 3]}, {"B": 2}]}
+```
+vs new
+```json
+{"data": [{"B": 2}, {"A": 1, "C": [3, 2, 1]}]}
+```
+would produce no difference.
+
+## CLI tool
+
+Moved to [`swaggest/json-cli`](https://github.com/swaggest/json-cli)
\ No newline at end of file
diff --git a/swaggest/json-diff/composer.json b/swaggest/json-diff/composer.json
new file mode 100644
index 0000000..bb0ce49
--- /dev/null
+++ b/swaggest/json-diff/composer.json
@@ -0,0 +1,33 @@
+{
+  "name": "swaggest/json-diff",
+  "description": "JSON diff/rearrange/patch/pointer library for PHP",
+  "type": "library",
+  "license": "MIT",
+  "authors": [
+    {
+      "name": "Viacheslav Poturaev",
+      "email": "vearutop@gmail.com"
+    }
+  ],
+  "require": {
+    "ext-json": "*"
+  },
+  "require-dev": {
+    "phpunit/phpunit": "^4.8.23"
+  },
+  "autoload": {
+    "psr-4": {
+      "Swaggest\\JsonDiff\\": "src/"
+    }
+  },
+  "autoload-dev": {
+    "psr-4": {
+      "Swaggest\\JsonDiff\\Tests\\": "tests/src"
+    }
+  },
+  "config": {
+    "platform": {
+      "php": "5.4.45"
+    }
+  }
+}
diff --git a/swaggest/json-diff/src/Exception.php b/swaggest/json-diff/src/Exception.php
new file mode 100644
index 0000000..1af1431
--- /dev/null
+++ b/swaggest/json-diff/src/Exception.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Swaggest\JsonDiff;
+
+
+class Exception extends \Exception
+{
+    const EMPTY_PROPERTY_NAME_UNSUPPORTED = 1;
+}
\ No newline at end of file
diff --git a/swaggest/json-diff/src/JsonDiff.php b/swaggest/json-diff/src/JsonDiff.php
new file mode 100644
index 0000000..dfd3c90
--- /dev/null
+++ b/swaggest/json-diff/src/JsonDiff.php
@@ -0,0 +1,542 @@
+<?php
+
+namespace Swaggest\JsonDiff;
+
+use Swaggest\JsonDiff\JsonPatch\Add;
+use Swaggest\JsonDiff\JsonPatch\Remove;
+use Swaggest\JsonDiff\JsonPatch\Replace;
+use Swaggest\JsonDiff\JsonPatch\Test;
+
+class JsonDiff
+{
+    /**
+     * REARRANGE_ARRAYS is an option to enable arrays rearrangement to minimize the difference.
+     */
+    const REARRANGE_ARRAYS = 1;
+
+    /**
+     * STOP_ON_DIFF is an option to improve performance by stopping comparison when a difference is found.
+     */
+    const STOP_ON_DIFF = 2;
+
+    /**
+     * JSON_URI_FRAGMENT_ID is an option to use URI Fragment Identifier Representation (example: "#/c%25d").
+     * If not set default JSON String Representation (example: "/c%d").
+     */
+    const JSON_URI_FRAGMENT_ID = 4;
+
+    /**
+     * SKIP_JSON_PATCH is an option to improve performance by not building JsonPatch for this diff.
+     */
+    const SKIP_JSON_PATCH = 8;
+
+    /**
+     * SKIP_JSON_MERGE_PATCH is an option to improve performance by not building JSON Merge Patch value for this diff.
+     */
+    const SKIP_JSON_MERGE_PATCH = 16;
+
+    /**
+     * TOLERATE_ASSOCIATIVE_ARRAYS is an option to allow associative arrays to mimic JSON objects (not recommended)
+     */
+    const TOLERATE_ASSOCIATIVE_ARRAYS = 32;
+
+    /**
+     * COLLECT_MODIFIED_DIFF is an option to enable getModifiedDiff.
+     */
+    const COLLECT_MODIFIED_DIFF = 64;
+
+
+    private $options = 0;
+
+    /**
+     * @var mixed Merge patch container
+     */
+    private $merge;
+
+    private $added;
+    private $addedCnt = 0;
+    private $addedPaths = array();
+
+    private $removed;
+    private $removedCnt = 0;
+    private $removedPaths = array();
+
+    private $modifiedOriginal;
+    private $modifiedNew;
+    private $modifiedCnt = 0;
+    private $modifiedPaths = array();
+    /**
+     * @var ModifiedPathDiff[]
+     */
+    private $modifiedDiff = array();
+
+    private $path = '';
+    private $pathItems = array();
+
+    private $rearranged;
+
+    /** @var JsonPatch */
+    private $jsonPatch;
+
+    /** @var JsonHash */
+    private $jsonHashOriginal;
+
+    /** @var JsonHash */
+    private $jsonHashNew;
+
+    /**
+     * @param mixed $original
+     * @param mixed $new
+     * @param int $options
+     * @throws Exception
+     */
+    public function __construct($original, $new, $options = 0)
+    {
+        if (!($options & self::SKIP_JSON_PATCH)) {
+            $this->jsonPatch = new JsonPatch();
+        }
+
+        $this->options = $options;
+
+        if ($options & self::JSON_URI_FRAGMENT_ID) {
+            $this->path = '#';
+        }
+
+        $this->rearranged = $this->process($original, $new);
+        if (($new !== null) && $this->merge === null) {
+            $this->merge = new \stdClass();
+        }
+    }
+
+    /**
+     * Returns total number of differences
+     * @return int
+     */
+    public function getDiffCnt()
+    {
+        return $this->addedCnt + $this->modifiedCnt + $this->removedCnt;
+    }
+
+    /**
+     * Returns removals as partial value of original.
+     * @return mixed
+     */
+    public function getRemoved()
+    {
+        return $this->removed;
+    }
+
+    /**
+     * Returns list of `JSON` paths that were removed from original.
+     * @return array
+     */
+    public function getRemovedPaths()
+    {
+        return $this->removedPaths;
+    }
+
+    /**
+     * Returns number of removals.
+     * @return int
+     */
+    public function getRemovedCnt()
+    {
+        return $this->removedCnt;
+    }
+
+    /**
+     * Returns additions as partial value of new.
+     * @return mixed
+     */
+    public function getAdded()
+    {
+        return $this->added;
+    }
+
+    /**
+     * Returns number of additions.
+     * @return int
+     */
+    public function getAddedCnt()
+    {
+        return $this->addedCnt;
+    }
+
+    /**
+     * Returns list of `JSON` paths that were added to new.
+     * @return array
+     */
+    public function getAddedPaths()
+    {
+        return $this->addedPaths;
+    }
+
+    /**
+     * Returns changes as partial value of original.
+     * @return mixed
+     */
+    public function getModifiedOriginal()
+    {
+        return $this->modifiedOriginal;
+    }
+
+    /**
+     * Returns changes as partial value of new.
+     * @return mixed
+     */
+    public function getModifiedNew()
+    {
+        return $this->modifiedNew;
+    }
+
+    /**
+     * Returns number of changes.
+     * @return int
+     */
+    public function getModifiedCnt()
+    {
+        return $this->modifiedCnt;
+    }
+
+    /**
+     * Returns list of `JSON` paths that were changed from original to new.
+     * @return array
+     */
+    public function getModifiedPaths()
+    {
+        return $this->modifiedPaths;
+    }
+
+    /**
+     * Returns list of paths with original and new values.
+     * @return ModifiedPathDiff[]
+     */
+    public function getModifiedDiff()
+    {
+        return $this->modifiedDiff;
+    }
+
+    /**
+     * Returns new value, rearranged with original order.
+     * @return array|object
+     */
+    public function getRearranged()
+    {
+        return $this->rearranged;
+    }
+
+    /**
+     * Returns JsonPatch of difference
+     * @return JsonPatch
+     */
+    public function getPatch()
+    {
+        return $this->jsonPatch;
+    }
+
+    /**
+     * Returns JSON Merge Patch value of difference
+     */
+    public function getMergePatch()
+    {
+        return $this->merge;
+
+    }
+
+
+    /**
+     * @param mixed $original
+     * @param mixed $new
+     * @return array|null|object|\stdClass
+     * @throws Exception
+     */
+    private function process($original, $new)
+    {
+        $merge = !($this->options & self::SKIP_JSON_MERGE_PATCH);
+
+        if ($this->options & self::TOLERATE_ASSOCIATIVE_ARRAYS) {
+            if (is_array($original) && !empty($original) && !array_key_exists(0, $original)) {
+                $original = (object)$original;
+            }
+
+            if (is_array($new) && !empty($new) && !array_key_exists(0, $new)) {
+                $new = (object)$new;
+            }
+        }
+
+        if (
+            (!$original instanceof \stdClass && !is_array($original))
+            || (!$new instanceof \stdClass && !is_array($new))
+        ) {
+            if ($original !== $new) {
+                $this->modifiedCnt++;
+                if ($this->options & self::STOP_ON_DIFF) {
+                    return null;
+                }
+                $this->modifiedPaths [] = $this->path;
+
+                if ($this->jsonPatch !== null) {
+                    $this->jsonPatch->op(new Test($this->path, $original));
+                    $this->jsonPatch->op(new Replace($this->path, $new));
+                }
+
+                JsonPointer::add($this->modifiedOriginal, $this->pathItems, $original);
+                JsonPointer::add($this->modifiedNew, $this->pathItems, $new);
+
+                if ($merge) {
+                    JsonPointer::add($this->merge, $this->pathItems, $new, JsonPointer::RECURSIVE_KEY_CREATION);
+                }
+
+                if ($this->options & self::COLLECT_MODIFIED_DIFF) {
+                    $this->modifiedDiff[] = new ModifiedPathDiff($this->path, $original, $new);
+                }
+            }
+            return $new;
+        }
+
+        if (
+            ($this->options & self::REARRANGE_ARRAYS)
+            && is_array($original) && is_array($new)
+        ) {
+            $new = $this->rearrangeArray($original, $new);
+        }
+
+        $newArray = $new instanceof \stdClass ? get_object_vars($new) : $new;
+        $newOrdered = array();
+
+        $originalKeys = $original instanceof \stdClass ? get_object_vars($original) : $original;
+        $isArray = is_array($original);
+        $removedOffset = 0;
+
+        if ($merge && is_array($new) && !is_array($original)) {
+            $merge = false;
+            JsonPointer::add($this->merge, $this->pathItems, $new);
+        } elseif ($merge && $new instanceof \stdClass && !$original instanceof \stdClass) {
+            $merge = false;
+            JsonPointer::add($this->merge, $this->pathItems, $new);
+        }
+
+        $isUriFragment = (bool)($this->options & self::JSON_URI_FRAGMENT_ID);
+        $diffCnt = $this->addedCnt + $this->modifiedCnt + $this->removedCnt;
+        foreach ($originalKeys as $key => $originalValue) {
+            if ($this->options & self::STOP_ON_DIFF) {
+                if ($this->modifiedCnt || $this->addedCnt || $this->removedCnt) {
+                    return null;
+                }
+            }
+
+            $path = $this->path;
+            $pathItems = $this->pathItems;
+            $actualKey = $key;
+            if ($isArray && is_int($actualKey)) {
+                $actualKey -= $removedOffset;
+            }
+            $this->path .= '/' . JsonPointer::escapeSegment((string)$actualKey, $isUriFragment);
+            $this->pathItems[] = $actualKey;
+
+            if (array_key_exists($key, $newArray)) {
+                $newOrdered[$key] = $this->process($originalValue, $newArray[$key]);
+                unset($newArray[$key]);
+            } else {
+                $this->removedCnt++;
+                if ($this->options & self::STOP_ON_DIFF) {
+                    return null;
+                }
+                $this->removedPaths [] = $this->path;
+                if ($isArray) {
+                    $removedOffset++;
+                }
+
+                if ($this->jsonPatch !== null) {
+                    $this->jsonPatch->op(new Remove($this->path));
+                }
+
+                JsonPointer::add($this->removed, $this->pathItems, $originalValue);
+                if ($merge) {
+                    JsonPointer::add($this->merge, $this->pathItems, null);
+                }
+
+            }
+            $this->path = $path;
+            $this->pathItems = $pathItems;
+        }
+
+        if ($merge && $isArray && $this->addedCnt + $this->modifiedCnt + $this->removedCnt > $diffCnt) {
+            JsonPointer::add($this->merge, $this->pathItems, $new);
+        }
+
+        // additions
+        foreach ($newArray as $key => $value) {
+            $this->addedCnt++;
+            if ($this->options & self::STOP_ON_DIFF) {
+                return null;
+            }
+            $newOrdered[$key] = $value;
+            $path = $this->path . '/' . JsonPointer::escapeSegment($key, $isUriFragment);
+            $pathItems = $this->pathItems;
+            $pathItems[] = $key;
+            JsonPointer::add($this->added, $pathItems, $value);
+            if ($merge) {
+                JsonPointer::add($this->merge, $pathItems, $value);
+            }
+
+            $this->addedPaths [] = $path;
+
+            if ($this->jsonPatch !== null) {
+                $this->jsonPatch->op(new Add($path, $value));
+            }
+
+        }
+
+        return is_array($new) ? $newOrdered : (object)$newOrdered;
+    }
+
+    /**
+     * @param array $original
+     * @param array $new
+     * @return array
+     */
+    private function rearrangeArray(array $original, array $new)
+    {
+        $first = reset($original);
+        if (!$first instanceof \stdClass) {
+            return $this->rearrangeEqualItems($original, $new);
+        }
+
+        $uniqueKey = false;
+        $uniqueIdx = array();
+
+        // find unique key for all items
+        /** @var mixed[string]  $f */
+        $f = get_object_vars($first);
+        foreach ($f as $key => $value) {
+            if (is_array($value) || $value instanceof \stdClass) {
+                continue;
+            }
+
+            $keyIsUnique = true;
+            $uniqueIdx = array();
+            foreach ($original as $idx => $item) {
+                if (!$item instanceof \stdClass) {
+                    return $new;
+                }
+                if (!isset($item->$key)) {
+                    $keyIsUnique = false;
+                    break;
+                }
+                $value = $item->$key;
+                if ($value instanceof \stdClass || is_array($value)) {
+                    $keyIsUnique = false;
+                    break;
+                }
+
+                if (isset($uniqueIdx[$value])) {
+                    $keyIsUnique = false;
+                    break;
+                }
+                $uniqueIdx[$value] = $idx;
+            }
+
+            if ($keyIsUnique) {
+                $uniqueKey = $key;
+                break;
+            }
+        }
+
+        if (!$uniqueKey) {
+            return $this->rearrangeEqualItems($original, $new);
+        }
+
+        $newRearranged = [];
+        $changedItems = [];
+
+        foreach ($new as $item) {
+            if (!$item instanceof \stdClass) {
+                return $new;
+            }
+
+            if (!property_exists($item, $uniqueKey)) {
+                return $new;
+            }
+
+            $value = $item->$uniqueKey;
+
+            if ($value instanceof \stdClass || is_array($value)) {
+                return $new;
+            }
+
+
+            if (isset($uniqueIdx[$value])) {
+                $idx = $uniqueIdx[$value];
+                // Abandon rearrangement if key is not unique in new array.
+                if (isset($newRearranged[$idx])) {
+                    return $new;
+                }
+
+                $newRearranged[$idx] = $item;
+            } else {
+                $changedItems[] = $item;
+            }
+
+            $newIdx[$value] = $item;
+        }
+
+        $idx = 0;
+        foreach ($changedItems as $item) {
+            while (array_key_exists($idx, $newRearranged)) {
+                $idx++;
+            }
+            $newRearranged[$idx] = $item;
+        }
+
+        ksort($newRearranged);
+        $newRearranged = array_values($newRearranged);
+        return $newRearranged;
+    }
+
+    private function rearrangeEqualItems(array $original, array $new)
+    {
+        if ($this->jsonHashOriginal === null) {
+            $this->jsonHashOriginal = new JsonHash($this->options);
+            $this->jsonHashNew = new JsonHash($this->options);
+        }
+
+        $origIdx = [];
+        foreach ($original as $i => $item) {
+            $hash = $this->jsonHashOriginal->xorHash($item);
+            $origIdx[$hash][] = $i;
+        }
+
+        $newIdx = [];
+        foreach ($new as $i => $item) {
+            $hash = $this->jsonHashNew->xorHash($item);
+            $newIdx[$i] = $hash;
+        }
+
+        $newRearranged = [];
+        $changedItems = [];
+        foreach ($newIdx as $i => $hash) {
+            if (!empty($origIdx[$hash])) {
+                $j = array_shift($origIdx[$hash]);
+
+                $newRearranged[$j] = $new[$i];
+            } else {
+                $changedItems []= $new[$i];
+            }
+
+        }
+
+        $idx = 0;
+        foreach ($changedItems as $item) {
+            while (array_key_exists($idx, $newRearranged)) {
+                $idx++;
+            }
+            $newRearranged[$idx] = $item;
+        }
+
+        ksort($newRearranged);
+        $newRearranged = array_values($newRearranged);
+
+        return $newRearranged;
+    }
+}
\ No newline at end of file
diff --git a/swaggest/json-diff/src/JsonHash.php b/swaggest/json-diff/src/JsonHash.php
new file mode 100644
index 0000000..4b03e86
--- /dev/null
+++ b/swaggest/json-diff/src/JsonHash.php
@@ -0,0 +1,76 @@
+<?php
+
+namespace Swaggest\JsonDiff;
+
+class JsonHash
+{
+    private $options = 0;
+
+    public function __construct($options = 0)
+    {
+        $this->options = $options;
+    }
+
+    /**
+     * @param mixed $data
+     * @param string $path
+     * @return string
+     */
+    public function xorHash($data, $path = '')
+    {
+        $xorHash = '';
+
+        if (!$data instanceof \stdClass && !is_array($data)) {
+            $s = $path . (string)$data;
+            if (strlen($xorHash) < strlen($s)) {
+                $xorHash = str_pad($xorHash, strlen($s));
+            }
+            $xorHash ^= $s;
+
+            return $xorHash;
+        }
+
+        if ($this->options & JsonDiff::TOLERATE_ASSOCIATIVE_ARRAYS) {
+            if (is_array($data) && !empty($data) && !array_key_exists(0, $data)) {
+                $data = (object)$data;
+            }
+        }
+
+        if (is_array($data)) {
+            if ($this->options & JsonDiff::REARRANGE_ARRAYS) {
+                foreach ($data as $key => $item) {
+                    $itemPath = $path . '/' . $key;
+                    $itemHash = $path . $this->xorHash($item, $itemPath);
+                    if (strlen($xorHash) < strlen($itemHash)) {
+                        $xorHash = str_pad($xorHash, strlen($itemHash));
+                    }
+                    $xorHash ^= $itemHash;
+                }
+            } else {
+                foreach ($data as $key => $item) {
+                    $itemPath = $path . '/' . $key;
+                    $itemHash = md5($itemPath . $this->xorHash($item, $itemPath), true);
+                    if (strlen($xorHash) < strlen($itemHash)) {
+                        $xorHash = str_pad($xorHash, strlen($itemHash));
+                    }
+                    $xorHash ^= $itemHash;
+                }
+            }
+
+            return $xorHash;
+        }
+
+        $dataKeys = get_object_vars($data);
+        foreach ($dataKeys as $key => $value) {
+            $propertyPath = $path . '/' .
+                JsonPointer::escapeSegment($key, (bool)($this->options & JsonDiff::JSON_URI_FRAGMENT_ID));
+            $propertyHash = $propertyPath . $this->xorHash($value, $propertyPath);
+            if (strlen($xorHash) < strlen($propertyHash)) {
+                $xorHash = str_pad($xorHash, strlen($propertyHash));
+            }
+            $xorHash ^= $propertyHash;
+        }
+
+        return $xorHash;
+    }
+}
\ No newline at end of file
diff --git a/swaggest/json-diff/src/JsonMergePatch.php b/swaggest/json-diff/src/JsonMergePatch.php
new file mode 100644
index 0000000..9b49543
--- /dev/null
+++ b/swaggest/json-diff/src/JsonMergePatch.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Swaggest\JsonDiff;
+
+
+class JsonMergePatch
+{
+    public static function apply(&$original, $patch)
+    {
+        if (null === $patch) {
+            $original = null;
+        } elseif (is_object($patch)) {
+            foreach (get_object_vars($patch) as $key => $val) {
+                if ($val === null) {
+                    unset($original->$key);
+                } else {
+                    if (!is_object($original)) {
+                        $original = new \stdClass();
+                    }
+                    $branch = &$original->$key;
+                    if (null === $branch) {
+                        $branch = new \stdClass();
+                    }
+                    self::apply($branch, $val);
+                }
+            }
+        } else {
+            $original = $patch;
+        }
+    }
+}
\ No newline at end of file
diff --git a/swaggest/json-diff/src/JsonPatch.php b/swaggest/json-diff/src/JsonPatch.php
new file mode 100644
index 0000000..eac3510
--- /dev/null
+++ b/swaggest/json-diff/src/JsonPatch.php
@@ -0,0 +1,187 @@
+<?php
+
+namespace Swaggest\JsonDiff;
+
+use Swaggest\JsonDiff\JsonPatch\Add;
+use Swaggest\JsonDiff\JsonPatch\Copy;
+use Swaggest\JsonDiff\JsonPatch\Move;
+use Swaggest\JsonDiff\JsonPatch\OpPath;
+use Swaggest\JsonDiff\JsonPatch\OpPathFrom;
+use Swaggest\JsonDiff\JsonPatch\OpPathValue;
+use Swaggest\JsonDiff\JsonPatch\Remove;
+use Swaggest\JsonDiff\JsonPatch\Replace;
+use Swaggest\JsonDiff\JsonPatch\Test;
+
+/**
+ * JSON Patch is specified in [RFC 6902](http://tools.ietf.org/html/rfc6902) from the IETF.
+ *
+ * Class JsonPatch
+ */
+class JsonPatch implements \JsonSerializable
+{
+    /**
+     * Disallow converting empty array to object for key creation
+     * @see JsonPointer::STRICT_MODE
+     */
+    const STRICT_MODE = 2;
+
+    /**
+     * Allow associative arrays to mimic JSON objects (not recommended)
+     */
+    const TOLERATE_ASSOCIATIVE_ARRAYS = 8;
+
+
+    private $flags = 0;
+
+    /**
+     * @param int $options
+     * @return $this
+     */
+    public function setFlags($options)
+    {
+        $this->flags = $options;
+        return $this;
+    }
+
+    /** @var OpPath[] */
+    private $operations = array();
+
+    /**
+     * @param array $data
+     * @return JsonPatch
+     * @throws Exception
+     */
+    public static function import(array $data)
+    {
+        $result = new JsonPatch();
+        foreach ($data as $operation) {
+            /** @var OpPath|OpPathValue|OpPathFrom $operation */
+            if (is_array($operation)) {
+                $operation = (object)$operation;
+            }
+
+            if (!isset($operation->op)) {
+                throw new Exception('Missing "op" in operation data');
+            }
+            if (!isset($operation->path)) {
+                throw new Exception('Missing "path" in operation data');
+            }
+
+            $op = null;
+            switch ($operation->op) {
+                case Add::OP:
+                    $op = new Add();
+                    break;
+                case Copy::OP:
+                    $op = new Copy();
+                    break;
+                case Move::OP:
+                    $op = new Move();
+                    break;
+                case Remove::OP:
+                    $op = new Remove();
+                    break;
+                case Replace::OP:
+                    $op = new Replace();
+                    break;
+                case Test::OP:
+                    $op = new Test();
+                    break;
+                default:
+                    throw new Exception('Unknown "op": ' . $operation->op);
+            }
+            $op->path = $operation->path;
+            if ($op instanceof OpPathValue) {
+                if (property_exists($operation, 'value')) {
+                    $op->value = $operation->value;
+                } else {
+                    throw new Exception('Missing "value" in operation data');
+                }
+            } elseif ($op instanceof OpPathFrom) {
+                if (!isset($operation->from)) {
+                    throw new Exception('Missing "from" in operation data');
+                }
+                $op->from = $operation->from;
+            }
+            $result->operations[] = $op;
+        }
+        return $result;
+    }
+
+    public static function export(JsonPatch $patch)
+    {
+        $result = array();
+        foreach ($patch->operations as $operation) {
+            $result[] = (object)(array)$operation;
+        }
+
+        return $result;
+    }
+
+    public function op(OpPath $op)
+    {
+        $this->operations[] = $op;
+        return $this;
+    }
+
+    public function jsonSerialize()
+    {
+        return self::export($this);
+    }
+
+    /**
+     * @param mixed $original
+     * @param bool $stopOnError
+     * @return Exception[] array of errors
+     * @throws Exception
+     */
+    public function apply(&$original, $stopOnError = true)
+    {
+        $errors = array();
+        foreach ($this->operations as $operation) {
+            try {
+                $pathItems = JsonPointer::splitPath($operation->path);
+                switch (true) {
+                    case $operation instanceof Add:
+                        JsonPointer::add($original, $pathItems, $operation->value, $this->flags);
+                        break;
+                    case $operation instanceof Copy:
+                        $fromItems = JsonPointer::splitPath($operation->from);
+                        $value = JsonPointer::get($original, $fromItems);
+                        JsonPointer::add($original, $pathItems, $value, $this->flags);
+                        break;
+                    case $operation instanceof Move:
+                        $fromItems = JsonPointer::splitPath($operation->from);
+                        $value = JsonPointer::get($original, $fromItems);
+                        JsonPointer::remove($original, $fromItems, $this->flags);
+                        JsonPointer::add($original, $pathItems, $value, $this->flags);
+                        break;
+                    case $operation instanceof Remove:
+                        JsonPointer::remove($original, $pathItems, $this->flags);
+                        break;
+                    case $operation instanceof Replace:
+                        JsonPointer::get($original, $pathItems);
+                        JsonPointer::remove($original, $pathItems, $this->flags);
+                        JsonPointer::add($original, $pathItems, $operation->value, $this->flags);
+                        break;
+                    case $operation instanceof Test:
+                        $value = JsonPointer::get($original, $pathItems);
+                        $diff = new JsonDiff($operation->value, $value,
+                            JsonDiff::STOP_ON_DIFF);
+                        if ($diff->getDiffCnt() !== 0) {
+                            throw new Exception('Test operation ' . json_encode($operation, JSON_UNESCAPED_SLASHES)
+                                . ' failed: ' . json_encode($value));
+                        }
+                        break;
+                }
+            } catch (Exception $exception) {
+                if ($stopOnError) {
+                    throw $exception;
+                } else {
+                    $errors[] = $exception;
+                }
+            }
+        }
+        return $errors;
+    }
+}
\ No newline at end of file
diff --git a/swaggest/json-diff/src/JsonPatch/Add.php b/swaggest/json-diff/src/JsonPatch/Add.php
new file mode 100644
index 0000000..41f3713
--- /dev/null
+++ b/swaggest/json-diff/src/JsonPatch/Add.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace Swaggest\JsonDiff\JsonPatch;
+
+class Add extends OpPathValue
+{
+    const OP = 'add';
+}
\ No newline at end of file
diff --git a/swaggest/json-diff/src/JsonPatch/Copy.php b/swaggest/json-diff/src/JsonPatch/Copy.php
new file mode 100644
index 0000000..1448df8
--- /dev/null
+++ b/swaggest/json-diff/src/JsonPatch/Copy.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace Swaggest\JsonDiff\JsonPatch;
+
+class Copy extends OpPathFrom
+{
+    const OP = 'copy';
+}
\ No newline at end of file
diff --git a/swaggest/json-diff/src/JsonPatch/Move.php b/swaggest/json-diff/src/JsonPatch/Move.php
new file mode 100644
index 0000000..6a643d9
--- /dev/null
+++ b/swaggest/json-diff/src/JsonPatch/Move.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace Swaggest\JsonDiff\JsonPatch;
+
+class Move extends OpPathFrom
+{
+    const OP = 'move';
+}
\ No newline at end of file
diff --git a/swaggest/json-diff/src/JsonPatch/OpPath.php b/swaggest/json-diff/src/JsonPatch/OpPath.php
new file mode 100644
index 0000000..14df806
--- /dev/null
+++ b/swaggest/json-diff/src/JsonPatch/OpPath.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Swaggest\JsonDiff\JsonPatch;
+
+
+abstract class OpPath
+{
+    const OP = null;
+
+    public $op;
+    public $path;
+
+    public function __construct($path = null)
+    {
+        $this->op = static::OP;
+        $this->path = $path;
+    }
+}
\ No newline at end of file
diff --git a/swaggest/json-diff/src/JsonPatch/OpPathFrom.php b/swaggest/json-diff/src/JsonPatch/OpPathFrom.php
new file mode 100644
index 0000000..fa6205c
--- /dev/null
+++ b/swaggest/json-diff/src/JsonPatch/OpPathFrom.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Swaggest\JsonDiff\JsonPatch;
+
+abstract class OpPathFrom extends OpPath
+{
+    public $from;
+
+    public function __construct($path = null, $from = null)
+    {
+        parent::__construct($path);
+        $this->from = $from;
+    }
+}
\ No newline at end of file
diff --git a/swaggest/json-diff/src/JsonPatch/OpPathValue.php b/swaggest/json-diff/src/JsonPatch/OpPathValue.php
new file mode 100644
index 0000000..5e06665
--- /dev/null
+++ b/swaggest/json-diff/src/JsonPatch/OpPathValue.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Swaggest\JsonDiff\JsonPatch;
+
+
+abstract class OpPathValue extends OpPath
+{
+    public $value;
+
+    public function __construct($path = null, $value = null)
+    {
+        parent::__construct($path);
+        $this->value = $value;
+    }
+
+}
\ No newline at end of file
diff --git a/swaggest/json-diff/src/JsonPatch/Remove.php b/swaggest/json-diff/src/JsonPatch/Remove.php
new file mode 100644
index 0000000..6420e7b
--- /dev/null
+++ b/swaggest/json-diff/src/JsonPatch/Remove.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace Swaggest\JsonDiff\JsonPatch;
+
+class Remove extends OpPath
+{
+    const OP = 'remove';
+}
\ No newline at end of file
diff --git a/swaggest/json-diff/src/JsonPatch/Replace.php b/swaggest/json-diff/src/JsonPatch/Replace.php
new file mode 100644
index 0000000..8efe4a8
--- /dev/null
+++ b/swaggest/json-diff/src/JsonPatch/Replace.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace Swaggest\JsonDiff\JsonPatch;
+
+class Replace extends OpPathValue
+{
+    const OP = 'replace';
+}
\ No newline at end of file
diff --git a/swaggest/json-diff/src/JsonPatch/Test.php b/swaggest/json-diff/src/JsonPatch/Test.php
new file mode 100644
index 0000000..eab9d53
--- /dev/null
+++ b/swaggest/json-diff/src/JsonPatch/Test.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace Swaggest\JsonDiff\JsonPatch;
+
+class Test extends OpPathValue
+{
+    const OP = 'test';
+}
\ No newline at end of file
diff --git a/swaggest/json-diff/src/JsonPointer.php b/swaggest/json-diff/src/JsonPointer.php
new file mode 100644
index 0000000..d9fc2e0
--- /dev/null
+++ b/swaggest/json-diff/src/JsonPointer.php
@@ -0,0 +1,305 @@
+<?php
+
+namespace Swaggest\JsonDiff;
+
+
+class JsonPointer
+{
+    /**
+     * Create intermediate keys if they don't exist
+     */
+    const RECURSIVE_KEY_CREATION = 1;
+
+    /**
+     * Disallow converting empty array to object for key creation
+     */
+    const STRICT_MODE = 2;
+
+    /**
+     * Skip action if holder already has a non-null value at path
+     */
+    const SKIP_IF_ISSET = 4;
+
+    /**
+     * Allow associative arrays to mimic JSON objects (not recommended)
+     */
+    const TOLERATE_ASSOCIATIVE_ARRAYS = 8;
+
+    /**
+     * @param string $key
+     * @param bool $isURIFragmentId
+     * @return string
+     */
+    public static function escapeSegment($key, $isURIFragmentId = false)
+    {
+        if ($isURIFragmentId) {
+            return str_replace(array('%7E', '%2F'), array('~0', '~1'), urlencode($key));
+        } else {
+            return str_replace(array('~', '/'), array('~0', '~1'), $key);
+        }
+    }
+
+    /**
+     * @param string[] $pathItems
+     * @param bool $isURIFragmentId
+     * @return string
+     */
+    public static function buildPath(array $pathItems, $isURIFragmentId = false)
+    {
+        $result = $isURIFragmentId ? '#' : '';
+        foreach ($pathItems as $pathItem) {
+            $result .= '/' . self::escapeSegment($pathItem, $isURIFragmentId);
+        }
+        return $result;
+    }
+
+    /**
+     * @param string $path
+     * @return string[]
+     * @throws Exception
+     */
+    public static function splitPath($path)
+    {
+        $pathItems = explode('/', $path);
+        $first = array_shift($pathItems);
+        if ($first === '#') {
+            return self::splitPathURIFragment($pathItems);
+        } else {
+            if ($first !== '') {
+                throw new Exception('Path must start with "/": ' . $path);
+            }
+            return self::splitPathJsonString($pathItems);
+        }
+    }
+
+    private static function splitPathURIFragment(array $pathItems)
+    {
+        $result = array();
+        foreach ($pathItems as $key) {
+            $key = str_replace(array('~1', '~0'), array('/', '~'), urldecode($key));
+            $result[] = $key;
+        }
+        return $result;
+    }
+
+    private static function splitPathJsonString(array $pathItems)
+    {
+        $result = array();
+        foreach ($pathItems as $key) {
+            $key = str_replace(array('~1', '~0'), array('/', '~'), $key);
+            $result[] = $key;
+        }
+        return $result;
+    }
+
+    /**
+     * @param mixed $holder
+     * @param string[] $pathItems
+     * @param mixed $value
+     * @param int $flags
+     * @throws Exception
+     */
+    public static function add(&$holder, $pathItems, $value, $flags = self::RECURSIVE_KEY_CREATION)
+    {
+        $ref = &$holder;
+        while (null !== $key = array_shift($pathItems)) {
+            if ($ref instanceof \stdClass || is_object($ref)) {
+                if (PHP_VERSION_ID < 70100 && '' === $key) {
+                    throw new Exception('Empty property name is not supported by PHP <7.1',
+                        Exception::EMPTY_PROPERTY_NAME_UNSUPPORTED);
+                }
+
+                if ($flags & self::RECURSIVE_KEY_CREATION) {
+                    $ref = &$ref->$key;
+                } else {
+                    if (!isset($ref->$key) && count($pathItems)) {
+                        throw new Exception('Non-existent path item: ' . $key);
+                    } else {
+                        $ref = &$ref->$key;
+                    }
+                }
+            } else { // null or array
+                $intKey = filter_var($key, FILTER_VALIDATE_INT);
+                if ($ref === null && (false === $intKey || $intKey !== 0)) {
+                    $key = (string)$key;
+                    if ($flags & self::RECURSIVE_KEY_CREATION) {
+                        $ref = new \stdClass();
+                        $ref = &$ref->{$key};
+                    } else {
+                        throw new Exception('Non-existent path item: ' . $key);
+                    }
+                } elseif ([] === $ref && 0 === ($flags & self::STRICT_MODE) && false === $intKey && '-' !== $key) {
+                    $ref = new \stdClass();
+                    $ref = &$ref->{$key};
+                } else {
+                    if ($flags & self::RECURSIVE_KEY_CREATION && $ref === null) $ref = array();
+                    if ('-' === $key) {
+                        $ref = &$ref[count($ref)];
+                    } else {
+                        if (false === $intKey) {
+                            if (0 === ($flags & self::TOLERATE_ASSOCIATIVE_ARRAYS)) {
+                                throw new Exception('Invalid key for array operation');
+                            }
+                            $ref = &$ref[$key];
+                            continue;
+                        }
+                        if (is_array($ref) && array_key_exists($key, $ref) && empty($pathItems)) {
+                            array_splice($ref, $intKey, 0, array($value));
+                        }
+                        if (0 === ($flags & self::TOLERATE_ASSOCIATIVE_ARRAYS)) {
+                            if ($intKey > count($ref) && 0 === ($flags & self::RECURSIVE_KEY_CREATION)) {
+                                throw new Exception('Index is greater than number of items in array');
+                            } elseif ($intKey < 0) {
+                                throw new Exception('Negative index');
+                            }
+                        }
+
+                        $ref = &$ref[$intKey];
+                    }
+                }
+            }
+        }
+        if ($ref !== null && $flags & self::SKIP_IF_ISSET) {
+            return;
+        }
+        $ref = $value;
+    }
+
+    private static function arrayKeyExists($key, array $a)
+    {
+        if (array_key_exists($key, $a)) {
+            return true;
+        }
+        $key = (string)$key;
+        foreach ($a as $k => $v) {
+            if ((string)$k === $key) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static function arrayGet($key, array $a)
+    {
+        $key = (string)$key;
+        foreach ($a as $k => $v) {
+            if ((string)$k === $key) {
+                return $v;
+            }
+        }
+        return false;
+    }
+
+
+    /**
+     * @param mixed $holder
+     * @param string[] $pathItems
+     * @return bool|mixed
+     * @throws Exception
+     */
+    public static function get($holder, $pathItems)
+    {
+        $ref = $holder;
+        while (null !== $key = array_shift($pathItems)) {
+            if ($ref instanceof \stdClass) {
+                if (PHP_VERSION_ID < 70100 && '' === $key) {
+                    throw new Exception('Empty property name is not supported by PHP <7.1',
+                        Exception::EMPTY_PROPERTY_NAME_UNSUPPORTED);
+                }
+
+                $vars = (array)$ref;
+                if (self::arrayKeyExists($key, $vars)) {
+                    $ref = self::arrayGet($key, $vars);
+                } else {
+                    throw new Exception('Key not found: ' . $key);
+                }
+            } elseif (is_array($ref)) {
+                if (self::arrayKeyExists($key, $ref)) {
+                    $ref = $ref[$key];
+                } else {
+                    throw new Exception('Key not found: ' . $key);
+                }
+            } elseif (is_object($ref)) {
+                if (isset($ref->$key)) {
+                    $ref = $ref->$key;
+                } else {
+                    throw new Exception('Key not found: ' . $key);
+                }
+            } else {
+                throw new Exception('Key not found: ' . $key);
+            }
+        }
+        return $ref;
+    }
+
+    /**
+     * @param mixed $holder
+     * @param string $pointer
+     * @return bool|mixed
+     * @throws Exception
+     */
+    public static function getByPointer($holder, $pointer)
+    {
+        return self::get($holder, self::splitPath($pointer));
+    }
+
+    /**
+     * @param mixed $holder
+     * @param string[] $pathItems
+     * @param int $flags
+     * @return mixed
+     * @throws Exception
+     */
+    public static function remove(&$holder, $pathItems, $flags = 0)
+    {
+        $ref = &$holder;
+        while (null !== $key = array_shift($pathItems)) {
+            $parent = &$ref;
+            $refKey = $key;
+            if ($ref instanceof \stdClass) {
+                if (property_exists($ref, $key)) {
+                    $ref = &$ref->$key;
+                } else {
+                    throw new Exception('Key not found: ' . $key);
+                }
+            } elseif (is_object($ref)) {
+                if (isset($ref->$key)) {
+                    $ref = &$ref->$key;
+                } else {
+                    throw new Exception('Key not found: ' . $key);
+                }
+            } else {
+                if (array_key_exists($key, $ref)) {
+                    $ref = &$ref[$key];
+                } else {
+                    throw new Exception('Key not found: ' . $key);
+                }
+            }
+        }
+
+        if (isset($parent) && isset($refKey)) {
+            if ($parent instanceof \stdClass || is_object($parent)) {
+                unset($parent->$refKey);
+            } else {
+                $isAssociative = false;
+                if ($flags & self::TOLERATE_ASSOCIATIVE_ARRAYS) {
+                    $i = 0;
+                    foreach ($parent as $index => $value) {
+                        if ($i !== $index) {
+                            $isAssociative = true;
+                            break;
+                        }
+                    }
+                }
+
+                unset($parent[$refKey]);
+                if (!$isAssociative && (int)$refKey !== count($parent)) {
+                    $parent = array_values($parent);
+                }
+            }
+        }
+
+        return $ref;
+    }
+
+}
diff --git a/swaggest/json-diff/src/JsonValueReplace.php b/swaggest/json-diff/src/JsonValueReplace.php
new file mode 100644
index 0000000..82099da
--- /dev/null
+++ b/swaggest/json-diff/src/JsonValueReplace.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace Swaggest\JsonDiff;
+
+
+class JsonValueReplace
+{
+    private $search;
+    private $replace;
+    private $pathFilterRegex;
+    private $path = '';
+    private $pathItems = array();
+
+    public $affectedPaths = array();
+
+    /**
+     * JsonReplace constructor.
+     * @param mixed $search
+     * @param mixed $replace
+     * @param null|string $pathFilter Regular expression to check path
+     */
+    public function __construct($search, $replace, $pathFilter = null)
+    {
+        $this->search = $search;
+        $this->replace = $replace;
+        $this->pathFilterRegex = $pathFilter;
+    }
+
+    /**
+     * Recursively replaces all nodes equal to `search` value with `replace` value.
+     * @param mixed $data
+     * @return mixed
+     * @throws Exception
+     */
+    public function process($data)
+    {
+        $check = true;
+        if ($this->pathFilterRegex && !preg_match($this->pathFilterRegex, $this->path)) {
+            $check = false;
+        }
+
+        if (!is_array($data) && !is_object($data)) {
+            if ($check && $data === $this->search) {
+                $this->affectedPaths[] = $this->path;
+                return $this->replace;
+            } else {
+                return $data;
+            }
+        }
+
+        /** @var string[] $originalKeys */
+        $originalKeys = $data instanceof \stdClass ? get_object_vars($data) : $data;
+
+        if ($check) {
+            $diff = new JsonDiff($data, $this->search, JsonDiff::STOP_ON_DIFF);
+            if ($diff->getDiffCnt() === 0) {
+                $this->affectedPaths[] = $this->path;
+                return $this->replace;
+            }
+        }
+
+        $result = array();
+
+        foreach ($originalKeys as $key => $originalValue) {
+            $path = $this->path;
+            $pathItems = $this->pathItems;
+            $actualKey = $key;
+            $this->path .= '/' . JsonPointer::escapeSegment($actualKey);
+            $this->pathItems[] = $actualKey;
+
+            $result[$key] = $this->process($originalValue);
+
+            $this->path = $path;
+            $this->pathItems = $pathItems;
+        }
+
+        return $data instanceof \stdClass ? (object)$result : $result;
+    }
+}
\ No newline at end of file
diff --git a/swaggest/json-diff/src/ModifiedPathDiff.php b/swaggest/json-diff/src/ModifiedPathDiff.php
new file mode 100644
index 0000000..c981e94
--- /dev/null
+++ b/swaggest/json-diff/src/ModifiedPathDiff.php
@@ -0,0 +1,30 @@
+<?php
+
+
+namespace Swaggest\JsonDiff;
+
+
+class ModifiedPathDiff
+{
+    public function __construct($path, $original, $new)
+    {
+        $this->path = $path;
+        $this->original = $original;
+        $this->new = $new;
+    }
+
+    /**
+     * @var string
+     */
+    public $path;
+
+    /**
+     * @var mixed
+     */
+    public $original;
+
+    /**
+     * @var mixed
+     */
+    public $new;
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/CHANGELOG.md b/swaggest/json-schema/CHANGELOG.md
new file mode 100644
index 0000000..f225929
--- /dev/null
+++ b/swaggest/json-schema/CHANGELOG.md
@@ -0,0 +1,70 @@
+# Changelog
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [0.12.29] - 2020-03-19
+
+### Added
+- AJV and JSON Shema Test suites updated. 
+
+### Fixed
+- Validating `enum` and `const` in case of float vs. int comparison for equal values.  
+- Validation of time format edge cases.
+
+## [0.12.28] - 2020-01-27
+
+### Fixed
+- Trying to access array offset on value of type null in PHP 7.4, [#101](https://github.com/swaggest/php-json-schema/pull/101).
+
+## [0.12.27] - 2020-01-26
+
+### Fixed
+- PHP version check for empty property name support.
+
+## [0.12.26] - 2020-01-24
+
+### Fixed
+- Export additional and pattern properties, [#97](https://github.com/swaggest/php-json-schema/pull/97), [#99](https://github.com/swaggest/php-json-schema/pull/99).
+
+## [0.12.25] - 2020-01-07
+
+### Fixed
+- Validation always passes with invalid JSON schema, [#96](https://github.com/swaggest/php-json-schema/pull/96).
+
+## [0.12.24] - 2019-12-03
+
+### Fixed
+- Behavior of `tolerateStrings` when decoding bool and applying values to original data (now for real).
+
+## [0.12.23] - 2019-12-02
+
+### Fixed
+- Behavior of `tolerateStrings` when decoding bool and applying values to original data.
+
+## [0.12.22] - 2019-10-22
+
+### Added
+- `Schema::unboolSchemaData` and `Schema::unboolSchema` to public visibility.
+
+## [0.12.21] - 2019-10-01
+
+### Fixed
+- Treating unresolvable schema as a prohibitive `false` schema instead of failing with exception.
+
+## [0.12.20] - 2019-09-22
+
+### Changed
+- Export `null` value instead of skipping it for properties having `null` type.
+
+[0.12.29]: https://github.com/swaggest/php-json-schema/compare/v0.12.28...v0.12.29
+[0.12.28]: https://github.com/swaggest/php-json-schema/compare/v0.12.27...v0.12.28
+[0.12.27]: https://github.com/swaggest/php-json-schema/compare/v0.12.26...v0.12.27
+[0.12.26]: https://github.com/swaggest/php-json-schema/compare/v0.12.25...v0.12.26
+[0.12.25]: https://github.com/swaggest/php-json-schema/compare/v0.12.24...v0.12.25
+[0.12.24]: https://github.com/swaggest/php-json-schema/compare/v0.12.23...v0.12.24
+[0.12.23]: https://github.com/swaggest/php-json-schema/compare/v0.12.22...v0.12.23
+[0.12.22]: https://github.com/swaggest/php-json-schema/compare/v0.12.21...v0.12.22
+[0.12.21]: https://github.com/swaggest/php-json-schema/compare/v0.12.20...v0.12.21
+[0.12.20]: https://github.com/swaggest/php-json-schema/compare/v0.12.19...v0.12.20
diff --git a/swaggest/json-schema/LICENSE b/swaggest/json-schema/LICENSE
new file mode 100644
index 0000000..223d849
--- /dev/null
+++ b/swaggest/json-schema/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Viacheslav Poturaev
+
+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/swaggest/json-schema/Makefile b/swaggest/json-schema/Makefile
new file mode 100644
index 0000000..cdfc0f6
--- /dev/null
+++ b/swaggest/json-schema/Makefile
@@ -0,0 +1,32 @@
+PHPSTAN_VERSION ?= 0.11.15
+PHPBENCH_VERSION ?= 0.16.10
+
+deps:
+	@git submodule init && git submodule update
+
+lint:
+	@test -f ${HOME}/.cache/composer/phpstan-${PHPSTAN_VERSION}.phar || (mkdir -p ${HOME}/.cache/composer/ && wget https://github.com/phpstan/phpstan/releases/download/${PHPSTAN_VERSION}/phpstan.phar -O ${HOME}/.cache/composer/phpstan-${PHPSTAN_VERSION}.phar)
+	@php $$HOME/.cache/composer/phpstan-${PHPSTAN_VERSION}.phar analyze -l 7 -c phpstan.neon ./src
+
+docker-lint:
+	@docker run -v $$PWD:/app --rm phpstan/phpstan analyze -l 7 -c phpstan.neon ./src
+
+test:
+	@php -derror_reporting="E_ALL & ~E_DEPRECATED" vendor/bin/phpunit --configuration phpunit.xml
+
+test-coverage:
+	@php -derror_reporting="E_ALL & ~E_DEPRECATED" -dzend_extension=xdebug.so vendor/bin/phpunit --configuration phpunit.xml --coverage-text --coverage-clover=coverage.xml
+
+phpbench:
+	@test -f ${HOME}/.cache/composer/phpbench-${PHPBENCH_VERSION}.phar || (mkdir -p ${HOME}/.cache/composer/ && wget https://github.com/phpbench/phpbench/releases/download/${PHPBENCH_VERSION}/phpbench.phar -O ${HOME}/.cache/composer/phpbench-${PHPBENCH_VERSION}.phar)
+
+bench: phpbench
+	@php $$HOME/.cache/composer/phpbench-${PHPBENCH_VERSION}.phar run benchmarks --tag=candidate --progress=none --bootstrap=vendor/autoload.php --revs=50 --iterations=5 --retry-threshold=3 --dump-file=phpbench-candidate.xml
+
+bench-master: phpbench
+	@git checkout --detach && git fetch origin '+refs/heads/master:refs/heads/master' && git checkout master -- ./src
+	@composer install --dev --no-interaction --prefer-dist
+	@php $$HOME/.cache/composer/phpbench-${PHPBENCH_VERSION}.phar run benchmarks --tag=master --progress=none --bootstrap=vendor/autoload.php --revs=50 --iterations=5 --retry-threshold=3 --dump-file=phpbench-master.xml
+
+bench-compare: phpbench
+	@php $$HOME/.cache/composer/phpbench-${PHPBENCH_VERSION}.phar report --file phpbench-master.xml --file phpbench-candidate.xml --report='generator: "table", cols: [ "set" ], compare: "tag", compare_fields: ["mean"], break: ["benchmark"]'
diff --git a/swaggest/json-schema/README.md b/swaggest/json-schema/README.md
new file mode 100644
index 0000000..e4c9186
--- /dev/null
+++ b/swaggest/json-schema/README.md
@@ -0,0 +1,480 @@
+# Swaggest JSON-schema implementation for PHP
+
+[![Build Status](https://travis-ci.org/swaggest/php-json-schema.svg?branch=master)](https://travis-ci.org/swaggest/php-json-schema)
+[![Code Climate](https://codeclimate.com/github/swaggest/php-json-schema/badges/gpa.svg)](https://codeclimate.com/github/swaggest/php-json-schema)
+[![codecov](https://codecov.io/gh/swaggest/php-json-schema/branch/master/graph/badge.svg)](https://codecov.io/gh/swaggest/php-json-schema)
+![Code lines](https://sloc.xyz/github/swaggest/php-json-schema/?category=code)
+![Comments](https://sloc.xyz/github/swaggest/php-json-schema/?category=comments)
+
+High definition PHP structures with JSON-schema based validation.
+
+Supported schemas:
+* [JSON Schema Draft 7](http://json-schema.org/specification-links.html#draft-7)
+* [JSON Schema Draft 6](http://json-schema.org/specification-links.html#draft-6)
+* [JSON Schema Draft 4](http://json-schema.org/specification-links.html#draft-4)
+
+## Installation
+
+```
+composer require swaggest/json-schema
+```
+
+## Usage
+
+Structure definition can be done either with `json-schema` or with
+`PHP` class extending `Swaggest\JsonSchema\Structure\ClassStructure`
+
+### Validating JSON data against given schema
+
+Define your json-schema      
+```php
+$schemaJson = <<<'JSON'
+{
+    "type": "object",
+    "properties": {
+        "id": {
+            "type": "integer"
+        },
+        "name": {
+            "type": "string"
+        },
+        "orders": {
+            "type": "array",
+            "items": {
+                "$ref": "#/definitions/order"
+            }
+        }
+    },
+    "required":["id"],
+    "definitions": {
+        "order": {
+            "type": "object",
+            "properties": {
+                "id": {
+                    "type": "integer"
+                },
+                "price": {
+                    "type": "number"
+                },
+                "updated": {
+                    "type": "string",
+                    "format": "date-time"
+                }
+            },
+            "required":["id"]
+        }
+    }
+}
+JSON;
+```
+
+Load it
+```php
+$schema = Schema::import(json_decode($schemaJson));
+```
+
+Validate data
+```php
+$schema->in(json_decode(<<<'JSON'
+{
+    "id": 1,
+    "name":"John Doe",
+    "orders":[
+        {
+            "id":1
+        },
+        {
+            "price":1.0
+        }
+    ]
+}
+JSON
+)); // Exception: Required property missing: id at #->properties:orders->items[1]->#/definitions/order
+```
+
+You can also call `Schema::import` on string `uri` to schema json data.
+```php
+$schema = Schema::import('http://localhost:1234/my_schema.json');
+```
+
+Or with boolean argument.
+```php
+$schema = Schema::import(true); // permissive schema, always validates
+$schema = Schema::import(false); // restrictive schema, always invalidates
+```
+
+### Understanding error cause
+
+With complex schemas it may be hard to find out what's wrong with your data. Exception message can look like:
+
+```
+No valid results for oneOf {
+ 0: Enum failed, enum: ["a"], data: "f" at #->properties:root->patternProperties[^[a-zA-Z0-9_]+$]:zoo->oneOf[0]
+ 1: Enum failed, enum: ["b"], data: "f" at #->properties:root->patternProperties[^[a-zA-Z0-9_]+$]:zoo->oneOf[1]
+ 2: No valid results for anyOf {
+   0: Enum failed, enum: ["c"], data: "f" at #->properties:root->patternProperties[^[a-zA-Z0-9_]+$]:zoo->oneOf[2]->$ref[#/cde]->anyOf[0]
+   1: Enum failed, enum: ["d"], data: "f" at #->properties:root->patternProperties[^[a-zA-Z0-9_]+$]:zoo->oneOf[2]->$ref[#/cde]->anyOf[1]
+   2: Enum failed, enum: ["e"], data: "f" at #->properties:root->patternProperties[^[a-zA-Z0-9_]+$]:zoo->oneOf[2]->$ref[#/cde]->anyOf[2]
+ } at #->properties:root->patternProperties[^[a-zA-Z0-9_]+$]:zoo->oneOf[2]->$ref[#/cde]
+} at #->properties:root->patternProperties[^[a-zA-Z0-9_]+$]:zoo
+```
+
+For ambiguous schemas defined with `oneOf`/`anyOf` message is indented multi-line string.
+
+Processing path is a combination of schema and data pointers. You can use `InvalidValue->getSchemaPointer()`
+and `InvalidValue->getDataPointer()` to extract schema/data pointer.
+
+You can receive `Schema` instance that failed validation with `InvalidValue->getFailedSubSchema`. 
+
+You can build error tree using `InvalidValue->inspect()`.
+
+### PHP structured classes with validation
+
+```php
+/**
+ * @property int $quantity PHPDoc defined dynamic properties will be validated on every set
+ */
+class User extends ClassStructure
+{
+    /* Native (public) properties will be validated only on import and export of structure data */
+
+    /** @var int */
+    public $id;
+    public $name;
+    /** @var Order[] */
+    public $orders;
+
+    /** @var UserInfo */
+    public $info;
+
+    /**
+     * @param Properties|static $properties
+     * @param Schema $ownerSchema
+     */
+    public static function setUpProperties($properties, Schema $ownerSchema)
+    {
+        // You can add custom meta to your schema
+        $dbTable = new DbTable;
+        $dbTable->tableName = 'users';
+        $ownerSchema->addMeta($dbTable);
+
+        // Setup property schemas
+        $properties->id = Schema::integer();
+        $properties->id->addMeta(new DbId($dbTable)); // You can add meta to property.
+
+        $properties->name = Schema::string();
+
+        // You can embed structures to main level with nested schemas
+        $properties->info = UserInfo::schema()->nested();
+
+        // You can set default value for property
+        $defaultOptions = new UserOptions();
+        $defaultOptions->autoLogin = true;
+        $defaultOptions->groupName = 'guest';
+        // UserOptions::schema() is safe to change as it is protected with lazy cloning
+        $properties->options = UserOptions::schema()->setDefault(UserOptions::export($defaultOptions));
+
+        // Dynamic (phpdoc-defined) properties can be used as well
+        $properties->quantity = Schema::integer();
+        $properties->quantity->minimum = 0;
+
+        // Property can be any complex structure
+        $properties->orders = Schema::create();
+        $properties->orders->items = Order::schema();
+
+        $ownerSchema->required = array(self::names()->id);
+    }
+}
+
+class UserInfo extends ClassStructure {
+    public $firstName;
+    public $lastName;
+    public $birthDay;
+
+    /**
+     * @param Properties|static $properties
+     * @param Schema $ownerSchema
+     */
+    public static function setUpProperties($properties, Schema $ownerSchema)
+    {
+        $properties->firstName = Schema::string();
+        $properties->lastName = Schema::string();
+        $properties->birthDay = Schema::string();
+    }
+}
+
+class UserOptions extends ClassStructure
+{
+    public $autoLogin;
+    public $groupName;
+
+    /**
+     * @param Properties|static $properties
+     * @param Schema $ownerSchema
+     */
+    public static function setUpProperties($properties, Schema $ownerSchema)
+    {
+        $properties->autoLogin = Schema::boolean();
+        $properties->groupName = Schema::string();
+    }
+}
+
+class Order implements ClassStructureContract
+{
+    use ClassStructureTrait; // You can use trait if you can't/don't want to extend ClassStructure
+
+    const FANCY_MAPPING = 'fAnCy'; // You can create additional mapping namespace
+
+    public $id;
+    public $userId;
+    public $dateTime;
+    public $price;
+
+    /**
+     * @param Properties|static $properties
+     * @param Schema $ownerSchema
+     */
+    public static function setUpProperties($properties, Schema $ownerSchema)
+    {
+        // Add some meta data to your schema
+        $dbMeta = new DbTable();
+        $dbMeta->tableName = 'orders';
+        $ownerSchema->addMeta($dbMeta);
+
+        // Define properties
+        $properties->id = Schema::integer();
+        $properties->userId = User::properties()->id; // referencing property of another schema keeps meta
+        $properties->dateTime = Schema::string();
+        $properties->dateTime->format = Format::DATE_TIME;
+        $properties->price = Schema::number();
+
+        $ownerSchema->required[] = self::names()->id;
+
+        // Define default mapping if any
+        $ownerSchema->addPropertyMapping('date_time', Order::names()->dateTime);
+
+        // Define additional mapping
+        $ownerSchema->addPropertyMapping('DaTe_TiMe', Order::names()->dateTime, self::FANCY_MAPPING);
+        $ownerSchema->addPropertyMapping('Id', Order::names()->id, self::FANCY_MAPPING);
+        $ownerSchema->addPropertyMapping('PrIcE', Order::names()->price, self::FANCY_MAPPING);
+    }
+}
+```
+
+Validation of dynamic properties is performed on set, 
+this can help to find source of invalid data at cost of 
+some performance drop
+```php
+$user = new User();
+$user->quantity = -1; // Exception: Value more than 0 expected, -1 received
+```
+
+Validation of native properties is performed only on import/export
+```php
+$user = new User();
+$user->quantity = 10;
+User::export($user); // Exception: Required property missing: id
+```
+
+Error messages provide a path to invalid data
+```php
+$user = new User();
+$user->id = 1;
+$user->name = 'John Doe';
+
+$order = new Order();
+$order->dateTime = (new \DateTime())->format(DATE_RFC3339);
+$user->orders[] = $order;
+
+User::export($user); // Exception: Required property missing: id at #->properties:orders->items[0]
+```
+
+#### Nested structures
+
+Nested structures allow you to make composition: flatten several objects in one and separate back.
+
+```php
+$user = new User();
+$user->id = 1;
+
+$info = new UserInfo();
+$info->firstName = 'John';
+$info->lastName = 'Doe';
+$info->birthDay = '1970-01-01';
+$user->info = $info;
+
+$json = <<<JSON
+{
+    "id": 1,
+    "firstName": "John",
+    "lastName": "Doe",
+    "birthDay": "1970-01-01"
+}
+JSON;
+$exported = User::export($user);
+$this->assertSame($json, json_encode($exported, JSON_PRETTY_PRINT));
+
+$imported = User::import(json_decode($json));
+$this->assertSame('John', $imported->info->firstName);
+$this->assertSame('Doe', $imported->info->lastName);
+```
+
+You can also use `\Swaggest\JsonSchema\Structure\Composition` to dynamically create schema compositions.
+This can be helpful to deal with results of database query on joined data.
+
+```php
+$schema = new Composition(UserInfo::schema(), Order::schema());
+$json = <<<JSON
+{
+    "id": 1,
+    "firstName": "John",
+    "lastName": "Doe",
+    "price": 2.66
+}
+JSON;
+$object = $schema->import(json_decode($json));
+
+// Get particular object with `pick` accessor
+$info = UserInfo::pick($object);
+$order = Order::pick($object);
+
+// Data is imported objects of according classes
+$this->assertTrue($order instanceof Order);
+$this->assertTrue($info instanceof UserInfo);
+
+$this->assertSame(1, $order->id);
+$this->assertSame('John', $info->firstName);
+$this->assertSame('Doe', $info->lastName);
+$this->assertSame(2.66, $order->price);
+```
+
+#### Keys mapping
+
+If property names of PHP objects should be different from raw data you 
+can call `->addPropertyMapping` on owner schema.
+
+```php
+// Define default mapping if any
+$ownerSchema->addPropertyMapping('date_time', Order::names()->dateTime);
+
+// Define additional mapping
+$ownerSchema->addPropertyMapping('DaTe_TiMe', Order::names()->dateTime, self::FANCY_MAPPING);
+$ownerSchema->addPropertyMapping('Id', Order::names()->id, self::FANCY_MAPPING);
+$ownerSchema->addPropertyMapping('PrIcE', Order::names()->price, self::FANCY_MAPPING);
+```
+
+It will affect data mapping:
+```php
+$order = new Order();
+$order->id = 1;
+$order->dateTime = '2015-10-28T07:28:00Z';
+$order->price = 2.2;
+$exported = Order::export($order);
+$json = <<<JSON
+{
+    "id": 1,
+    "date_time": "2015-10-28T07:28:00Z",
+    "price": 2.2
+}
+JSON;
+$this->assertSame($json, json_encode($exported, JSON_PRETTY_PRINT));
+
+$imported = Order::import(json_decode($json));
+$this->assertSame('2015-10-28T07:28:00Z', $imported->dateTime);
+```
+
+You can have multiple mapping namespaces, controlling with `mapping` property of `Context`
+```php
+$options = new Context();
+$options->mapping = Order::FANCY_MAPPING;
+
+$exported = Order::export($order, $options);
+$json = <<<JSON
+{
+    "Id": 1,
+    "DaTe_TiMe": "2015-10-28T07:28:00Z",
+    "PrIcE": 2.2
+}
+JSON;
+$this->assertSame($json, json_encode($exported, JSON_PRETTY_PRINT));
+
+$imported = Order::import(json_decode($json), $options);
+$this->assertSame('2015-10-28T07:28:00Z', $imported->dateTime);
+```
+
+You can create your own pre-processor implementing `Swaggest\JsonSchema\DataPreProcessor`.
+
+#### Meta
+
+`Meta` is a way to complement `Schema` with your own data. You can keep and retrieve it.
+
+You can store it.
+```php
+$dbMeta = new DbTable();
+$dbMeta->tableName = 'orders';
+$ownerSchema->addMeta($dbMeta);
+```
+
+And get back.
+```php
+// Retrieving meta
+$dbTable = DbTable::get(Order::schema());
+$this->assertSame('orders', $dbTable->tableName);
+```
+
+
+#### Mapping without validation
+
+If you want to tolerate invalid data or improve mapping performance you can specify `skipValidation` flag in processing `Context`
+
+```php
+$schema = Schema::object();
+$schema->setProperty('one', Schema::integer());
+$schema->properties->one->minimum = 5;
+
+$options = new Context();
+$options->skipValidation = true;
+
+$res = $schema->in(json_decode('{"one":4}'), $options);
+$this->assertSame(4, $res->one);
+```
+
+
+#### Overriding mapping classes
+
+If you want to map data to a different class you can register mapping at top level of your importer structure.
+
+```php
+class CustomSwaggerSchema extends SwaggerSchema
+{
+    public static function import($data, Context $options = null)
+    {
+        if ($options === null) {
+            $options = new Context();
+        }
+        $options->objectItemClassMapping[Schema::className()] = CustomSchema::className();
+        return parent::import($data, $options);
+    }
+}
+```
+
+Or specify it in processing context
+
+```php
+$context = new Context();
+$context->objectItemClassMapping[Schema::className()] = CustomSchema::className();
+$schema = SwaggerSchema::schema()->in(json_decode(
+    file_get_contents(__DIR__ . '/../../../../spec/petstore-swagger.json')
+), $context);
+$this->assertInstanceOf(CustomSchema::className(), $schema->definitions['User']);
+```
+
+## Code quality and test coverage
+
+Some code quality best practices are deliberately violated here
+(see [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/swaggest/php-json-schema/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/swaggest/php-json-schema/?branch=master)
+) to allow best performance at maintenance cost.
+
+Those violations are secured by comprehensive test coverage:
+ * draft-04, draft-06, draft-07 of [JSON-Schema-Test-Suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite)
+ * test cases (excluding `$data` and few tests) of [epoberezkin/ajv](https://github.com/epoberezkin/ajv/tree/master/spec) (a mature js implementation)
\ No newline at end of file
diff --git a/swaggest/json-schema/benchmarks/AjvSchemasBench.php b/swaggest/json-schema/benchmarks/AjvSchemasBench.php
new file mode 100644
index 0000000..3e69bbe
--- /dev/null
+++ b/swaggest/json-schema/benchmarks/AjvSchemasBench.php
@@ -0,0 +1,144 @@
+<?php
+
+
+use Swaggest\JsonSchema\Context;
+use Swaggest\JsonSchema\InvalidValue;
+use Swaggest\JsonSchema\RemoteRef\Preloaded;
+use Swaggest\JsonSchema\Schema;
+
+class AjvSchemasBench
+{
+    private static $cases;
+
+    public function provide()
+    {
+        foreach (self::$cases as $name => $tmp) {
+            yield $name => ['name' => $name];
+        }
+    }
+
+    /**
+     * @ParamProviders({"provide"})
+     */
+    public function benchSpec($params)
+    {
+        $case = self::$cases[$params['name']];
+
+        $actualValid = true;
+        try {
+            $options = $this->makeOptions(Schema::VERSION_DRAFT_07);
+            $options->schemasCache = self::$schemas;
+
+            $schema = Schema::import($case['schema'], $options);
+            // import with defaults applied
+            $schema->in($case['data'], $options);
+
+            // default is not required to pass validation, so result might be invalid
+            // for back-exporting defaults have to be disabled
+            $options->applyDefaults = false;
+            $imported = $schema->in($case['data'], $options);
+            $schema->out($imported);
+        } catch (InvalidValue $exception) {
+            $actualValid = false;
+        }
+
+        if ($actualValid !== $case['isValid']) {
+            throw new Exception('Assertion failed');
+        }
+    }
+
+    /** @var \SplObjectStorage */
+    private static $schemas;
+
+    protected function makeOptions($version)
+    {
+        $refProvider = static::getProvider();
+
+        $options = new Context();
+        $options->setRemoteRefProvider($refProvider);
+        $options->version = $version;
+        $options->strictBase64Validation = true;
+
+        return $options;
+    }
+
+    public static function getProvider()
+    {
+        static $refProvider = null;
+
+        if (null === $refProvider) {
+            $refProvider = new Preloaded();
+            $refProvider
+                ->setSchemaData(
+                    'http://localhost:1234/integer.json',
+                    json_decode(file_get_contents(__DIR__
+                        . '/../spec/JSON-Schema-Test-Suite/remotes/integer.json')))
+                ->setSchemaData(
+                    'http://localhost:1234/subSchemas.json',
+                    json_decode(file_get_contents(__DIR__
+                        . '/../spec/JSON-Schema-Test-Suite/remotes/subSchemas.json')))
+                ->setSchemaData(
+                    'http://localhost:1234/name.json',
+                    json_decode(file_get_contents(__DIR__
+                        . '/../spec/JSON-Schema-Test-Suite/remotes/name.json')))
+                ->setSchemaData(
+                    'http://localhost:1234/folder/folderInteger.json',
+                    json_decode(file_get_contents(__DIR__
+                        . '/../spec/JSON-Schema-Test-Suite/remotes/folder/folderInteger.json')));
+        }
+
+        return $refProvider;
+    }
+
+    private static function provider($path)
+    {
+        $testCases = array();
+
+        if ($handle = opendir($path)) {
+            while (false !== ($entry = readdir($handle))) {
+                if ($entry != "." && $entry != "..") {
+                    if ('.json' !== substr($entry, -5)) {
+                        continue;
+                    }
+                    $tests = json_decode(file_get_contents($path . '/' . $entry));
+
+                    foreach ($tests as $test) {
+                        foreach ($test->tests as $c => $case) {
+                            $name = $entry . ' ' . $test->description . ': ' . $case->description . ' [' . $c . ']';
+                            if (!isset($test->schema)) {
+                                if (isset($test->schemas)) {
+                                    foreach ($test->schemas as $i => $schema) {
+                                        $testCases[$name . '_' . $i] = array(
+                                            'schema' => $schema,
+                                            'data' => $case->data,
+                                            'isValid' => $case->valid,
+                                            'name' => $name,
+                                        );
+                                    }
+                                }
+                                continue;
+                            }
+                            $testCases[$name] = array(
+                                'schema' => $test->schema,
+                                'data' => $case->data,
+                                'isValid' => $case->valid,
+                                'name' => $name,
+                            );
+                        }
+                    }
+                }
+            }
+            closedir($handle);
+        }
+
+        return $testCases;
+    }
+
+    public static function init()
+    {
+        self::$cases = self::provider(__DIR__ . '/../spec/ajv/spec/tests/schemas');
+        self::$schemas = new \SplObjectStorage();
+    }
+}
+
+AjvSchemasBench::init();
\ No newline at end of file
diff --git a/swaggest/json-schema/benchmarks/Draft7Bench.php b/swaggest/json-schema/benchmarks/Draft7Bench.php
new file mode 100644
index 0000000..afc564b
--- /dev/null
+++ b/swaggest/json-schema/benchmarks/Draft7Bench.php
@@ -0,0 +1,144 @@
+<?php
+
+
+use Swaggest\JsonSchema\Context;
+use Swaggest\JsonSchema\InvalidValue;
+use Swaggest\JsonSchema\RemoteRef\Preloaded;
+use Swaggest\JsonSchema\Schema;
+
+class Draft7Bench
+{
+    private static $cases;
+
+    public function provide()
+    {
+        foreach (self::$cases as $name => $tmp) {
+            yield $name => ['name' => $name];
+        }
+    }
+
+    /**
+     * @ParamProviders({"provide"})
+     */
+    public function benchSpec($params)
+    {
+        $case = self::$cases[$params['name']];
+
+        $actualValid = true;
+        try {
+            $options = $this->makeOptions(Schema::VERSION_DRAFT_07);
+            $options->schemasCache = self::$schemas;
+
+            $schema = Schema::import($case['schema'], $options);
+            // import with defaults applied
+            $schema->in($case['data'], $options);
+
+            // default is not required to pass validation, so result might be invalid
+            // for back-exporting defaults have to be disabled
+            $options->applyDefaults = false;
+            $imported = $schema->in($case['data'], $options);
+            $schema->out($imported);
+        } catch (InvalidValue $exception) {
+            $actualValid = false;
+        }
+
+        if ($actualValid !== $case['isValid']) {
+            throw new Exception('Assertion failed');
+        }
+    }
+
+    /** @var \SplObjectStorage */
+    private static $schemas;
+
+    protected function makeOptions($version)
+    {
+        $refProvider = static::getProvider();
+
+        $options = new Context();
+        $options->setRemoteRefProvider($refProvider);
+        $options->version = $version;
+        $options->strictBase64Validation = true;
+
+        return $options;
+    }
+
+    public static function getProvider()
+    {
+        static $refProvider = null;
+
+        if (null === $refProvider) {
+            $refProvider = new Preloaded();
+            $refProvider
+                ->setSchemaData(
+                    'http://localhost:1234/integer.json',
+                    json_decode(file_get_contents(__DIR__
+                        . '/../spec/JSON-Schema-Test-Suite/remotes/integer.json')))
+                ->setSchemaData(
+                    'http://localhost:1234/subSchemas.json',
+                    json_decode(file_get_contents(__DIR__
+                        . '/../spec/JSON-Schema-Test-Suite/remotes/subSchemas.json')))
+                ->setSchemaData(
+                    'http://localhost:1234/name.json',
+                    json_decode(file_get_contents(__DIR__
+                        . '/../spec/JSON-Schema-Test-Suite/remotes/name.json')))
+                ->setSchemaData(
+                    'http://localhost:1234/folder/folderInteger.json',
+                    json_decode(file_get_contents(__DIR__
+                        . '/../spec/JSON-Schema-Test-Suite/remotes/folder/folderInteger.json')));
+        }
+
+        return $refProvider;
+    }
+
+    private static function provider($path)
+    {
+        $testCases = array();
+
+        if ($handle = opendir($path)) {
+            while (false !== ($entry = readdir($handle))) {
+                if ($entry != "." && $entry != "..") {
+                    if ('.json' !== substr($entry, -5)) {
+                        continue;
+                    }
+                    $tests = json_decode(file_get_contents($path . '/' . $entry));
+
+                    foreach ($tests as $test) {
+                        foreach ($test->tests as $c => $case) {
+                            $name = $entry . ' ' . $test->description . ': ' . $case->description . ' [' . $c . ']';
+                            if (!isset($test->schema)) {
+                                if (isset($test->schemas)) {
+                                    foreach ($test->schemas as $i => $schema) {
+                                        $testCases[$name . '_' . $i] = array(
+                                            'schema' => $schema,
+                                            'data' => $case->data,
+                                            'isValid' => $case->valid,
+                                            'name' => $name,
+                                        );
+                                    }
+                                }
+                                continue;
+                            }
+                            $testCases[$name] = array(
+                                'schema' => $test->schema,
+                                'data' => $case->data,
+                                'isValid' => $case->valid,
+                                'name' => $name,
+                            );
+                        }
+                    }
+                }
+            }
+            closedir($handle);
+        }
+
+        return $testCases;
+    }
+
+    public static function init()
+    {
+        self::$cases = self::provider(__DIR__ . '/../spec/JSON-Schema-Test-Suite/tests/draft7');
+        self::$schemas = new \SplObjectStorage();
+    }
+}
+
+Draft7Bench::init();
\ No newline at end of file
diff --git a/swaggest/json-schema/composer.json b/swaggest/json-schema/composer.json
new file mode 100644
index 0000000..a96e1e4
--- /dev/null
+++ b/swaggest/json-schema/composer.json
@@ -0,0 +1,42 @@
+{
+  "name": "swaggest/json-schema",
+  "description": "High definition PHP structures with JSON-schema based validation",
+  "type": "library",
+  "require": {
+    "php": ">=5.4",
+    "ext-json": "*",
+    "ext-mbstring": "*",
+    "phplang/scope-exit": "^1.0",
+    "swaggest/json-diff": "^3.5.1"
+  },
+  "require-dev": {
+    "phpunit/phpunit": "4.8.35",
+    "phpunit/php-code-coverage": "2.2.4"
+  },
+  "license": "MIT",
+  "authors": [
+    {
+      "name": "Viacheslav Poturaev",
+      "email": "vearutop@gmail.com"
+    }
+  ],
+  "support": {
+    "email": "vearutop@gmail.com"
+  },
+  "prefer-stable": true,
+  "autoload": {
+    "psr-4": {
+      "Swaggest\\JsonSchema\\": "src/"
+    }
+  },
+  "autoload-dev": {
+    "psr-4": {
+      "Swaggest\\JsonSchema\\Tests\\": "tests/src/"
+    }
+  },
+  "config": {
+    "platform": {
+      "php": "5.4.45"
+    }
+  }
+}
diff --git a/swaggest/json-schema/composer.lock b/swaggest/json-schema/composer.lock
new file mode 100644
index 0000000..0696c72
--- /dev/null
+++ b/swaggest/json-schema/composer.lock
@@ -0,0 +1,1132 @@
+{
+    "_readme": [
+        "This file locks the dependencies of your project to a known state",
+        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+        "This file is @generated automatically"
+    ],
+    "content-hash": "4f3a0a46f69c1095422bf99acd191102",
+    "packages": [
+        {
+            "name": "phplang/scope-exit",
+            "version": "1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phplang/scope-exit.git",
+                "reference": "239b73abe89f9414aa85a7ca075ec9445629192b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phplang/scope-exit/zipball/239b73abe89f9414aa85a7ca075ec9445629192b",
+                "reference": "239b73abe89f9414aa85a7ca075ec9445629192b",
+                "shasum": ""
+            },
+            "require-dev": {
+                "phpunit/phpunit": "*"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "PhpLang\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD"
+            ],
+            "authors": [
+                {
+                    "name": "Sara Golemon",
+                    "email": "pollita@php.net",
+                    "homepage": "https://twitter.com/SaraMG",
+                    "role": "Developer"
+                }
+            ],
+            "description": "Emulation of SCOPE_EXIT construct from C++",
+            "homepage": "https://github.com/phplang/scope-exit",
+            "keywords": [
+                "cleanup",
+                "exit",
+                "scope"
+            ],
+            "time": "2016-09-17T00:15:18+00:00"
+        },
+        {
+            "name": "swaggest/json-diff",
+            "version": "v3.7.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/swaggest/json-diff.git",
+                "reference": "fccbbd6ee31b5949ab52e9c53f7a1e1b06d84716"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/swaggest/json-diff/zipball/fccbbd6ee31b5949ab52e9c53f7a1e1b06d84716",
+                "reference": "fccbbd6ee31b5949ab52e9c53f7a1e1b06d84716",
+                "shasum": ""
+            },
+            "require": {
+                "ext-json": "*"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.8.23"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Swaggest\\JsonDiff\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Viacheslav Poturaev",
+                    "email": "vearutop@gmail.com"
+                }
+            ],
+            "description": "JSON diff/rearrange/patch/pointer library for PHP",
+            "time": "2020-01-26T20:52:06+00:00"
+        }
+    ],
+    "packages-dev": [
+        {
+            "name": "doctrine/instantiator",
+            "version": "1.0.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/doctrine/instantiator.git",
+                "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d",
+                "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3,<8.0-DEV"
+            },
+            "require-dev": {
+                "athletic/athletic": "~0.1.8",
+                "ext-pdo": "*",
+                "ext-phar": "*",
+                "phpunit/phpunit": "~4.0",
+                "squizlabs/php_codesniffer": "~2.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Marco Pivetta",
+                    "email": "ocramius@gmail.com",
+                    "homepage": "http://ocramius.github.com/"
+                }
+            ],
+            "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
+            "homepage": "https://github.com/doctrine/instantiator",
+            "keywords": [
+                "constructor",
+                "instantiate"
+            ],
+            "time": "2015-06-14T21:17:01+00:00"
+        },
+        {
+            "name": "phpdocumentor/reflection-docblock",
+            "version": "2.0.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
+                "reference": "e6a969a640b00d8daa3c66518b0405fb41ae0c4b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/e6a969a640b00d8daa3c66518b0405fb41ae0c4b",
+                "reference": "e6a969a640b00d8daa3c66518b0405fb41ae0c4b",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.0"
+            },
+            "suggest": {
+                "dflydev/markdown": "~1.0",
+                "erusev/parsedown": "~1.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "phpDocumentor": [
+                        "src/"
+                    ]
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Mike van Riel",
+                    "email": "mike.vanriel@naenius.com"
+                }
+            ],
+            "time": "2016-01-25T08:17:30+00:00"
+        },
+        {
+            "name": "phpspec/prophecy",
+            "version": "v1.10.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpspec/prophecy.git",
+                "reference": "b4400efc9d206e83138e2bb97ed7f5b14b831cd9"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/b4400efc9d206e83138e2bb97ed7f5b14b831cd9",
+                "reference": "b4400efc9d206e83138e2bb97ed7f5b14b831cd9",
+                "shasum": ""
+            },
+            "require": {
+                "doctrine/instantiator": "^1.0.2",
+                "php": "^5.3|^7.0",
+                "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0",
+                "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0",
+                "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0"
+            },
+            "require-dev": {
+                "phpspec/phpspec": "^2.5 || ^3.2",
+                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.10.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Prophecy\\": "src/Prophecy"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Konstantin Kudryashov",
+                    "email": "ever.zet@gmail.com",
+                    "homepage": "http://everzet.com"
+                },
+                {
+                    "name": "Marcello Duarte",
+                    "email": "marcello.duarte@gmail.com"
+                }
+            ],
+            "description": "Highly opinionated mocking framework for PHP 5.3+",
+            "homepage": "https://github.com/phpspec/prophecy",
+            "keywords": [
+                "Double",
+                "Dummy",
+                "fake",
+                "mock",
+                "spy",
+                "stub"
+            ],
+            "time": "2020-01-20T15:57:02+00:00"
+        },
+        {
+            "name": "phpunit/php-code-coverage",
+            "version": "2.2.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+                "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979",
+                "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3",
+                "phpunit/php-file-iterator": "~1.3",
+                "phpunit/php-text-template": "~1.2",
+                "phpunit/php-token-stream": "~1.3",
+                "sebastian/environment": "^1.3.2",
+                "sebastian/version": "~1.0"
+            },
+            "require-dev": {
+                "ext-xdebug": ">=2.1.4",
+                "phpunit/phpunit": "~4"
+            },
+            "suggest": {
+                "ext-dom": "*",
+                "ext-xdebug": ">=2.2.1",
+                "ext-xmlwriter": "*"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.2.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "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": "2015-10-06T15:47:00+00:00"
+        },
+        {
+            "name": "phpunit/php-file-iterator",
+            "version": "1.4.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+                "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4",
+                "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.4.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "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": "https://github.com/sebastianbergmann/php-file-iterator/",
+            "keywords": [
+                "filesystem",
+                "iterator"
+            ],
+            "time": "2017-11-27T13:52:08+00:00"
+        },
+        {
+            "name": "phpunit/php-text-template",
+            "version": "1.2.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-text-template.git",
+                "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+                "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Simple template engine.",
+            "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+            "keywords": [
+                "template"
+            ],
+            "time": "2015-06-21T13:50:34+00:00"
+        },
+        {
+            "name": "phpunit/php-timer",
+            "version": "1.0.9",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-timer.git",
+                "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
+                "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.3.3 || ^7.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "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": "2017-02-26T11:10:40+00:00"
+        },
+        {
+            "name": "phpunit/php-token-stream",
+            "version": "1.4.12",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-token-stream.git",
+                "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1ce90ba27c42e4e44e6d8458241466380b51fa16",
+                "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16",
+                "shasum": ""
+            },
+            "require": {
+                "ext-tokenizer": "*",
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.2"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.4-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Wrapper around PHP's tokenizer extension.",
+            "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
+            "keywords": [
+                "tokenizer"
+            ],
+            "time": "2017-12-04T08:55:13+00:00"
+        },
+        {
+            "name": "phpunit/phpunit",
+            "version": "4.8.35",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/phpunit.git",
+                "reference": "791b1a67c25af50e230f841ee7a9c6eba507dc87"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/791b1a67c25af50e230f841ee7a9c6eba507dc87",
+                "reference": "791b1a67c25af50e230f841ee7a9c6eba507dc87",
+                "shasum": ""
+            },
+            "require": {
+                "ext-dom": "*",
+                "ext-json": "*",
+                "ext-pcre": "*",
+                "ext-reflection": "*",
+                "ext-spl": "*",
+                "php": ">=5.3.3",
+                "phpspec/prophecy": "^1.3.1",
+                "phpunit/php-code-coverage": "~2.1",
+                "phpunit/php-file-iterator": "~1.4",
+                "phpunit/php-text-template": "~1.2",
+                "phpunit/php-timer": "^1.0.6",
+                "phpunit/phpunit-mock-objects": "~2.3",
+                "sebastian/comparator": "~1.2.2",
+                "sebastian/diff": "~1.2",
+                "sebastian/environment": "~1.3",
+                "sebastian/exporter": "~1.2",
+                "sebastian/global-state": "~1.0",
+                "sebastian/version": "~1.0",
+                "symfony/yaml": "~2.1|~3.0"
+            },
+            "suggest": {
+                "phpunit/php-invoker": "~1.1"
+            },
+            "bin": [
+                "phpunit"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.8.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "The PHP Unit Testing framework.",
+            "homepage": "https://phpunit.de/",
+            "keywords": [
+                "phpunit",
+                "testing",
+                "xunit"
+            ],
+            "time": "2017-02-06T05:18:07+00:00"
+        },
+        {
+            "name": "phpunit/phpunit-mock-objects",
+            "version": "2.3.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
+                "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983",
+                "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983",
+                "shasum": ""
+            },
+            "require": {
+                "doctrine/instantiator": "^1.0.2",
+                "php": ">=5.3.3",
+                "phpunit/php-text-template": "~1.2",
+                "sebastian/exporter": "~1.2"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.4"
+            },
+            "suggest": {
+                "ext-soap": "*"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.3.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "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"
+            ],
+            "abandoned": true,
+            "time": "2015-10-02T06:51:40+00:00"
+        },
+        {
+            "name": "sebastian/comparator",
+            "version": "1.2.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/comparator.git",
+                "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be",
+                "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3",
+                "sebastian/diff": "~1.2",
+                "sebastian/exporter": "~1.2 || ~2.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.4"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.2.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Volker Dusch",
+                    "email": "github@wallbash.com"
+                },
+                {
+                    "name": "Bernhard Schussek",
+                    "email": "bschussek@2bepublished.at"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Provides the functionality to compare PHP values for equality",
+            "homepage": "http://www.github.com/sebastianbergmann/comparator",
+            "keywords": [
+                "comparator",
+                "compare",
+                "equality"
+            ],
+            "time": "2017-01-29T09:50:25+00:00"
+        },
+        {
+            "name": "sebastian/diff",
+            "version": "1.4.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/diff.git",
+                "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4",
+                "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.3.3 || ^7.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.4-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Kore Nordmann",
+                    "email": "mail@kore-nordmann.de"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Diff implementation",
+            "homepage": "https://github.com/sebastianbergmann/diff",
+            "keywords": [
+                "diff"
+            ],
+            "time": "2017-05-22T07:24:03+00:00"
+        },
+        {
+            "name": "sebastian/environment",
+            "version": "1.3.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/environment.git",
+                "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea",
+                "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.3.3 || ^7.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.8 || ^5.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.3.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Provides functionality to handle HHVM/PHP environments",
+            "homepage": "http://www.github.com/sebastianbergmann/environment",
+            "keywords": [
+                "Xdebug",
+                "environment",
+                "hhvm"
+            ],
+            "time": "2016-08-18T05:49:44+00:00"
+        },
+        {
+            "name": "sebastian/exporter",
+            "version": "1.2.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/exporter.git",
+                "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4",
+                "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3",
+                "sebastian/recursion-context": "~1.0"
+            },
+            "require-dev": {
+                "ext-mbstring": "*",
+                "phpunit/phpunit": "~4.4"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.3.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Volker Dusch",
+                    "email": "github@wallbash.com"
+                },
+                {
+                    "name": "Bernhard Schussek",
+                    "email": "bschussek@2bepublished.at"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                },
+                {
+                    "name": "Adam Harvey",
+                    "email": "aharvey@php.net"
+                }
+            ],
+            "description": "Provides the functionality to export PHP variables for visualization",
+            "homepage": "http://www.github.com/sebastianbergmann/exporter",
+            "keywords": [
+                "export",
+                "exporter"
+            ],
+            "time": "2016-06-17T09:04:28+00:00"
+        },
+        {
+            "name": "sebastian/global-state",
+            "version": "1.1.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/global-state.git",
+                "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4",
+                "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.2"
+            },
+            "suggest": {
+                "ext-uopz": "*"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Snapshotting of global state",
+            "homepage": "http://www.github.com/sebastianbergmann/global-state",
+            "keywords": [
+                "global state"
+            ],
+            "time": "2015-10-12T03:26:01+00:00"
+        },
+        {
+            "name": "sebastian/recursion-context",
+            "version": "1.0.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/recursion-context.git",
+                "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b19cc3298482a335a95f3016d2f8a6950f0fbcd7",
+                "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.4"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                },
+                {
+                    "name": "Adam Harvey",
+                    "email": "aharvey@php.net"
+                }
+            ],
+            "description": "Provides functionality to recursively process PHP variables",
+            "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
+            "time": "2016-10-03T07:41:43+00:00"
+        },
+        {
+            "name": "sebastian/version",
+            "version": "1.0.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/version.git",
+                "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
+                "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
+                "shasum": ""
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+            "homepage": "https://github.com/sebastianbergmann/version",
+            "time": "2015-06-21T13:59:46+00:00"
+        },
+        {
+            "name": "symfony/polyfill-ctype",
+            "version": "v1.13.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-ctype.git",
+                "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f8f0b461be3385e56d6de3dbb5a0df24c0c275e3",
+                "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "suggest": {
+                "ext-ctype": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.13-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Ctype\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Gert de Pagter",
+                    "email": "BackEndTea@gmail.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for ctype functions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "ctype",
+                "polyfill",
+                "portable"
+            ],
+            "time": "2019-11-27T13:56:44+00:00"
+        },
+        {
+            "name": "symfony/yaml",
+            "version": "v2.8.52",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/yaml.git",
+                "reference": "02c1859112aa779d9ab394ae4f3381911d84052b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/yaml/zipball/02c1859112aa779d9ab394ae4f3381911d84052b",
+                "reference": "02c1859112aa779d9ab394ae4f3381911d84052b",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.9",
+                "symfony/polyfill-ctype": "~1.8"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.8-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Yaml\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony Yaml Component",
+            "homepage": "https://symfony.com",
+            "time": "2018-11-11T11:18:13+00:00"
+        }
+    ],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": [],
+    "prefer-stable": true,
+    "prefer-lowest": false,
+    "platform": {
+        "php": ">=5.4",
+        "ext-json": "*",
+        "ext-mbstring": "*"
+    },
+    "platform-dev": [],
+    "platform-overrides": {
+        "php": "5.4.45"
+    }
+}
diff --git a/swaggest/json-schema/spec/json-schema-draft6.json b/swaggest/json-schema/spec/json-schema-draft6.json
new file mode 100644
index 0000000..5656240
--- /dev/null
+++ b/swaggest/json-schema/spec/json-schema-draft6.json
@@ -0,0 +1,154 @@
+{
+    "$schema": "http://json-schema.org/draft-06/schema#",
+    "$id": "http://json-schema.org/draft-06/schema#",
+    "title": "Core schema meta-schema",
+    "definitions": {
+        "schemaArray": {
+            "type": "array",
+            "minItems": 1,
+            "items": { "$ref": "#" }
+        },
+        "nonNegativeInteger": {
+            "type": "integer",
+            "minimum": 0
+        },
+        "nonNegativeIntegerDefault0": {
+            "allOf": [
+                { "$ref": "#/definitions/nonNegativeInteger" },
+                { "default": 0 }
+            ]
+        },
+        "simpleTypes": {
+            "enum": [
+                "array",
+                "boolean",
+                "integer",
+                "null",
+                "number",
+                "object",
+                "string"
+            ]
+        },
+        "stringArray": {
+            "type": "array",
+            "items": { "type": "string" },
+            "uniqueItems": true,
+            "default": []
+        }
+    },
+    "type": ["object", "boolean"],
+    "properties": {
+        "$id": {
+            "type": "string",
+            "format": "uri-reference"
+        },
+        "$schema": {
+            "type": "string",
+            "format": "uri"
+        },
+        "$ref": {
+            "type": "string",
+            "format": "uri-reference"
+        },
+        "title": {
+            "type": "string"
+        },
+        "description": {
+            "type": "string"
+        },
+        "default": {},
+        "examples": {
+            "type": "array",
+            "items": {}
+        },
+        "multipleOf": {
+            "type": "number",
+            "exclusiveMinimum": 0
+        },
+        "maximum": {
+            "type": "number"
+        },
+        "exclusiveMaximum": {
+            "type": "number"
+        },
+        "minimum": {
+            "type": "number"
+        },
+        "exclusiveMinimum": {
+            "type": "number"
+        },
+        "maxLength": { "$ref": "#/definitions/nonNegativeInteger" },
+        "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+        "pattern": {
+            "type": "string",
+            "format": "regex"
+        },
+        "additionalItems": { "$ref": "#" },
+        "items": {
+            "anyOf": [
+                { "$ref": "#" },
+                { "$ref": "#/definitions/schemaArray" }
+            ],
+            "default": {}
+        },
+        "maxItems": { "$ref": "#/definitions/nonNegativeInteger" },
+        "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+        "uniqueItems": {
+            "type": "boolean",
+            "default": false
+        },
+        "contains": { "$ref": "#" },
+        "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" },
+        "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+        "required": { "$ref": "#/definitions/stringArray" },
+        "additionalProperties": { "$ref": "#" },
+        "definitions": {
+            "type": "object",
+            "additionalProperties": { "$ref": "#" },
+            "default": {}
+        },
+        "properties": {
+            "type": "object",
+            "additionalProperties": { "$ref": "#" },
+            "default": {}
+        },
+        "patternProperties": {
+            "type": "object",
+            "additionalProperties": { "$ref": "#" },
+            "default": {}
+        },
+        "dependencies": {
+            "type": "object",
+            "additionalProperties": {
+                "anyOf": [
+                    { "$ref": "#" },
+                    { "$ref": "#/definitions/stringArray" }
+                ]
+            }
+        },
+        "propertyNames": { "$ref": "#" },
+        "const": {},
+        "enum": {
+            "type": "array",
+            "minItems": 1,
+            "uniqueItems": true
+        },
+        "type": {
+            "anyOf": [
+                { "$ref": "#/definitions/simpleTypes" },
+                {
+                    "type": "array",
+                    "items": { "$ref": "#/definitions/simpleTypes" },
+                    "minItems": 1,
+                    "uniqueItems": true
+                }
+            ]
+        },
+        "format": { "type": "string" },
+        "allOf": { "$ref": "#/definitions/schemaArray" },
+        "anyOf": { "$ref": "#/definitions/schemaArray" },
+        "oneOf": { "$ref": "#/definitions/schemaArray" },
+        "not": { "$ref": "#" }
+    },
+    "default": {}
+}
diff --git a/swaggest/json-schema/spec/json-schema-draft7.json b/swaggest/json-schema/spec/json-schema-draft7.json
new file mode 100644
index 0000000..5bee90e
--- /dev/null
+++ b/swaggest/json-schema/spec/json-schema-draft7.json
@@ -0,0 +1,168 @@
+{
+    "$schema": "http://json-schema.org/draft-07/schema#",
+    "$id": "http://json-schema.org/draft-07/schema#",
+    "title": "Core schema meta-schema",
+    "definitions": {
+        "schemaArray": {
+            "type": "array",
+            "minItems": 1,
+            "items": { "$ref": "#" }
+        },
+        "nonNegativeInteger": {
+            "type": "integer",
+            "minimum": 0
+        },
+        "nonNegativeIntegerDefault0": {
+            "allOf": [
+                { "$ref": "#/definitions/nonNegativeInteger" },
+                { "default": 0 }
+            ]
+        },
+        "simpleTypes": {
+            "enum": [
+                "array",
+                "boolean",
+                "integer",
+                "null",
+                "number",
+                "object",
+                "string"
+            ]
+        },
+        "stringArray": {
+            "type": "array",
+            "items": { "type": "string" },
+            "uniqueItems": true,
+            "default": []
+        }
+    },
+    "type": ["object", "boolean"],
+    "properties": {
+        "$id": {
+            "type": "string",
+            "format": "uri-reference"
+        },
+        "$schema": {
+            "type": "string",
+            "format": "uri"
+        },
+        "$ref": {
+            "type": "string",
+            "format": "uri-reference"
+        },
+        "$comment": {
+            "type": "string"
+        },
+        "title": {
+            "type": "string"
+        },
+        "description": {
+            "type": "string"
+        },
+        "default": true,
+        "readOnly": {
+            "type": "boolean",
+            "default": false
+        },
+        "examples": {
+            "type": "array",
+            "items": true
+        },
+        "multipleOf": {
+            "type": "number",
+            "exclusiveMinimum": 0
+        },
+        "maximum": {
+            "type": "number"
+        },
+        "exclusiveMaximum": {
+            "type": "number"
+        },
+        "minimum": {
+            "type": "number"
+        },
+        "exclusiveMinimum": {
+            "type": "number"
+        },
+        "maxLength": { "$ref": "#/definitions/nonNegativeInteger" },
+        "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+        "pattern": {
+            "type": "string",
+            "format": "regex"
+        },
+        "additionalItems": { "$ref": "#" },
+        "items": {
+            "anyOf": [
+                { "$ref": "#" },
+                { "$ref": "#/definitions/schemaArray" }
+            ],
+            "default": true
+        },
+        "maxItems": { "$ref": "#/definitions/nonNegativeInteger" },
+        "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+        "uniqueItems": {
+            "type": "boolean",
+            "default": false
+        },
+        "contains": { "$ref": "#" },
+        "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" },
+        "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
+        "required": { "$ref": "#/definitions/stringArray" },
+        "additionalProperties": { "$ref": "#" },
+        "definitions": {
+            "type": "object",
+            "additionalProperties": { "$ref": "#" },
+            "default": {}
+        },
+        "properties": {
+            "type": "object",
+            "additionalProperties": { "$ref": "#" },
+            "default": {}
+        },
+        "patternProperties": {
+            "type": "object",
+            "additionalProperties": { "$ref": "#" },
+            "propertyNames": { "format": "regex" },
+            "default": {}
+        },
+        "dependencies": {
+            "type": "object",
+            "additionalProperties": {
+                "anyOf": [
+                    { "$ref": "#" },
+                    { "$ref": "#/definitions/stringArray" }
+                ]
+            }
+        },
+        "propertyNames": { "$ref": "#" },
+        "const": true,
+        "enum": {
+            "type": "array",
+            "items": true,
+            "minItems": 1,
+            "uniqueItems": true
+        },
+        "type": {
+            "anyOf": [
+                { "$ref": "#/definitions/simpleTypes" },
+                {
+                    "type": "array",
+                    "items": { "$ref": "#/definitions/simpleTypes" },
+                    "minItems": 1,
+                    "uniqueItems": true
+                }
+            ]
+        },
+        "format": { "type": "string" },
+        "contentMediaType": { "type": "string" },
+        "contentEncoding": { "type": "string" },
+        "if": {"$ref": "#"},
+        "then": {"$ref": "#"},
+        "else": {"$ref": "#"},
+        "allOf": { "$ref": "#/definitions/schemaArray" },
+        "anyOf": { "$ref": "#/definitions/schemaArray" },
+        "oneOf": { "$ref": "#/definitions/schemaArray" },
+        "not": { "$ref": "#" }
+    },
+    "default": true
+}
diff --git a/swaggest/json-schema/spec/json-schema.json b/swaggest/json-schema/spec/json-schema.json
new file mode 100644
index 0000000..3d81b55
--- /dev/null
+++ b/swaggest/json-schema/spec/json-schema.json
@@ -0,0 +1,152 @@
+{
+    "id": "http://json-schema.org/draft-04/schema#",
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "description": "Core schema meta-schema",
+    "definitions": {
+        "schemaArray": {
+            "type": "array",
+            "minItems": 1,
+            "items": { "$ref": "#" }
+        },
+        "positiveInteger": {
+            "type": "integer",
+            "minimum": 0
+        },
+        "positiveIntegerDefault0": {
+            "allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ]
+        },
+        "simpleTypes": {
+            "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
+        },
+        "stringArray": {
+            "type": "array",
+            "items": { "type": "string" },
+            "minItems": 1,
+            "uniqueItems": true
+        }
+    },
+    "type": "object",
+    "properties": {
+        "id": {
+            "type": "string",
+            "format": "uri"
+        },
+        "$schema": {
+            "type": "string",
+            "format": "uri"
+        },
+        "title": {
+            "type": "string"
+        },
+        "description": {
+            "type": "string"
+        },
+        "default": {},
+        "multipleOf": {
+            "type": "number",
+            "minimum": 0,
+            "exclusiveMinimum": true
+        },
+        "maximum": {
+            "type": "number"
+        },
+        "exclusiveMaximum": {
+            "type": "boolean",
+            "default": false
+        },
+        "minimum": {
+            "type": "number"
+        },
+        "exclusiveMinimum": {
+            "type": "boolean",
+            "default": false
+        },
+        "maxLength": { "$ref": "#/definitions/positiveInteger" },
+        "minLength": { "$ref": "#/definitions/positiveIntegerDefault0" },
+        "pattern": {
+            "type": "string",
+            "format": "regex"
+        },
+        "additionalItems": {
+            "anyOf": [
+                { "type": "boolean" },
+                { "$ref": "#" }
+            ],
+            "default": {}
+        },
+        "items": {
+            "anyOf": [
+                { "$ref": "#" },
+                { "$ref": "#/definitions/schemaArray" }
+            ],
+            "default": {}
+        },
+        "maxItems": { "$ref": "#/definitions/positiveInteger" },
+        "minItems": { "$ref": "#/definitions/positiveIntegerDefault0" },
+        "uniqueItems": {
+            "type": "boolean",
+            "default": false
+        },
+        "maxProperties": { "$ref": "#/definitions/positiveInteger" },
+        "minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" },
+        "required": { "$ref": "#/definitions/stringArray" },
+        "additionalProperties": {
+            "anyOf": [
+                { "type": "boolean" },
+                { "$ref": "#" }
+            ],
+            "default": {}
+        },
+        "definitions": {
+            "type": "object",
+            "additionalProperties": { "$ref": "#" },
+            "default": {}
+        },
+        "properties": {
+            "type": "object",
+            "additionalProperties": { "$ref": "#" },
+            "default": {}
+        },
+        "patternProperties": {
+            "type": "object",
+            "additionalProperties": { "$ref": "#" },
+            "default": {}
+        },
+        "dependencies": {
+            "type": "object",
+            "additionalProperties": {
+                "anyOf": [
+                    { "$ref": "#" },
+                    { "$ref": "#/definitions/stringArray" }
+                ]
+            }
+        },
+        "enum": {
+            "type": "array",
+            "minItems": 1,
+            "uniqueItems": true
+        },
+        "type": {
+            "anyOf": [
+                { "$ref": "#/definitions/simpleTypes" },
+                {
+                    "type": "array",
+                    "items": { "$ref": "#/definitions/simpleTypes" },
+                    "minItems": 1,
+                    "uniqueItems": true
+                }
+            ]
+        },
+        "format": { "type": "string" },
+        "$ref": {"type": "string"},
+        "allOf": { "$ref": "#/definitions/schemaArray" },
+        "anyOf": { "$ref": "#/definitions/schemaArray" },
+        "oneOf": { "$ref": "#/definitions/schemaArray" },
+        "not": { "$ref": "#" }
+    },
+    "dependencies": {
+        "exclusiveMaximum": [ "maximum" ],
+        "exclusiveMinimum": [ "minimum" ]
+    },
+    "default": {}
+}
diff --git a/swaggest/json-schema/spec/petstore-swagger.json b/swaggest/json-schema/spec/petstore-swagger.json
new file mode 100644
index 0000000..65f39d5
--- /dev/null
+++ b/swaggest/json-schema/spec/petstore-swagger.json
@@ -0,0 +1,1035 @@
+{
+  "swagger": "2.0",
+  "info": {
+    "description": "This is a sample server Petstore server.  You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/).  For this sample, you can use the api key `special-key` to test the authorization filters.",
+    "version": "1.0.0",
+    "title": "Swagger Petstore",
+    "termsOfService": "http://swagger.io/terms/",
+    "contact": {
+      "email": "apiteam@swagger.io"
+    },
+    "license": {
+      "name": "Apache 2.0",
+      "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
+    }
+  },
+  "host": "petstore.swagger.io",
+  "basePath": "/v2",
+  "tags": [
+    {
+      "name": "pet",
+      "description": "Everything about your Pets",
+      "externalDocs": {
+        "description": "Find out more",
+        "url": "http://swagger.io"
+      }
+    },
+    {
+      "name": "store",
+      "description": "Access to Petstore orders"
+    },
+    {
+      "name": "user",
+      "description": "Operations about user",
+      "externalDocs": {
+        "description": "Find out more about our store",
+        "url": "http://swagger.io"
+      }
+    }
+  ],
+  "schemes": [
+    "http"
+  ],
+  "paths": {
+    "/pet": {
+      "post": {
+        "tags": [
+          "pet"
+        ],
+        "summary": "Add a new pet to the store",
+        "description": "",
+        "operationId": "addPet",
+        "consumes": [
+          "application/json",
+          "application/xml"
+        ],
+        "produces": [
+          "application/xml",
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "in": "body",
+            "name": "body",
+            "description": "Pet object that needs to be added to the store",
+            "required": true,
+            "schema": {
+              "$ref": "#/definitions/Pet"
+            }
+          }
+        ],
+        "responses": {
+          "405": {
+            "description": "Invalid input"
+          }
+        },
+        "security": [
+          {
+            "petstore_auth": [
+              "write:pets",
+              "read:pets"
+            ]
+          }
+        ]
+      },
+      "put": {
+        "tags": [
+          "pet"
+        ],
+        "summary": "Update an existing pet",
+        "description": "",
+        "operationId": "updatePet",
+        "consumes": [
+          "application/json",
+          "application/xml"
+        ],
+        "produces": [
+          "application/xml",
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "in": "body",
+            "name": "body",
+            "description": "Pet object that needs to be added to the store",
+            "required": true,
+            "schema": {
+              "$ref": "#/definitions/Pet"
+            }
+          }
+        ],
+        "responses": {
+          "400": {
+            "description": "Invalid ID supplied"
+          },
+          "404": {
+            "description": "Pet not found"
+          },
+          "405": {
+            "description": "Validation exception"
+          }
+        },
+        "security": [
+          {
+            "petstore_auth": [
+              "write:pets",
+              "read:pets"
+            ]
+          }
+        ]
+      }
+    },
+    "/pet/findByStatus": {
+      "get": {
+        "tags": [
+          "pet"
+        ],
+        "summary": "Finds Pets by status",
+        "description": "Multiple status values can be provided with comma separated strings",
+        "operationId": "findPetsByStatus",
+        "produces": [
+          "application/xml",
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "name": "status",
+            "in": "query",
+            "description": "Status values that need to be considered for filter",
+            "required": true,
+            "type": "array",
+            "items": {
+              "type": "string",
+              "enum": [
+                "available",
+                "pending",
+                "sold"
+              ],
+              "default": "available"
+            },
+            "collectionFormat": "multi"
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "successful operation",
+            "schema": {
+              "type": "array",
+              "items": {
+                "$ref": "#/definitions/Pet"
+              }
+            }
+          },
+          "400": {
+            "description": "Invalid status value"
+          }
+        },
+        "security": [
+          {
+            "petstore_auth": [
+              "write:pets",
+              "read:pets"
+            ]
+          }
+        ]
+      }
+    },
+    "/pet/findByTags": {
+      "get": {
+        "tags": [
+          "pet"
+        ],
+        "summary": "Finds Pets by tags",
+        "description": "Muliple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.",
+        "operationId": "findPetsByTags",
+        "produces": [
+          "application/xml",
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "name": "tags",
+            "in": "query",
+            "description": "Tags to filter by",
+            "required": true,
+            "type": "array",
+            "items": {
+              "type": "string"
+            },
+            "collectionFormat": "multi"
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "successful operation",
+            "schema": {
+              "type": "array",
+              "items": {
+                "$ref": "#/definitions/Pet"
+              }
+            }
+          },
+          "400": {
+            "description": "Invalid tag value"
+          }
+        },
+        "security": [
+          {
+            "petstore_auth": [
+              "write:pets",
+              "read:pets"
+            ]
+          }
+        ],
+        "deprecated": true
+      }
+    },
+    "/pet/{petId}": {
+      "get": {
+        "tags": [
+          "pet"
+        ],
+        "summary": "Find pet by ID",
+        "description": "Returns a single pet",
+        "operationId": "getPetById",
+        "produces": [
+          "application/xml",
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "name": "petId",
+            "in": "path",
+            "description": "ID of pet to return",
+            "required": true,
+            "type": "integer",
+            "format": "int64"
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "successful operation",
+            "schema": {
+              "$ref": "#/definitions/Pet"
+            }
+          },
+          "400": {
+            "description": "Invalid ID supplied"
+          },
+          "404": {
+            "description": "Pet not found"
+          }
+        },
+        "security": [
+          {
+            "api_key": []
+          }
+        ]
+      },
+      "post": {
+        "tags": [
+          "pet"
+        ],
+        "summary": "Updates a pet in the store with form data",
+        "description": "",
+        "operationId": "updatePetWithForm",
+        "consumes": [
+          "application/x-www-form-urlencoded"
+        ],
+        "produces": [
+          "application/xml",
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "name": "petId",
+            "in": "path",
+            "description": "ID of pet that needs to be updated",
+            "required": true,
+            "type": "integer",
+            "format": "int64"
+          },
+          {
+            "name": "name",
+            "in": "formData",
+            "description": "Updated name of the pet",
+            "required": false,
+            "type": "string"
+          },
+          {
+            "name": "status",
+            "in": "formData",
+            "description": "Updated status of the pet",
+            "required": false,
+            "type": "string"
+          }
+        ],
+        "responses": {
+          "405": {
+            "description": "Invalid input"
+          }
+        },
+        "security": [
+          {
+            "petstore_auth": [
+              "write:pets",
+              "read:pets"
+            ]
+          }
+        ]
+      },
+      "delete": {
+        "tags": [
+          "pet"
+        ],
+        "summary": "Deletes a pet",
+        "description": "",
+        "operationId": "deletePet",
+        "produces": [
+          "application/xml",
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "name": "api_key",
+            "in": "header",
+            "required": false,
+            "type": "string"
+          },
+          {
+            "name": "petId",
+            "in": "path",
+            "description": "Pet id to delete",
+            "required": true,
+            "type": "integer",
+            "format": "int64"
+          }
+        ],
+        "responses": {
+          "400": {
+            "description": "Invalid ID supplied"
+          },
+          "404": {
+            "description": "Pet not found"
+          }
+        },
+        "security": [
+          {
+            "petstore_auth": [
+              "write:pets",
+              "read:pets"
+            ]
+          }
+        ]
+      }
+    },
+    "/pet/{petId}/uploadImage": {
+      "post": {
+        "tags": [
+          "pet"
+        ],
+        "summary": "uploads an image",
+        "description": "",
+        "operationId": "uploadFile",
+        "consumes": [
+          "multipart/form-data"
+        ],
+        "produces": [
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "name": "petId",
+            "in": "path",
+            "description": "ID of pet to update",
+            "required": true,
+            "type": "integer",
+            "format": "int64"
+          },
+          {
+            "name": "additionalMetadata",
+            "in": "formData",
+            "description": "Additional data to pass to server",
+            "required": false,
+            "type": "string"
+          },
+          {
+            "name": "file",
+            "in": "formData",
+            "description": "file to upload",
+            "required": false,
+            "type": "file"
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "successful operation",
+            "schema": {
+              "$ref": "#/definitions/ApiResponse"
+            }
+          }
+        },
+        "security": [
+          {
+            "petstore_auth": [
+              "write:pets",
+              "read:pets"
+            ]
+          }
+        ]
+      }
+    },
+    "/store/inventory": {
+      "get": {
+        "tags": [
+          "store"
+        ],
+        "summary": "Returns pet inventories by status",
+        "description": "Returns a map of status codes to quantities",
+        "operationId": "getInventory",
+        "produces": [
+          "application/json"
+        ],
+        "parameters": [],
+        "responses": {
+          "200": {
+            "description": "successful operation",
+            "schema": {
+              "type": "object",
+              "additionalProperties": {
+                "type": "integer",
+                "format": "int32"
+              }
+            }
+          }
+        },
+        "security": [
+          {
+            "api_key": []
+          }
+        ]
+      }
+    },
+    "/store/order": {
+      "post": {
+        "tags": [
+          "store"
+        ],
+        "summary": "Place an order for a pet",
+        "description": "",
+        "operationId": "placeOrder",
+        "produces": [
+          "application/xml",
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "in": "body",
+            "name": "body",
+            "description": "order placed for purchasing the pet",
+            "required": true,
+            "schema": {
+              "$ref": "#/definitions/Order"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "successful operation",
+            "schema": {
+              "$ref": "#/definitions/Order"
+            }
+          },
+          "400": {
+            "description": "Invalid Order"
+          }
+        }
+      }
+    },
+    "/store/order/{orderId}": {
+      "get": {
+        "tags": [
+          "store"
+        ],
+        "summary": "Find purchase order by ID",
+        "description": "For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions",
+        "operationId": "getOrderById",
+        "produces": [
+          "application/xml",
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "name": "orderId",
+            "in": "path",
+            "description": "ID of pet that needs to be fetched",
+            "required": true,
+            "type": "integer",
+            "maximum": 10.0,
+            "minimum": 1.0,
+            "format": "int64"
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "successful operation",
+            "schema": {
+              "$ref": "#/definitions/Order"
+            }
+          },
+          "400": {
+            "description": "Invalid ID supplied"
+          },
+          "404": {
+            "description": "Order not found"
+          }
+        }
+      },
+      "delete": {
+        "tags": [
+          "store"
+        ],
+        "summary": "Delete purchase order by ID",
+        "description": "For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors",
+        "operationId": "deleteOrder",
+        "produces": [
+          "application/xml",
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "name": "orderId",
+            "in": "path",
+            "description": "ID of the order that needs to be deleted",
+            "required": true,
+            "type": "integer",
+            "minimum": 1.0,
+            "format": "int64"
+          }
+        ],
+        "responses": {
+          "400": {
+            "description": "Invalid ID supplied"
+          },
+          "404": {
+            "description": "Order not found"
+          }
+        }
+      }
+    },
+    "/user": {
+      "post": {
+        "tags": [
+          "user"
+        ],
+        "summary": "Create user",
+        "description": "This can only be done by the logged in user.",
+        "operationId": "createUser",
+        "produces": [
+          "application/xml",
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "in": "body",
+            "name": "body",
+            "description": "Created user object",
+            "required": true,
+            "schema": {
+              "$ref": "#/definitions/User"
+            }
+          }
+        ],
+        "responses": {
+          "default": {
+            "description": "successful operation"
+          }
+        }
+      }
+    },
+    "/user/createWithArray": {
+      "post": {
+        "tags": [
+          "user"
+        ],
+        "summary": "Creates list of users with given input array",
+        "description": "",
+        "operationId": "createUsersWithArrayInput",
+        "produces": [
+          "application/xml",
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "in": "body",
+            "name": "body",
+            "description": "List of user object",
+            "required": true,
+            "schema": {
+              "type": "array",
+              "items": {
+                "$ref": "#/definitions/User"
+              }
+            }
+          }
+        ],
+        "responses": {
+          "default": {
+            "description": "successful operation"
+          }
+        }
+      }
+    },
+    "/user/createWithList": {
+      "post": {
+        "tags": [
+          "user"
+        ],
+        "summary": "Creates list of users with given input array",
+        "description": "",
+        "operationId": "createUsersWithListInput",
+        "produces": [
+          "application/xml",
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "in": "body",
+            "name": "body",
+            "description": "List of user object",
+            "required": true,
+            "schema": {
+              "type": "array",
+              "items": {
+                "$ref": "#/definitions/User"
+              }
+            }
+          }
+        ],
+        "responses": {
+          "default": {
+            "description": "successful operation"
+          }
+        }
+      }
+    },
+    "/user/login": {
+      "get": {
+        "tags": [
+          "user"
+        ],
+        "summary": "Logs user into the system",
+        "description": "",
+        "operationId": "loginUser",
+        "produces": [
+          "application/xml",
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "name": "username",
+            "in": "query",
+            "description": "The user name for login",
+            "required": true,
+            "type": "string"
+          },
+          {
+            "name": "password",
+            "in": "query",
+            "description": "The password for login in clear text",
+            "required": true,
+            "type": "string"
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "successful operation",
+            "schema": {
+              "type": "string"
+            },
+            "headers": {
+              "X-Rate-Limit": {
+                "type": "integer",
+                "format": "int32",
+                "description": "calls per hour allowed by the user"
+              },
+              "X-Expires-After": {
+                "type": "string",
+                "format": "date-time",
+                "description": "date in UTC when token expires"
+              }
+            }
+          },
+          "400": {
+            "description": "Invalid username/password supplied"
+          }
+        }
+      }
+    },
+    "/user/logout": {
+      "get": {
+        "tags": [
+          "user"
+        ],
+        "summary": "Logs out current logged in user session",
+        "description": "",
+        "operationId": "logoutUser",
+        "produces": [
+          "application/xml",
+          "application/json"
+        ],
+        "parameters": [],
+        "responses": {
+          "default": {
+            "description": "successful operation"
+          }
+        }
+      }
+    },
+    "/user/{username}": {
+      "get": {
+        "tags": [
+          "user"
+        ],
+        "summary": "Get user by user name",
+        "description": "",
+        "operationId": "getUserByName",
+        "produces": [
+          "application/xml",
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "name": "username",
+            "in": "path",
+            "description": "The name that needs to be fetched. Use user1 for testing. ",
+            "required": true,
+            "type": "string"
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "successful operation",
+            "schema": {
+              "$ref": "#/definitions/User"
+            }
+          },
+          "400": {
+            "description": "Invalid username supplied"
+          },
+          "404": {
+            "description": "User not found"
+          }
+        }
+      },
+      "put": {
+        "tags": [
+          "user"
+        ],
+        "summary": "Updated user",
+        "description": "This can only be done by the logged in user.",
+        "operationId": "updateUser",
+        "produces": [
+          "application/xml",
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "name": "username",
+            "in": "path",
+            "description": "name that need to be updated",
+            "required": true,
+            "type": "string"
+          },
+          {
+            "in": "body",
+            "name": "body",
+            "description": "Updated user object",
+            "required": true,
+            "schema": {
+              "$ref": "#/definitions/User"
+            }
+          }
+        ],
+        "responses": {
+          "400": {
+            "description": "Invalid user supplied"
+          },
+          "404": {
+            "description": "User not found"
+          }
+        }
+      },
+      "delete": {
+        "tags": [
+          "user"
+        ],
+        "summary": "Delete user",
+        "description": "This can only be done by the logged in user.",
+        "operationId": "deleteUser",
+        "produces": [
+          "application/xml",
+          "application/json"
+        ],
+        "parameters": [
+          {
+            "name": "username",
+            "in": "path",
+            "description": "The name that needs to be deleted",
+            "required": true,
+            "type": "string"
+          }
+        ],
+        "responses": {
+          "400": {
+            "description": "Invalid username supplied"
+          },
+          "404": {
+            "description": "User not found"
+          }
+        }
+      }
+    }
+  },
+  "securityDefinitions": {
+    "petstore_auth": {
+      "type": "oauth2",
+      "authorizationUrl": "http://petstore.swagger.io/oauth/dialog",
+      "flow": "implicit",
+      "scopes": {
+        "write:pets": "modify pets in your account",
+        "read:pets": "read your pets"
+      }
+    },
+    "api_key": {
+      "type": "apiKey",
+      "name": "api_key",
+      "in": "header"
+    }
+  },
+  "definitions": {
+    "Order": {
+      "type": "object",
+      "properties": {
+        "id": {
+          "type": "integer",
+          "format": "int64"
+        },
+        "petId": {
+          "type": "integer",
+          "format": "int64"
+        },
+        "quantity": {
+          "type": "integer",
+          "format": "int32"
+        },
+        "shipDate": {
+          "type": "string",
+          "format": "date-time"
+        },
+        "status": {
+          "type": "string",
+          "description": "Order Status",
+          "enum": [
+            "placed",
+            "approved",
+            "delivered"
+          ]
+        },
+        "complete": {
+          "type": "boolean",
+          "default": false
+        }
+      },
+      "xml": {
+        "name": "Order"
+      }
+    },
+    "Category": {
+      "type": "object",
+      "properties": {
+        "id": {
+          "type": "integer",
+          "format": "int64"
+        },
+        "name": {
+          "type": "string"
+        }
+      },
+      "xml": {
+        "name": "Category"
+      }
+    },
+    "User": {
+      "type": "object",
+      "properties": {
+        "id": {
+          "type": "integer",
+          "format": "int64"
+        },
+        "username": {
+          "type": "string"
+        },
+        "firstName": {
+          "type": "string"
+        },
+        "lastName": {
+          "type": "string"
+        },
+        "email": {
+          "type": "string"
+        },
+        "password": {
+          "type": "string"
+        },
+        "phone": {
+          "type": "string"
+        },
+        "userStatus": {
+          "type": "integer",
+          "format": "int32",
+          "description": "User Status"
+        }
+      },
+      "xml": {
+        "name": "User"
+      }
+    },
+    "Tag": {
+      "type": "object",
+      "properties": {
+        "id": {
+          "type": "integer",
+          "format": "int64"
+        },
+        "name": {
+          "type": "string"
+        }
+      },
+      "xml": {
+        "name": "Tag"
+      }
+    },
+    "Pet": {
+      "type": "object",
+      "required": [
+        "name",
+        "photoUrls"
+      ],
+      "properties": {
+        "id": {
+          "type": "integer",
+          "format": "int64"
+        },
+        "category": {
+          "$ref": "#/definitions/Category"
+        },
+        "name": {
+          "type": "string",
+          "example": "doggie"
+        },
+        "photoUrls": {
+          "type": "array",
+          "xml": {
+            "name": "photoUrl",
+            "wrapped": true
+          },
+          "items": {
+            "type": "string"
+          }
+        },
+        "tags": {
+          "type": "array",
+          "xml": {
+            "name": "tag",
+            "wrapped": true
+          },
+          "items": {
+            "$ref": "#/definitions/Tag"
+          }
+        },
+        "status": {
+          "type": "string",
+          "description": "pet status in the store",
+          "enum": [
+            "available",
+            "pending",
+            "sold"
+          ]
+        }
+      },
+      "xml": {
+        "name": "Pet"
+      }
+    },
+    "ApiResponse": {
+      "type": "object",
+      "properties": {
+        "code": {
+          "type": "integer",
+          "format": "int32"
+        },
+        "type": {
+          "type": "string"
+        },
+        "message": {
+          "type": "string"
+        }
+      }
+    }
+  },
+  "externalDocs": {
+    "description": "Find out more about Swagger",
+    "url": "http://swagger.io"
+  }
+}
\ No newline at end of file
diff --git a/swaggest/json-schema/spec/swagger-schema.json b/swaggest/json-schema/spec/swagger-schema.json
new file mode 100644
index 0000000..e34bfa0
--- /dev/null
+++ b/swaggest/json-schema/spec/swagger-schema.json
@@ -0,0 +1,1608 @@
+{
+  "title": "A JSON Schema for Swagger 2.0 API.",
+  "id": "http://swagger.io/v2/schema.json#",
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "type": "object",
+  "required": [
+    "swagger",
+    "info",
+    "paths"
+  ],
+  "additionalProperties": false,
+  "patternProperties": {
+    "^x-": {
+      "$ref": "#/definitions/vendorExtension"
+    }
+  },
+  "properties": {
+    "swagger": {
+      "type": "string",
+      "enum": [
+        "2.0"
+      ],
+      "description": "The Swagger version of this document."
+    },
+    "info": {
+      "$ref": "#/definitions/info"
+    },
+    "host": {
+      "type": "string",
+      "pattern": "^[^{}/ :\\\\]+(?::\\d+)?$",
+      "description": "The host (name or ip) of the API. Example: 'swagger.io'"
+    },
+    "basePath": {
+      "type": "string",
+      "pattern": "^/",
+      "description": "The base path to the API. Example: '/api'."
+    },
+    "schemes": {
+      "$ref": "#/definitions/schemesList"
+    },
+    "consumes": {
+      "description": "A list of MIME types accepted by the API.",
+      "allOf": [
+        {
+          "$ref": "#/definitions/mediaTypeList"
+        }
+      ]
+    },
+    "produces": {
+      "description": "A list of MIME types the API can produce.",
+      "allOf": [
+        {
+          "$ref": "#/definitions/mediaTypeList"
+        }
+      ]
+    },
+    "paths": {
+      "$ref": "#/definitions/paths"
+    },
+    "definitions": {
+      "$ref": "#/definitions/definitions"
+    },
+    "parameters": {
+      "$ref": "#/definitions/parameterDefinitions"
+    },
+    "responses": {
+      "$ref": "#/definitions/responseDefinitions"
+    },
+    "security": {
+      "$ref": "#/definitions/security"
+    },
+    "securityDefinitions": {
+      "$ref": "#/definitions/securityDefinitions"
+    },
+    "tags": {
+      "type": "array",
+      "items": {
+        "$ref": "#/definitions/tag"
+      },
+      "uniqueItems": true
+    },
+    "externalDocs": {
+      "$ref": "#/definitions/externalDocs"
+    }
+  },
+  "definitions": {
+    "info": {
+      "type": "object",
+      "description": "General information about the API.",
+      "required": [
+        "version",
+        "title"
+      ],
+      "additionalProperties": false,
+      "patternProperties": {
+        "^x-": {
+          "$ref": "#/definitions/vendorExtension"
+        }
+      },
+      "properties": {
+        "title": {
+          "type": "string",
+          "description": "A unique and precise title of the API."
+        },
+        "version": {
+          "type": "string",
+          "description": "A semantic version number of the API."
+        },
+        "description": {
+          "type": "string",
+          "description": "A longer description of the API. Should be different from the title.  GitHub Flavored Markdown is allowed."
+        },
+        "termsOfService": {
+          "type": "string",
+          "description": "The terms of service for the API."
+        },
+        "contact": {
+          "$ref": "#/definitions/contact"
+        },
+        "license": {
+          "$ref": "#/definitions/license"
+        }
+      }
+    },
+    "contact": {
+      "type": "object",
+      "description": "Contact information for the owners of the API.",
+      "additionalProperties": false,
+      "properties": {
+        "name": {
+          "type": "string",
+          "description": "The identifying name of the contact person/organization."
+        },
+        "url": {
+          "type": "string",
+          "description": "The URL pointing to the contact information.",
+          "format": "uri"
+        },
+        "email": {
+          "type": "string",
+          "description": "The email address of the contact person/organization.",
+          "format": "email"
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+          "$ref": "#/definitions/vendorExtension"
+        }
+      }
+    },
+    "license": {
+      "type": "object",
+      "required": [
+        "name"
+      ],
+      "additionalProperties": false,
+      "properties": {
+        "name": {
+          "type": "string",
+          "description": "The name of the license type. It's encouraged to use an OSI compatible license."
+        },
+        "url": {
+          "type": "string",
+          "description": "The URL pointing to the license.",
+          "format": "uri"
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+          "$ref": "#/definitions/vendorExtension"
+        }
+      }
+    },
+    "paths": {
+      "type": "object",
+      "description": "Relative paths to the individual endpoints. They must be relative to the 'basePath'.",
+      "patternProperties": {
+        "^x-": {
+          "$ref": "#/definitions/vendorExtension"
+        },
+        "^/": {
+          "$ref": "#/definitions/pathItem"
+        }
+      },
+      "additionalProperties": false
+    },
+    "definitions": {
+      "type": "object",
+      "additionalProperties": {
+        "$ref": "#/definitions/schema"
+      },
+      "description": "One or more JSON objects describing the schemas being consumed and produced by the API."
+    },
+    "parameterDefinitions": {
+      "type": "object",
+      "additionalProperties": {
+        "$ref": "#/definitions/parameter"
+      },
+      "description": "One or more JSON representations for parameters"
+    },
+    "responseDefinitions": {
+      "type": "object",
+      "additionalProperties": {
+        "$ref": "#/definitions/response"
+      },
+      "description": "One or more JSON representations for parameters"
+    },
+    "externalDocs": {
+      "type": "object",
+      "additionalProperties": false,
+      "description": "information about external documentation",
+      "required": [
+        "url"
+      ],
+      "properties": {
+        "description": {
+          "type": "string"
+        },
+        "url": {
+          "type": "string",
+          "format": "uri"
+        }
+      },
+      "patternProperties": {
+        "^x-": {
+          "$ref": "#/definitions/vendorExtension"
+        }
+      }
+    },
+    "examples": {
+      "type": "object",
+      "additionalProperties": true
+    },
+    "mimeType": {
+      "type": "string",
+      "description": "The MIME type of the HTTP message."
+    },
+    "operation": {
+      "type": "object",
+      "required": [
+        "responses"
+      ],
+      "additionalProperties": false,
+      "patternProperties": {
+        "^x-": {
+          "$ref": "#/definitions/vendorExtension"
+        }
+      },
+      "properties": {
+        "tags": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          },
+          "uniqueItems": true
+        },
+        "summary": {
+          "type": "string",
+          "description": "A brief summary of the operation."
+        },
+        "description": {
+          "type": "string",
+          "description": "A longer description of the operation, GitHub Flavored Markdown is allowed."
+        },
+        "externalDocs": {
+          "$ref": "#/definitions/externalDocs"
+        },
+        "operationId": {
+          "type": "string",
+          "description": "A unique identifier of the operation."
+        },
+        "produces": {
+          "description": "A list of MIME types the API can produce.",
+          "allOf": [
+            {
+              "$ref": "#/definitions/mediaTypeList"
+            }
+          ]
+        },
+        "consumes": {
+          "description": "A list of MIME types the API can consume.",
+          "allOf": [
+            {
+              "$ref": "#/definitions/mediaTypeList"
+            }
+          ]
+        },
+        "parameters": {
+          "$ref": "#/definitions/parametersList"
+        },
+        "responses": {
+          "$ref": "#/definitions/responses"
+        },
+        "schemes": {
+          "$ref": "#/definitions/schemesList"
+        },
+        "deprecated": {
+          "type": "boolean",
+          "default": false
+        },
+        "security": {
+          "$ref": "#/definitions/security"
+        }
+      }
+    },
+    "pathItem": {
+      "type": "object",
+      "additionalProperties": false,
+      "patternProperties": {
+        "^x-": {
+          "$ref": "#/definitions/vendorExtension"
+        }
+