niti: fix type in tool description
[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 model
47 import metrics_base
48 import mclasses_metrics
49 import phase
50 import frontend
51
52 redef class ToolContext
53 var mendel_metrics_phase: Phase = new MendelMetricsPhase(self, null)
54 end
55
56 private class MendelMetricsPhase
57 super Phase
58 redef fun process_mainmodule(mainmodule, given_mmodules)
59 do
60 if not toolcontext.opt_mendel.value and not toolcontext.opt_all.value then return
61 var csv = toolcontext.opt_csv.value
62 var out = "{toolcontext.opt_dir.value or else "metrics"}/mendel"
63 out.mkdir
64
65 print toolcontext.format_h1("\n# Mendel metrics")
66
67 var vis = protected_visibility
68 var model = toolcontext.modelbuilder.model
69
70 var mclasses = new HashSet[MClass]
71 for mclass in model.mclasses do
72 if mclass.visibility < vis then continue
73 if mclass.is_interface then continue
74 mclasses.add(mclass)
75 end
76
77 var cnblp = new CNBLP(mainmodule, vis)
78 var cnvi = new CNVI(mainmodule)
79 var cnvs = new CNVS(mainmodule)
80
81 var metrics = new MetricSet
82 metrics.register(cnblp, cnvi, cnvs)
83 metrics.collect(mclasses)
84 if csv then metrics.to_csv.save("{out}/mendel.csv")
85
86 print toolcontext.format_h4("\tlarge mclasses (threshold: {cnblp.threshold})")
87 for mclass in cnblp.above_threshold do
88 print toolcontext.format_p("\t {mclass.name}: {cnblp.values[mclass]}")
89 end
90
91 print toolcontext.format_h4("\tbudding mclasses (threshold: {cnvi.threshold})")
92 for mclass in cnvi.above_threshold do
93 print toolcontext.format_p("\t {mclass.name}: {cnvi.values[mclass]}")
94 end
95
96 print toolcontext.format_h4("\tblooming mclasses (threshold: {cnvs.threshold})")
97 for mclass in cnvs.above_threshold do
98 print toolcontext.format_p("\t {mclass.name}: {cnvs.values[mclass]}")
99 end
100
101 print toolcontext.format_h4("\tblooming mclasses (threshold: {cnvs.threshold})")
102 for mclass in cnvs.above_threshold do
103 print toolcontext.format_p("\t {mclass.name}: {cnvs.values[mclass]}")
104 end
105
106 if csv then
107 var csvh = new CSVDocument
108 csvh.header = ["povr", "ovr", "pext", "ext", "pspe", "spe", "prep", "rep", "eq"]
109 for mclass in mclasses do
110 var povr = mclass.is_pure_overrider(vis).object_id
111 var ovr = mclass.is_overrider(vis).object_id
112 var pext = mclass.is_pure_extender(vis).object_id
113 var ext = mclass.is_extender(vis).object_id
114 var pspe = mclass.is_pure_specializer(vis).object_id
115 var spe = mclass.is_pure_specializer(vis).object_id
116 var prep = mclass.is_pure_replacer(vis).object_id
117 var rep = mclass.is_replacer(vis).object_id
118 var eq = mclass.is_equal(vis).object_id
119 csvh.add_line(povr, ovr, pext, ext, pspe, spe, prep, rep, eq)
120 end
121 csvh.save("{out}/inheritance_behaviour.csv")
122 end
123 end
124 end
125
126 # Class Branch Mean Size
127 # cbms(class) = |TotS(class)| / (DIT(class) + 1)
128 class CBMS
129 super MClassMetric
130 super FloatMetric
131 redef fun name do return "cbms"
132 redef fun desc do return "branch mean size, mean number of introduction available among ancestors"
133
134 var mainmodule: MModule
135 init(mainmodule: MModule) do self.mainmodule = mainmodule
136
137 redef fun collect(mclasses) do
138 for mclass in mclasses do
139 var totc = mclass.all_mproperties(mainmodule, protected_visibility).length
140 var ditc = mclass.in_hierarchy(mainmodule).depth
141 values[mclass] = totc.to_f / (ditc + 1).to_f
142 end
143 end
144 end
145
146 # Class Novelty Index
147 # cnvi = |LocS(class)| / cbms(parents(class))
148 class CNVI
149 super MClassMetric
150 super FloatMetric
151 redef fun name do return "cnvi"
152 redef fun desc do return "class novelty index, contribution of the class to its branch in term of introductions"
153
154 var mainmodule: MModule
155 init(mainmodule: MModule) do self.mainmodule = mainmodule
156
157 redef fun collect(mclasses) do
158 var cbms = new CBMS(mainmodule)
159 for mclass in mclasses do
160 # compute branch mean size
161 var parents = mclass.in_hierarchy(mainmodule).direct_greaters
162 if parents.length > 0 then
163 cbms.clear
164 cbms.collect(new HashSet[MClass].from(parents))
165 # compute class novelty index
166 var locc = mclass.local_mproperties(protected_visibility).length
167 values[mclass] = locc.to_f / cbms.avg
168 else
169 values[mclass] = 0.0
170 end
171 end
172 end
173 end
174
175 # Class Novelty Score
176 # cnvs = |LocS(class)| x nvi
177 class CNVS
178 super MClassMetric
179 super FloatMetric
180 redef fun name do return "cnvs"
181 redef fun desc do return "class novelty score, importance of the contribution of the class to its branch"
182
183 var mainmodule: MModule
184 init(mainmodule: MModule) do self.mainmodule = mainmodule
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