123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166 |
- # Copyright 2020 The Chromium Authors. All rights reserved.
- # Use of this source code is governed by a BSD-style license that can be
- # found in the LICENSE file.
- """Helper module for the de/serialization of graphs to/from files."""
- import json
- from typing import Dict, Tuple
- import class_dependency
- import class_json_consts
- import git_utils
- import graph
- import json_consts
- import package_dependency
- def create_json_obj_from_node(node: graph.Node) -> Dict:
- """Generates a JSON representation of a given node.
- Structure:
- {
- 'name': str,
- 'meta': { see Node.get_node_metadata },
- }
- """
- json_obj = {
- json_consts.NAME: node.name,
- }
- node_meta = node.get_node_metadata()
- if node_meta is not None:
- json_obj[json_consts.META] = node_meta
- return json_obj
- def create_json_obj_from_graph(graph_obj: graph.Graph) -> Dict:
- """Generates a JSON representation of the current graph.
- The list of nodes and edges is sorted in order to help with testing.
- Structure:
- {
- 'nodes': [
- { see create_json_obj_from_node }, ...
- ],
- 'edges': [
- {
- 'begin': str,
- 'end': str,
- 'meta': { see Graph.get_edge_metadata },
- }, ...
- ],
- }
- """
- sorted_nodes = graph.sorted_nodes_by_name(graph_obj.nodes)
- json_nodes = [create_json_obj_from_node(node) for node in sorted_nodes]
- json_edges = []
- for begin_node, end_node in graph.sorted_edges_by_name(graph_obj.edges):
- edge_json_obj = {
- json_consts.BEGIN: begin_node.name,
- json_consts.END: end_node.name,
- }
- edge_meta = graph_obj.get_edge_metadata(begin_node, end_node)
- if edge_meta is not None:
- edge_json_obj[json_consts.META] = edge_meta
- json_edges.append(edge_json_obj)
- return {
- json_consts.NODES: json_nodes,
- json_consts.EDGES: json_edges,
- }
- def create_class_graph_from_json_obj(
- json_obj: Dict) -> class_dependency.JavaClassDependencyGraph:
- """Creates a JavaClassDependencyGraph from a JSON representation."""
- class_graph = class_dependency.JavaClassDependencyGraph()
- for node_json_obj in json_obj[json_consts.NODES]:
- name = node_json_obj[json_consts.NAME]
- nested = node_json_obj[json_consts.META][
- class_json_consts.NESTED_CLASSES]
- build_targets = node_json_obj[json_consts.META][
- class_json_consts.BUILD_TARGETS]
- added_node = class_graph.add_node_if_new(name)
- added_node.nested_classes = set(nested)
- added_node.build_targets = set(build_targets)
- for edge_json_obj in json_obj[json_consts.EDGES]:
- begin_key = edge_json_obj[json_consts.BEGIN]
- end_key = edge_json_obj[json_consts.END]
- class_graph.add_edge_if_new(begin_key, end_key)
- return class_graph
- def create_build_metadata() -> Dict:
- """Creates metadata about the build the graph was extracted from.
- """
- return {
- json_consts.COMMIT_HASH: git_utils.get_last_commit_hash(),
- json_consts.COMMIT_CR_POSITION:
- git_utils.get_last_commit_cr_position(),
- json_consts.COMMIT_TIME: git_utils.get_last_commit_time(),
- }
- def dump_class_and_package_graphs_to_file(
- class_graph: class_dependency.JavaClassDependencyGraph,
- package_graph: package_dependency.JavaPackageDependencyGraph,
- filename: str):
- """Dumps a JSON representation of the class + package graph to a file.
- We dump both graphs together because the package graph in-memory holds
- references to class nodes (for storing class edges comprising
- a package edge), and hence the class graph is needed to recreate the
- package graph. Since our use cases always want the package graph over the
- class graph, there currently no point in dumping the class graph separately.
- Structure:
- {
- 'class_graph': { see JavaClassDependencyGraph.to_json },
- 'package_graph': { see JavaPackageDependencyGraph.to_json },
- }
- """
- json_obj = {
- json_consts.CLASS_GRAPH: create_json_obj_from_graph(class_graph),
- json_consts.PACKAGE_GRAPH: create_json_obj_from_graph(package_graph),
- json_consts.BUILD_METADATA: create_build_metadata(),
- }
- with open(filename, 'w') as json_file:
- json.dump(json_obj, json_file, separators=(',', ':'))
- def load_class_graph_from_file(
- filename: str
- ) -> Tuple[class_dependency.JavaClassDependencyGraph, Dict]:
- """Recreates a JavaClassDependencyGraph from a JSON file.
- The file is expected to be in the format dumped by
- `dump_package_graph_to_file`.
- """
- with open(filename, 'r') as json_file:
- json_obj = json.load(json_file)
- class_graph_json_obj = json_obj[json_consts.CLASS_GRAPH]
- return create_class_graph_from_json_obj(
- class_graph_json_obj), json_obj.get(json_consts.BUILD_METADATA)
- def load_class_and_package_graphs_from_file(
- filename: str
- ) -> Tuple[class_dependency.JavaClassDependencyGraph, package_dependency.
- JavaPackageDependencyGraph, Dict]:
- """Recreates a Java(Class+Package)DependencyGraph from a JSON file.
- The file is expected to be in the format dumped by
- `dump_class_and_package_graphs_to_file`.
- Note that we construct the package graph from the deserialized class graph,
- not using the serialized package graph at all. This aligns with how we
- construct the package graph when using jdeps. However, we still output
- a serialized package graph for other consumers of the JSON (eg. JS-side)
- which may want to bypass the costly conversion from class to package graph.
- """
- class_graph, metadata = load_class_graph_from_file(filename)
- package_graph = package_dependency.JavaPackageDependencyGraph(class_graph)
- return class_graph, package_graph, metadata
|