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