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