1#!/usr/bin/python3
2"""A script for parsing and filtering component dot file.
3Adapted from vendor/google_clockwork/packages/SystemUI/daggervis/parser.py
4
5Input: input_dot_file output_dot_file [beginning_nodes_filter]
6Output: create a new dot file with styles applied. The output dot file will only contain nodes
7reachable from the beginning_nodes_filter if it's specified.
8"""
9import sys
10import os
11try:
12  import pydot
13except ImportError as e:
14  print("Error: python3-pydot is not installed. Please run \"sudo apt install python3-pydot\" first.", file=sys.stderr)
15  sys.exit(1)
16
17def main():
18  # Parse args
19  if len(sys.argv) < 2:
20    print("Error: please specify an input dot file", file=sys.stderr)
21    sys.exit(1)
22  if len(sys.argv) < 3:
23    print("Error: please specify an output dot file", file=sys.stderr)
24    sys.exit(1)
25  input_path = sys.argv[1]
26  output_path = sys.argv[2]
27  if len(sys.argv) > 3:
28    beginning_nodes_filter= sys.argv[3]
29  else:
30    beginning_nodes_filter= None
31
32  # Load graph
33  try:
34    graph = pydot.graph_from_dot_file(input_path)[0]
35  except Exception as e:
36    print("Error: unable to load dot file \"" + input_path + "\"", file=sys.stderr)
37    sys.exit(1)
38  print("Loaded dot file from " + input_path)
39
40  # Trim graph
41  if beginning_nodes_filter!= None:
42    trim_graph(graph, beginning_nodes_filter)
43
44  # Add styles
45  style_graph(graph)
46
47  with open(output_path, "w") as f:
48    f.write(str(graph))
49    print("Saved output dot file " + output_path)
50
51"""
52Trim a graph by only keeping nodes/edges reachable from beginning nodes.
53"""
54def trim_graph(graph, beginning_nodes_filter):
55  beginning_node_names = set()
56  all_nodes = graph.get_nodes()
57  for n in all_nodes:
58    if beginning_nodes_filter in get_label(n):
59      beginning_node_names.add(n.get_name())
60  if len(beginning_node_names) == 0:
61    print("Error: unable to find nodes matching \"" + beginning_nodes_filter + "\"", file=sys.stderr)
62    sys.exit(1)
63  filtered_node_names = set()
64  all_edges = graph.get_edges()
65  for node_name in beginning_node_names:
66    dfs(all_edges, node_name, filtered_node_names)
67  cnt_trimmed_nodes = 0
68  for node in all_nodes:
69    if not node.get_name() in filtered_node_names:
70      graph.del_node(node.get_name())
71      cnt_trimmed_nodes += 1
72  cnt_trimmed_edges = 0
73  for edge in all_edges:
74    if not edge.get_source() in filtered_node_names:
75      graph.del_edge(edge.get_source(), edge.get_destination())
76      cnt_trimmed_edges += 1
77  print("Trimed " + str(cnt_trimmed_nodes) + " nodes and " + str(cnt_trimmed_edges) + " edges")
78
79def dfs(all_edges, node_name, filtered_node_names):
80  if node_name in filtered_node_names:
81    return
82  filtered_node_names.add(node_name)
83  for edge in all_edges:
84    if edge.get_source() == node_name:
85      dfs(all_edges, edge.get_destination(), filtered_node_names)
86
87"""
88Apply styles to the dot graph.
89"""
90def style_graph(graph):
91  for n in graph.get_nodes():
92    label = get_label(n)
93    # Style SystemUI nodes
94    if "com.android.systemui" in label:
95      n.obj_dict["attributes"]["color"] = "burlywood"
96      n.obj_dict["attributes"]["shape"] = "box"
97      n.add_style("filled")
98    # Style CarSystemUI nodes
99    elif ("car" in label):
100      n.obj_dict["attributes"]["color"] = "darkolivegreen1"
101      n.add_style("filled")
102
103    # Trim common labels
104    trim_replacements = [("java.util.", ""), ("javax.inject.", "") , ("com.", "c."),
105                         ("google.", "g."), ("android.", "a."), ("car.", "c."),
106                         ("java.lang.", ""), ("dagger.Lazy", "Lazy"), ("java.util.function.", "")]
107    for (before, after) in trim_replacements:
108      if before in label:
109         n.obj_dict["attributes"]["label"] = label = label.replace(before, after)
110
111def get_label(node):
112  try:
113    return node.obj_dict["attributes"]["label"]
114  except Exception:
115    return ""
116
117if __name__ == "__main__":
118    main()