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
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 vis
= protected_visibility
70 var model
= toolcontext
.modelbuilder
.model
72 var mclasses
= new HashSet[MClass]
73 for mclass
in model
.mclasses
do
74 if mclass
.visibility
< vis
then continue
75 if mclass
.is_interface
then continue
79 var cnblp
= new CNBLP(mainmodule
, vis
)
80 var cnvi
= new CNVI(mainmodule
)
81 var cnvs
= new CNVS(mainmodule
)
83 var metrics
= new MetricSet
84 metrics
.register
(cnblp
, cnvi
, cnvs
)
85 metrics
.collect
(mclasses
)
86 if csv
then metrics
.to_csv
.save
("{out}/mendel.csv")
88 var threshold
= cnblp
.threshold
89 print toolcontext
.format_h4
("\tlarge mclasses (threshold: {threshold})")
90 for mclass
in cnblp
.sort
do
91 var val
= cnblp
.values
[mclass
]
92 if val
.to_f
< threshold
then break
93 print toolcontext
.format_p
("\t {mclass.name}: {val}")
96 threshold
= cnvi
.threshold
97 print toolcontext
.format_h4
("\tbudding mclasses (threshold: {threshold})")
98 for mclass
in cnvi
.sort
do
99 var val
= cnvi
.values
[mclass
]
100 if val
.to_f
< threshold
then break
101 print toolcontext
.format_p
("\t {mclass.name}: {val}")
104 threshold
= cnvs
.threshold
105 print toolcontext
.format_h4
("\tblooming mclasses (threshold: {threshold})")
106 for mclass
in cnvs
.sort
do
107 var val
= cnvs
.values
[mclass
]
108 if val
.to_f
< threshold
then break
109 print toolcontext
.format_p
("\t {mclass.name}: {val}")
113 var csvh
= new CsvDocument
114 csvh
.format
= new CsvFormat('"', ';', "\n")
115 csvh
.header
= ["povr", "ovr", "pext", "ext", "pspe", "spe", "prep", "rep", "eq"]
116 for mclass
in mclasses
do
117 var povr
= mclass
.is_pure_overrider
(vis
).object_id
118 var ovr
= mclass
.is_overrider
(vis
).object_id
119 var pext
= mclass
.is_pure_extender
(vis
).object_id
120 var ext
= mclass
.is_extender
(vis
).object_id
121 var pspe
= mclass
.is_pure_specializer
(vis
).object_id
122 var spe
= mclass
.is_pure_specializer
(vis
).object_id
123 var prep
= mclass
.is_pure_replacer
(vis
).object_id
124 var rep
= mclass
.is_replacer
(vis
).object_id
125 var eq
= mclass
.is_equal
(vis
).object_id
126 csvh
.add_record
(povr
, ovr
, pext
, ext
, pspe
, spe
, prep
, rep
, eq
)
128 csvh
.save
("{out}/inheritance_behaviour.csv")
133 # Class Branch Mean Size
134 # cbms(class) = |TotS(class)| / (DIT(class) + 1)
138 redef fun name
do return "cbms"
139 redef fun desc
do return "branch mean size, mean number of introduction available among ancestors"
141 # Mainmodule used to compute class hierarchy.
142 var mainmodule
: MModule
144 redef fun collect
(mclasses
) do
145 for mclass
in mclasses
do
146 var totc
= mclass
.collect_accessible_mproperties
(protected_visibility
).length
147 var ditc
= mclass
.in_hierarchy
(mainmodule
).depth
148 values
[mclass
] = totc
.to_f
/ (ditc
+ 1).to_f
153 # Module Branch Mean Size
154 # mbms(module) = |mclassdefs(module)| / (DIT(module) + 1)
158 redef fun name
do return "mbms"
159 redef fun desc
do return "branch mean size, mean number of class definition available among ancestors"
161 redef fun collect
(mmodules
) do
162 for mmodule
in mmodules
do
163 var totc
= mmodule
.collect_intro_mclassdefs
(protected_visibility
).length
164 totc
+= mmodule
.collect_redef_mclassdefs
(protected_visibility
).length
165 var ditc
= mmodule
.in_importation
.depth
166 values
[mmodule
] = totc
.to_f
/ (ditc
+ 1).to_f
171 # Class Novelty Index
172 # cnvi = |LocS(class)| / cbms(parents(class))
176 redef fun name
do return "cnvi"
177 redef fun desc
do return "class novelty index, contribution of the class to its branch in term of introductions"
179 # Mainmodule used to compute class hierarchy.
180 var mainmodule
: MModule
182 redef fun collect
(mclasses
) do
183 var cbms
= new CBMS(mainmodule
)
184 for mclass
in mclasses
do
185 # compute branch mean size
186 var parents
= mclass
.in_hierarchy
(mainmodule
).direct_greaters
187 if parents
.length
> 0 then
189 cbms
.collect
(new HashSet[MClass].from
(parents
))
190 # compute class novelty index
191 var locc
= mclass
.collect_accessible_mproperties
(protected_visibility
).length
192 values
[mclass
] = locc
.to_f
/ cbms
.avg
200 # Module Novelty Index
201 # mnvi = |LocS(module)| / mbms(parents(module))
205 redef fun name
do return "mnvi"
206 redef fun desc
do return "module novelty index, contribution of the module to its branch in term of introductions"
208 redef fun collect
(mmodules
) do
210 for mmodule
in mmodules
do
211 # compute branch mean size
212 var parents
= mmodule
.in_importation
.direct_greaters
213 if parents
.length
> 0 then
215 mbms
.collect
(new HashSet[MModule].from
(parents
))
216 # compute module novelty index
217 var locc
= mmodule
.collect_intro_mclassdefs
(protected_visibility
).length
218 locc
+= mmodule
.collect_redef_mclassdefs
(protected_visibility
).length
219 values
[mmodule
] = locc
.to_f
/ mbms
.avg
221 values
[mmodule
] = 0.0
227 # Class Novelty Score
228 # cnvs = |LocS(class)| x nvi
232 redef fun name
do return "cnvs"
233 redef fun desc
do return "class novelty score, importance of the contribution of the class to its branch"
235 # Mainmodule used to compute class hierarchy.
236 var mainmodule
: MModule
238 redef fun collect
(mclasses
) do
239 var cnvi
= new CNVI(mainmodule
)
240 cnvi
.collect
(mclasses
)
241 for mclass
in mclasses
do
242 var locc
= mclass
.collect_local_mproperties
(protected_visibility
).length
243 values
[mclass
] = cnvi
.values
[mclass
] * locc
.to_f
248 # Module Novelty Score
249 # mnvs = |LocS(module)| x nvi
253 redef fun name
do return "mnvs"
254 redef fun desc
do return "module novelty score, importance of the contribution of the module to its branch"
256 redef fun collect
(mmodules
) do
258 mnvi
.collect
(mmodules
)
259 for mmodule
in mmodules
do
260 var locc
= mmodule
.collect_intro_mclassdefs
(protected_visibility
).length
261 locc
+= mmodule
.collect_redef_mclassdefs
(protected_visibility
).length
262 values
[mmodule
] = mnvi
.values
[mmodule
] * locc
.to_f
268 # the set of redefition that call to super
269 fun extended_mproperties
(min_visibility
: MVisibility): Set[MProperty] do
270 var set
= new HashSet[MProperty]
271 for mclassdef
in mclassdefs
do
272 for mpropdef
in mclassdef
.mpropdefs
do
273 if mpropdef
.mproperty
.visibility
< min_visibility
then continue
274 if not mpropdef
.has_supercall
then continue
275 if mpropdef
.mproperty
.intro_mclassdef
.mclass
!= self then set
.add
(mpropdef
.mproperty
)
281 # the set of redefition that do not call to super
282 fun overriden_mproperties
(min_visibility
: MVisibility): Set[MProperty] do
283 var set
= new HashSet[MProperty]
284 for mclassdef
in mclassdefs
do
285 for mpropdef
in mclassdef
.mpropdefs
do
286 if mpropdef
.mproperty
.visibility
< min_visibility
then continue
287 if mpropdef
.has_supercall
then continue
288 if mpropdef
.mproperty
.intro_mclassdef
.mclass
!= self then set
.add
(mpropdef
.mproperty
)
294 # pure overriders contain only redefinitions
295 private fun is_pure_overrider
(min_visibility
: MVisibility): Bool do
296 var news
= collect_intro_mproperties
(min_visibility
).length
297 var locs
= collect_local_mproperties
(min_visibility
).length
298 if news
== 0 and locs
> 0 then return true
302 # overriders contain more definitions than introductions
303 private fun is_overrider
(min_visibility
: MVisibility): Bool do
304 var rdfs
= collect_redef_mproperties
(min_visibility
).length
305 var news
= collect_intro_mproperties
(min_visibility
).length
306 var locs
= collect_local_mproperties
(min_visibility
).length
307 if rdfs
>= news
and locs
> 0 then return true
311 # pure extenders contain only introductions
312 private fun is_pure_extender
(min_visibility
: MVisibility): Bool do
313 var rdfs
= collect_redef_mproperties
(min_visibility
).length
314 var locs
= collect_local_mproperties
(min_visibility
).length
315 if rdfs
== 0 and locs
> 0 then return true
319 # extenders contain more introduction than redefinitions
320 private fun is_extender
(min_visibility
: MVisibility): Bool do
321 var rdfs
= collect_redef_mproperties
(min_visibility
).length
322 var news
= collect_intro_mproperties
(min_visibility
).length
323 var locs
= collect_local_mproperties
(min_visibility
).length
324 if news
> rdfs
and locs
> 0 then return true
328 # pure specializers always call to super in its redefinitions
329 private fun is_pure_specializer
(min_visibility
: MVisibility): Bool do
330 var ovrs
= overriden_mproperties
(min_visibility
).length
331 var rdfs
= collect_redef_mproperties
(min_visibility
).length
332 if ovrs
== 0 and rdfs
> 0 then return true
336 # specializers have more redefinitions that call super than not calling it
337 private fun is_specializer
(min_visibility
: MVisibility): Bool do
338 var spcs
= extended_mproperties
(min_visibility
).length
339 var ovrs
= overriden_mproperties
(min_visibility
).length
340 var rdfs
= collect_redef_mproperties
(min_visibility
).length
341 if spcs
> ovrs
and rdfs
> 0 then return true
345 # pure replacers never call to super in its redefinitions
346 private fun is_pure_replacer
(min_visibility
: MVisibility): Bool do
347 var spcs
= extended_mproperties
(min_visibility
).length
348 var rdfs
= collect_redef_mproperties
(min_visibility
).length
349 if spcs
== 0 and rdfs
> 0 then return true
353 # replacers have less redefinitions that call super than not calling it
354 private fun is_replacer
(min_visibility
: MVisibility): Bool do
355 var spcs
= extended_mproperties
(min_visibility
).length
356 var ovrs
= overriden_mproperties
(min_visibility
).length
357 var rdfs
= collect_redef_mproperties
(min_visibility
).length
358 if ovrs
> spcs
and rdfs
> 0 then return true
362 # equals contain as redifinition than introduction
363 private fun is_equal
(min_visibility
: MVisibility): Bool do
364 var spcs
= extended_mproperties
(min_visibility
).length
365 var ovrs
= overriden_mproperties
(min_visibility
).length
366 var rdfs
= collect_redef_mproperties
(min_visibility
).length
367 if spcs
== ovrs
and rdfs
> 0 then return true