# This file is part of NIT ( http://www.nitlanguage.org ). # # Copyright 2014 Alexandre Terrasa # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Collect metrics about inheritance usage module inheritance_metrics import model import mmodules_metrics import mclasses_metrics import phase import frontend redef class ToolContext var inheritance_metrics_phase = new InheritanceMetricsPhase(self, null) end # Extract metrics about inheritance from model. private class InheritanceMetricsPhase super Phase redef fun process_mainmodule(mainmodule) do if not toolcontext.opt_inheritance.value and not toolcontext.opt_all.value then return print toolcontext.format_h1("\n# Inheritance metrics") var hmetrics = new InheritanceMetricSet hmetrics.register(new MDUI, new MDUIC, new MDUII, new MIF, new MIFC, new MIFI) var cmetrics = new MClassMetricSet cmetrics.register(new CNOA, new CNOP, new CNOC, new CNODC) cmetrics.register(new CNOA, new CNOP, new CNOC, new CNODI) cmetrics.register(new CDIT, new CDITI) var model = toolcontext.modelbuilder.model var mmodules = new HashSet[MModule] var mclasses = new HashSet[MClass] for mproject in model.mprojects do print toolcontext.format_h2("\n ## project {mproject}") for mgroup in mproject.mgroups do if mgroup.mmodules.is_empty then continue # Scalar metrics print toolcontext.format_h3(" `- group {mgroup.full_name}") var mod_mclasses = new HashSet[MClass] for mmodule in mgroup.mmodules do mod_mclasses.add_all(mmodule.intro_mclasses) if mod_mclasses.is_empty then continue mmodules.add_all(mgroup.mmodules) mclasses.add_all(mod_mclasses) cmetrics.collect(new HashSet[MClass].from(mod_mclasses), mainmodule) for name, metric in cmetrics.metrics do print toolcontext.format_h4("\t{name}: {metric.desc}") print toolcontext.format_p("\t avg: {metric.avg}") var max = metric.max print toolcontext.format_p("\t max: {max.first} ({max.second})") var min = metric.min print toolcontext.format_p("\t min: {min.first} ({min.second})") end hmetrics.collect(new HashSet[MModule].from(mgroup.mmodules), mainmodule) for name, metric in hmetrics.metrics do print toolcontext.format_h4("\t{name}: {metric.desc}") print toolcontext.format_p("\t avg: {metric.avg}") var max = metric.max print toolcontext.format_p("\t max: {max.first} ({max.second})") var min = metric.min print toolcontext.format_p("\t min: {min.first} ({min.second})") end end end if not mclasses.is_empty then # Global metrics print toolcontext.format_h2("\n ## global metrics") cmetrics.collect(mclasses, mainmodule) for name, metric in cmetrics.metrics do print toolcontext.format_h4("\t{name}: {metric.desc}") print toolcontext.format_p("\t avg: {metric.avg}") var max = metric.max print toolcontext.format_p("\t max: {max.first} ({max.second})") var min = metric.min print toolcontext.format_p("\t min: {min.first} ({min.second})") end hmetrics.collect(mmodules, mainmodule) for name, metric in hmetrics.metrics do print toolcontext.format_h4("\t{name}: {metric.desc}") print toolcontext.format_p("\t avg: {metric.avg}") var max = metric.max print toolcontext.format_p("\t max: {max.first} ({max.second})") var min = metric.min print toolcontext.format_p("\t min: {min.first} ({min.second})") end end end end # Metric Set used to collect data about inheritance in each module class InheritanceMetricSet super MetricSet redef type METRIC: InheritanceMetric fun collect(mmodules: Set[MModule], mainmodule: MModule) do clear for metric in metrics.values do for mmodule in mmodules do metric.collect(mmodule, mainmodule) end end end end # An abstract metric used to collect data about inheritance usage # # The metric is based on a module abstract class InheritanceMetric super FloatMetric[MModule] fun collect(mmodule: MModule, mainmodule: MModule) is abstract end # Module metric: proportion of MClasses Defined Using Inheritance # # Count MClasses that have another parents than Object class MDUI super InheritanceMetric redef fun name do return "mdui" redef fun desc do return "proportion of mclass defined using inheritance (has other parent than Object)" redef fun collect(mmodule, mainmodule) do var count = 0 for mclass in mmodule.intro_mclasses do if mclass.in_hierarchy(mainmodule).greaters.length > 2 then count += 1 end if mmodule.intro_mclasses.is_empty then values[mmodule] = 0.0 else values[mmodule] = count.to_f / mmodule.intro_mclasses.length.to_f end end end # Module metric: proportion of abstract, concrete and extern Classes Defined Using Inheritance # # Count classes that have another parents than Object class MDUIC super InheritanceMetric redef fun name do return "mduic" redef fun desc do return "proportion of class_kind defined using inheritance" redef fun collect(mmodule, mainmodule) do var count = 0 var nb = 0 for mclass in mmodule.intro_mclasses do if mclass.kind == abstract_kind or mclass.kind == concrete_kind or mclass.kind == extern_kind then if mclass.in_hierarchy(mainmodule).greaters.length > 2 then count += 1 end nb += 1 end if mmodule.intro_mclasses.is_empty then values[mmodule] = 0.0 else values[mmodule] = count.to_f / nb.to_f end end end # Module metric: proportion of Interface Defined Using Inheritance # # Count interface that have another parents than Object class MDUII super InheritanceMetric redef fun name do return "mduii" redef fun desc do return "proportion of interface_kind defined using inheritance" redef fun collect(mmodule, mainmodule) do var count = 0 var nb = 0 for mclass in mmodule.intro_mclasses do if mclass.kind == interface_kind then if mclass.in_hierarchy(mainmodule).greaters.length > 2 then count += 1 end nb += 1 end if mmodule.intro_mclasses.is_empty then values[mmodule] = 0.0 else values[mmodule] = count.to_f / nb.to_f end end end # Module metric: proportion of MClass Inherited From # # Count classes that have at least a child class MIF super InheritanceMetric redef fun name do return "mif" redef fun desc do return "proportion of mclass inherited from" redef fun collect(mmodule, mainmodule) do var count = 0 for mclass in mmodule.intro_mclasses do if mclass.in_hierarchy(mainmodule).direct_smallers.length > 0 then count += 1 end if mmodule.intro_mclasses.is_empty then values[mmodule] = 0.0 else values[mmodule] = count.to_f / mmodule.intro_mclasses.length.to_f end end end # Module metric: proportion of abstract, concrete and extern Class Inherited From # # Count classes that have at least a child class MIFC super InheritanceMetric redef fun name do return "mifc" redef fun desc do return "proportion of class_kind inherited from" redef fun collect(mmodule, mainmodule) do var count = 0 var nb = 0 for mclass in mmodule.intro_mclasses do if mclass.kind == abstract_kind or mclass.kind == concrete_kind or mclass.kind == extern_kind then if mclass.in_hierarchy(mainmodule).direct_smallers.length > 0 then count += 1 end nb += 1 end if mmodule.intro_mclasses.is_empty then values[mmodule] = 0.0 else values[mmodule] = count.to_f / nb.to_f end end end # Module metric: proportion of Interface Inherited From # # Count interfaces that have at least a child class MIFI super InheritanceMetric redef fun name do return "mifi" redef fun desc do return "proportion of interface_kind inherited from" redef fun collect(mmodule, mainmodule) do var count = 0 var nb = 0 for mclass in mmodule.intro_mclasses do if mclass.kind == interface_kind then if mclass.in_hierarchy(mainmodule).direct_smallers.length > 0 then count += 1 end nb += 1 end if mmodule.intro_mclasses.is_empty then values[mmodule] = 0.0 else values[mmodule] = count.to_f / nb.to_f end end end # MClass metric: Number of Class Ancestors # # Count only absrtract, concrete and extern classes class CNOAC super MClassMetric redef fun name do return "cnoac" redef fun desc do return "number of class_kind ancestor" redef fun collect(mclass, mainmodule) do var count = 0 for parent in mclass.in_hierarchy(mainmodule).greaters do if parent == mclass then continue if parent.kind == abstract_kind or parent.kind == concrete_kind or parent.kind == extern_kind then count += 1 end end values[mclass] = count end end # MClass metric: Number of Class Parents # # Count only absrtract, concrete and extern classes class CNOPC super MClassMetric redef fun name do return "cnopc" redef fun desc do return "number of class_kind parent" redef fun collect(mclass, mainmodule) do var count = 0 for parent in mclass.in_hierarchy(mainmodule).direct_greaters do if parent == mclass then continue if parent.kind == abstract_kind or parent.kind == concrete_kind or parent.kind == extern_kind then count += 1 end end values[mclass] = count end end # MClass metric: Number of Class Children # # Count only absrtract, concrete and extern classes class CNOCC super MClassMetric redef fun name do return "cnocc" redef fun desc do return "number of class_kind children" redef fun collect(mclass, mainmodule) do var count = 0 for parent in mclass.in_hierarchy(mainmodule).direct_smallers do if parent == mclass then continue if parent.kind == abstract_kind or parent.kind == concrete_kind or parent.kind == extern_kind then count += 1 end end values[mclass] = count end end # MClass metric: Number of Class Descendants # # Count only absrtract, concrete and extern classes class CNODC super MClassMetric redef fun name do return "cnodc" redef fun desc do return "number of class_kind descendants" redef fun collect(mclass, mainmodule) do var count = 0 for parent in mclass.in_hierarchy(mainmodule).smallers do if parent == mclass then continue if parent.kind == abstract_kind or parent.kind == concrete_kind or parent.kind == extern_kind then count += 1 end end values[mclass] = count end end # MClass metric: Number of Interface Ancestors # # Count only interfaces class CNOAI super MClassMetric redef fun name do return "cnoai" redef fun desc do return "number of interface_kind ancestor" redef fun collect(mclass, mainmodule) do var count = 0 for parent in mclass.in_hierarchy(mainmodule).greaters do if parent == mclass then continue if parent.kind == interface_kind then count += 1 end end values[mclass] = count end end # MClass metric: Number of Interface Parents # # Count only interfaces class CNOPI super MClassMetric redef fun name do return "cnopi" redef fun desc do return "number of interface_kind parent" redef fun collect(mclass, mainmodule) do var count = 0 for parent in mclass.in_hierarchy(mainmodule).direct_greaters do if parent == mclass then continue if parent.kind == interface_kind then count += 1 end end values[mclass] = count end end # MClass metric: Number of Interface Children # # Count only interfaces class CNOCI super MClassMetric redef fun name do return "cnoci" redef fun desc do return "number of interface_kind children" redef fun collect(mclass, mainmodule) do var count = 0 for parent in mclass.in_hierarchy(mainmodule).direct_smallers do if parent == mclass then continue if parent.kind == interface_kind then count += 1 end end values[mclass] = count end end # MClass metric: Number of Interface Descendants # # Count only interfaces class CNODI super MClassMetric redef fun name do return "cnodi" redef fun desc do return "number of interface_kind descendants" redef fun collect(mclass, mainmodule) do var count = 0 for parent in mclass.in_hierarchy(mainmodule).smallers do if parent == mclass then continue if parent.kind == interface_kind then count += 1 end end values[mclass] = count end end # MClass metric: Class Depth in Inheritance Tree # # Following the longest path composed only of extends edges from self to Object class CDITC super MClassMetric redef fun name do return "cditc" redef fun desc do return "depth in class tree following only class, abstract, extern kind" redef fun collect(mclass, mainmodule) do values[mclass] = mclass.ditc(mainmodule) end end # MClass metric: Interface Depth in Inheritance Tree # # Following the longest path composed only of implements edges from self to Object class CDITI super MClassMetric redef fun name do return "cditi" redef fun desc do return "depth in class tree following only interface_kind" redef fun collect(mclass, mainmodule) do values[mclass] = mclass.diti(mainmodule) end end # model redef redef class MClass # Class Depth in Inheritance Tree # # Following the longest path composed only of extends edges from self to Object fun ditc(mainmodule: MModule): Int do if in_hierarchy(mainmodule).direct_greaters.is_empty then return 0 end var min = -1 for p in in_hierarchy(mainmodule).direct_greaters do if p.kind != abstract_kind and p.kind != concrete_kind and p.kind != extern_kind then continue var d = p.ditc(mainmodule) + 1 if min == -1 or d < min then min = d end end if min == -1 then min = 0 return min end # Interface Depth in Inheritance Tree # # Following the longest path composed only of implements edges from self to Object fun diti(mainmodule: MModule): Int do if in_hierarchy(mainmodule).direct_greaters.is_empty then return 0 end var min = -1 for p in in_hierarchy(mainmodule).direct_greaters do if p.kind != interface_kind then continue var d = p.diti(mainmodule) + 1 if min == -1 or d < min then min = d end end if min == -1 then min = 0 return min end end