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