PageRenderTime 44ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/pdepend/pdepend/src/main/php/PDepend/Metrics/Analyzer/DependencyAnalyzer.php

https://gitlab.com/yousafsyed/easternglamor
PHP | 480 lines | 227 code | 57 blank | 196 comment | 17 complexity | 09fea6dff2f2896dcd45ac13eba03e97 MD5 | raw file
  1. <?php
  2. /**
  3. * This file is part of PDepend.
  4. *
  5. * PHP Version 5
  6. *
  7. * Copyright (c) 2008-2015, Manuel Pichler <mapi@pdepend.org>.
  8. * All rights reserved.
  9. *
  10. * Redistribution and use in source and binary forms, with or without
  11. * modification, are permitted provided that the following conditions
  12. * are met:
  13. *
  14. * * Redistributions of source code must retain the above copyright
  15. * notice, this list of conditions and the following disclaimer.
  16. *
  17. * * Redistributions in binary form must reproduce the above copyright
  18. * notice, this list of conditions and the following disclaimer in
  19. * the documentation and/or other materials provided with the
  20. * distribution.
  21. *
  22. * * Neither the name of Manuel Pichler nor the names of his
  23. * contributors may be used to endorse or promote products derived
  24. * from this software without specific prior written permission.
  25. *
  26. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  27. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  28. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  29. * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  30. * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  31. * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  32. * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  33. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  34. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  35. * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  36. * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  37. * POSSIBILITY OF SUCH DAMAGE.
  38. *
  39. * @copyright 2008-2015 Manuel Pichler. All rights reserved.
  40. * @license http://www.opensource.org/licenses/bsd-license.php BSD License
  41. */
  42. namespace PDepend\Metrics\Analyzer;
  43. use PDepend\Metrics\AbstractAnalyzer;
  44. use PDepend\Source\AST\AbstractASTArtifact;
  45. use PDepend\Source\AST\AbstractASTClassOrInterface;
  46. use PDepend\Source\AST\ASTArtifactList;
  47. use PDepend\Source\AST\ASTClass;
  48. use PDepend\Source\AST\ASTInterface;
  49. use PDepend\Source\AST\ASTMethod;
  50. use PDepend\Source\AST\ASTNamespace;
  51. /**
  52. * This visitor generates the metrics for the analyzed namespaces.
  53. *
  54. * @copyright 2008-2015 Manuel Pichler. All rights reserved.
  55. * @license http://www.opensource.org/licenses/bsd-license.php BSD License
  56. */
  57. class DependencyAnalyzer extends AbstractAnalyzer
  58. {
  59. /**
  60. * Metrics provided by the analyzer implementation.
  61. */
  62. const M_NUMBER_OF_CLASSES = 'tc',
  63. M_NUMBER_OF_CONCRETE_CLASSES = 'cc',
  64. M_NUMBER_OF_ABSTRACT_CLASSES = 'ac',
  65. M_AFFERENT_COUPLING = 'ca',
  66. M_EFFERENT_COUPLING = 'ce',
  67. M_ABSTRACTION = 'a',
  68. M_INSTABILITY = 'i',
  69. M_DISTANCE = 'd';
  70. /**
  71. * Hash with all calculated node metrics.
  72. *
  73. * <code>
  74. * array(
  75. * '0375e305-885a-4e91-8b5c-e25bda005438' => array(
  76. * 'loc' => 42,
  77. * 'ncloc' => 17,
  78. * 'cc' => 12
  79. * ),
  80. * 'e60c22f0-1a63-4c40-893e-ed3b35b84d0b' => array(
  81. * 'loc' => 42,
  82. * 'ncloc' => 17,
  83. * 'cc' => 12
  84. * )
  85. * )
  86. * </code>
  87. *
  88. * @var array(string=>array)
  89. */
  90. private $nodeMetrics = null;
  91. protected $nodeSet = array();
  92. private $efferentNodes = array();
  93. private $afferentNodes = array();
  94. /**
  95. * All collected cycles for the input code.
  96. *
  97. * <code>
  98. * array(
  99. * <namespace-id> => array(
  100. * \PDepend\Source\AST\ASTNamespace {},
  101. * \PDepend\Source\AST\ASTNamespace {},
  102. * ),
  103. * <namespace-id> => array(
  104. * \PDepend\Source\AST\ASTNamespace {},
  105. * \PDepend\Source\AST\ASTNamespace {},
  106. * ),
  107. * )
  108. * </code>
  109. *
  110. * @var array(string=>array)
  111. */
  112. private $collectedCycles = array();
  113. /**
  114. * Processes all {@link \PDepend\Source\AST\ASTNamespace} code nodes.
  115. *
  116. * @param \PDepend\Source\AST\ASTNamespace[] $namespaces
  117. * @return void
  118. */
  119. public function analyze($namespaces)
  120. {
  121. if ($this->nodeMetrics === null) {
  122. $this->fireStartAnalyzer();
  123. $this->nodeMetrics = array();
  124. foreach ($namespaces as $namespace) {
  125. $namespace->accept($this);
  126. }
  127. $this->postProcess();
  128. $this->calculateAbstractness();
  129. $this->calculateInstability();
  130. $this->calculateDistance();
  131. $this->fireEndAnalyzer();
  132. }
  133. }
  134. /**
  135. * Returns the statistics for the requested node.
  136. *
  137. * @param \PDepend\Source\AST\AbstractASTArtifact $node
  138. * @return array
  139. */
  140. public function getStats(AbstractASTArtifact $node)
  141. {
  142. $stats = array();
  143. if (isset($this->nodeMetrics[$node->getId()])) {
  144. $stats = $this->nodeMetrics[$node->getId()];
  145. }
  146. return $stats;
  147. }
  148. /**
  149. * Returns an array of all afferent nodes.
  150. *
  151. * @param \PDepend\Source\AST\AbstractASTArtifact $node
  152. * @return \PDepend\Source\AST\AbstractASTArtifact[]
  153. */
  154. public function getAfferents(AbstractASTArtifact $node)
  155. {
  156. $afferents = array();
  157. if (isset($this->afferentNodes[$node->getId()])) {
  158. $afferents = $this->afferentNodes[$node->getId()];
  159. }
  160. return $afferents;
  161. }
  162. /**
  163. * Returns an array of all efferent nodes.
  164. *
  165. * @param \PDepend\Source\AST\AbstractASTArtifact $node
  166. * @return \PDepend\Source\AST\AbstractASTArtifact[]
  167. */
  168. public function getEfferents(AbstractASTArtifact $node)
  169. {
  170. $efferents = array();
  171. if (isset($this->efferentNodes[$node->getId()])) {
  172. $efferents = $this->efferentNodes[$node->getId()];
  173. }
  174. return $efferents;
  175. }
  176. /**
  177. * Returns an array of nodes that build a cycle for the requested node or it
  178. * returns <b>null</b> if no cycle exists .
  179. *
  180. * @param \PDepend\Source\AST\AbstractASTArtifact $node
  181. * @return \PDepend\Source\AST\AbstractASTArtifact[]
  182. */
  183. public function getCycle(AbstractASTArtifact $node)
  184. {
  185. if (array_key_exists($node->getId(), $this->collectedCycles)) {
  186. return $this->collectedCycles[$node->getId()];
  187. }
  188. $list = array();
  189. if ($this->collectCycle($list, $node)) {
  190. $this->collectedCycles[$node->getId()] = $list;
  191. } else {
  192. $this->collectedCycles[$node->getId()] = null;
  193. }
  194. return $this->collectedCycles[$node->getId()];
  195. }
  196. /**
  197. * Visits a method node.
  198. *
  199. * @param \PDepend\Source\AST\ASTMethod $method
  200. * @return void
  201. */
  202. public function visitMethod(ASTMethod $method)
  203. {
  204. $this->fireStartMethod($method);
  205. $namespace = $method->getParent()->getNamespace();
  206. foreach ($method->getDependencies() as $dependency) {
  207. $this->collectDependencies($namespace, $dependency->getNamespace());
  208. }
  209. $this->fireEndMethod($method);
  210. }
  211. /**
  212. * Visits a namespace node.
  213. *
  214. * @param \PDepend\Source\AST\ASTNamespace $namespace
  215. * @return void
  216. */
  217. public function visitNamespace(ASTNamespace $namespace)
  218. {
  219. $this->fireStartNamespace($namespace);
  220. $this->initNamespaceMetric($namespace);
  221. $this->nodeSet[$namespace->getId()] = $namespace;
  222. foreach ($namespace->getTypes() as $type) {
  223. $type->accept($this);
  224. }
  225. $this->fireEndNamespace($namespace);
  226. }
  227. /**
  228. * Visits a class node.
  229. *
  230. * @param \PDepend\Source\AST\ASTClass $class
  231. * @return void
  232. */
  233. public function visitClass(ASTClass $class)
  234. {
  235. $this->fireStartClass($class);
  236. $this->visitType($class);
  237. $this->fireEndClass($class);
  238. }
  239. /**
  240. * Visits an interface node.
  241. *
  242. * @param \PDepend\Source\AST\ASTInterface $interface
  243. * @return void
  244. */
  245. public function visitInterface(ASTInterface $interface)
  246. {
  247. $this->fireStartInterface($interface);
  248. $this->visitType($interface);
  249. $this->fireEndInterface($interface);
  250. }
  251. /**
  252. * Generic visit method for classes and interfaces. Both visit methods
  253. * delegate calls to this method.
  254. *
  255. * @param \PDepend\Source\AST\AbstractASTClassOrInterface $type
  256. * @return void
  257. */
  258. protected function visitType(AbstractASTClassOrInterface $type)
  259. {
  260. $id = $type->getNamespace()->getId();
  261. // Increment total classes count
  262. ++$this->nodeMetrics[$id][self::M_NUMBER_OF_CLASSES];
  263. // Check for abstract or concrete class
  264. if ($type->isAbstract()) {
  265. ++$this->nodeMetrics[$id][self::M_NUMBER_OF_ABSTRACT_CLASSES];
  266. } else {
  267. ++$this->nodeMetrics[$id][self::M_NUMBER_OF_CONCRETE_CLASSES];
  268. }
  269. foreach ($type->getDependencies() as $dependency) {
  270. $this->collectDependencies(
  271. $type->getNamespace(),
  272. $dependency->getNamespace()
  273. );
  274. }
  275. foreach ($type->getMethods() as $method) {
  276. $method->accept($this);
  277. }
  278. }
  279. /**
  280. * Collects the dependencies between the two given namespaces.
  281. *
  282. * @param \PDepend\Source\AST\ASTNamespace $namespaceA
  283. * @param \PDepend\Source\AST\ASTNamespace $namespaceB
  284. *
  285. * @return void
  286. */
  287. private function collectDependencies(ASTNamespace $namespaceA, ASTNamespace $namespaceB)
  288. {
  289. $idA = $namespaceA->getId();
  290. $idB = $namespaceB->getId();
  291. if ($idB === $idA) {
  292. return;
  293. }
  294. // Create a container for this dependency
  295. $this->initNamespaceMetric($namespaceB);
  296. if (!in_array($idB, $this->nodeMetrics[$idA][self::M_EFFERENT_COUPLING])) {
  297. $this->nodeMetrics[$idA][self::M_EFFERENT_COUPLING][] = $idB;
  298. $this->nodeMetrics[$idB][self::M_AFFERENT_COUPLING][] = $idA;
  299. }
  300. }
  301. /**
  302. * Initializes the node metric record for the given <b>$namespace</b>.
  303. *
  304. * @param \PDepend\Source\AST\ASTNamespace $namespace
  305. * @return void
  306. */
  307. protected function initNamespaceMetric(ASTNamespace $namespace)
  308. {
  309. $id = $namespace->getId();
  310. if (!isset($this->nodeMetrics[$id])) {
  311. $this->nodeSet[$id] = $namespace;
  312. $this->nodeMetrics[$id] = array(
  313. self::M_NUMBER_OF_CLASSES => 0,
  314. self::M_NUMBER_OF_CONCRETE_CLASSES => 0,
  315. self::M_NUMBER_OF_ABSTRACT_CLASSES => 0,
  316. self::M_AFFERENT_COUPLING => array(),
  317. self::M_EFFERENT_COUPLING => array(),
  318. self::M_ABSTRACTION => 0,
  319. self::M_INSTABILITY => 0,
  320. self::M_DISTANCE => 0
  321. );
  322. }
  323. }
  324. /**
  325. * Post processes all analyzed nodes.
  326. *
  327. * @return void
  328. */
  329. protected function postProcess()
  330. {
  331. foreach ($this->nodeMetrics as $id => $metrics) {
  332. $this->afferentNodes[$id] = array();
  333. foreach ($metrics[self::M_AFFERENT_COUPLING] as $caId) {
  334. $this->afferentNodes[$id][] = $this->nodeSet[$caId];
  335. }
  336. sort($this->afferentNodes[$id]);
  337. $this->efferentNodes[$id] = array();
  338. foreach ($metrics[self::M_EFFERENT_COUPLING] as $ceId) {
  339. $this->efferentNodes[$id][] = $this->nodeSet[$ceId];
  340. }
  341. sort($this->efferentNodes[$id]);
  342. $afferent = count($metrics[self::M_AFFERENT_COUPLING]);
  343. $efferent = count($metrics[self::M_EFFERENT_COUPLING]);
  344. $this->nodeMetrics[$id][self::M_AFFERENT_COUPLING] = $afferent;
  345. $this->nodeMetrics[$id][self::M_EFFERENT_COUPLING] = $efferent;
  346. }
  347. }
  348. /**
  349. * Calculates the abstractness for all analyzed nodes.
  350. *
  351. * @return void
  352. */
  353. protected function calculateAbstractness()
  354. {
  355. foreach ($this->nodeMetrics as $id => $metrics) {
  356. if ($metrics[self::M_NUMBER_OF_CLASSES] !== 0) {
  357. $this->nodeMetrics[$id][self::M_ABSTRACTION] = (
  358. $metrics[self::M_NUMBER_OF_ABSTRACT_CLASSES] /
  359. $metrics[self::M_NUMBER_OF_CLASSES]
  360. );
  361. }
  362. }
  363. }
  364. /**
  365. * Calculates the instability for all analyzed nodes.
  366. *
  367. * @return void
  368. */
  369. protected function calculateInstability()
  370. {
  371. foreach ($this->nodeMetrics as $id => $metrics) {
  372. // Count total incoming and outgoing dependencies
  373. $total = (
  374. $metrics[self::M_AFFERENT_COUPLING] +
  375. $metrics[self::M_EFFERENT_COUPLING]
  376. );
  377. if ($total !== 0) {
  378. $this->nodeMetrics[$id][self::M_INSTABILITY] = (
  379. $metrics[self::M_EFFERENT_COUPLING] / $total
  380. );
  381. }
  382. }
  383. }
  384. /**
  385. * Calculates the distance to an optimal value.
  386. *
  387. * @return void
  388. */
  389. protected function calculateDistance()
  390. {
  391. foreach ($this->nodeMetrics as $id => $metrics) {
  392. $this->nodeMetrics[$id][self::M_DISTANCE] = abs(
  393. ($metrics[self::M_ABSTRACTION] + $metrics[self::M_INSTABILITY]) - 1
  394. );
  395. }
  396. }
  397. /**
  398. * Collects a single cycle that is reachable by this namespace. All namespaces
  399. * that are part of the cylce are stored in the given <b>$list</b> array.
  400. *
  401. * @param \PDepend\Source\AST\ASTNamespace[] &$list
  402. * @param \PDepend\Source\AST\ASTNamespace $namespace
  403. * @return boolean If this method detects a cycle the return value is <b>true</b>
  404. * otherwise this method will return <b>false</b>.
  405. */
  406. protected function collectCycle(array &$list, ASTNamespace $namespace)
  407. {
  408. if (in_array($namespace, $list, true)) {
  409. $list[] = $namespace;
  410. return true;
  411. }
  412. $list[] = $namespace;
  413. foreach ($this->getEfferents($namespace) as $efferent) {
  414. if ($this->collectCycle($list, $efferent)) {
  415. return true;
  416. }
  417. }
  418. if (is_int($idx = array_search($namespace, $list, true))) {
  419. unset($list[$idx]);
  420. }
  421. return false;
  422. }
  423. }