package_dependency.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. # Copyright 2020 The Chromium Authors. All rights reserved.
  2. # Use of this source code is governed by a BSD-style license that can be
  3. # found in the LICENSE file.
  4. """Implementation of the graph module for a [Java package] dependency graph."""
  5. import collections
  6. from typing import Set, Tuple
  7. import class_dependency
  8. import graph
  9. import package_json_consts
  10. class JavaPackage(graph.Node):
  11. """A representation of a Java package."""
  12. def __init__(self, package_name: str):
  13. """Initializes a new Java package structure.
  14. Args:
  15. package_name: The name of the package.
  16. """
  17. super().__init__(package_name)
  18. self._classes = {}
  19. self._class_dependency_edges = collections.defaultdict(set)
  20. @property
  21. def classes(self):
  22. """A map { name -> JavaClass } of classes within this package."""
  23. return self._classes
  24. def add_class(self, java_class: class_dependency.JavaClass):
  25. """Adds a JavaClass to the package, if its key doesn't already exist.
  26. Notably, this does /not/ automatically update the inbound/outbound
  27. dependencies of the package with the dependencies of the class.
  28. """
  29. if java_class.name not in self._classes:
  30. self._classes[java_class.name] = java_class
  31. def add_class_dependency_edge(self, end_package: 'JavaPackage',
  32. begin_class: class_dependency.JavaClass,
  33. end_class: class_dependency.JavaClass):
  34. """Adds a class dependency edge as part of a package dependency.
  35. Each package dependency is comprised of one or more class dependencies,
  36. we manually update the nodes with this info when parsing class graphs.
  37. Args:
  38. end_package: the end node of the package dependency edge
  39. which starts from this node.
  40. begin_class: the start node of the class dependency edge.
  41. end_class: the end node of the class dependency edge.
  42. """
  43. class_edge = (begin_class, end_class)
  44. if class_edge not in self._class_dependency_edges[end_package]:
  45. self._class_dependency_edges[end_package].add(class_edge)
  46. def get_class_dependencies_in_outbound_edge(
  47. self, end_node: 'JavaPackage') -> Set[Tuple]:
  48. """Breaks down a package dependency edge into class dependencies.
  49. For package A to depend on another package B, there must exist
  50. at least one class in A depending on a class in B. This method, given
  51. a package dependency edge A -> B, returns a set of class
  52. dependency edges satisfying (class in A) -> (class in B).
  53. Args:
  54. end_node: The destination node. This method will return the class
  55. dependencies forming the edge from the current node to end_node.
  56. Returns:
  57. A set of tuples of `JavaClass` nodes, where a tuple (a, b)
  58. indicates a class dependency a -> b. If there are no relevant
  59. class dependencies, returns an empty set.
  60. """
  61. return self._class_dependency_edges[end_node]
  62. def get_node_metadata(self):
  63. """Generates JSON metadata for the current node.
  64. The list of classes is sorted in order to help with testing.
  65. Structure:
  66. {
  67. 'classes': [ class_key, ... ],
  68. }
  69. """
  70. return {
  71. package_json_consts.CLASSES: sorted(self.classes.keys()),
  72. }
  73. class JavaPackageDependencyGraph(graph.Graph):
  74. """A graph representation of the dependencies between Java packages.
  75. A directed edge A -> B indicates that A depends on B.
  76. """
  77. def __init__(self, class_graph: class_dependency.JavaClassDependencyGraph):
  78. """Initializes a new package-level dependency graph
  79. by "collapsing" a class-level dependency graph into its packages.
  80. Args:
  81. class_graph: A class-level graph to collapse to a package-level one.
  82. """
  83. super().__init__()
  84. # Create list of all packages using class nodes
  85. # so we don't miss packages with no dependencies (edges).
  86. for class_node in class_graph.nodes:
  87. self.add_node_if_new(class_node.package)
  88. for begin_class, end_class in class_graph.edges:
  89. begin_package = begin_class.package
  90. end_package = end_class.package
  91. self.add_edge_if_new(begin_package, end_package)
  92. begin_package_node = self.get_node_by_key(begin_package)
  93. end_package_node = self.get_node_by_key(end_package)
  94. begin_package_node.add_class(begin_class)
  95. end_package_node.add_class(end_class)
  96. begin_package_node.add_class_dependency_edge(
  97. end_package_node, begin_class, end_class)
  98. def create_node_from_key(self, key: str):
  99. """Create a JavaPackage node from the given key (package name)."""
  100. return JavaPackage(key)
  101. def get_edge_metadata(self, begin_node, end_node):
  102. """Generates JSON metadata for the current edge.
  103. The list of edges is sorted in order to help with testing.
  104. Structure:
  105. {
  106. 'class_edges': [
  107. [begin_key, end_key], ...
  108. ],
  109. }
  110. """
  111. return {
  112. package_json_consts.CLASS_EDGES:
  113. sorted(
  114. [begin.name, end.name] for begin, end in
  115. begin_node.get_class_dependencies_in_outbound_edge(end_node)),
  116. }