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 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
)
80 var cnvs
= new CNVS(mainmodule
)
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 # Mainmodule used to compute class hierarchy.
141 var mainmodule
: MModule
142 private var protected_view
: ModelView = mainmodule
.model
.protected_view
is lateinit
144 redef fun collect
(mclasses
) do
145 for mclass
in mclasses
do
146 var totc
= mclass
.collect_accessible_mproperties
(protected_view
).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
(mmodule
.protected_view
).length
164 totc
+= mmodule
.collect_redef_mclassdefs
(mmodule
.protected_view
).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
181 private var protected_view
: ModelView = mainmodule
.model
.protected_view
is lateinit
183 redef fun collect
(mclasses
) do
184 var cbms
= new CBMS(mainmodule
)
185 for mclass
in mclasses
do
186 # compute branch mean size
187 var parents
= mclass
.in_hierarchy
(mainmodule
).direct_greaters
188 if parents
.length
> 0 then
190 cbms
.collect
(new HashSet[MClass].from
(parents
))
191 # compute class novelty index
192 var locc
= mclass
.collect_accessible_mproperties
(protected_view
).length
193 values
[mclass
] = locc
.to_f
/ cbms
.avg
201 # Module Novelty Index
202 # mnvi = |LocS(module)| / mbms(parents(module))
206 redef fun name
do return "mnvi"
207 redef fun desc
do return "module novelty index, contribution of the module to its branch in term of introductions"
209 redef fun collect
(mmodules
) do
211 for mmodule
in mmodules
do
212 # compute branch mean size
213 var parents
= mmodule
.in_importation
.direct_greaters
214 if parents
.length
> 0 then
216 mbms
.collect
(new HashSet[MModule].from
(parents
))
217 # compute module novelty index
218 var locc
= mmodule
.collect_intro_mclassdefs
(mmodule
.protected_view
).length
219 locc
+= mmodule
.collect_redef_mclassdefs
(mmodule
.protected_view
).length
220 values
[mmodule
] = locc
.to_f
/ mbms
.avg
222 values
[mmodule
] = 0.0
228 # Class Novelty Score
229 # cnvs = |LocS(class)| x nvi
233 redef fun name
do return "cnvs"
234 redef fun desc
do return "class novelty score, importance of the contribution of the class to its branch"
236 # Mainmodule used to compute class hierarchy.
237 var mainmodule
: MModule
238 private var protected_view
: ModelView = mainmodule
.model
.protected_view
is lateinit
240 redef fun collect
(mclasses
) do
241 var cnvi
= new CNVI(mainmodule
)
242 cnvi
.collect
(mclasses
)
243 for mclass
in mclasses
do
244 var locc
= mclass
.collect_local_mproperties
(protected_view
).length
245 values
[mclass
] = cnvi
.values
[mclass
] * locc
.to_f
250 # Module Novelty Score
251 # mnvs = |LocS(module)| x nvi
255 redef fun name
do return "mnvs"
256 redef fun desc
do return "module novelty score, importance of the contribution of the module to its branch"
258 redef fun collect
(mmodules
) do
260 mnvi
.collect
(mmodules
)
261 for mmodule
in mmodules
do
262 var locc
= mmodule
.collect_intro_mclassdefs
(mmodule
.protected_view
).length
263 locc
+= mmodule
.collect_redef_mclassdefs
(mmodule
.protected_view
).length
264 values
[mmodule
] = mnvi
.values
[mmodule
] * locc
.to_f
270 # the set of redefition that call to super
271 fun extended_mproperties
(view
: ModelView): Set[MProperty] do
272 var set
= new HashSet[MProperty]
273 for mclassdef
in mclassdefs
do
274 for mpropdef
in mclassdef
.mpropdefs
do
275 if not view
.accept_mentity
(mpropdef
) then continue
276 if not mpropdef
.has_supercall
then continue
277 if mpropdef
.mproperty
.intro_mclassdef
.mclass
!= self then set
.add
(mpropdef
.mproperty
)
283 # the set of redefition that do not call to super
284 fun overriden_mproperties
(view
: ModelView): Set[MProperty] do
285 var set
= new HashSet[MProperty]
286 for mclassdef
in mclassdefs
do
287 for mpropdef
in mclassdef
.mpropdefs
do
288 if not view
.accept_mentity
(mpropdef
) then continue
289 if mpropdef
.has_supercall
then continue
290 if mpropdef
.mproperty
.intro_mclassdef
.mclass
!= self then set
.add
(mpropdef
.mproperty
)
296 # pure overriders contain only redefinitions
297 private fun is_pure_overrider
(view
: ModelView): Bool do
298 var news
= collect_intro_mproperties
(view
).length
299 var locs
= collect_local_mproperties
(view
).length
300 if news
== 0 and locs
> 0 then return true
304 # overriders contain more definitions than introductions
305 private fun is_overrider
(view
: ModelView): Bool do
306 var rdfs
= collect_redef_mproperties
(view
).length
307 var news
= collect_intro_mproperties
(view
).length
308 var locs
= collect_local_mproperties
(view
).length
309 if rdfs
>= news
and locs
> 0 then return true
313 # pure extenders contain only introductions
314 private fun is_pure_extender
(view
: ModelView): Bool do
315 var rdfs
= collect_redef_mproperties
(view
).length
316 var locs
= collect_local_mproperties
(view
).length
317 if rdfs
== 0 and locs
> 0 then return true
321 # extenders contain more introduction than redefinitions
322 private fun is_extender
(view
: ModelView): Bool do
323 var rdfs
= collect_redef_mproperties
(view
).length
324 var news
= collect_intro_mproperties
(view
).length
325 var locs
= collect_local_mproperties
(view
).length
326 if news
> rdfs
and locs
> 0 then return true
330 # pure specializers always call to super in its redefinitions
331 private fun is_pure_specializer
(view
: ModelView): Bool do
332 var ovrs
= overriden_mproperties
(view
).length
333 var rdfs
= collect_redef_mproperties
(view
).length
334 if ovrs
== 0 and rdfs
> 0 then return true
338 # specializers have more redefinitions that call super than not calling it
339 private fun is_specializer
(view
: ModelView): Bool do
340 var spcs
= extended_mproperties
(view
).length
341 var ovrs
= overriden_mproperties
(view
).length
342 var rdfs
= collect_redef_mproperties
(view
).length
343 if spcs
> ovrs
and rdfs
> 0 then return true
347 # pure replacers never call to super in its redefinitions
348 private fun is_pure_replacer
(view
: ModelView): Bool do
349 var spcs
= extended_mproperties
(view
).length
350 var rdfs
= collect_redef_mproperties
(view
).length
351 if spcs
== 0 and rdfs
> 0 then return true
355 # replacers have less redefinitions that call super than not calling it
356 private fun is_replacer
(view
: ModelView): Bool do
357 var spcs
= extended_mproperties
(view
).length
358 var ovrs
= overriden_mproperties
(view
).length
359 var rdfs
= collect_redef_mproperties
(view
).length
360 if ovrs
> spcs
and rdfs
> 0 then return true
364 # equals contain as redifinition than introduction
365 private fun is_equal
(view
: ModelView): Bool do
366 var spcs
= extended_mproperties
(view
).length
367 var ovrs
= overriden_mproperties
(view
).length
368 var rdfs
= collect_redef_mproperties
(view
).length
369 if spcs
== ovrs
and rdfs
> 0 then return true