serialization.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  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. """Helper module for the de/serialization of graphs to/from files."""
  5. import json
  6. from typing import Dict, Tuple
  7. import class_dependency
  8. import class_json_consts
  9. import git_utils
  10. import graph
  11. import json_consts
  12. import package_dependency
  13. def create_json_obj_from_node(node: graph.Node) -> Dict:
  14. """Generates a JSON representation of a given node.
  15. Structure:
  16. {
  17. 'name': str,
  18. 'meta': { see Node.get_node_metadata },
  19. }
  20. """
  21. json_obj = {
  22. json_consts.NAME: node.name,
  23. }
  24. node_meta = node.get_node_metadata()
  25. if node_meta is not None:
  26. json_obj[json_consts.META] = node_meta
  27. return json_obj
  28. def create_json_obj_from_graph(graph_obj: graph.Graph) -> Dict:
  29. """Generates a JSON representation of the current graph.
  30. The list of nodes and edges is sorted in order to help with testing.
  31. Structure:
  32. {
  33. 'nodes': [
  34. { see create_json_obj_from_node }, ...
  35. ],
  36. 'edges': [
  37. {
  38. 'begin': str,
  39. 'end': str,
  40. 'meta': { see Graph.get_edge_metadata },
  41. }, ...
  42. ],
  43. }
  44. """
  45. sorted_nodes = graph.sorted_nodes_by_name(graph_obj.nodes)
  46. json_nodes = [create_json_obj_from_node(node) for node in sorted_nodes]
  47. json_edges = []
  48. for begin_node, end_node in graph.sorted_edges_by_name(graph_obj.edges):
  49. edge_json_obj = {
  50. json_consts.BEGIN: begin_node.name,
  51. json_consts.END: end_node.name,
  52. }
  53. edge_meta = graph_obj.get_edge_metadata(begin_node, end_node)
  54. if edge_meta is not None:
  55. edge_json_obj[json_consts.META] = edge_meta
  56. json_edges.append(edge_json_obj)
  57. return {
  58. json_consts.NODES: json_nodes,
  59. json_consts.EDGES: json_edges,
  60. }
  61. def create_class_graph_from_json_obj(
  62. json_obj: Dict) -> class_dependency.JavaClassDependencyGraph:
  63. """Creates a JavaClassDependencyGraph from a JSON representation."""
  64. class_graph = class_dependency.JavaClassDependencyGraph()
  65. for node_json_obj in json_obj[json_consts.NODES]:
  66. name = node_json_obj[json_consts.NAME]
  67. nested = node_json_obj[json_consts.META][
  68. class_json_consts.NESTED_CLASSES]
  69. build_targets = node_json_obj[json_consts.META][
  70. class_json_consts.BUILD_TARGETS]
  71. added_node = class_graph.add_node_if_new(name)
  72. added_node.nested_classes = set(nested)
  73. added_node.build_targets = set(build_targets)
  74. for edge_json_obj in json_obj[json_consts.EDGES]:
  75. begin_key = edge_json_obj[json_consts.BEGIN]
  76. end_key = edge_json_obj[json_consts.END]
  77. class_graph.add_edge_if_new(begin_key, end_key)
  78. return class_graph
  79. def create_build_metadata() -> Dict:
  80. """Creates metadata about the build the graph was extracted from.
  81. """
  82. return {
  83. json_consts.COMMIT_HASH: git_utils.get_last_commit_hash(),
  84. json_consts.COMMIT_CR_POSITION:
  85. git_utils.get_last_commit_cr_position(),
  86. json_consts.COMMIT_TIME: git_utils.get_last_commit_time(),
  87. }
  88. def dump_class_and_package_graphs_to_file(
  89. class_graph: class_dependency.JavaClassDependencyGraph,
  90. package_graph: package_dependency.JavaPackageDependencyGraph,
  91. filename: str):
  92. """Dumps a JSON representation of the class + package graph to a file.
  93. We dump both graphs together because the package graph in-memory holds
  94. references to class nodes (for storing class edges comprising
  95. a package edge), and hence the class graph is needed to recreate the
  96. package graph. Since our use cases always want the package graph over the
  97. class graph, there currently no point in dumping the class graph separately.
  98. Structure:
  99. {
  100. 'class_graph': { see JavaClassDependencyGraph.to_json },
  101. 'package_graph': { see JavaPackageDependencyGraph.to_json },
  102. }
  103. """
  104. json_obj = {
  105. json_consts.CLASS_GRAPH: create_json_obj_from_graph(class_graph),
  106. json_consts.PACKAGE_GRAPH: create_json_obj_from_graph(package_graph),
  107. json_consts.BUILD_METADATA: create_build_metadata(),
  108. }
  109. with open(filename, 'w') as json_file:
  110. json.dump(json_obj, json_file, separators=(',', ':'))
  111. def load_class_graph_from_file(
  112. filename: str
  113. ) -> Tuple[class_dependency.JavaClassDependencyGraph, Dict]:
  114. """Recreates a JavaClassDependencyGraph from a JSON file.
  115. The file is expected to be in the format dumped by
  116. `dump_package_graph_to_file`.
  117. """
  118. with open(filename, 'r') as json_file:
  119. json_obj = json.load(json_file)
  120. class_graph_json_obj = json_obj[json_consts.CLASS_GRAPH]
  121. return create_class_graph_from_json_obj(
  122. class_graph_json_obj), json_obj.get(json_consts.BUILD_METADATA)
  123. def load_class_and_package_graphs_from_file(
  124. filename: str
  125. ) -> Tuple[class_dependency.JavaClassDependencyGraph, package_dependency.
  126. JavaPackageDependencyGraph, Dict]:
  127. """Recreates a Java(Class+Package)DependencyGraph from a JSON file.
  128. The file is expected to be in the format dumped by
  129. `dump_class_and_package_graphs_to_file`.
  130. Note that we construct the package graph from the deserialized class graph,
  131. not using the serialized package graph at all. This aligns with how we
  132. construct the package graph when using jdeps. However, we still output
  133. a serialized package graph for other consumers of the JSON (eg. JS-side)
  134. which may want to bypass the costly conversion from class to package graph.
  135. """
  136. class_graph, metadata = load_class_graph_from_file(filename)
  137. package_graph = package_dependency.JavaPackageDependencyGraph(class_graph)
  138. return class_graph, package_graph, metadata