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 mndel 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 Understan Class Hierarchies
42 # S. Denier and Y. Gueheneuc
43 # in Proceedings of the 16th IEEE International Conference on Program Comprehension (OCPC'08)
47 import mclasses_metrics
50 redef class ToolContext
51 var mendel_metrics_phase
: Phase = new MendelMetricsPhase(self, null)
54 private class MendelMetricsPhase
56 redef fun process_mainmodule
(mainmodule
, given_mmodules
)
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"
63 print toolcontext
.format_h1
("\n# Mendel metrics")
65 var vis
= protected_visibility
66 var model
= toolcontext
.modelbuilder
.model
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
75 var cnblp
= new CNBLP(mainmodule
, vis
)
76 var cnvi
= new CNVI(mainmodule
)
77 var cnvs
= new CNVS(mainmodule
)
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")
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]}")
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]}")
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]}")
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]}")
105 var csvh
= new CsvDocument
106 csvh
.format
= new CsvFormat('"', ';', "\n")
107 csvh
.header
= ["povr", "ovr", "pext", "ext", "pspe", "spe", "prep", "rep", "eq"]
108 for mclass
in mclasses
do
109 var povr
= mclass
.is_pure_overrider
(vis
).object_id
110 var ovr
= mclass
.is_overrider
(vis
).object_id
111 var pext
= mclass
.is_pure_extender
(vis
).object_id
112 var ext
= mclass
.is_extender
(vis
).object_id
113 var pspe
= mclass
.is_pure_specializer
(vis
).object_id
114 var spe
= mclass
.is_pure_specializer
(vis
).object_id
115 var prep
= mclass
.is_pure_replacer
(vis
).object_id
116 var rep
= mclass
.is_replacer
(vis
).object_id
117 var eq
= mclass
.is_equal
(vis
).object_id
118 csvh
.add_record
(povr
, ovr
, pext
, ext
, pspe
, spe
, prep
, rep
, eq
)
120 csvh
.save
("{out}/inheritance_behaviour.csv")
125 # Class Branch Mean Size
126 # cbms(class) = |TotS(class)| / (DIT(class) + 1)
130 redef fun name
do return "cbms"
131 redef fun desc
do return "branch mean size, mean number of introduction available among ancestors"
133 var mainmodule
: MModule
134 init(mainmodule
: MModule) do self.mainmodule
= mainmodule
136 redef fun collect
(mclasses
) do
137 for mclass
in mclasses
do
138 var totc
= mclass
.all_mproperties
(mainmodule
, protected_visibility
).length
139 var ditc
= mclass
.in_hierarchy
(mainmodule
).depth
140 values
[mclass
] = totc
.to_f
/ (ditc
+ 1).to_f
145 # Class Novelty Index
146 # cnvi = |LocS(class)| / cbms(parents(class))
150 redef fun name
do return "cnvi"
151 redef fun desc
do return "class novelty index, contribution of the class to its branch in term of introductions"
153 var mainmodule
: MModule
154 init(mainmodule
: MModule) do self.mainmodule
= mainmodule
156 redef fun collect
(mclasses
) do
157 var cbms
= new CBMS(mainmodule
)
158 for mclass
in mclasses
do
159 # compute branch mean size
160 var parents
= mclass
.in_hierarchy
(mainmodule
).direct_greaters
161 if parents
.length
> 0 then
163 cbms
.collect
(new HashSet[MClass].from
(parents
))
164 # compute class novelty index
165 var locc
= mclass
.local_mproperties
(protected_visibility
).length
166 values
[mclass
] = locc
.to_f
/ cbms
.avg
174 # Class Novelty Score
175 # cnvs = |LocS(class)| x nvi
179 redef fun name
do return "cnvs"
180 redef fun desc
do return "class novelty score, importance of the contribution of the class to its branch"
182 var mainmodule
: MModule
183 init(mainmodule
: MModule) do self.mainmodule
= mainmodule
185 redef fun collect
(mclasses
) do
186 var cnvi
= new CNVI(mainmodule
)
187 cnvi
.collect
(mclasses
)
188 for mclass
in mclasses
do
189 var locc
= mclass
.local_mproperties
(protected_visibility
).length
190 values
[mclass
] = cnvi
.values
[mclass
] * locc
.to_f
196 # the set of redefition that call to super
197 fun extended_mproperties
(min_visibility
: MVisibility): Set[MProperty] do
198 var set
= new HashSet[MProperty]
199 for mclassdef
in mclassdefs
do
200 for mpropdef
in mclassdef
.mpropdefs
do
201 if mpropdef
.mproperty
.visibility
< min_visibility
then continue
202 if not mpropdef
.has_supercall
then continue
203 if mpropdef
.mproperty
.intro_mclassdef
.mclass
!= self then set
.add
(mpropdef
.mproperty
)
209 # the set of redefition that do not call to super
210 fun overriden_mproperties
(min_visibility
: MVisibility): Set[MProperty] do
211 var set
= new HashSet[MProperty]
212 for mclassdef
in mclassdefs
do
213 for mpropdef
in mclassdef
.mpropdefs
do
214 if mpropdef
.mproperty
.visibility
< min_visibility
then continue
215 if mpropdef
.has_supercall
then continue
216 if mpropdef
.mproperty
.intro_mclassdef
.mclass
!= self then set
.add
(mpropdef
.mproperty
)
222 # pure overriders contain only redefinitions
223 private fun is_pure_overrider
(min_visibility
: MVisibility): Bool do
224 var news
= intro_mproperties
(min_visibility
).length
225 var locs
= local_mproperties
(min_visibility
).length
226 if news
== 0 and locs
> 0 then return true
230 # overriders contain more definitions than introductions
231 private fun is_overrider
(min_visibility
: MVisibility): Bool do
232 var rdfs
= redef_mproperties
(min_visibility
).length
233 var news
= intro_mproperties
(min_visibility
).length
234 var locs
= local_mproperties
(min_visibility
).length
235 if rdfs
>= news
and locs
> 0 then return true
239 # pure extenders contain only introductions
240 private fun is_pure_extender
(min_visibility
: MVisibility): Bool do
241 var rdfs
= redef_mproperties
(min_visibility
).length
242 var locs
= local_mproperties
(min_visibility
).length
243 if rdfs
== 0 and locs
> 0 then return true
247 # extenders contain more introduction than redefinitions
248 private fun is_extender
(min_visibility
: MVisibility): Bool do
249 var rdfs
= redef_mproperties
(min_visibility
).length
250 var news
= intro_mproperties
(min_visibility
).length
251 var locs
= local_mproperties
(min_visibility
).length
252 if news
> rdfs
and locs
> 0 then return true
256 # pure specializers always call to super in its redefinitions
257 private fun is_pure_specializer
(min_visibility
: MVisibility): Bool do
258 var ovrs
= overriden_mproperties
(min_visibility
).length
259 var rdfs
= redef_mproperties
(min_visibility
).length
260 if ovrs
== 0 and rdfs
> 0 then return true
264 # specializers have more redefinitions that call super than not calling it
265 private fun is_specializer
(min_visibility
: MVisibility): Bool do
266 var spcs
= extended_mproperties
(min_visibility
).length
267 var ovrs
= overriden_mproperties
(min_visibility
).length
268 var rdfs
= redef_mproperties
(min_visibility
).length
269 if spcs
> ovrs
and rdfs
> 0 then return true
273 # pure replacers never call to super in its redefinitions
274 private fun is_pure_replacer
(min_visibility
: MVisibility): Bool do
275 var spcs
= extended_mproperties
(min_visibility
).length
276 var rdfs
= redef_mproperties
(min_visibility
).length
277 if spcs
== 0 and rdfs
> 0 then return true
281 # replacers have less redefinitions that call super than not calling it
282 private fun is_replacer
(min_visibility
: MVisibility): Bool do
283 var spcs
= extended_mproperties
(min_visibility
).length
284 var ovrs
= overriden_mproperties
(min_visibility
).length
285 var rdfs
= redef_mproperties
(min_visibility
).length
286 if ovrs
> spcs
and rdfs
> 0 then return true
290 # equals contain as redifinition than introduction
291 private fun is_equal
(min_visibility
: MVisibility): Bool do
292 var spcs
= extended_mproperties
(min_visibility
).length
293 var ovrs
= overriden_mproperties
(min_visibility
).length
294 var rdfs
= redef_mproperties
(min_visibility
).length
295 if spcs
== ovrs
and rdfs
> 0 then return true