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 filter
= new ModelFilter(min_visibility
= protected_visibility
)
71 var model_view
= new ModelView(model
, mainmodule
, filter
)
73 var mclasses
= new HashSet[MClass]
74 for mclass
in model_view
.mclasses
do
75 if mclass
.is_interface
then continue
79 var cnblp
= new CNBLP(model_view
)
80 var cnvi
= new CNVI(model_view
)
81 var cnvs
= new CNVS(model_view
)
83 var metrics
= new MetricSet
84 metrics
.register
(cnblp
, cnvi
, cnvs
)
85 metrics
.collect
(mclasses
)
86 if csv
then metrics
.to_csv
.write_to_file
("{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
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
(model_view
).object_id
118 var ovr
= mclass
.is_overrider
(model_view
).object_id
119 var pext
= mclass
.is_pure_extender
(model_view
).object_id
120 var ext
= mclass
.is_extender
(model_view
).object_id
121 var pspe
= mclass
.is_pure_specializer
(model_view
).object_id
122 var spe
= mclass
.is_pure_specializer
(model_view
).object_id
123 var prep
= mclass
.is_pure_replacer
(model_view
).object_id
124 var rep
= mclass
.is_replacer
(model_view
).object_id
125 var eq
= mclass
.is_equal
(model_view
).object_id
126 csvh
.add_record
(povr
, ovr
, pext
, ext
, pspe
, spe
, prep
, rep
, eq
)
128 csvh
.write_to_file
("{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 redef fun collect
(mclasses
) do
142 for mclass
in mclasses
do
143 var totc
= mclass
.collect_accessible_mproperties
(model_view
).length
144 var ditc
= mclass
.in_hierarchy
(model_view
.mainmodule
).depth
145 values
[mclass
] = totc
.to_f
/ (ditc
+ 1).to_f
150 # Module Branch Mean Size
151 # mbms(module) = |mclassdefs(module)| / (DIT(module) + 1)
155 redef fun name
do return "mbms"
156 redef fun desc
do return "branch mean size, mean number of class definition available among ancestors"
158 redef fun collect
(mmodules
) do
159 for mmodule
in mmodules
do
160 var totc
= mmodule
.collect_intro_mclassdefs
(model_view
).length
161 totc
+= mmodule
.collect_redef_mclassdefs
(model_view
).length
162 var ditc
= mmodule
.in_importation
.depth
163 values
[mmodule
] = totc
.to_f
/ (ditc
+ 1).to_f
168 # Class Novelty Index
169 # cnvi = |LocS(class)| / cbms(parents(class))
173 redef fun name
do return "cnvi"
174 redef fun desc
do return "class novelty index, contribution of the class to its branch in term of introductions"
176 redef fun collect
(mclasses
) do
177 var cbms
= new CBMS(model_view
)
178 for mclass
in mclasses
do
179 # compute branch mean size
180 var parents
= mclass
.in_hierarchy
(model_view
.mainmodule
).direct_greaters
181 if parents
.length
> 0 then
183 cbms
.collect
(new HashSet[MClass].from
(parents
))
184 # compute class novelty index
185 var locc
= mclass
.collect_accessible_mproperties
(model_view
).length
186 values
[mclass
] = locc
.to_f
/ cbms
.avg
194 # Module Novelty Index
195 # mnvi = |LocS(module)| / mbms(parents(module))
199 redef fun name
do return "mnvi"
200 redef fun desc
do return "module novelty index, contribution of the module to its branch in term of introductions"
202 redef fun collect
(mmodules
) do
203 var mbms
= new MBMS(model_view
)
204 for mmodule
in mmodules
do
205 # compute branch mean size
206 var parents
= mmodule
.in_importation
.direct_greaters
207 if parents
.length
> 0 then
209 mbms
.collect
(new HashSet[MModule].from
(parents
))
210 # compute module novelty index
211 var locc
= mmodule
.collect_intro_mclassdefs
(model_view
).length
212 locc
+= mmodule
.collect_redef_mclassdefs
(model_view
).length
213 values
[mmodule
] = locc
.to_f
/ mbms
.avg
215 values
[mmodule
] = 0.0
221 # Class Novelty Score
222 # cnvs = |LocS(class)| x nvi
226 redef fun name
do return "cnvs"
227 redef fun desc
do return "class novelty score, importance of the contribution of the class to its branch"
229 redef fun collect
(mclasses
) do
230 var cnvi
= new CNVI(model_view
)
231 cnvi
.collect
(mclasses
)
232 for mclass
in mclasses
do
233 var locc
= mclass
.collect_local_mproperties
(model_view
).length
234 values
[mclass
] = cnvi
.values
[mclass
] * locc
.to_f
239 # Module Novelty Score
240 # mnvs = |LocS(module)| x nvi
244 redef fun name
do return "mnvs"
245 redef fun desc
do return "module novelty score, importance of the contribution of the module to its branch"
247 redef fun collect
(mmodules
) do
248 var mnvi
= new MNVI(model_view
)
249 mnvi
.collect
(mmodules
)
250 for mmodule
in mmodules
do
251 var locc
= mmodule
.collect_intro_mclassdefs
(model_view
).length
252 locc
+= mmodule
.collect_redef_mclassdefs
(model_view
).length
253 values
[mmodule
] = mnvi
.values
[mmodule
] * locc
.to_f
259 # the set of redefition that call to super
260 fun extended_mproperties
(view
: ModelView): Set[MProperty] do
261 var set
= new HashSet[MProperty]
262 for mclassdef
in mclassdefs
do
263 for mpropdef
in mclassdef
.mpropdefs
do
264 if not view
.accept_mentity
(mpropdef
) then continue
265 if not mpropdef
.has_supercall
then continue
266 if mpropdef
.mproperty
.intro_mclassdef
.mclass
!= self then set
.add
(mpropdef
.mproperty
)
272 # the set of redefition that do not call to super
273 fun overriden_mproperties
(view
: ModelView): Set[MProperty] do
274 var set
= new HashSet[MProperty]
275 for mclassdef
in mclassdefs
do
276 for mpropdef
in mclassdef
.mpropdefs
do
277 if not view
.accept_mentity
(mpropdef
) then continue
278 if mpropdef
.has_supercall
then continue
279 if mpropdef
.mproperty
.intro_mclassdef
.mclass
!= self then set
.add
(mpropdef
.mproperty
)
285 # pure overriders contain only redefinitions
286 private fun is_pure_overrider
(view
: ModelView): Bool do
287 var news
= collect_intro_mproperties
(view
).length
288 var locs
= collect_local_mproperties
(view
).length
289 if news
== 0 and locs
> 0 then return true
293 # overriders contain more definitions than introductions
294 private fun is_overrider
(view
: ModelView): Bool do
295 var rdfs
= collect_redef_mproperties
(view
).length
296 var news
= collect_intro_mproperties
(view
).length
297 var locs
= collect_local_mproperties
(view
).length
298 if rdfs
>= news
and locs
> 0 then return true
302 # pure extenders contain only introductions
303 private fun is_pure_extender
(view
: ModelView): Bool do
304 var rdfs
= collect_redef_mproperties
(view
).length
305 var locs
= collect_local_mproperties
(view
).length
306 if rdfs
== 0 and locs
> 0 then return true
310 # extenders contain more introduction than redefinitions
311 private fun is_extender
(view
: ModelView): Bool do
312 var rdfs
= collect_redef_mproperties
(view
).length
313 var news
= collect_intro_mproperties
(view
).length
314 var locs
= collect_local_mproperties
(view
).length
315 if news
> rdfs
and locs
> 0 then return true
319 # pure specializers always call to super in its redefinitions
320 private fun is_pure_specializer
(view
: ModelView): Bool do
321 var ovrs
= overriden_mproperties
(view
).length
322 var rdfs
= collect_redef_mproperties
(view
).length
323 if ovrs
== 0 and rdfs
> 0 then return true
327 # specializers have more redefinitions that call super than not calling it
328 private fun is_specializer
(view
: ModelView): Bool do
329 var spcs
= extended_mproperties
(view
).length
330 var ovrs
= overriden_mproperties
(view
).length
331 var rdfs
= collect_redef_mproperties
(view
).length
332 if spcs
> ovrs
and rdfs
> 0 then return true
336 # pure replacers never call to super in its redefinitions
337 private fun is_pure_replacer
(view
: ModelView): Bool do
338 var spcs
= extended_mproperties
(view
).length
339 var rdfs
= collect_redef_mproperties
(view
).length
340 if spcs
== 0 and rdfs
> 0 then return true
344 # replacers have less redefinitions that call super than not calling it
345 private fun is_replacer
(view
: ModelView): Bool do
346 var spcs
= extended_mproperties
(view
).length
347 var ovrs
= overriden_mproperties
(view
).length
348 var rdfs
= collect_redef_mproperties
(view
).length
349 if ovrs
> spcs
and rdfs
> 0 then return true
353 # equals contain as redifinition than introduction
354 private fun is_equal
(view
: ModelView): Bool do
355 var spcs
= extended_mproperties
(view
).length
356 var ovrs
= overriden_mproperties
(view
).length
357 var rdfs
= collect_redef_mproperties
(view
).length
358 if spcs
== ovrs
and rdfs
> 0 then return true