1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Copyright 2014 Alexandre Terrasa <alexandre@moz-code.org>
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
9 # http://www.apache.org/licenses/LICENSE-2.0
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.
17 # The Mendel model helps to understand class hierarchies.
19 # It provides metrics to extract interesting classes:
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
25 # Also, this model helps to understand inheritance behviours between classes.
26 # It provide metrics to categorize classes as:
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
33 # Finally, this model can characterize overriding behaviors
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
40 # For more details see
41 # “Mendel: A Model, Metrics and Rules to Understand Class Hierarchies,”
42 # by S. Denier and Y. Gueheneuc,
43 # in *Proceedings of the 16th IEEE International Conference on Program Comprehension* (OCPC'08).
47 import mclasses_metrics
48 import mmodules_metrics
51 redef class ToolContext
52 # Compute MENDEL metrics.
54 # See `mendel_metrics` module documentation.
55 var mendel_metrics_phase
: Phase = new MendelMetricsPhase(self, null)
58 private class MendelMetricsPhase
60 redef fun process_mainmodule
(mainmodule
, given_mmodules
)
62 if not toolcontext
.opt_mendel
.value
and not toolcontext
.opt_all
.value
then return
63 var csv
= toolcontext
.opt_csv
.value
64 var out
= "{toolcontext.opt_dir.value or else "metrics"}/mendel"
67 print toolcontext
.format_h1
("\n# Mendel metrics")
69 var model
= toolcontext
.modelbuilder
.model
70 var model_view
= model
.protected_view
72 var mclasses
= new HashSet[MClass]
73 for mclass
in model_view
.mclasses
do
74 if mclass
.is_interface
then continue
78 var cnblp
= new CNBLP(mainmodule
, model_view
)
79 var cnvi
= new CNVI(mainmodule
, model_view
)
80 var cnvs
= new CNVS(mainmodule
, model_view
)
82 var metrics
= new MetricSet
83 metrics
.register
(cnblp
, cnvi
, cnvs
)
84 metrics
.collect
(mclasses
)
85 if csv
then metrics
.to_csv
.write_to_file
("{out}/mendel.csv")
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}")
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}")
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}")
112 var csvh
= new CsvDocument
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
(model_view
).object_id
117 var ovr
= mclass
.is_overrider
(model_view
).object_id
118 var pext
= mclass
.is_pure_extender
(model_view
).object_id
119 var ext
= mclass
.is_extender
(model_view
).object_id
120 var pspe
= mclass
.is_pure_specializer
(model_view
).object_id
121 var spe
= mclass
.is_pure_specializer
(model_view
).object_id
122 var prep
= mclass
.is_pure_replacer
(model_view
).object_id
123 var rep
= mclass
.is_replacer
(model_view
).object_id
124 var eq
= mclass
.is_equal
(model_view
).object_id
125 csvh
.add_record
(povr
, ovr
, pext
, ext
, pspe
, spe
, prep
, rep
, eq
)
127 csvh
.write_to_file
("{out}/inheritance_behaviour.csv")
132 # Class Branch Mean Size
133 # cbms(class) = |TotS(class)| / (DIT(class) + 1)
137 redef fun name
do return "cbms"
138 redef fun desc
do return "branch mean size, mean number of introduction available among ancestors"
140 redef fun collect
(mclasses
) do
141 for mclass
in mclasses
do
142 var totc
= mclass
.collect_accessible_mproperties
(model_view
).length
143 var ditc
= mclass
.in_hierarchy
(mainmodule
).depth
144 values
[mclass
] = totc
.to_f
/ (ditc
+ 1).to_f
149 # Module Branch Mean Size
150 # mbms(module) = |mclassdefs(module)| / (DIT(module) + 1)
154 redef fun name
do return "mbms"
155 redef fun desc
do return "branch mean size, mean number of class definition available among ancestors"
157 redef fun collect
(mmodules
) do
158 for mmodule
in mmodules
do
159 var totc
= mmodule
.collect_intro_mclassdefs
(model_view
).length
160 totc
+= mmodule
.collect_redef_mclassdefs
(model_view
).length
161 var ditc
= mmodule
.in_importation
.depth
162 values
[mmodule
] = totc
.to_f
/ (ditc
+ 1).to_f
167 # Class Novelty Index
168 # cnvi = |LocS(class)| / cbms(parents(class))
172 redef fun name
do return "cnvi"
173 redef fun desc
do return "class novelty index, contribution of the class to its branch in term of introductions"
175 redef fun collect
(mclasses
) do
176 var cbms
= new CBMS(mainmodule
, model_view
)
177 for mclass
in mclasses
do
178 # compute branch mean size
179 var parents
= mclass
.in_hierarchy
(mainmodule
).direct_greaters
180 if parents
.length
> 0 then
182 cbms
.collect
(new HashSet[MClass].from
(parents
))
183 # compute class novelty index
184 var locc
= mclass
.collect_accessible_mproperties
(model_view
).length
185 values
[mclass
] = locc
.to_f
/ cbms
.avg
193 # Module Novelty Index
194 # mnvi = |LocS(module)| / mbms(parents(module))
198 redef fun name
do return "mnvi"
199 redef fun desc
do return "module novelty index, contribution of the module to its branch in term of introductions"
201 redef fun collect
(mmodules
) do
202 var mbms
= new MBMS(mainmodule
, model_view
)
203 for mmodule
in mmodules
do
204 # compute branch mean size
205 var parents
= mmodule
.in_importation
.direct_greaters
206 if parents
.length
> 0 then
208 mbms
.collect
(new HashSet[MModule].from
(parents
))
209 # compute module novelty index
210 var locc
= mmodule
.collect_intro_mclassdefs
(model_view
).length
211 locc
+= mmodule
.collect_redef_mclassdefs
(model_view
).length
212 values
[mmodule
] = locc
.to_f
/ mbms
.avg
214 values
[mmodule
] = 0.0
220 # Class Novelty Score
221 # cnvs = |LocS(class)| x nvi
225 redef fun name
do return "cnvs"
226 redef fun desc
do return "class novelty score, importance of the contribution of the class to its branch"
228 redef fun collect
(mclasses
) do
229 var cnvi
= new CNVI(mainmodule
, model_view
)
230 cnvi
.collect
(mclasses
)
231 for mclass
in mclasses
do
232 var locc
= mclass
.collect_local_mproperties
(model_view
).length
233 values
[mclass
] = cnvi
.values
[mclass
] * locc
.to_f
238 # Module Novelty Score
239 # mnvs = |LocS(module)| x nvi
243 redef fun name
do return "mnvs"
244 redef fun desc
do return "module novelty score, importance of the contribution of the module to its branch"
246 redef fun collect
(mmodules
) do
247 var mnvi
= new MNVI(mainmodule
, model_view
)
248 mnvi
.collect
(mmodules
)
249 for mmodule
in mmodules
do
250 var locc
= mmodule
.collect_intro_mclassdefs
(model_view
).length
251 locc
+= mmodule
.collect_redef_mclassdefs
(model_view
).length
252 values
[mmodule
] = mnvi
.values
[mmodule
] * locc
.to_f
258 # the set of redefition that call to super
259 fun extended_mproperties
(view
: ModelView): Set[MProperty] do
260 var set
= new HashSet[MProperty]
261 for mclassdef
in mclassdefs
do
262 for mpropdef
in mclassdef
.mpropdefs
do
263 if not view
.accept_mentity
(mpropdef
) then continue
264 if not mpropdef
.has_supercall
then continue
265 if mpropdef
.mproperty
.intro_mclassdef
.mclass
!= self then set
.add
(mpropdef
.mproperty
)
271 # the set of redefition that do not call to super
272 fun overriden_mproperties
(view
: ModelView): Set[MProperty] do
273 var set
= new HashSet[MProperty]
274 for mclassdef
in mclassdefs
do
275 for mpropdef
in mclassdef
.mpropdefs
do
276 if not view
.accept_mentity
(mpropdef
) then continue
277 if mpropdef
.has_supercall
then continue
278 if mpropdef
.mproperty
.intro_mclassdef
.mclass
!= self then set
.add
(mpropdef
.mproperty
)
284 # pure overriders contain only redefinitions
285 private fun is_pure_overrider
(view
: ModelView): Bool do
286 var news
= collect_intro_mproperties
(view
).length
287 var locs
= collect_local_mproperties
(view
).length
288 if news
== 0 and locs
> 0 then return true
292 # overriders contain more definitions than introductions
293 private fun is_overrider
(view
: ModelView): Bool do
294 var rdfs
= collect_redef_mproperties
(view
).length
295 var news
= collect_intro_mproperties
(view
).length
296 var locs
= collect_local_mproperties
(view
).length
297 if rdfs
>= news
and locs
> 0 then return true
301 # pure extenders contain only introductions
302 private fun is_pure_extender
(view
: ModelView): Bool do
303 var rdfs
= collect_redef_mproperties
(view
).length
304 var locs
= collect_local_mproperties
(view
).length
305 if rdfs
== 0 and locs
> 0 then return true
309 # extenders contain more introduction than redefinitions
310 private fun is_extender
(view
: ModelView): Bool do
311 var rdfs
= collect_redef_mproperties
(view
).length
312 var news
= collect_intro_mproperties
(view
).length
313 var locs
= collect_local_mproperties
(view
).length
314 if news
> rdfs
and locs
> 0 then return true
318 # pure specializers always call to super in its redefinitions
319 private fun is_pure_specializer
(view
: ModelView): Bool do
320 var ovrs
= overriden_mproperties
(view
).length
321 var rdfs
= collect_redef_mproperties
(view
).length
322 if ovrs
== 0 and rdfs
> 0 then return true
326 # specializers have more redefinitions that call super than not calling it
327 private fun is_specializer
(view
: ModelView): Bool do
328 var spcs
= extended_mproperties
(view
).length
329 var ovrs
= overriden_mproperties
(view
).length
330 var rdfs
= collect_redef_mproperties
(view
).length
331 if spcs
> ovrs
and rdfs
> 0 then return true
335 # pure replacers never call to super in its redefinitions
336 private fun is_pure_replacer
(view
: ModelView): Bool do
337 var spcs
= extended_mproperties
(view
).length
338 var rdfs
= collect_redef_mproperties
(view
).length
339 if spcs
== 0 and rdfs
> 0 then return true
343 # replacers have less redefinitions that call super than not calling it
344 private fun is_replacer
(view
: ModelView): Bool do
345 var spcs
= extended_mproperties
(view
).length
346 var ovrs
= overriden_mproperties
(view
).length
347 var rdfs
= collect_redef_mproperties
(view
).length
348 if ovrs
> spcs
and rdfs
> 0 then return true
352 # equals contain as redifinition than introduction
353 private fun is_equal
(view
: ModelView): Bool do
354 var spcs
= extended_mproperties
(view
).length
355 var ovrs
= overriden_mproperties
(view
).length
356 var rdfs
= collect_redef_mproperties
(view
).length
357 if spcs
== ovrs
and rdfs
> 0 then return true