metrics: update ModelView
[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 Mendel 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 Understand Class Hierarchies,”
42 # by 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 = new ModelView(model, mainmodule)
71 model_view.min_visibility = protected_visibility
72
73 var mclasses = new HashSet[MClass]
74 for mclass in model_view.mclasses do
75 if mclass.is_interface then continue
76 mclasses.add(mclass)
77 end
78
79 var cnblp = new CNBLP(model_view)
80 var cnvi = new CNVI(model_view)
81 var cnvs = new CNVS(model_view)
82
83 var metrics = new MetricSet
84 metrics.register(cnblp, cnvi, cnvs)
85 metrics.collect(mclasses)
86 if csv then metrics.to_csv.write_to_file("{out}/mendel.csv")
87
88 var threshold = cnblp.threshold
89 print toolcontext.format_h4("\tlarge mclasses (threshold: {threshold})")
90 for mclass in cnblp.sort do
91 var val = cnblp.values[mclass]
92 if val.to_f < threshold then break
93 print toolcontext.format_p("\t {mclass.name}: {val}")
94 end
95
96 threshold = cnvi.threshold
97 print toolcontext.format_h4("\tbudding mclasses (threshold: {threshold})")
98 for mclass in cnvi.sort do
99 var val = cnvi.values[mclass]
100 if val.to_f < threshold then break
101 print toolcontext.format_p("\t {mclass.name}: {val}")
102 end
103
104 threshold = cnvs.threshold
105 print toolcontext.format_h4("\tblooming mclasses (threshold: {threshold})")
106 for mclass in cnvs.sort do
107 var val = cnvs.values[mclass]
108 if val.to_f < threshold then break
109 print toolcontext.format_p("\t {mclass.name}: {val}")
110 end
111
112 if csv then
113 var csvh = new CsvDocument
114 csvh.separator = ';'
115 csvh.header = ["povr", "ovr", "pext", "ext", "pspe", "spe", "prep", "rep", "eq"]
116 for mclass in mclasses do
117 var povr = mclass.is_pure_overrider(model_view).object_id
118 var ovr = mclass.is_overrider(model_view).object_id
119 var pext = mclass.is_pure_extender(model_view).object_id
120 var ext = mclass.is_extender(model_view).object_id
121 var pspe = mclass.is_pure_specializer(model_view).object_id
122 var spe = mclass.is_pure_specializer(model_view).object_id
123 var prep = mclass.is_pure_replacer(model_view).object_id
124 var rep = mclass.is_replacer(model_view).object_id
125 var eq = mclass.is_equal(model_view).object_id
126 csvh.add_record(povr, ovr, pext, ext, pspe, spe, prep, rep, eq)
127 end
128 csvh.write_to_file("{out}/inheritance_behaviour.csv")
129 end
130 end
131 end
132
133 # Class Branch Mean Size
134 # cbms(class) = |TotS(class)| / (DIT(class) + 1)
135 class CBMS
136 super MClassMetric
137 super FloatMetric
138 redef fun name do return "cbms"
139 redef fun desc do return "branch mean size, mean number of introduction available among ancestors"
140
141 redef fun collect(mclasses) do
142 for mclass in mclasses do
143 var totc = mclass.collect_accessible_mproperties(model_view).length
144 var ditc = mclass.in_hierarchy(model_view.mainmodule).depth
145 values[mclass] = totc.to_f / (ditc + 1).to_f
146 end
147 end
148 end
149
150 # Module Branch Mean Size
151 # mbms(module) = |mclassdefs(module)| / (DIT(module) + 1)
152 class MBMS
153 super MModuleMetric
154 super FloatMetric
155 redef fun name do return "mbms"
156 redef fun desc do return "branch mean size, mean number of class definition available among ancestors"
157
158 redef fun collect(mmodules) do
159 for mmodule in mmodules do
160 var totc = mmodule.collect_intro_mclassdefs(model_view).length
161 totc += mmodule.collect_redef_mclassdefs(model_view).length
162 var ditc = mmodule.in_importation.depth
163 values[mmodule] = totc.to_f / (ditc + 1).to_f
164 end
165 end
166 end
167
168 # Class Novelty Index
169 # cnvi = |LocS(class)| / cbms(parents(class))
170 class CNVI
171 super MClassMetric
172 super FloatMetric
173 redef fun name do return "cnvi"
174 redef fun desc do return "class novelty index, contribution of the class to its branch in term of introductions"
175
176 redef fun collect(mclasses) do
177 var cbms = new CBMS(model_view)
178 for mclass in mclasses do
179 # compute branch mean size
180 var parents = mclass.in_hierarchy(model_view.mainmodule).direct_greaters
181 if parents.length > 0 then
182 cbms.clear
183 cbms.collect(new HashSet[MClass].from(parents))
184 # compute class novelty index
185 var locc = mclass.collect_accessible_mproperties(model_view).length
186 values[mclass] = locc.to_f / cbms.avg
187 else
188 values[mclass] = 0.0
189 end
190 end
191 end
192 end
193
194 # Module Novelty Index
195 # mnvi = |LocS(module)| / mbms(parents(module))
196 class MNVI
197 super MModuleMetric
198 super FloatMetric
199 redef fun name do return "mnvi"
200 redef fun desc do return "module novelty index, contribution of the module to its branch in term of introductions"
201
202 redef fun collect(mmodules) do
203 var mbms = new MBMS(model_view)
204 for mmodule in mmodules do
205 # compute branch mean size
206 var parents = mmodule.in_importation.direct_greaters
207 if parents.length > 0 then
208 mbms.clear
209 mbms.collect(new HashSet[MModule].from(parents))
210 # compute module novelty index
211 var locc = mmodule.collect_intro_mclassdefs(model_view).length
212 locc += mmodule.collect_redef_mclassdefs(model_view).length
213 values[mmodule] = locc.to_f / mbms.avg
214 else
215 values[mmodule] = 0.0
216 end
217 end
218 end
219 end
220
221 # Class Novelty Score
222 # cnvs = |LocS(class)| x nvi
223 class CNVS
224 super MClassMetric
225 super FloatMetric
226 redef fun name do return "cnvs"
227 redef fun desc do return "class novelty score, importance of the contribution of the class to its branch"
228
229 redef fun collect(mclasses) do
230 var cnvi = new CNVI(model_view)
231 cnvi.collect(mclasses)
232 for mclass in mclasses do
233 var locc = mclass.collect_local_mproperties(model_view).length
234 values[mclass] = cnvi.values[mclass] * locc.to_f
235 end
236 end
237 end
238
239 # Module Novelty Score
240 # mnvs = |LocS(module)| x nvi
241 class MNVS
242 super MModuleMetric
243 super FloatMetric
244 redef fun name do return "mnvs"
245 redef fun desc do return "module novelty score, importance of the contribution of the module to its branch"
246
247 redef fun collect(mmodules) do
248 var mnvi = new MNVI(model_view)
249 mnvi.collect(mmodules)
250 for mmodule in mmodules do
251 var locc = mmodule.collect_intro_mclassdefs(model_view).length
252 locc += mmodule.collect_redef_mclassdefs(model_view).length
253 values[mmodule] = mnvi.values[mmodule] * locc.to_f
254 end
255 end
256 end
257
258 redef class MClass
259 # the set of redefition that call to super
260 fun extended_mproperties(view: ModelView): Set[MProperty] do
261 var set = new HashSet[MProperty]
262 for mclassdef in mclassdefs do
263 for mpropdef in mclassdef.mpropdefs do
264 if not view.accept_mentity(mpropdef) then continue
265 if not mpropdef.has_supercall then continue
266 if mpropdef.mproperty.intro_mclassdef.mclass != self then set.add(mpropdef.mproperty)
267 end
268 end
269 return set
270 end
271
272 # the set of redefition that do not call to super
273 fun overriden_mproperties(view: ModelView): Set[MProperty] do
274 var set = new HashSet[MProperty]
275 for mclassdef in mclassdefs do
276 for mpropdef in mclassdef.mpropdefs do
277 if not view.accept_mentity(mpropdef) then continue
278 if mpropdef.has_supercall then continue
279 if mpropdef.mproperty.intro_mclassdef.mclass != self then set.add(mpropdef.mproperty)
280 end
281 end
282 return set
283 end
284
285 # pure overriders contain only redefinitions
286 private fun is_pure_overrider(view: ModelView): Bool do
287 var news = collect_intro_mproperties(view).length
288 var locs = collect_local_mproperties(view).length
289 if news == 0 and locs > 0 then return true
290 return false
291 end
292
293 # overriders contain more definitions than introductions
294 private fun is_overrider(view: ModelView): Bool do
295 var rdfs = collect_redef_mproperties(view).length
296 var news = collect_intro_mproperties(view).length
297 var locs = collect_local_mproperties(view).length
298 if rdfs >= news and locs > 0 then return true
299 return false
300 end
301
302 # pure extenders contain only introductions
303 private fun is_pure_extender(view: ModelView): Bool do
304 var rdfs = collect_redef_mproperties(view).length
305 var locs = collect_local_mproperties(view).length
306 if rdfs == 0 and locs > 0 then return true
307 return false
308 end
309
310 # extenders contain more introduction than redefinitions
311 private fun is_extender(view: ModelView): Bool do
312 var rdfs = collect_redef_mproperties(view).length
313 var news = collect_intro_mproperties(view).length
314 var locs = collect_local_mproperties(view).length
315 if news > rdfs and locs > 0 then return true
316 return false
317 end
318
319 # pure specializers always call to super in its redefinitions
320 private fun is_pure_specializer(view: ModelView): Bool do
321 var ovrs = overriden_mproperties(view).length
322 var rdfs = collect_redef_mproperties(view).length
323 if ovrs == 0 and rdfs > 0 then return true
324 return false
325 end
326
327 # specializers have more redefinitions that call super than not calling it
328 private fun is_specializer(view: ModelView): Bool do
329 var spcs = extended_mproperties(view).length
330 var ovrs = overriden_mproperties(view).length
331 var rdfs = collect_redef_mproperties(view).length
332 if spcs > ovrs and rdfs > 0 then return true
333 return false
334 end
335
336 # pure replacers never call to super in its redefinitions
337 private fun is_pure_replacer(view: ModelView): Bool do
338 var spcs = extended_mproperties(view).length
339 var rdfs = collect_redef_mproperties(view).length
340 if spcs == 0 and rdfs > 0 then return true
341 return false
342 end
343
344 # replacers have less redefinitions that call super than not calling it
345 private fun is_replacer(view: ModelView): Bool do
346 var spcs = extended_mproperties(view).length
347 var ovrs = overriden_mproperties(view).length
348 var rdfs = collect_redef_mproperties(view).length
349 if ovrs > spcs and rdfs > 0 then return true
350 return false
351 end
352
353 # equals contain as redifinition than introduction
354 private fun is_equal(view: ModelView): Bool do
355 var spcs = extended_mproperties(view).length
356 var ovrs = overriden_mproperties(view).length
357 var rdfs = collect_redef_mproperties(view).length
358 if spcs == ovrs and rdfs > 0 then return true
359 return false
360 end
361 end