5 * Copyright (c) 2002-2009, Sebastian Bergmann <sb@sebastian-bergmann.de>.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
12 * * Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
15 * * Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in
17 * the documentation and/or other materials provided with the
20 * * Neither the name of Sebastian Bergmann nor the names of his
21 * contributors may be used to endorse or promote products derived
22 * from this software without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
27 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
28 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
29 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
30 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
34 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35 * POSSIBILITY OF SUCH DAMAGE.
39 * @author Sebastian Bergmann <sb@sebastian-bergmann.de>
40 * @copyright 2002-2009 Sebastian Bergmann <sb@sebastian-bergmann.de>
41 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
43 * @link http://www.phpunit.de/
44 * @since File available since Release 3.2.0
47 require_once 'PHPUnit/Util/Filter.php';
48 require_once 'PHPUnit/Util/Metrics.php';
50 PHPUnit_Util_Filter::addFileToFilter(__FILE__, 'PHPUNIT');
53 * Project-Level Metrics.
57 * @author Sebastian Bergmann <sb@sebastian-bergmann.de>
58 * @copyright 2002-2009 Sebastian Bergmann <sb@sebastian-bergmann.de>
59 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
60 * @version Release: 3.3.17
61 * @link http://www.phpunit.de/
62 * @since Class available since Release 3.2.0
64 class PHPUnit_Util_Metrics_Project extends PHPUnit_Util_Metrics
66 protected static $CPD_IGNORE_LIST = array(
76 protected $classes = array();
77 protected $files = array();
78 protected $functions = array();
83 protected $interfs = 0;
86 protected $maxDit = 0;
88 protected $cpdDuplicates = array();
89 protected $cpdHashes = array();
90 protected $dependencies = array();
96 * @param array $codeCoverage
98 * @param integer $cpdMinLines
99 * @param integer $cpdMinMatches
101 public function __construct(Array $files, &$codeCoverage = array(), $cpd = FALSE, $cpdMinLines = 5, $cpdMinMatches = 70)
103 foreach ($files as $file) {
104 if (file_exists($file)) {
105 $this->files[$file] = PHPUnit_Util_Metrics_File::factory(
109 foreach ($this->files[$file]->getFunctions() as $function) {
110 $this->functions[$function->getFunction()->getName()] = $function;
113 foreach ($this->files[$file]->getClasses() as $class) {
114 $className = $class->getClass()->getName();
115 $package = $class->getPackage();
117 $this->classes[$className] = $class;
119 if ($class->getClass()->isInterface()) {
122 if ($class->getClass()->isAbstract()) {
134 $numClasses = count($this->classes);
136 foreach ($this->classes as $a => $b) {
137 foreach ($this->classes as $c => $d) {
138 $this->dependencies[$a][$c] = 0;
142 foreach ($this->classes as $className => $class) {
143 foreach ($class->getDependencies() as $dependency) {
144 $this->dependencies[$className][$dependency] = 1;
147 $class->setProject($this);
149 if ($class->getNOC() == 0) {
153 else if ($class->getClass()->getParentClass() === FALSE) {
157 $this->maxDit = max($this->maxDit, $class->getDit());
161 $this->copyPasteDetection($cpdMinLines, $cpdMinMatches);
166 * Returns the classes of this project.
170 public function getClasses()
172 return $this->classes;
178 * @param string $className
179 * @return ReflectionClass
181 public function getClass($className)
183 return $this->classes[$className];
187 * Returns the dependencies between the classes of this project.
191 public function getDependencies()
193 return $this->dependencies;
197 * Returns the dependencies between the classes of this project
198 * as GraphViz/DOT markup.
200 * @return Image_GraphViz
201 * @since Method available since Release 3.2.2
203 public function getDependenciesAsDOT()
205 if (PHPUnit_Util_Filesystem::fileExistsInIncludePath('Image/GraphViz.php')) {
206 require_once 'Image/GraphViz.php';
208 throw new RuntimeException('Image_GraphViz is not available.');
211 $graph = new Image_GraphViz(
214 'overlap' => 'scale',
221 foreach (array_keys($this->dependencies) as $className) {
222 $graph->addNode($className);
225 foreach ($this->dependencies as $from => $dependencies) {
226 foreach ($dependencies as $to => $flag) {
228 $graph->addEdge(array($from => $to));
237 * Returns the duplicates found by the Copy & Paste Detection (CPD).
241 public function getDuplicates()
243 return $this->cpdDuplicates;
247 * Returns the files of this project.
251 public function getFiles()
259 * @param string $className
260 * @return ReflectionClass
262 public function getFile($filename)
264 return $this->files[$filename];
272 public function getFunctions()
274 return $this->functions;
280 * @param string $functionName
281 * @return ReflectionClass
283 public function getFunction($functionName)
285 return $this->functions[$functionName];
289 * Returns the Number of Classes (CLS) for the project.
292 * @see http://www.aivosto.com/project/help/pm-oo-misc.html
294 public function getCLS()
300 * Returns the Number of Abstract Classes (CLSa) for the project.
303 * @see http://www.aivosto.com/project/help/pm-oo-misc.html
305 public function getCLSa()
311 * Returns the Number of Concrete Classes (CLSc) for the project.
314 * @see http://www.aivosto.com/project/help/pm-oo-misc.html
316 public function getCLSc()
322 * Returns the Number of Root Classes (ROOTS) for the project.
325 * @see http://www.aivosto.com/project/help/pm-oo-misc.html
327 public function getRoots()
333 * Returns the Number of Leaf Classes (LEAFS) for the project.
336 * @see http://www.aivosto.com/project/help/pm-oo-misc.html
338 public function getLeafs()
344 * Returns the Number of Interfaces (INTERFS) for the project.
347 * @see http://www.aivosto.com/project/help/pm-oo-misc.html
349 public function getInterfs()
351 return $this->interfs;
355 * Returns the Maximum Depth of Intheritance Tree (maxDIT) for the project.
358 * @see http://www.aivosto.com/project/help/pm-oo-misc.html
360 public function getMaxDit()
362 return $this->maxDit;
366 * Copy & Paste Detection (CPD).
368 * @param integer $minLines
369 * @param integer $minMatches
370 * @author Johann-Peter Hartmann <johann-peter.hartmann@mayflower.de>
372 protected function copyPasteDetection($minLines, $minMatches)
374 foreach ($this->files as $file) {
375 $currentTokenPositions = array();
376 $currentSignature = '';
377 $tokens = $file->getTokens();
381 foreach (array_keys($tokens) as $key) {
382 $token = $tokens[$key];
384 if (is_string($token)) {
385 $line += substr_count($token, "\n");
387 if (!in_array($token[0], self::$CPD_IGNORE_LIST)) {
388 $currentTokenPositions[$tokenNr++] = $line;
389 $currentSignature .= chr($token[0] & 255) . pack('N*', crc32($token[1]));
392 $line += substr_count($token[1], "\n");
400 if (count($currentTokenPositions) > 0) {
402 $line = $currentTokenPositions[$tokenNr];
407 $currentSignature, $tokenNr * 5,
416 if (isset($this->cpdHashes[$hash])) {
419 if ($firstLine === 0) {
422 $firstToken = $tokenNr;
426 $fileA = $this->cpdHashes[$firstHash][0];
427 $firstLineA = $this->cpdHashes[$firstHash][1];
429 if ($line + 1 - $firstLine > $minLines &&
430 ($fileA->getPath() != $file->getPath() ||
431 $firstLineA != $firstLine)) {
432 $this->cpdDuplicates[] = array(
434 'firstLineA' => $firstLineA,
436 'firstLineB' => $firstLine,
437 'numLines' => $line + 1 - $firstLine,
438 'numTokens' => $tokenNr + 1 - $firstToken
446 $this->cpdHashes[$hash] = array($file, $line);
450 } while ($tokenNr <= (count($currentTokenPositions) -
455 $fileA = $this->cpdHashes[$firstHash][0];
456 $firstLineA = $this->cpdHashes[$firstHash][1];
458 if ($line + 1 - $firstLine > $minLines &&
459 ($fileA->getPath() != $file->getPath() ||
460 $firstLineA != $firstLine)) {
461 $this->cpdDuplicates[] = array(
463 'firstLineA' => $firstLineA,
465 'firstLineB' => $firstLine,
466 'numLines' => $line + 1 - $firstLine,
467 'numTokens' => $tokenNr + 1 - $firstToken