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