diff --git a/clover-merge b/clover-merge
index b7ec767..a5773ea 100755
--- a/clover-merge
+++ b/clover-merge
@@ -2,8 +2,6 @@
execute();
diff --git a/spec/Accumulator.spec.php b/spec/Accumulator.spec.php
index 2fc44a2..40d67bb 100644
--- a/spec/Accumulator.spec.php
+++ b/spec/Accumulator.spec.php
@@ -5,7 +5,6 @@
use d0x2f\CloverMerge\Accumulator;
use d0x2f\CloverMerge\File;
use d0x2f\CloverMerge\Utilities;
-use d0x2f\CloverMerge\Metrics;
/**
* @phan-closure-scope \Kahlan\Scope
@@ -31,9 +30,6 @@
'test.php'
]);
- $metrics = $this->accumulator->getMetrics();
- expect($metrics)->toBeAnInstanceOf(Metrics::class);
-
$file = $result->get('test.php');
expect($file)->toBeAnInstanceOf(File::class);
diff --git a/spec/ClassT.spec.php b/spec/ClassT.spec.php
index a3997a2..6f844c3 100644
--- a/spec/ClassT.spec.php
+++ b/spec/ClassT.spec.php
@@ -3,7 +3,6 @@
namespace d0x2f\CloverMerge\Spec;
use d0x2f\CloverMerge\ClassT;
-use d0x2f\CloverMerge\Metrics;
/**
* @phan-closure-scope \Kahlan\Scope
@@ -12,11 +11,11 @@
describe('__construct', function () {
context('Receives a namespace and metrics.', function () {
beforeEach(function () {
- $this->metrics = new Metrics(new \Ds\Map([
+ $properties = new \Ds\Map([
'foo' => 'bar',
'baz' => 'fred'
- ]));
- $this->instance = new ClassT('Example\Namespace', $this->metrics);
+ ]);
+ $this->instance = new ClassT($properties);
});
it('produces a valid instance.', function () {
@@ -29,9 +28,7 @@
beforeEach(function () {
$xml_element = simplexml_load_string(
'
-
-
- '
+ '
);
assert($xml_element !== false);
$this->instance = ClassT::fromXML($xml_element);
@@ -41,44 +38,28 @@
expect($this->instance)->toBeAnInstanceOf(ClassT::class);
});
- it('has the correct namespace set.', function () {
- expect($this->instance->getNamespace())->toBe('Example\Namespace');
- });
-
- it('has the correct metrics set.', function () {
- expect($this->instance->getMetrics()->getProperties()->toArray())->toBe([
+ it('has the correct properties set.', function () {
+ expect($this->instance->getProperties()->toArray())->toBe([
+ 'name' => 'Example\Namespace\Class',
+ 'namespace' => 'Example\Namespace',
'foo' => 'bar',
'baz' => 'fred'
]);
});
});
});
- describe('getNamespace', function () {
- beforeEach(function () {
- $this->metrics = new Metrics(new \Ds\Map([
- 'foo' => 'bar',
- 'baz' => 'fred'
- ]));
- $this->instance = new ClassT('Example\Namespace', $this->metrics);
- $this->result = $this->instance->getNamespace();
- });
-
- it('returns the properties map.', function () {
- expect($this->result)->toBe('Example\Namespace');
- });
- });
- describe('getMetrics', function () {
+ describe('getProperties', function () {
beforeEach(function () {
- $this->metrics = new Metrics(new \Ds\Map([
+ $properties = new \Ds\Map([
'foo' => 'bar',
'baz' => 'fred'
- ]));
- $this->instance = new ClassT('Example\Namespace', $this->metrics);
- $this->result = $this->instance->getMetrics();
+ ]);
+ $instance = new ClassT($properties);
+ $this->result = $instance->getProperties();
});
it('returns the properties map.', function () {
- expect($this->result->getProperties()->toArray())->toBe([
+ expect($this->result->toArray())->toBe([
'foo' => 'bar',
'baz' => 'fred'
]);
diff --git a/spec/File.spec.php b/spec/File.spec.php
index 5aecb09..6c67fa7 100644
--- a/spec/File.spec.php
+++ b/spec/File.spec.php
@@ -4,6 +4,7 @@
use d0x2f\CloverMerge\File;
use d0x2f\CloverMerge\Utilities;
+use d0x2f\CloverMerge\Metrics;
/**
* @phan-closure-scope \Kahlan\Scope
@@ -30,8 +31,10 @@
-
-
+
+
+
+
');
@@ -54,19 +57,24 @@
$class = $classes->first();
expect($class->key)->toBe('Example\Namespace\Class');
- expect($class->value->getNamespace())->toBe('Example\Namespace');
+ expect($class->value->getProperties()->toArray())->toBe([
+ 'name' => 'Example\Namespace\Class',
+ 'namespace' => 'Example\Namespace'
+ ]);
});
it('has the correct lines set.', function () {
$lines = $this->instance->getLines();
- expect($lines)->toHaveLength(3);
+ expect($lines)->toHaveLength(5);
$keys = $lines->keys();
- expect($keys->toArray())->toBe([22, 28, 29]);
+ expect($keys->toArray())->toBe([22, 28, 29, 30, 31]);
expect($lines->get(22)->getCount())->toBe(1);
expect($lines->get(28)->getCount())->toBe(1);
expect($lines->get(29)->getCount())->toBe(0);
+ expect($lines->get(30)->getCount())->toBe(0);
+ expect($lines->get(31)->getCount())->toBe(1);
});
});
describe('Receives a XML element with errors.', function () {
@@ -99,6 +107,35 @@
});
});
});
+ describe('toXml', function () {
+ beforeEach(function () {
+ $xml_element = simplexml_load_string('
+
+
+
+
+
+
+
+
+
+
+
+
+ ');
+ assert($xml_element !== false);
+ $instance = File::fromXML($xml_element, 'package_name');
+ $this->result = $instance->toXml(new \DOMDocument(), '/src/Example/Namespace/Class.php');
+ });
+
+ it('produces a DOM element.', function () {
+ expect($this->result[0])->toBeAnInstanceOf(\DOMElement::class);
+ });
+
+ it('produces a metrics object.', function () {
+ expect($this->result[1])->toBeAnInstanceOf(Metrics::class);
+ });
+ });
describe('merge', function () {
context('Receives a second File instance to merge into this one.', function () {
beforeEach(function () {
@@ -143,8 +180,14 @@
'Example\Namespace\Class'
]);
- expect($classes->get('Example\Namespace\Class')->getNamespace())->toBe('Example\Namespace');
- expect($classes->get('Example\Namespace\OtherClass')->getNamespace())->toBe('Example\OtherNamespace');
+ expect($classes->get('Example\Namespace\Class')->getProperties()->toArray())->toBe([
+ 'name' => 'Example\Namespace\Class',
+ 'namespace' => 'Example\Namespace'
+ ]);
+ expect($classes->get('Example\Namespace\OtherClass')->getProperties()->toArray())->toBe([
+ 'name' => 'Example\Namespace\OtherClass',
+ 'namespace' => 'Example\OtherNamespace'
+ ]);
});
it('has the correct lines set.', function () {
diff --git a/spec/Invocation.spec.php b/spec/Invocation.spec.php
index 14f34c1..9fdef2a 100644
--- a/spec/Invocation.spec.php
+++ b/spec/Invocation.spec.php
@@ -5,6 +5,7 @@
use d0x2f\CloverMerge\Invocation;
use d0x2f\CloverMerge\Accumulator;
use d0x2f\CloverMerge\Utilities;
+use d0x2f\CloverMerge\Metrics;
/**
* @phan-closure-scope \Kahlan\Scope
@@ -133,7 +134,7 @@
new \SimpleXMLElement('')
);
allow(Accumulator::class)->toReceive('parseAll')->andReturn();
- allow(Accumulator::class)->toReceive('toXml')->andReturn(new \Ds\Map());
+ allow(Accumulator::class)->toReceive('toXml')->andReturn([new \Ds\Map(), new Metrics()]);
$this->invocation = new Invocation(['prog', '-o', 'test', 'path', 'path2']);
$this->closure = function () {
$this->invocation->execute();
diff --git a/spec/Metrics.spec.php b/spec/Metrics.spec.php
index d2b2b39..a3e9ece 100644
--- a/spec/Metrics.spec.php
+++ b/spec/Metrics.spec.php
@@ -9,12 +9,9 @@
*/
describe('Metrics', function () {
describe('__construct', function () {
- context('Receives a map of properties.', function () {
+ context('Receives a set of statistics.', function () {
beforeEach(function () {
- $this->instance = new Metrics(new \Ds\Map([
- 'foo' => 'bar',
- 'baz' => 'fred'
- ]));
+ $this->instance = new Metrics(10, 9, 8, 7, 6, 5, 4, 3, 2);
});
it('produces a valid instance.', function () {
@@ -22,43 +19,157 @@
});
});
});
- describe('fromXML', function () {
- context('Receives an XML element.', function () {
- beforeEach(function () {
- $xml_element = simplexml_load_string(
- '
- '
- );
- assert($xml_element !== false);
- $this->instance = Metrics::fromXML($xml_element);
- });
+ describe('toFileXml', function () {
+ beforeEach(function () {
+ $instance = new Metrics(10, 9, 8, 7, 6, 5, 4, 3, 2);
+ $xml_document = new \DOMDocument();
+ $this->result = $instance->toFileXml($xml_document);
+ });
- it('produces a valid instance.', function () {
- expect($this->instance)->toBeAnInstanceOf(Metrics::class);
- });
+ it("produces a XML element containing an 'elements' attribute.", function () {
+ expect($this->result->getAttribute('elements'))->toBe('24');
+ });
- it('has the correct properties set.', function () {
- expect($this->instance->getProperties()->toArray())->toBe([
- 'foo' => 'bar',
- 'baz' => 'fred'
- ]);
- });
+ it("produces a XML element containing a 'coveredelements' attribute.", function () {
+ expect($this->result->getAttribute('coveredelements'))->toBe('21');
+ });
+
+ it("produces a XML element containing a 'conditionals' attribute.", function () {
+ expect($this->result->getAttribute('conditionals'))->toBe('8');
+ });
+
+ it("produces a XML element containing a 'coveredconditionals' attribute.", function () {
+ expect($this->result->getAttribute('coveredconditionals'))->toBe('7');
+ });
+
+ it("produces a XML element containing a 'statements' attribute.", function () {
+ expect($this->result->getAttribute('statements'))->toBe('10');
+ });
+
+ it("produces a XML element containing a 'coveredstatements' attribute.", function () {
+ expect($this->result->getAttribute('coveredstatements'))->toBe('9');
+ });
+
+ it("produces a XML element containing a 'methods' attribute.", function () {
+ expect($this->result->getAttribute('methods'))->toBe('6');
+ });
+
+ it("produces a XML element containing a 'coveredmethods' attribute.", function () {
+ expect($this->result->getAttribute('coveredmethods'))->toBe('5');
+ });
+
+ it("produces a XML element containing a 'classes' attribute.", function () {
+ expect($this->result->getAttribute('classes'))->toBe('4');
+ });
+
+ it("produces a XML element without a 'files' attribute.", function () {
+ expect($this->result->hasAttribute('files'))->toBe(false);
+ });
+
+ it("produces a XML element without a 'packages' attribute.", function () {
+ expect($this->result->hasAttribute('packages'))->toBe(false);
+ });
+ });
+ describe('toPackageXml', function () {
+ beforeEach(function () {
+ $instance = new Metrics(10, 9, 8, 7, 6, 5, 4, 3, 2);
+ $xml_document = new \DOMDocument();
+ $this->result = $instance->toPackageXml($xml_document);
+ });
+
+ it("produces a XML element containing an 'elements' attribute.", function () {
+ expect($this->result->getAttribute('elements'))->toBe('24');
+ });
+
+ it("produces a XML element containing a 'coveredelements' attribute.", function () {
+ expect($this->result->getAttribute('coveredelements'))->toBe('21');
+ });
+
+ it("produces a XML element containing a 'conditionals' attribute.", function () {
+ expect($this->result->getAttribute('conditionals'))->toBe('8');
+ });
+
+ it("produces a XML element containing a 'coveredconditionals' attribute.", function () {
+ expect($this->result->getAttribute('coveredconditionals'))->toBe('7');
+ });
+
+ it("produces a XML element containing a 'statements' attribute.", function () {
+ expect($this->result->getAttribute('statements'))->toBe('10');
+ });
+
+ it("produces a XML element containing a 'coveredstatements' attribute.", function () {
+ expect($this->result->getAttribute('coveredstatements'))->toBe('9');
+ });
+
+ it("produces a XML element containing a 'methods' attribute.", function () {
+ expect($this->result->getAttribute('methods'))->toBe('6');
+ });
+
+ it("produces a XML element containing a 'coveredmethods' attribute.", function () {
+ expect($this->result->getAttribute('coveredmethods'))->toBe('5');
+ });
+
+ it("produces a XML element containing a 'classes' attribute.", function () {
+ expect($this->result->getAttribute('classes'))->toBe('4');
+ });
+
+ it("produces a XML element containing a 'files' attribute.", function () {
+ expect($this->result->getAttribute('files'))->toBe('3');
+ });
+
+ it("produces a XML element without a 'packages' attribute.", function () {
+ expect($this->result->hasAttribute('packages'))->toBe(false);
});
});
- describe('getProperties', function () {
+ describe('toProjectXml', function () {
beforeEach(function () {
- $this->instance = new Metrics(new \Ds\Map([
- 'foo' => 'bar',
- 'baz' => 'fred'
- ]));
- $this->result = $this->instance->getProperties();
- });
-
- it('returns the properties map.', function () {
- expect($this->result->toArray())->toBe([
- 'foo' => 'bar',
- 'baz' => 'fred'
- ]);
+ $instance = new Metrics(10, 9, 8, 7, 6, 5, 4, 3, 2);
+ $xml_document = new \DOMDocument();
+ $this->result = $instance->toProjectXml($xml_document);
+ });
+
+ it("produces a XML element containing an 'elements' attribute.", function () {
+ expect($this->result->getAttribute('elements'))->toBe('24');
+ });
+
+ it("produces a XML element containing a 'coveredelements' attribute.", function () {
+ expect($this->result->getAttribute('coveredelements'))->toBe('21');
+ });
+
+ it("produces a XML element containing a 'conditionals' attribute.", function () {
+ expect($this->result->getAttribute('conditionals'))->toBe('8');
+ });
+
+ it("produces a XML element containing a 'coveredconditionals' attribute.", function () {
+ expect($this->result->getAttribute('coveredconditionals'))->toBe('7');
+ });
+
+ it("produces a XML element containing a 'statements' attribute.", function () {
+ expect($this->result->getAttribute('statements'))->toBe('10');
+ });
+
+ it("produces a XML element containing a 'coveredstatements' attribute.", function () {
+ expect($this->result->getAttribute('coveredstatements'))->toBe('9');
+ });
+
+ it("produces a XML element containing a 'methods' attribute.", function () {
+ expect($this->result->getAttribute('methods'))->toBe('6');
+ });
+
+ it("produces a XML element containing a 'coveredmethods' attribute.", function () {
+ expect($this->result->getAttribute('coveredmethods'))->toBe('5');
+ });
+
+ it("produces a XML element containing a 'classes' attribute.", function () {
+ expect($this->result->getAttribute('classes'))->toBe('4');
+ });
+
+ it("produces a XML element containing a 'files' attribute.", function () {
+ expect($this->result->getAttribute('files'))->toBe('3');
+ });
+
+ it("produces a XML element containing a 'packages' attribute.", function () {
+ expect($this->result->getAttribute('packages'))->toBe('2');
});
});
});
diff --git a/src/Accumulator.php b/src/Accumulator.php
index 1a699cf..81e1610 100644
--- a/src/Accumulator.php
+++ b/src/Accumulator.php
@@ -28,13 +28,6 @@ class Accumulator
*/
private $files;
- /**
- * Document metrics.
- *
- * @var ?Metrics
- */
- private $metrics = null;
-
/**
* If this accumulator is empty.
*
@@ -64,44 +57,6 @@ public function getFiles() : \Ds\Map
return $this->files;
}
- /**
- * Get the top level metrics object.
- *
- * @return Metrics|null
- */
- public function getMetrics() : ?Metrics
- {
- return $this->metrics;
- }
-
- /**
- * Get the number of files discovered.
- *
- * @return integer
- */
- public function getFileCount() : int
- {
- return $this->files->count();
- }
-
- /**
- * Sum coverage information.
- *
- * @return array{0:int,1:int}
- */
- public function getCoverage() : array
- {
- return $this->files->reduce(
- function (array $carry, string $_, File $file) {
- [$covered, $total] = $file->getCoverage();
- $carry[0] += $covered;
- $carry[1] += $total;
- return $carry;
- },
- [0, 0]
- );
- }
-
/**
* Parse each document in the given collection.
*
@@ -199,10 +154,7 @@ private function parseItem(
} elseif ($name === 'file') {
$this->parseFile($element, $package_name);
} elseif ($name === 'metrics') {
- $metrics = Metrics::fromXml($element);
- if (is_null($this->metrics)) {
- $this->metrics = $metrics;
- }
+ // Ignore input metrics, we'll compute our own.
} else {
Utilities::logWarning("Ignoring unexpected element: {$name}.");
}
@@ -235,10 +187,11 @@ private function parseFile(
/**
* Build an XML representation.
+ * Returns a tuple of the xml string and a metrics object.
*
- * @return string
+ * @return array{0:string,1:Metrics}
*/
- public function toXml() : string
+ public function toXml() : array
{
// Sort files by name
$this->files->ksort();
@@ -254,96 +207,39 @@ public function toXml() : string
$xml_project->setAttribute('timestamp', $_SERVER['REQUEST_TIME']);
$xml_coverage->appendChild($xml_project);
+ $project_metrics = new Metrics();
+
$packages = new \Ds\Map();
- foreach ($this->files as $name => $item) {
- $xml_file = Accumulator::buildFile($xml_document, $name, $item);
- $package_name = $item->getPackageName();
+ foreach ($this->files as $name => $file) {
+ [$xml_file, $file_metrics] = $file->toXml($xml_document, $name);
+ $project_metrics->merge($file_metrics);
+ $package_name = $file->getPackageName();
if (is_null($package_name)) {
$xml_project->appendChild($xml_file);
} elseif ($packages->hasKey($package_name)) {
- $packages->get($package_name)->appendChild($xml_file);
+ $packages->get($package_name)[0]->appendChild($xml_file);
+ $packages->get($package_name)[1]->merge($file_metrics);
} else {
$xml_package = $xml_document->createElement('package');
$xml_package->setAttribute('name', $package_name);
$xml_project->appendChild($xml_package);
$xml_package->appendChild($xml_file);
- $packages->put($package_name, $xml_package);
- }
- }
-
- if (!is_null($this->metrics)) {
- $xml_project->appendChild(Accumulator::buildMetrics($xml_document, $this->metrics));
- }
-
- return $xml_document->saveXML();
- }
-
- /**
- * Build an xml representation of a file.
- *
- * @param \DomDocument $xml_document
- * @param string $name
- * @param File $file
- * @return \DOMElement
- */
- private static function buildFile(
- \DomDocument $xml_document,
- string $name,
- File $file
- ) : \DOMElement {
- $xml_file = $xml_document->createElement('file');
- $xml_file->setAttribute('name', $name);
-
- foreach ($file->getClasses() as $name => $class) {
- $xml_class = $xml_document->createElement('class');
- $xml_class->setAttribute('name', $name);
- $namespace = $class->getNamespace();
- if (!is_null($namespace)) {
- $xml_class->setAttribute('namespace', $namespace);
- }
- $metrics = $class->getMetrics();
- if (!is_null($metrics)) {
- $xml_metrics = Accumulator::buildMetrics($xml_document, $metrics);
- $xml_class->appendChild($xml_metrics);
+ $package_metrics = new Metrics();
+ $package_metrics->package_count = 1;
+ $package_metrics->merge($file_metrics);
+ $packages->put($package_name, [$xml_package, $package_metrics]);
}
- $xml_file->appendChild($xml_class);
}
- foreach ($file->getLines() as $number => $line) {
- $xml_line = $xml_document->createElement('line');
- $xml_line->setAttribute('num', $number);
- foreach ($line->getProperties() as $name => $value) {
- $xml_line->setAttribute($name, $value);
- }
- $xml_line->setAttribute('count', $line->getCount());
- $xml_file->appendChild($xml_line);
+ foreach ($packages as $package) {
+ $package_xml = $package[0];
+ $package_metrics = $package[1];
+ $package_xml->appendChild($package_metrics->toPackageXml($xml_document));
}
- $metrics = $file->getMetrics();
- if (!is_null($metrics)) {
- $xml_metrics = Accumulator::buildMetrics($xml_document, $metrics);
- $xml_file->appendChild($xml_metrics);
- }
+ $xml_project->appendChild($project_metrics->toProjectXml($xml_document));
- return $xml_file;
- }
-
- /**
- * Build an xml representation of a set of metrics.
- *
- * @param \DomDocument $xml_document
- * @param Metrics $metrics
- * @return \DOMElement
- */
- private static function buildMetrics(
- \DomDocument $xml_document,
- Metrics $metrics
- ) : \DOMElement {
- $xml_metrics = $xml_document->createElement('metrics');
- foreach ($metrics->getProperties() as $name => $value) {
- $xml_metrics->setAttribute($name, $value);
- }
- return $xml_metrics;
+ return [$xml_document->saveXML(), $project_metrics];
}
}
diff --git a/src/ClassT.php b/src/ClassT.php
index 8d942b8..6658548 100644
--- a/src/ClassT.php
+++ b/src/ClassT.php
@@ -7,30 +7,23 @@
*/
class ClassT
{
- /**
- * Class namespace.
- *
- * @var string|null
- */
- private $namespace;
/**
- * Class metrics
+ * Other properties on the line.
+ * E.g. name, visibility, complexity, crap.
*
- * @var Metrics|null
+ * @var \Ds\Map $properties
*/
- private $metrics;
+ private $properties;
/**
* Constructor.
*
- * @param string|null $namespace
- * @param Metrics|null $metrics
+ * @param \Ds\Map $properties Any properties on the XML node.
*/
- public function __construct(?string $namespace = null, ?Metrics $metrics = null)
+ public function __construct(\Ds\Map $properties)
{
- $this->namespace = $namespace;
- $this->metrics = $metrics;
+ $this->properties = $properties;
}
/**
@@ -41,48 +34,36 @@ public function __construct(?string $namespace = null, ?Metrics $metrics = null)
*/
public static function fromXML(\SimpleXMLElement $xml) : ClassT
{
- $attributes = $xml->attributes();
- $class = new ClassT($attributes['namespace'] ?? null);
- $children = $xml->children();
- foreach ($children as $child) {
- $name = $child->getName();
- if ($name === 'metrics') {
- $class->mergeMetrics(Metrics::fromXml($child));
- } else {
- Utilities::logWarning("Ignoring unknown element: {$name}.");
- }
- }
- return $class;
- }
-
- /**
- * Set metrics.
- *
- * @param Metrics $metrics
- * @return void
- */
- public function mergeMetrics(Metrics $metrics) : void
- {
- $this->metrics = $this->metrics ?? $metrics;
+ $properties = new \Ds\Map($xml->attributes());
+ $properties->apply(function ($_, $value) {
+ return (string) $value;
+ });
+ return new ClassT($properties);
}
/**
- * Get the namespace.
+ * Produce an XML representation.
*
- * @return string|null
+ * @param \DomDocument $document The parent document.
+ * @return \DOMElement
*/
- public function getNamespace() : ?string
- {
- return $this->namespace;
+ public function toXml(
+ \DomDocument $document
+ ) : \DOMElement {
+ $xml_class = $document->createElement('class');
+ foreach ($this->properties as $key => $value) {
+ $xml_class->setAttribute($key, $value);
+ }
+ return $xml_class;
}
/**
- * Get the metrics.
+ * Get the properties.
*
- * @return Metrics|null
+ * @return \Ds\Map
*/
- public function getMetrics() : ?Metrics
+ public function getProperties() : \Ds\Map
{
- return $this->metrics;
+ return $this->properties;
}
}
diff --git a/src/File.php b/src/File.php
index 8a2daa1..15ed1ec 100644
--- a/src/File.php
+++ b/src/File.php
@@ -28,13 +28,6 @@ class File
*/
private $package_name;
- /**
- * Metrics element.
- *
- * @var Metrics|null
- */
- private $metrics;
-
/**
* Construct with the given package name.
*
@@ -45,7 +38,6 @@ public function __construct(?string $package_name = null)
$this->classes = new \Ds\Map();
$this->lines = new \Ds\Map();
$this->package_name = $package_name;
- $this->metrics = null;
}
/**
@@ -83,7 +75,7 @@ private static function parseChildXML(File &$file, \SimpleXMLElement $child)
}
$file->mergeClass($attributes['name'] ?? '', ClassT::fromXml($child));
} elseif ($name === 'metrics') {
- $file->mergeMetrics(Metrics::fromXml($child));
+ // Ignore input metrics, we'll compute our own.
} elseif ($name === 'line') {
if (!Utilities::xmlHasAttributes($child, ['num', 'count'])) {
Utilities::logWarning('Ignoring line with no num or count.');
@@ -96,16 +88,76 @@ private static function parseChildXML(File &$file, \SimpleXMLElement $child)
}
/**
- * Get the covered and total lines count.
+ * Produce an XML element representing this file.
*
- * @return array{0:int,1:int}
+ * @param \DomDocument $xml_document The parent document.
+ * @param string $name The name of the file.
+ * @return array{0:\DOMElement,1:Metrics}
*/
- public function getCoverage() : array
- {
- $covered = $this->lines->filter(function (int $_, Line $line) {
- return $line->getCount() > 0;
- })->count();
- return [$covered, $this->lines->count()];
+ public function toXml(
+ \DomDocument $xml_document,
+ string $name
+ ) : array {
+ $xml_file = $xml_document->createElement('file');
+ $xml_file->setAttribute('name', $name);
+
+ // Metric counts
+ $statement_count = 0;
+ $covered_statement_count = 0;
+ $conditional_count = 0;
+ $covered_conditional_count = 0;
+ $method_count = 0;
+ $covered_method_count = 0;
+ $class_count = $this->classes->count();
+
+ // Classes
+ foreach ($this->classes as $class) {
+ $xml_file->appendChild($class->toXml($xml_document));
+ }
+
+ // Lines
+ foreach ($this->lines as $line) {
+ $xml_file->appendChild($line->toXml($xml_document));
+ $properties = $line->getProperties();
+
+ $covered = $line->getCount() > 0;
+ $type = $properties['type'] ?? 'stmt';
+
+ if ($type === 'method') {
+ $method_count ++;
+ if ($covered) {
+ $covered_method_count ++;
+ }
+ } elseif ($type === 'stmt') {
+ $statement_count ++;
+ if ($covered) {
+ $covered_statement_count ++;
+ }
+ } elseif ($type === 'cond') {
+ $conditional_count ++;
+ if ($covered) {
+ $covered_conditional_count ++;
+ }
+ } else {
+ Utilities::logWarning("Ignoring unexpected line type: {$type}.");
+ }
+ }
+
+ // Metrics
+ $metrics = new Metrics(
+ $statement_count,
+ $covered_statement_count,
+ $conditional_count,
+ $covered_conditional_count,
+ $method_count,
+ $covered_method_count,
+ $class_count,
+ 1
+ );
+ $xml_file->appendChild($metrics->toFileXml($xml_document));
+
+ // Return a tuple of the XML node and the metrics to carry over.
+ return [$xml_file, $metrics];
}
/**
@@ -120,7 +172,6 @@ public function merge(File $other, string $merge_mode = 'inclusive', bool $lock_
{
$this->classes = $other->getClasses()->merge($this->classes);
$this->package_name = $this->package_name ?? $other->package_name;
- $this->metrics = $this->metrics ?? $other->metrics;
$other_lines = $other->getLines();
@@ -165,17 +216,6 @@ public function mergeLine(int $number, Line $line, bool $lock_lines = false) : v
}
}
- /**
- * Set metrics if not already set.
- *
- * @param Metrics $metrics
- * @return void
- */
- public function mergeMetrics(Metrics $metrics)
- {
- $this->metrics = $this->metrics ?? $metrics;
- }
-
/**
* Get the classes.
*
@@ -205,14 +245,4 @@ public function getPackageName() : ?string
{
return $this->package_name;
}
-
- /**
- * Get the metrics.
- *
- * @return Metrics|null
- */
- public function getMetrics() : ?Metrics
- {
- return $this->metrics;
- }
}
diff --git a/src/Invocation.php b/src/Invocation.php
index 659a652..db81257 100644
--- a/src/Invocation.php
+++ b/src/Invocation.php
@@ -96,21 +96,22 @@ public function execute() : void
$accumulator->parseAll($this->documents);
// Output
- $output = $accumulator->toXml();
- $write_result = file_put_contents($this->output_path, $output);
+ [$xml, $metrics] = $accumulator->toXml();
+ $write_result = file_put_contents($this->output_path, $xml);
if ($write_result === false) {
throw new FileException("Unable to write to given output file.");
}
// Stats
- $files_discovered = $accumulator->getFileCount();
- [$covered, $total] = $accumulator->getCoverage();
- if ($total === 0) {
+ $files_discovered = $metrics->file_count;
+ $element_count = $metrics->getElementCount();
+ $covered_element_count = $metrics->getCoveredElementCount();
+ if ($element_count === 0) {
$coverage_percentage = 0;
} else {
- $coverage_percentage = 100 * $covered/$total;
+ $coverage_percentage = 100 * $covered_element_count/$element_count;
}
printf("Files Discovered: %d\n", $files_discovered);
- printf("Final Coverage: %d/%d (%.2f%%)\n", $covered, $total, $coverage_percentage);
+ printf("Final Coverage: %d/%d (%.2f%%)\n", $covered_element_count, $element_count, $coverage_percentage);
}
}
diff --git a/src/Line.php b/src/Line.php
index 37417c2..29e3863 100644
--- a/src/Line.php
+++ b/src/Line.php
@@ -25,6 +25,7 @@ class Line
/**
* Initialise with a hit count.
*
+ * @param \Ds\Map $properties Any other properties on the XML node.
* @param integer $count
*/
public function __construct(\Ds\Map $properties, int $count = 0)
@@ -52,6 +53,23 @@ public static function fromXML(\SimpleXMLElement $xml) : Line
return new Line($properties, (int)$properties->remove('count'));
}
+ /**
+ * Produce an XML representation.
+ *
+ * @param \DomDocument $document The parent document.
+ * @return \DOMElement
+ */
+ public function toXml(
+ \DomDocument $document
+ ) : \DOMElement {
+ $xml_line = $document->createElement('line');
+ foreach ($this->properties as $key => $value) {
+ $xml_line->setAttribute($key, $value);
+ }
+ $xml_line->setAttribute('count', (string)$this->count);
+ return $xml_line;
+ }
+
/**
* Merge another line with this one.
*
diff --git a/src/Metrics.php b/src/Metrics.php
index d5ad187..3144e37 100644
--- a/src/Metrics.php
+++ b/src/Metrics.php
@@ -7,45 +7,166 @@
*/
class Metrics
{
+ /** @var integer */
+ public $statement_count = 0;
+
+ /** @var integer */
+ public $covered_statement_count = 0;
+
+ /** @var integer */
+ public $conditional_count = 0;
+
+ /** @var integer */
+ public $covered_conditional_count = 0;
+
+ /** @var integer */
+ public $method_count = 0;
+
+ /** @var integer */
+ public $covered_method_count = 0;
+
+ /** @var integer */
+ public $class_count = 0;
+
+ /** @var integer */
+ public $file_count = 0;
+
+ /** @var integer */
+ public $package_count = 0;
+
+ /**
+ * Constructor
+ *
+ * @param integer $statement_count
+ * @param integer $covered_statement_count
+ * @param integer $conditional_count
+ * @param integer $covered_conditional_count
+ * @param integer $method_count
+ * @param integer $covered_method_count
+ * @param integer $class_count
+ * @param integer $file_count
+ * @param integer $package_count
+ */
+ public function __construct(
+ int $statement_count = 0,
+ int $covered_statement_count = 0,
+ int $conditional_count = 0,
+ int $covered_conditional_count = 0,
+ int $method_count = 0,
+ int $covered_method_count = 0,
+ int $class_count = 0,
+ int $file_count = 0,
+ int $package_count = 0
+ ) {
+ $this->statement_count = $statement_count;
+ $this->covered_statement_count = $covered_statement_count;
+ $this->conditional_count = $conditional_count;
+ $this->covered_conditional_count = $covered_conditional_count;
+ $this->method_count = $method_count;
+ $this->covered_method_count = $covered_method_count;
+ $this->class_count = $class_count;
+ $this->file_count = $file_count;
+ $this->package_count = $package_count;
+ }
+
/**
- * Properties.
+ * Create an XML element to represent these metrics under a file.
*
- * @var \Ds\Map
+ * @param \DOMDocument $xml_document The parent document.
+ * @return \DOMElement
*/
- private $properties;
+ public function toFileXml(
+ \DOMDocument $xml_document
+ ) : \DOMElement {
+ $xml_metrics = $xml_document->createElement('metrics');
+
+ // We can't know the complexity, just set 0
+ // (attribute required by the clover xml schema)
+ $xml_metrics->setAttribute('complexity', '0');
+
+ $xml_metrics->setAttribute('elements', (string)$this->getElementCount());
+ $xml_metrics->setAttribute('coveredelements', (string)$this->getCoveredElementCount());
+ $xml_metrics->setAttribute('conditionals', (string)$this->conditional_count);
+ $xml_metrics->setAttribute('coveredconditionals', (string)$this->covered_conditional_count);
+ $xml_metrics->setAttribute('statements', (string)$this->statement_count);
+ $xml_metrics->setAttribute('coveredstatements', (string)$this->covered_statement_count);
+ $xml_metrics->setAttribute('methods', (string)$this->method_count);
+ $xml_metrics->setAttribute('coveredmethods', (string)$this->covered_method_count);
+ $xml_metrics->setAttribute('classes', (string)$this->class_count);
+
+ return $xml_metrics;
+ }
+
+ /**
+ * Create an XML element to represent these metrics under a package.
+ * Contains all the attributes of the file context plus the number of files.
+ *
+ * @param \DOMDocument $xml_document The parent document.
+ * @return \DOMElement
+ */
+ public function toPackageXml(
+ \DOMDocument $xml_document
+ ) : \DOMElement {
+ $xml_metrics = $this->toFileXml($xml_document);
+ $xml_metrics->setAttribute('files', (string)$this->file_count);
+ return $xml_metrics;
+ }
+
+ /**
+ * Create an XML element to represent these metrics under a project.
+ * Contains all the attributes of the package context plus the number of packages.
+ *
+ * @param \DOMDocument $xml_document The parent document.
+ * @return \DOMElement
+ */
+ public function toProjectXml(
+ \DOMDocument $xml_document
+ ) : \DOMElement {
+ $xml_metrics = $this->toPackageXml($xml_document);
+ $xml_metrics->setAttribute('packages', (string)$this->package_count);
+ return $xml_metrics;
+ }
/**
- * Constructor.
+ * Merge another set of metrics into this one.
*
- * @param \Ds\Map $properties
+ * @param Metrics $metrics
+ * @return void
*/
- public function __construct(\Ds\Map $properties)
+ public function merge(Metrics $metrics) : void
{
- $this->properties = $properties;
+ $this->statement_count += $metrics->statement_count;
+ $this->covered_statement_count += $metrics->covered_statement_count;
+ $this->conditional_count += $metrics->conditional_count;
+ $this->covered_conditional_count += $metrics->covered_conditional_count;
+ $this->method_count += $metrics->method_count;
+ $this->covered_method_count += $metrics->covered_method_count;
+ $this->class_count += $metrics->class_count;
+ $this->file_count += $metrics->file_count;
+ $this->package_count += $metrics->package_count;
}
/**
- * Construct from XML.
+ * Return the number of elements.
*
- * @param \SimpleXMLElement $xml
- * @return Metrics
+ * @return integer
*/
- public static function fromXML(\SimpleXMLElement $xml) : Metrics
+ public function getElementCount() : int
{
- $properties = new \Ds\Map($xml->attributes());
- $properties->apply(function ($_, $value) {
- return (string) $value;
- });
- return new Metrics($properties);
+ return $this->statement_count +
+ $this->conditional_count +
+ $this->method_count;
}
/**
- * Get properties.
+ * Return the number of covered elemetns.
*
- * @return \Ds\Map
+ * @return integer
*/
- public function getProperties() : \Ds\Map
+ public function getCoveredElementCount() : int
{
- return $this->properties;
+ return $this->covered_statement_count +
+ $this->covered_conditional_count +
+ $this->covered_method_count;
}
}
diff --git a/test.xml b/test.xml
new file mode 100644
index 0000000..2a48cc3
--- /dev/null
+++ b/test.xml
@@ -0,0 +1,9471 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+