src: Updated uses of CSV library
[nit.git] / src / metrics / mendel_metrics.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2014 Alexandre Terrasa <alexandre@moz-code.org>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # The mndel model helps to understand class hierarchies
18 #
19 # It provides metrics to extract interesting classes:
20 #
21 # * Large classes that have a lot of local mproperties
22 # * Budding classes that provide more mproperties than their superclasses
23 # * Blooming classes that are both large and budding
24 #
25 # Also, this model helps to understand inheritance behviours between classes.
26 # It provide metrics to categorize classes as:
27 #
28 # * pure overriders that contain only redefinitions
29 # * overriders that contain more definitions than introductions
30 # * pure extenders that contain only introductions
31 # * extenders that contain more introduction than redefinitions
32 #
33 # Finally, this model can characterize overriding behaviors
34 #
35 # * pure specializers that always call to super in its redefinitions
36 # * specializers that have more redefinitions that call super than not calling it
37 # * pure replacers that never call to super in its redefinitions
38 # * replacers that have less redefinitions that call super than not calling it
39 #
40 # For more details see
41 # Mendel: A Model, Metrics and Rules to Understan Class Hierarchies
42 # S. Denier and Y. Gueheneuc
43 # in Proceedings of the 16th IEEE International Conference on Program Comprehension (OCPC'08)
44 module mendel_metrics
45
46 import metrics_base
47 import mclasses_metrics
48 import mmodules_metrics
49 import modelize
50
51 redef class ToolContext
52 # Compute MENDEL metrics.
53 #
54 # See `mendel_metrics` module documentation.
55 var mendel_metrics_phase: Phase = new MendelMetricsPhase(self, null)
56 end
57
58 private class MendelMetricsPhase
59 super Phase
60 redef fun process_mainmodule(mainmodule, given_mmodules)
61 do
62 if not toolcontext.opt_mendel.value and not toolcontext.opt_all.value then return
63 var csv = toolcontext.opt_csv.value
64 var out = "{toolcontext.opt_dir.value or else "metrics"}/mendel"
65 out.mkdir
66
67 print toolcontext.format_h1("\n# Mendel metrics")
68
69 var model = toolcontext.modelbuilder.model
70 var model_view = model.protected_view
71
72 var mclasses = new HashSet[MClass]
73 for mclass in model_view.mclasses do
74 if mclass.is_interface then continue
75 mclasses.add(mclass)
76 end
77
78 var cnblp = new CNBLP(mainmodule, model_view)
79 var cnvi = new CNVI(mainmodule)
80 var cnvs = new CNVS(mainmodule)
81
82 var metrics = new MetricSet
83 metrics.register(cnblp, cnvi, cnvs)
84 metrics.collect(mclasses)
85 if csv then metrics.to_csv.write_to_file("{out}/mendel.csv")
86
87 var threshold = cnblp.threshold
88 print toolcontext.format_h4("\tlarge mclasses (threshold: {threshold})")
89 for mclass in cnblp.sort do
90 var val = cnblp.values[mclass]
91 if val.to_f < threshold then break
92 print toolcontext.format_p("\t {mclass.name}: {val}")
93 end
94
95 threshold = cnvi.threshold
96 print toolcontext.format_h4("\tbudding mclasses (threshold: {threshold})")
97 for mclass in cnvi.sort do
98 var val = cnvi.values[mclass]
99 if val.to_f < threshold then break
100 print toolcontext.format_p("\t {mclass.name}: {val}")
101 end
102
103 threshold = cnvs.threshold
104 print toolcontext.format_h4("\tblooming mclasses (threshold: {threshold})")
105 for mclass in cnvs.sort do
106 var val = cnvs.values[mclass]
107 if val.to_f < threshold then break
108 print toolcontext.format_p("\t {mclass.name}: {val}")
109 end
110
111 if csv then
112 var csvh = new CsvDocument
113 csvh.separator = ';'
114 csvh.header = ["povr", "ovr", "pext", "ext", "pspe", "spe", "prep", "rep", "eq"]
115 for mclass in mclasses do
116 var povr = mclass.is_pure_overrider(model_view).object_id
117 var ovr = mclass.is_overrider(model_view).object_id
118 var pext = mclass.is_pure_extender(model_view).object_id
119 var ext = mclass.is_extender(model_view).object_id
120 var pspe = mclass.is_pure_specializer(model_view).object_id
121 var spe = mclass.is_pure_specializer(model_view).object_id
122 var prep = mclass.is_pure_replacer(model_view).object_id
123 var rep = mclass.is_replacer(model_view).object_id
124 var eq = mclass.is_equal(model_view).object_id
125 csvh.add_record(povr, ovr, pext, ext, pspe, spe, prep, rep, eq)
126 end
127 csvh.write_to_file("{out}/inheritance_behaviour.csv")
128 end
129 end
130 end
131
132 # Class Branch Mean Size
133 # cbms(class) = |TotS(class)| / (DIT(class) + 1)
134 class CBMS
135 super MClassMetric
136 super FloatMetric
137 redef fun name do return "cbms"
138 redef fun desc do return "branch mean size, mean number of introduction available among ancestors"
139
140 # Mainmodule used to compute class hierarchy.
141 var mainmodule: MModule
142 private var protected_view: ModelView = mainmodule.model.protected_view is lateinit
143
144 redef fun collect(mclasses) do
145 for mclass in mclasses do
146 var totc = mclass.collect_accessible_mproperties(protected_view).length
147 var ditc = mclass.in_hierarchy(mainmodule).depth
148 values[mclass] = totc.to_f / (ditc + 1).to_f
149 end
150 end
151 end
152
153 # Module Branch Mean Size
154 # mbms(module) = |mclassdefs(module)| / (DIT(module) + 1)
155 class MBMS
156 super MModuleMetric
157 super FloatMetric
158 redef fun name do return "mbms"
159 redef fun desc do return "branch mean size, mean number of class definition available among ancestors"
160
161 redef fun collect(mmodules) do
162 for mmodule in mmodules do
163 var totc = mmodule.collect_intro_mclassdefs(mmodule.protected_view).length
164 totc += mmodule.collect_redef_mclassdefs(mmodule.protected_view).length
165 var ditc = mmodule.in_importation.depth
166 values[mmodule] = totc.to_f / (ditc + 1).to_f
167 end
168 end
169 end
170
171 # Class Novelty Index
172 # cnvi = |LocS(class)| / cbms(parents(class))
173 class CNVI
174 super MClassMetric
175 super FloatMetric
176 redef fun name do return "cnvi"
177 redef fun desc do return "class novelty index, contribution of the class to its branch in term of introductions"
178
179 # Mainmodule used to compute class hierarchy.
180 var mainmodule: MModule
181 private var protected_view: ModelView = mainmodule.model.protected_view is lateinit
182
183 redef fun collect(mclasses) do
184 var cbms = new CBMS(mainmodule)
185 for mclass in mclasses do
186 # compute branch mean size
187 var parents = mclass.in_hierarchy(mainmodule).direct_greaters
188 if parents.length > 0 then
189 cbms.clear
190 cbms.collect(new HashSet[MClass].from(parents))
191 # compute class novelty index
192 var locc = mclass.collect_accessible_mproperties(protected_view).length
193 values[mclass] = locc.to_f / cbms.avg
194 else
195 values[mclass] = 0.0
196 end
197 end
198 end
199 end
200
201 # Module Novelty Index
202 # mnvi = |LocS(module)| / mbms(parents(module))
203 class MNVI
204 super MModuleMetric
205 super FloatMetric
206 redef fun name do return "mnvi"
207 redef fun desc do return "module novelty index, contribution of the module to its branch in term of introductions"
208
209 redef fun collect(mmodules) do
210 var mbms = new MBMS
211 for mmodule in mmodules do
212 # compute branch mean size
213 var parents = mmodule.in_importation.direct_greaters
214 if parents.length > 0 then
215 mbms.clear
216 mbms.collect(new HashSet[MModule].from(parents))
217 # compute module novelty index
218 var locc = mmodule.collect_intro_mclassdefs(mmodule.protected_view).length
219 locc += mmodule.collect_redef_mclassdefs(mmodule.protected_view).length
220 values[mmodule] = locc.to_f / mbms.avg
221 else
222 values[mmodule] = 0.0
223 end
224 end
225 end
226 end
227
228 # Class Novelty Score
229 # cnvs = |LocS(class)| x nvi
230 class CNVS
231 super MClassMetric
232 super FloatMetric
233 redef fun name do return "cnvs"
234 redef fun desc do return "class novelty score, importance of the contribution of the class to its branch"
235
236 # Mainmodule used to compute class hierarchy.
237 var mainmodule: MModule
238 private var protected_view: ModelView = mainmodule.model.protected_view is lateinit
239
240 redef fun collect(mclasses) do
241 var cnvi = new CNVI(mainmodule)
242 cnvi.collect(mclasses)
243 for mclass in mclasses do
244 var locc = mclass.collect_local_mproperties(protected_view).length
245 values[mclass] = cnvi.values[mclass] * locc.to_f
246 end
247 end
248 end
249
250 # Module Novelty Score
251 # mnvs = |LocS(module)| x nvi
252 class MNVS
253 super MModuleMetric
254 super FloatMetric
255 redef fun name do return "mnvs"
256 redef fun desc do return "module novelty score, importance of the contribution of the module to its branch"
257
258 redef fun collect(mmodules) do
259 var mnvi = new MNVI
260 mnvi.collect(mmodules)
261 for mmodule in mmodules do
262 var locc = mmodule.collect_intro_mclassdefs(mmodule.protected_view).length
263 locc += mmodule.collect_redef_mclassdefs(mmodule.protected_view).length
264 values[mmodule] = mnvi.values[mmodule] * locc.to_f
265 end
266 end
267 end
268
269 redef class MClass
270 # the set of redefition that call to super
271 fun extended_mproperties(view: ModelView): Set[MProperty] do
272 var set = new HashSet[MProperty]
273 for mclassdef in mclassdefs do
274 for mpropdef in mclassdef.mpropdefs do
275 if not view.accept_mentity(mpropdef) then continue
276 if not mpropdef.has_supercall then continue
277 if mpropdef.mproperty.intro_mclassdef.mclass != self then set.add(mpropdef.mproperty)
278 end
279 end
280 return set
281 end
282
283 # the set of redefition that do not call to super
284 fun overriden_mproperties(view: ModelView): Set[MProperty] do
285 var set = new HashSet[MProperty]
286 for mclassdef in mclassdefs do
287 for mpropdef in mclassdef.mpropdefs do
288 if not view.accept_mentity(mpropdef) then continue
289 if mpropdef.has_supercall then continue
290 if mpropdef.mproperty.intro_mclassdef.mclass != self then set.add(mpropdef.mproperty)
291 end
292 end
293 return set
294 end
295
296 # pure overriders contain only redefinitions
297 private fun is_pure_overrider(view: ModelView): Bool do
298 var news = collect_intro_mproperties(view).length
299 var locs = collect_local_mproperties(view).length
300 if news == 0 and locs > 0 then return true
301 return false
302 end
303
304 # overriders contain more definitions than introductions
305 private fun is_overrider(view: ModelView): Bool do
306 var rdfs = collect_redef_mproperties(view).length
307 var news = collect_intro_mproperties(view).length
308 var locs = collect_local_mproperties(view).length
309 if rdfs >= news and locs > 0 then return true
310 return false
311 end
312
313 # pure extenders contain only introductions
314 private fun is_pure_extender(view: ModelView): Bool do
315 var rdfs = collect_redef_mproperties(view).length
316 var locs = collect_local_mproperties(view).length
317 if rdfs == 0 and locs > 0 then return true
318 return false
319 end
320
321 # extenders contain more introduction than redefinitions
322 private fun is_extender(view: ModelView): Bool do
323 var rdfs = collect_redef_mproperties(view).length
324 var news = collect_intro_mproperties(view).length
325 var locs = collect_local_mproperties(view).length
326 if news > rdfs and locs > 0 then return true
327 return false
328 end
329
330 # pure specializers always call to super in its redefinitions
331 private fun is_pure_specializer(view: ModelView): Bool do
332 var ovrs = overriden_mproperties(view).length
333 var rdfs = collect_redef_mproperties(view).length
334 if ovrs == 0 and rdfs > 0 then return true
335 return false
336 end
337
338 # specializers have more redefinitions that call super than not calling it
339 private fun is_specializer(view: ModelView): Bool do
340 var spcs = extended_mproperties(view).length
341 var ovrs = overriden_mproperties(view).length
342 var rdfs = collect_redef_mproperties(view).length
343 if spcs > ovrs and rdfs > 0 then return true
344 return false
345 end
346
347 # pure replacers never call to super in its redefinitions
348 private fun is_pure_replacer(view: ModelView): Bool do
349 var spcs = extended_mproperties(view).length
350 var rdfs = collect_redef_mproperties(view).length
351 if spcs == 0 and rdfs > 0 then return true
352 return false
353 end
354
355 # replacers have less redefinitions that call super than not calling it
356 private fun is_replacer(view: ModelView): Bool do
357 var spcs = extended_mproperties(view).length
358 var ovrs = overriden_mproperties(view).length
359 var rdfs = collect_redef_mproperties(view).length
360 if ovrs > spcs and rdfs > 0 then return true
361 return false
362 end
363
364 # equals contain as redifinition than introduction
365 private fun is_equal(view: ModelView): Bool do
366 var spcs = extended_mproperties(view).length
367 var ovrs = overriden_mproperties(view).length
368 var rdfs = collect_redef_mproperties(view).length
369 if spcs == ovrs and rdfs > 0 then return true
370 return false
371 end
372 end