model/model_viz: use OrderedTree[MConcern]
[nit.git] / src / model / 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 packages, groups and modules
22 class MPackageTree
23 super OrderedTree[MConcern]
24
25 # The model where to look for information
26 var model: Model
27
28 redef fun display(a) do
29 if a isa MGroup then
30 if a.parent == null then return "{a.mpackage.name} ({a.filepath.to_s})"
31 return a.name + " (group)"
32 else if a isa MModule then
33 return a.name
34 else
35 abort
36 end
37 end
38
39 private var linex_comparator: nullable LinexComparator = null
40
41 # Sort modules and groups with their names
42 fun sort_with_alpha
43 do
44 sort_with(alpha_comparator)
45 end
46
47 # Sort modules and groups with a loosely adaptation of the linearization of modules
48 fun sort_with_linex
49 do
50 var c = linex_comparator
51 if c == null then
52 c = new LinexComparator(self)
53 linex_comparator = c
54 end
55 sort_with(c)
56 end
57 end
58
59 # Compare modules and groups using the
60 private class LinexComparator
61 super Comparator
62
63 redef type COMPARED: MConcern
64
65 var mins = new HashMap [MGroup, nullable MModule]
66 var maxs = new HashMap [MGroup, nullable MModule]
67 fun mini(o: Object): nullable MModule do
68 if o isa MModule then return o
69 assert o isa MGroup
70 if not mins.has_key(o) then computeminmax(o)
71 return mins[o]
72 end
73 fun maxi(o: Object): nullable MModule do
74 if o isa MModule then return o
75 assert o isa MGroup
76 if not maxs.has_key(o) then computeminmax(o)
77 return maxs[o]
78 end
79 fun computeminmax(o: MGroup) do
80 if not tree.sub.has_key(o) then
81 mins[o] = null
82 maxs[o] = null
83 return
84 end
85 var subs = tree.sub[o]
86 var minres = mini(subs.first)
87 var maxres = maxi(subs.first)
88 var order = minres.model.mmodule_importation_hierarchy
89 for o2 in subs do
90 var c = mini(o2)
91 if c == null then continue
92 if minres == null or order.compare(minres, c) > 0 then minres = c
93 c = maxi(o2)
94 if c == null then continue
95 if maxres == null or order.compare(maxres, c) < 0 then maxres = c
96 end
97 mins[o] = minres
98 maxs[o] = maxres
99 #if minres != maxres then print "* {o} {minres}..{maxres}"
100 end
101 redef fun compare(a,b) do
102 var ma = mini(a)
103 var mb = mini(b)
104 if ma == null then
105 if mb == null then return 0 else return -1
106 else if mb == null then
107 return 1
108 end
109 var order = ma.model.mmodule_importation_hierarchy
110 return order.compare(ma, mb)
111 end
112 var tree: OrderedTree[MConcern]
113 end
114
115 redef class Model
116 # Generate a MPackageTree based on the packages, groups and modules known in the model
117 fun to_mpackage_tree: MPackageTree
118 do
119 var res = new MPackageTree(self)
120 for p in mpackages do
121 for g in p.mgroups do
122 res.add(g.parent, g)
123 for m in g.mmodules do
124 res.add(g, m)
125 end
126 end
127 end
128 return res
129 end
130 end
131
132 # Generate graphiz files based on packages, groups and modules
133 #
134 # Interesting elements must be selected. See `mmodules`, ``
135 # Display configuration can be set. See `cluster_group`, `package_group`
136 class MPackageDot
137 super Writable
138
139 # The model where to look for information
140 var model: Model
141
142 # Set of packages to expand fully (ie all groups and modules are displayed)
143 # Initially empty, packages can be added
144 var mpackages = new HashSet[MPackage]
145
146 # Set of modules to display
147 # Initially empty, modules can be added
148 var mmodules = new HashSet[MModule]
149
150 private fun node_for(mmodule: MModule): String
151 do
152 return "m_{mmodule.object_id}"
153 end
154
155 # Should groups be shown as clusters?
156 var cluster_group = true is writable
157
158 # Should packages be shown as clusters?
159 var package_group = true is writable
160
161 # Recursively generate node and clusters for a mgroup
162 private fun dot_cluster(o: Writer, mgroup: MGroup)
163 do
164 # Open the cluster, if required
165 if mgroup.parent == null then
166 # is is a root group, so display the package
167 if package_group then
168 o.write("subgraph cluster_{mgroup.object_id} \{\nlabel=\"{mgroup.mpackage.name}\\n({mgroup.filepath.to_s})\"\ncolor=black\nstyle=dotted\n")
169 end
170 else
171 if cluster_group then
172 o.write("subgraph cluster_{mgroup.object_id} \{\nlabel=\"{mgroup.name}\"\ncolor=blue\nstyle=dotted\n")
173 end
174 end
175
176 # outputs the mmodules to show
177 for mmodule in mgroup.mmodules do
178 if not mmodules.has(mmodule) then continue
179 o.write("\t{node_for(mmodule)} [label=\"{mmodule.name}\",color=green]\n")
180 end
181
182 # recursively progress on sub-clusters
183 for d in mgroup.in_nesting.direct_smallers do
184 dot_cluster(o, d)
185 end
186
187 # close the cluster if required
188 if mgroup.parent == null then
189 if package_group then o.write("\}\n")
190 else
191 if cluster_group then o.write("\}\n")
192 end
193 end
194
195 # Extends the set of `mmodules` by recursively adding the most specific imported modules of foreign packages
196 fun collect_important_importation
197 do
198 var todo = new List[MModule]
199 todo.add_all(mmodules)
200 while not todo.is_empty do
201 var m = todo.pop
202
203 for psm in m.in_importation.greaters do
204 if m.mgroup.mpackage != psm.mgroup.mpackage then continue
205 for ssm in psm.in_importation.direct_greaters do
206 if psm.mgroup.mpackage == ssm.mgroup.mpackage then continue
207 mmodules.add(ssm)
208 todo.add(ssm)
209 end
210 end
211 end
212 end
213
214 # Generate the dot content with the current configuration
215 redef fun write_to(stream)
216 do
217 # Collect interesting nodes
218 for m in model.mmodules do
219 # filter out modules outside wanted packages
220 if m.mgroup == null then continue
221 if not mpackages.has(m.mgroup.mpackage) then continue
222
223 mmodules.add(m)
224 end
225
226 collect_important_importation
227
228 # Collect interesting edges
229 var sub_hierarchy = new POSet[MModule]
230 for m in mmodules do
231 sub_hierarchy.add_node(m)
232 for sm in m.in_importation.greaters do
233 if sm == m then continue
234 if not mmodules.has(sm) then continue
235 sub_hierarchy.add_edge(m, sm)
236 end
237 end
238
239 stream.write("digraph g \{\n")
240 stream.write("rankdir=BT;node[shape=box];\n")
241 # Generate the nodes
242 for p in model.mpackages do
243 dot_cluster(stream, p.root.as(not null))
244 end
245 # Generate the edges
246 for m in mmodules do
247 for sm in sub_hierarchy[m].direct_greaters do
248 var nm = node_for(m)
249 var nsm = node_for(sm)
250 if m.in_importation.direct_greaters.has(sm) then
251 stream.write("\t{nm} -> {nsm}[style=bold]\n")
252 else
253 stream.write("\t{nm} -> {nsm}[style=solid]\n")
254 end
255 end
256 end
257 stream.write("\}\n")
258 end
259 end