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