csv: Allow output customization.
[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 modelize
49
50 redef class ToolContext
51 var mendel_metrics_phase: Phase = new MendelMetricsPhase(self, null)
52 end
53
54 private class MendelMetricsPhase
55 super Phase
56 redef fun process_mainmodule(mainmodule, given_mmodules)
57 do
58 if not toolcontext.opt_mendel.value and not toolcontext.opt_all.value then return
59 var csv = toolcontext.opt_csv.value
60 var out = "{toolcontext.opt_dir.value or else "metrics"}/mendel"
61 out.mkdir
62
63 print toolcontext.format_h1("\n# Mendel metrics")
64
65 var vis = protected_visibility
66 var model = toolcontext.modelbuilder.model
67
68 var mclasses = new HashSet[MClass]
69 for mclass in model.mclasses do
70 if mclass.visibility < vis then continue
71 if mclass.is_interface then continue
72 mclasses.add(mclass)
73 end
74
75 var cnblp = new CNBLP(mainmodule, vis)
76 var cnvi = new CNVI(mainmodule)
77 var cnvs = new CNVS(mainmodule)
78
79 var metrics = new MetricSet
80 metrics.register(cnblp, cnvi, cnvs)
81 metrics.collect(mclasses)
82 if csv then metrics.to_csv.save("{out}/mendel.csv")
83
84 print toolcontext.format_h4("\tlarge mclasses (threshold: {cnblp.threshold})")
85 for mclass in cnblp.above_threshold do
86 print toolcontext.format_p("\t {mclass.name}: {cnblp.values[mclass]}")
87 end
88
89 print toolcontext.format_h4("\tbudding mclasses (threshold: {cnvi.threshold})")
90 for mclass in cnvi.above_threshold do
91 print toolcontext.format_p("\t {mclass.name}: {cnvi.values[mclass]}")
92 end
93
94 print toolcontext.format_h4("\tblooming mclasses (threshold: {cnvs.threshold})")
95 for mclass in cnvs.above_threshold do
96 print toolcontext.format_p("\t {mclass.name}: {cnvs.values[mclass]}")
97 end
98
99 print toolcontext.format_h4("\tblooming mclasses (threshold: {cnvs.threshold})")
100 for mclass in cnvs.above_threshold do
101 print toolcontext.format_p("\t {mclass.name}: {cnvs.values[mclass]}")
102 end
103
104 if csv then
105 var csvh = new CsvDocument
106 csvh.format = new CsvFormat('"', ';', "\n")
107 csvh.header = ["povr", "ovr", "pext", "ext", "pspe", "spe", "prep", "rep", "eq"]
108 for mclass in mclasses do
109 var povr = mclass.is_pure_overrider(vis).object_id
110 var ovr = mclass.is_overrider(vis).object_id
111 var pext = mclass.is_pure_extender(vis).object_id
112 var ext = mclass.is_extender(vis).object_id
113 var pspe = mclass.is_pure_specializer(vis).object_id
114 var spe = mclass.is_pure_specializer(vis).object_id
115 var prep = mclass.is_pure_replacer(vis).object_id
116 var rep = mclass.is_replacer(vis).object_id
117 var eq = mclass.is_equal(vis).object_id
118 csvh.add_record(povr, ovr, pext, ext, pspe, spe, prep, rep, eq)
119 end
120 csvh.save("{out}/inheritance_behaviour.csv")
121 end
122 end
123 end
124
125 # Class Branch Mean Size
126 # cbms(class) = |TotS(class)| / (DIT(class) + 1)
127 class CBMS
128 super MClassMetric
129 super FloatMetric
130 redef fun name do return "cbms"
131 redef fun desc do return "branch mean size, mean number of introduction available among ancestors"
132
133 var mainmodule: MModule
134 init(mainmodule: MModule) do self.mainmodule = mainmodule
135
136 redef fun collect(mclasses) do
137 for mclass in mclasses do
138 var totc = mclass.all_mproperties(mainmodule, protected_visibility).length
139 var ditc = mclass.in_hierarchy(mainmodule).depth
140 values[mclass] = totc.to_f / (ditc + 1).to_f
141 end
142 end
143 end
144
145 # Class Novelty Index
146 # cnvi = |LocS(class)| / cbms(parents(class))
147 class CNVI
148 super MClassMetric
149 super FloatMetric
150 redef fun name do return "cnvi"
151 redef fun desc do return "class novelty index, contribution of the class to its branch in term of introductions"
152
153 var mainmodule: MModule
154 init(mainmodule: MModule) do self.mainmodule = mainmodule
155
156 redef fun collect(mclasses) do
157 var cbms = new CBMS(mainmodule)
158 for mclass in mclasses do
159 # compute branch mean size
160 var parents = mclass.in_hierarchy(mainmodule).direct_greaters
161 if parents.length > 0 then
162 cbms.clear
163 cbms.collect(new HashSet[MClass].from(parents))
164 # compute class novelty index
165 var locc = mclass.local_mproperties(protected_visibility).length
166 values[mclass] = locc.to_f / cbms.avg
167 else
168 values[mclass] = 0.0
169 end
170 end
171 end
172 end
173
174 # Class Novelty Score
175 # cnvs = |LocS(class)| x nvi
176 class CNVS
177 super MClassMetric
178 super FloatMetric
179 redef fun name do return "cnvs"
180 redef fun desc do return "class novelty score, importance of the contribution of the class to its branch"
181
182 var mainmodule: MModule
183 init(mainmodule: MModule) do self.mainmodule = mainmodule
184
185 redef fun collect(mclasses) do
186 var cnvi = new CNVI(mainmodule)
187 cnvi.collect(mclasses)
188 for mclass in mclasses do
189 var locc = mclass.local_mproperties(protected_visibility).length
190 values[mclass] = cnvi.values[mclass] * locc.to_f
191 end
192 end
193 end
194
195 redef class MClass
196 # the set of redefition that call to super
197 fun extended_mproperties(min_visibility: MVisibility): Set[MProperty] do
198 var set = new HashSet[MProperty]
199 for mclassdef in mclassdefs do
200 for mpropdef in mclassdef.mpropdefs do
201 if mpropdef.mproperty.visibility < min_visibility then continue
202 if not mpropdef.has_supercall then continue
203 if mpropdef.mproperty.intro_mclassdef.mclass != self then set.add(mpropdef.mproperty)
204 end
205 end
206 return set
207 end
208
209 # the set of redefition that do not call to super
210 fun overriden_mproperties(min_visibility: MVisibility): Set[MProperty] do
211 var set = new HashSet[MProperty]
212 for mclassdef in mclassdefs do
213 for mpropdef in mclassdef.mpropdefs do
214 if mpropdef.mproperty.visibility < min_visibility then continue
215 if mpropdef.has_supercall then continue
216 if mpropdef.mproperty.intro_mclassdef.mclass != self then set.add(mpropdef.mproperty)
217 end
218 end
219 return set
220 end
221
222 # pure overriders contain only redefinitions
223 private fun is_pure_overrider(min_visibility: MVisibility): Bool do
224 var news = intro_mproperties(min_visibility).length
225 var locs = local_mproperties(min_visibility).length
226 if news == 0 and locs > 0 then return true
227 return false
228 end
229
230 # overriders contain more definitions than introductions
231 private fun is_overrider(min_visibility: MVisibility): Bool do
232 var rdfs = redef_mproperties(min_visibility).length
233 var news = intro_mproperties(min_visibility).length
234 var locs = local_mproperties(min_visibility).length
235 if rdfs >= news and locs > 0 then return true
236 return false
237 end
238
239 # pure extenders contain only introductions
240 private fun is_pure_extender(min_visibility: MVisibility): Bool do
241 var rdfs = redef_mproperties(min_visibility).length
242 var locs = local_mproperties(min_visibility).length
243 if rdfs == 0 and locs > 0 then return true
244 return false
245 end
246
247 # extenders contain more introduction than redefinitions
248 private fun is_extender(min_visibility: MVisibility): Bool do
249 var rdfs = redef_mproperties(min_visibility).length
250 var news = intro_mproperties(min_visibility).length
251 var locs = local_mproperties(min_visibility).length
252 if news > rdfs and locs > 0 then return true
253 return false
254 end
255
256 # pure specializers always call to super in its redefinitions
257 private fun is_pure_specializer(min_visibility: MVisibility): Bool do
258 var ovrs = overriden_mproperties(min_visibility).length
259 var rdfs = redef_mproperties(min_visibility).length
260 if ovrs == 0 and rdfs > 0 then return true
261 return false
262 end
263
264 # specializers have more redefinitions that call super than not calling it
265 private fun is_specializer(min_visibility: MVisibility): Bool do
266 var spcs = extended_mproperties(min_visibility).length
267 var ovrs = overriden_mproperties(min_visibility).length
268 var rdfs = redef_mproperties(min_visibility).length
269 if spcs > ovrs and rdfs > 0 then return true
270 return false
271 end
272
273 # pure replacers never call to super in its redefinitions
274 private fun is_pure_replacer(min_visibility: MVisibility): Bool do
275 var spcs = extended_mproperties(min_visibility).length
276 var rdfs = redef_mproperties(min_visibility).length
277 if spcs == 0 and rdfs > 0 then return true
278 return false
279 end
280
281 # replacers have less redefinitions that call super than not calling it
282 private fun is_replacer(min_visibility: MVisibility): Bool do
283 var spcs = extended_mproperties(min_visibility).length
284 var ovrs = overriden_mproperties(min_visibility).length
285 var rdfs = redef_mproperties(min_visibility).length
286 if ovrs > spcs and rdfs > 0 then return true
287 return false
288 end
289
290 # equals contain as redifinition than introduction
291 private fun is_equal(min_visibility: MVisibility): Bool do
292 var spcs = extended_mproperties(min_visibility).length
293 var ovrs = overriden_mproperties(min_visibility).length
294 var rdfs = redef_mproperties(min_visibility).length
295 if spcs == ovrs and rdfs > 0 then return true
296 return false
297 end
298 end
299