Merge branch 'dump_rta'
[nit.git] / src / model_viz.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Visualisation of Nit models
16 module model_viz
17
18 import model
19 import ordered_tree
20
21 # A simple specialisation of OrderedTree to display projects, groups and modules
22 # FIXME do not use Object, but a better common interface of MModule and MGroup
23 class MProjectTree
24 super OrderedTree[Object]
25
26 # The model where to look for information
27 var model: Model
28
29 init(model: Model) do self.model = model
30
31 redef fun display(a) do
32 if a isa MGroup then
33 if a.parent == null then return "{a.mproject.name} ({a.filepath})"
34 return a.name + " (group)"
35 else if a isa MModule then
36 return a.name
37 else
38 abort
39 end
40 end
41
42 var linex_comparator: nullable LinexComparator = null
43
44 # Sort modules and groups with their names
45 fun sort_with_alpha
46 do
47 sort_with(alpha_comparator)
48 end
49
50 # Sort modules and groups with a loosly adaptation of the linerarisation of modules
51 fun sort_with_linex
52 do
53 var c = linex_comparator
54 if c == null then
55 c = new LinexComparator(self)
56 linex_comparator = c
57 end
58 sort_with(c)
59 end
60 end
61
62 # Compare modules and groups using the
63 # FIXME do not use Object, but a better common interface of MModule and MGroup
64 private class LinexComparator
65 super AbstractSorter[Object]
66 var mins = new HashMap [MGroup, nullable MModule]
67 var maxs = new HashMap [MGroup, nullable MModule]
68 fun min(o: Object): nullable MModule do
69 if o isa MModule then return o
70 assert o isa MGroup
71 if not mins.has_key(o) then computeminmax(o)
72 return mins[o]
73 end
74 fun max(o: Object): nullable MModule do
75 if o isa MModule then return o
76 assert o isa MGroup
77 if not maxs.has_key(o) then computeminmax(o)
78 return maxs[o]
79 end
80 fun computeminmax(o: MGroup) do
81 if not tree.sub.has_key(o) then
82 mins[o] = null
83 maxs[o] = null
84 return
85 end
86 var subs = tree.sub[o]
87 var minres = min(subs.first)
88 var maxres = max(subs.first)
89 var order = minres.model.mmodule_importation_hierarchy
90 for o2 in subs do
91 var c = min(o2)
92 if c == null then continue
93 if minres == null or order.compare(minres, c) > 0 then minres = c
94 c = max(o2)
95 if c == null then continue
96 if maxres == null or order.compare(maxres, c) < 0 then maxres = c
97 end
98 mins[o] = minres
99 maxs[o] = maxres
100 #if minres != maxres then print "* {o} {minres}..{maxres}"
101 end
102 redef fun compare(a,b) do
103 var ma = min(a)
104 var mb = min(b)
105 if ma == null then
106 if mb == null then return 0 else return -1
107 else if mb == null then
108 return 1
109 end
110 var order = ma.model.mmodule_importation_hierarchy
111 return order.compare(ma, mb)
112 end
113 var tree: OrderedTree[Object]
114 end
115
116 redef class Model
117 # Generate a MProjectTree based on the projects, groups and modules known in the model
118 fun to_mproject_tree: MProjectTree
119 do
120 var res = new MProjectTree(self)
121 for p in mprojects do
122 for g in p.mgroups do
123 res.add(g.parent, g)
124 for m in g.mmodules do
125 res.add(g, m)
126 end
127 end
128 end
129 return res
130 end
131 end
132
133 # Generate graphiz files based on projects, groups and modules
134 #
135 # Interessing elements must be selected. See `mmodules`, ``
136 # Display configuration can be set. See `cluster_group`, `project_group`
137 class MProjectDot
138 # The model where to look for information
139 var model: Model
140
141 # Set of projects to expand fully (ie all groups and modules are displayed)
142 # Initially empty, projects can be added
143 var mprojects = new HashSet[MProject]
144
145 # Set of modules to display
146 # Initially empty, modules can be added
147 var mmodules = new HashSet[MModule]
148
149 private fun node_for(mmodule: MModule): nullable String
150 do
151 return "m_{mmodule.object_id}"
152 end
153
154 # Should groups be shown as clusters?
155 var cluster_group writable = true
156
157 # Should projects be shown as clusters?
158 var project_group writable = true
159
160 # Recursively generate noed ans clusters for a mroup
161 private fun dot_cluster(o: OStream, mgroup: MGroup)
162 do
163 # Open the cluster, if required
164 if mgroup.parent == null then
165 # is is a root group, so display the project
166 if project_group then
167 o.write("subgraph cluster_{mgroup.object_id} \{\nlabel=\"{mgroup.mproject.name}\\n({mgroup.filepath})\"\ncolor=black\nstyle=dotted\n")
168 end
169 else
170 if cluster_group then
171 o.write("subgraph cluster_{mgroup.object_id} \{\nlabel=\"{mgroup.name}\"\ncolor=blue\nstyle=dotted\n")
172 end
173 end
174
175 # outputs the mmodules to show
176 for mmodule in mgroup.mmodules do
177 if not mmodules.has(mmodule) then continue
178 o.write("\t{node_for(mmodule)} [label=\"{mmodule.name}\",color=green]\n")
179 end
180
181 # recusively progress on sub-clusters
182 for d in mgroup.in_nesting.direct_smallers do
183 dot_cluster(o, d)
184 end
185
186 # close the cluster if required
187 if mgroup.parent == null then
188 if project_group then o.write("\}\n")
189 else
190 if cluster_group then o.write("\}\n")
191 end
192 end
193
194 # Extends the set of `mmodules` by recurdively adding the most specific imported modules of foreign projects
195 fun collect_important_importation
196 do
197 var todo = new List[MModule]
198 todo.add_all(mmodules)
199 while not todo.is_empty do
200 var m = todo.pop
201
202 for psm in m.in_importation.greaters do
203 if m.mgroup.mproject != psm.mgroup.mproject then continue
204 for ssm in psm.in_importation.direct_greaters do
205 if psm.mgroup.mproject == ssm.mgroup.mproject then continue
206 mmodules.add(ssm)
207 todo.add(ssm)
208 end
209 end
210 end
211 end
212
213 # Generate the dot-file named `filepath` with the current configuration
214 fun render(filepath: String)
215 do
216 # Collect interessing nodes
217 for m in model.mmodules do
218 # filter out modules outside wanted projects
219 if m.mgroup == null then continue
220 if not mprojects.has(m.mgroup.mproject) then continue
221
222 mmodules.add(m)
223 end
224
225 collect_important_importation
226
227 # Collect interessing edges
228 var sub_hierarchy = new POSet[MModule]
229 for m in mmodules do
230 sub_hierarchy.add_node(m)
231 for sm in m.in_importation.greaters do
232 if sm == m then continue
233 if not mmodules.has(sm) then continue
234 sub_hierarchy.add_edge(m, sm)
235 end
236 end
237
238 print "generating {filepath}"
239 var dot = new OFStream.open(filepath)
240 dot.write("digraph g \{\n")
241 dot.write("rankdir=BT;node[shape=box];\n")
242 # Generate the nodes
243 for p in model.mprojects do
244 dot_cluster(dot, p.root.as(not null))
245 end
246 # Generate the edges
247 for m in mmodules do
248 for sm in sub_hierarchy[m].direct_greaters do
249 var nm = node_for(m)
250 var nsm = node_for(sm)
251 if m.in_importation.direct_greaters.has(sm) then
252 dot.write("\t{nm} -> {nsm}[style=bold]\n")
253 else
254 dot.write("\t{nm} -> {nsm}[style=solid]\n")
255 end
256 end
257 end
258 dot.write("\}\n")
259 dot.close
260 # sys.system("xdot -f dot {filepath}")
261 end
262 end