diff --git a/tools/io.incquery.cameo.model-coverage-report/META-INF/MANIFEST.MF b/tools/io.incquery.cameo.model-coverage-report/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..9cf35b3
--- /dev/null
+++ b/tools/io.incquery.cameo.model-coverage-report/META-INF/MANIFEST.MF
@@ -0,0 +1,8 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: io.incquery.cameo.model-coverage-report
+Bundle-SymbolicName: io.incquery.cameo.model-coverage-report;singleton:=true
+Bundle-Version: 0.1.0.qualifier
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
+Automatic-Module-Name: io.incquery.cameo.model-coverage-report
+Export-Package: .
diff --git a/tools/io.incquery.cameo.model-coverage-report/src/main/dist/template/data/resourcemanager/MDR_Example_Plugin_18351_descriptor.xml b/tools/io.incquery.cameo.model-coverage-report/src/main/dist/template/data/resourcemanager/MDR_Example_Plugin_18351_descriptor.xml
new file mode 100644
index 0000000..bd824d6
--- /dev/null
+++ b/tools/io.incquery.cameo.model-coverage-report/src/main/dist/template/data/resourcemanager/MDR_Example_Plugin_18351_descriptor.xml
@@ -0,0 +1,32 @@
+
+
+
+
+ Reader
+ Community
+ Standard
+ Professional Java
+ Professional C++
+ Professional C#
+ Professional ArcStyler
+ Professional EFFS ArcStyler
+ OptimalJ
+ Professional
+ Architect
+ Enterprise
+
+
+
+
+
+
+
+
diff --git a/tools/io.incquery.cameo.model-coverage-report/src/main/dist/template/plugins/${group}/plugin.xml b/tools/io.incquery.cameo.model-coverage-report/src/main/dist/template/plugins/${group}/plugin.xml
new file mode 100644
index 0000000..8ba7bc2
--- /dev/null
+++ b/tools/io.incquery.cameo.model-coverage-report/src/main/dist/template/plugins/${group}/plugin.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tools/io.incquery.cameo.model-coverage-report/src/main/java/io/incquery/cameo/modelcoveragereport/MainMenuConfigurator.java b/tools/io.incquery.cameo.model-coverage-report/src/main/java/io/incquery/cameo/modelcoveragereport/MainMenuConfigurator.java
new file mode 100644
index 0000000..ee5909e
--- /dev/null
+++ b/tools/io.incquery.cameo.model-coverage-report/src/main/java/io/incquery/cameo/modelcoveragereport/MainMenuConfigurator.java
@@ -0,0 +1,58 @@
+/*
+ *
+ * Copyright (c) 2002 NoMagic, Inc. All Rights Reserved.
+ */
+
+package io.incquery.cameo.modelcoveragereport;
+
+import com.nomagic.actions.AMConfigurator;
+import com.nomagic.actions.ActionsCategory;
+import com.nomagic.actions.ActionsManager;
+import com.nomagic.actions.NMAction;
+import com.nomagic.magicdraw.actions.MDActionsCategory;
+
+public class MainMenuConfigurator implements AMConfigurator {
+
+ String REPORTING = "Report";
+
+ /**
+ * Action will be added to manager.
+ */
+ private NMAction[] actions;
+
+ /**
+ * Creates configurator.
+ *
+ * @param action
+ * action to be added to main menu.
+ */
+ public MainMenuConfigurator(NMAction... actions) {
+ this.actions = actions;
+ }
+
+ /**
+ * @see com.nomagic.actions.AMConfigurator#configure(ActionsManager) Methods
+ * adds action to given manager Examples category.
+ */
+ @Override
+ public void configure(ActionsManager manager) {
+ // searching for Examples action category
+ ActionsCategory category = (ActionsCategory) manager.getActionFor(REPORTING);
+
+ if (category == null) {
+ // creating new category
+ category = new MDActionsCategory(REPORTING, REPORTING);
+ category.setNested(true);
+ manager.addCategory(category);
+ }
+ for (NMAction action : actions) {
+ category.addAction(action);
+ }
+ }
+
+ @Override
+ public int getPriority() {
+ return AMConfigurator.MEDIUM_PRIORITY;
+ }
+
+}
\ No newline at end of file
diff --git a/tools/io.incquery.cameo.model-coverage-report/src/main/java/io/incquery/cameo/modelcoveragereport/ModelCoverageReportPlugin.java b/tools/io.incquery.cameo.model-coverage-report/src/main/java/io/incquery/cameo/modelcoveragereport/ModelCoverageReportPlugin.java
new file mode 100644
index 0000000..8d83873
--- /dev/null
+++ b/tools/io.incquery.cameo.model-coverage-report/src/main/java/io/incquery/cameo/modelcoveragereport/ModelCoverageReportPlugin.java
@@ -0,0 +1,28 @@
+package io.incquery.cameo.modelcoveragereport;
+
+import io.incquery.cameo.modelcoveragereport.actions.MetaModelCoverageAction;
+import com.nomagic.magicdraw.actions.ActionsConfiguratorsManager;
+import com.nomagic.magicdraw.plugins.Plugin;
+
+public class ModelCoverageReportPlugin extends Plugin {
+
+ @Override
+ public boolean close() {
+ return true;
+ }
+
+ @Override
+ public void init() {
+
+ ActionsConfiguratorsManager manager = ActionsConfiguratorsManager.getInstance();
+ manager.addMainMenuConfigurator(new MainMenuConfigurator(new MetaModelCoverageAction()));
+ manager.addContainmentBrowserContextConfigurator(new ProfileCoverageActionConfigurator());
+
+ }
+
+ @Override
+ public boolean isSupported() {
+ return true;
+ }
+
+}
diff --git a/tools/io.incquery.cameo.model-coverage-report/src/main/java/io/incquery/cameo/modelcoveragereport/ProfileCoverageActionConfigurator.java b/tools/io.incquery.cameo.model-coverage-report/src/main/java/io/incquery/cameo/modelcoveragereport/ProfileCoverageActionConfigurator.java
new file mode 100644
index 0000000..5b53537
--- /dev/null
+++ b/tools/io.incquery.cameo.model-coverage-report/src/main/java/io/incquery/cameo/modelcoveragereport/ProfileCoverageActionConfigurator.java
@@ -0,0 +1,39 @@
+package io.incquery.cameo.modelcoveragereport;
+
+import java.util.Arrays;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import io.incquery.cameo.modelcoveragereport.actions.ProfileCoverageAction;
+import com.nomagic.actions.AMConfigurator;
+import com.nomagic.actions.ActionsCategory;
+import com.nomagic.actions.ActionsManager;
+import com.nomagic.magicdraw.actions.ActionsStateUpdater;
+import com.nomagic.magicdraw.actions.BrowserContextAMConfigurator;
+import com.nomagic.magicdraw.actions.MDActionsCategory;
+import com.nomagic.magicdraw.ui.browser.Node;
+import com.nomagic.magicdraw.ui.browser.Tree;
+import com.nomagic.uml2.ext.magicdraw.mdprofiles.Profile;
+
+public class ProfileCoverageActionConfigurator implements BrowserContextAMConfigurator {
+
+ @Override
+ public void configure(ActionsManager manager, Tree tree) {
+ ActionsCategory cat = manager.getCategory("REPORT");
+ if(cat == null) {
+ cat = new MDActionsCategory("REPORT", "Generate Report...");
+ manager.addCategory(cat);
+ }
+ Set selectedObjects = Arrays.stream(tree.getSelectedNodes()).map(Node::getUserObject)
+ .filter(Profile.class::isInstance).map(Profile.class::cast).collect(Collectors.toSet());
+ if(!selectedObjects.isEmpty()) {
+ cat.addAction(new ProfileCoverageAction(selectedObjects.iterator().next()));
+ ActionsStateUpdater.updateActionsState();
+ }
+ }
+
+ @Override
+ public int getPriority() {
+ return AMConfigurator.MEDIUM_PRIORITY;
+ }
+}
diff --git a/tools/io.incquery.cameo.model-coverage-report/src/main/java/io/incquery/cameo/modelcoveragereport/actions/CoverageReportAction.java b/tools/io.incquery.cameo.model-coverage-report/src/main/java/io/incquery/cameo/modelcoveragereport/actions/CoverageReportAction.java
new file mode 100644
index 0000000..d2d2dbf
--- /dev/null
+++ b/tools/io.incquery.cameo.model-coverage-report/src/main/java/io/incquery/cameo/modelcoveragereport/actions/CoverageReportAction.java
@@ -0,0 +1,105 @@
+package io.incquery.cameo.modelcoveragereport.actions;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.nomagic.magicdraw.actions.MDAction;
+import com.nomagic.magicdraw.core.Application;
+import com.nomagic.magicdraw.core.GUILog;
+import com.nomagic.magicdraw.core.Project;
+
+import javax.annotation.CheckForNull;
+import javax.swing.*;
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.sql.Timestamp;
+import java.text.SimpleDateFormat;
+
+public abstract class CoverageReportAction extends MDAction {
+ private static final long serialVersionUID = 1L;
+ protected static final String TEMPLATE__CAT_ID = "%S_COVERAGE_REPORT_GENERATOR";
+ protected static final String TEMPLATE__CAT_NAME = "Generate %s Coverage Report";
+ protected final String type;
+ private static File baseDir = null;
+
+ @Override
+ public final void actionPerformed(@CheckForNull ActionEvent actionEvent) {
+ // select output directory
+ JFileChooser outputDirectorySelector = new JFileChooser();
+ outputDirectorySelector.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+ outputDirectorySelector.setDialogTitle("Output Directory Selector");
+ outputDirectorySelector.setCurrentDirectory(baseDir);
+ if(outputDirectorySelector.showOpenDialog(Application.getInstance().getMainFrame()) == JFileChooser.APPROVE_OPTION) {
+ baseDir = outputDirectorySelector.getSelectedFile();
+ } else {
+ return;
+ }
+
+ // execute the report generation
+ Result result = actionPerformed();
+
+ // user feedback
+ GUILog log = Application.getInstance().getGUILog();
+ if(result.succeeded) {
+ log.log(result.message);
+ } else if(result.error != null) {
+ log.showError(result.message, result.error);
+ } else {
+ log.showError(result.message);
+ }
+ }
+
+ protected File getBaseDir() {
+ return baseDir;
+ }
+
+ public abstract Result actionPerformed();
+
+ protected CoverageReportAction(String type) {
+ super(String.format(TEMPLATE__CAT_ID, type), String.format(TEMPLATE__CAT_NAME, type), null, null);
+ this.type = type.toLowerCase();
+ }
+ protected final void serializeToJson(Object coverageDTO, File outputDir) {
+ Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();
+ File outputFile = new File(outputDir, String.format("%sCoverageInfo.json", type));
+ try (FileWriter writer = new FileWriter(outputFile)) {
+ gson.toJson(coverageDTO, writer);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ protected final File setupDestinationDirectory(Project project) {
+ Timestamp timestamp = new Timestamp(System.currentTimeMillis());
+ SimpleDateFormat simpleDate = new SimpleDateFormat("yyyy.MM.dd_HH.mm.ss");
+ String projectName = project.getName();
+
+ return new File(Paths.get(
+ getBaseDir() != null
+ ? getBaseDir().getAbsolutePath()
+ : "./ModelCoverageInfo",
+ projectName,
+ simpleDate.format(timestamp)).toString());
+ }
+
+ protected static final class Result {
+ public final boolean succeeded;
+ public final String message;
+ public final Throwable error;
+
+ private static final String OUTPUT_TEMPLATE = "Report files were successfully generated into the following directory: %s";
+
+ public Result(File outputDir) {
+ this.succeeded = true;
+ this.message = String.format(OUTPUT_TEMPLATE, outputDir.toString());
+ this.error = null;
+ }
+ public Result(String message, Throwable error) {
+ this.succeeded = true;
+ this.message = message;
+ this.error = error;
+ }
+ }
+}
diff --git a/tools/io.incquery.cameo.model-coverage-report/src/main/java/io/incquery/cameo/modelcoveragereport/actions/MetaModelCoverageAction.java b/tools/io.incquery.cameo.model-coverage-report/src/main/java/io/incquery/cameo/modelcoveragereport/actions/MetaModelCoverageAction.java
new file mode 100644
index 0000000..2837534
--- /dev/null
+++ b/tools/io.incquery.cameo.model-coverage-report/src/main/java/io/incquery/cameo/modelcoveragereport/actions/MetaModelCoverageAction.java
@@ -0,0 +1,236 @@
+package io.incquery.cameo.modelcoveragereport.actions;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.eclipse.emf.ecore.EClass;
+import org.eclipse.emf.ecore.EClassifier;
+import org.eclipse.emf.ecore.EDataType;
+import org.eclipse.emf.ecore.EStructuralFeature;
+
+import io.incquery.cameo.modelcoveragereport.dtos.MetaModelCoverageDTO;
+import com.nomagic.magicdraw.core.Application;
+import com.nomagic.magicdraw.core.Project;
+import com.nomagic.magicdraw.core.ProjectUtilities;
+import com.nomagic.magicdraw.uml.BaseElement;
+import com.nomagic.magicdraw.uml.Finder;
+import com.nomagic.uml2.ext.magicdraw.classes.mdkernel.Element;
+import com.nomagic.uml2.ext.magicdraw.metadata.UMLPackage;
+
+public class MetaModelCoverageAction extends CoverageReportAction {
+
+ private static final long serialVersionUID = 2609117531520837581L;
+ private static final String MDTRANSIENT = "mdTransient";
+
+ public MetaModelCoverageAction() {
+ super("UML");
+ }
+
+ @Override
+ public Result actionPerformed() {
+ Project project = Application.getInstance().getProject();
+ if(project == null) {
+ return new Result("You must open a project before generating the report", null);
+ }
+ File outputDir;
+ UMLPackage metamodel = UMLPackage.eINSTANCE;
+
+ List eClasses = getEclasses(metamodel);
+ Set structuralFeatures =
+ getAllStructuralFeatures(metamodel);
+ Set filteredStructuralFeatures =
+ structuralFeatures.stream().filter(MetaModelCoverageAction::isFiltered).collect(Collectors.toSet());
+ Set pureUmlFeatures =
+ structuralFeatures.stream().filter(MetaModelCoverageAction::isUmlStandard).collect(Collectors.toSet());
+
+ Map> eCoreCoverageMap = getClassCoverageMap(eClasses, project);
+
+ outputDir = setupDestinationDirectory(project);
+ outputDir.mkdirs();
+
+ MetaModelCoverageDTO modelCoverage = MetaModelCoverageDTO.getInitializedInstance(
+ eClasses.size(),
+ structuralFeatures.size(),
+ filteredStructuralFeatures.size(),
+ pureUmlFeatures.size());
+
+ calculateEClassCoverage(eCoreCoverageMap, modelCoverage);
+ calculateEDataTypeCoverage(structuralFeatures, eCoreCoverageMap, modelCoverage);
+ modelCoverage.numOfCoveredFeatures =
+ calculateStructuralFeatureCoverage(structuralFeatures, eCoreCoverageMap, modelCoverage.coveredFeatures, modelCoverage.uncoveredFeatures, modelCoverage.features);
+ modelCoverage.coveredFilteredFeatures =
+ calculateStructuralFeatureCoverage(filteredStructuralFeatures, eCoreCoverageMap, null, modelCoverage.uncoveredFilteredFeatures, null);
+
+ modelCoverage.coveredPureUMLFeatures =
+ calculateStructuralFeatureCoverage(pureUmlFeatures, eCoreCoverageMap, null, modelCoverage.uncoveredPureUMLFeatures, null);
+
+ serializeToJson(modelCoverage, outputDir);
+ serializeToCsv(modelCoverage, outputDir);
+
+ return new Result(outputDir);
+ }
+
+ public static boolean isUmlStandard(EStructuralFeature struct) {
+ return !struct.getName().startsWith("_");
+ }
+
+ public static boolean isFiltered(EStructuralFeature struct) {
+ return !struct.isDerived() && struct.isChangeable() && !struct.isTransient()
+ && struct.getEAnnotations().stream().noneMatch(annot -> MDTRANSIENT.equals(annot.getSource()));
+ }
+
+ private Map> getClassCoverageMap(List eClasses, Project project) {
+ Map> eCoreToModelElementsMap = new HashMap<>();
+ for (EClassifier eclass : eClasses) {
+ Collection foundElements = Finder.byTypeRecursively()
+ .find(project.getPrimaryModel(), new Class[] { eclass.getInstanceClass() }).stream()
+ .collect(Collectors.toList());
+
+ if (!ProjectUtilities.isRemote(project.getPrimaryProject())) {
+ foundElements = foundElements.stream().filter(BaseElement::isEditable).collect(Collectors.toList());
+ }
+ eCoreToModelElementsMap.put(eclass, foundElements);
+ }
+ return eCoreToModelElementsMap;
+ }
+
+ private Set getAllStructuralFeatures(UMLPackage metamodel) {
+ return metamodel.getEClassifiers().stream()
+ .filter(EClass.class::isInstance).map(EClass.class::cast)
+ .flatMap(cls -> cls.getEStructuralFeatures().stream()).collect(Collectors.toSet());
+ }
+
+ private List getEclasses(UMLPackage metamodel) {
+ List eclasses = new ArrayList<>();
+ for (EClassifier classifier : metamodel.getEClassifiers()) {
+ if((classifier instanceof EClass && !((EClass) classifier).isAbstract() && !((EClass) classifier).isInterface()) ||
+ (classifier instanceof EDataType && classifier.getInstanceClass() != null && !classifier.getInstanceClass().isPrimitive() && classifier != metamodel.getString())) {
+ eclasses.add(classifier);
+ }
+ }
+
+ return eclasses;
+ }
+
+ public int calculateStructuralFeatureCoverage(Set structuralFeatures,
+ Map> eclassMap, Collection coveredFeatures, Collection uncoveredFeatures, Map features) {
+ int numOfCoveredFeatures = 0;
+ for (EStructuralFeature feature : structuralFeatures) {
+ if (isFeatureCovered(feature, eclassMap)) {
+ numOfCoveredFeatures++;
+ if(coveredFeatures != null) {
+ coveredFeatures.add(getStringRepresentationOfFeature(feature));
+ }
+ if(features != null) {
+ features.put(feature, true);
+ }
+ } else {
+ uncoveredFeatures.add(getStringRepresentationOfFeature(feature));
+ if(features != null) {
+ features.put(feature, false);
+ }
+ }
+ }
+ return numOfCoveredFeatures;
+ }
+
+ private String getStringRepresentationOfFeature(EStructuralFeature feature) {
+ return feature.getEContainingClass().getName() + "::" + feature.getName();
+ }
+
+ public boolean isFeatureCovered(EStructuralFeature feature, Map> eclassMap) {
+ EClass containingEClass = feature.getEContainingClass();
+ for (Entry> eClass : eclassMap.entrySet()) {
+ EClassifier cls = eClass.getKey();
+ if (cls instanceof EClass && (cls == containingEClass || ((EClass) cls).getEAllSuperTypes().contains(containingEClass))) {
+ for (Element instance : eClass.getValue()) {
+ if (instance.eGet(feature) != null) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ public void calculateEClassCoverage(Map> eclassMap,
+ MetaModelCoverageDTO coverageInfo) {
+ for (Entry> cls : eclassMap.entrySet()) {
+ if(cls.getKey() instanceof EClass) {
+ if (cls.getValue().isEmpty()) {
+ coverageInfo.uncoveredClasses.add(cls.getKey().getName());
+ } else {
+ coverageInfo.numOfCoveredClasses++;
+ coverageInfo.coveredClasses.add(cls.getKey().getName());
+ }
+ }
+ }
+ }
+
+ private void calculateEDataTypeCoverage(Set structuralFeatures,
+ Map> eCoreCoverageMap, MetaModelCoverageDTO modelCoverage) {
+ eCoreCoverageMap.keySet().stream()
+ .filter(EDataType.class::isInstance).map(EDataType.class::cast)
+ .forEach(dt -> {
+ boolean dtFound = structuralFeatures.stream()
+ // sometimes different EDataTypes has the same instance class so practically they
+ // can be covered the same features (e.g. ParameterEffectKind and ParameterParameterEffectKind)
+ .filter(feature -> dt == feature.getEType() || dt.getInstanceClass() == feature.getEType().getInstanceClass())
+ .anyMatch(feature -> isFeatureCovered(feature, eCoreCoverageMap));
+ if(dtFound) {
+ modelCoverage.numOfCoveredClasses++;
+ modelCoverage.coveredClasses.add(dt.getName());
+ } else {
+ modelCoverage.uncoveredClasses.add(dt.getName());
+ }
+ });
+ }
+
+ private void serializeToCsv(MetaModelCoverageDTO coverageDTO, File outputDir) {
+ File typeOutputFile = new File(outputDir, String.format("%sTypeCoverageInfo.csv", type));
+ try (FileWriter writer = new FileWriter(typeOutputFile)) {
+ writer.write("Type,Covered"+System.lineSeparator());
+ for(String cl : coverageDTO.coveredClasses) {
+ writer.write(cl+",true"+System.lineSeparator());
+ }
+ for(String cl : coverageDTO.uncoveredClasses) {
+ writer.write(cl+",false"+System.lineSeparator());
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ File featureOutputFile = new File(outputDir, String.format("%sFeatureCoverageInfo.csv", type));
+ try (FileWriter writer = new FileWriter(featureOutputFile)) {
+ writer.write("Feature,Covered,Defining Type,UML Standard,Filtered"+System.lineSeparator());
+ for(Map.Entry feature : coverageDTO.features.entrySet()) {
+ writer.write(String.format("%s,%b,%s,%b,%b%s",
+ feature.getKey().getName(),
+ feature.getValue(),
+ definingTypeString(feature.getKey()),
+ isUmlStandard(feature.getKey()),
+ isFiltered(feature.getKey()),
+ System.lineSeparator()));
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private String definingTypeString(EStructuralFeature feature) {
+ EClass type = feature.getEContainingClass();
+ if(type.isAbstract() || type.isInterface()) {
+ return "[Abstract] "+type.getName();
+ } else {
+ return type.getName();
+ }
+ }
+}
diff --git a/tools/io.incquery.cameo.model-coverage-report/src/main/java/io/incquery/cameo/modelcoveragereport/actions/ProfileCoverageAction.java b/tools/io.incquery.cameo.model-coverage-report/src/main/java/io/incquery/cameo/modelcoveragereport/actions/ProfileCoverageAction.java
new file mode 100644
index 0000000..b3ae6fd
--- /dev/null
+++ b/tools/io.incquery.cameo.model-coverage-report/src/main/java/io/incquery/cameo/modelcoveragereport/actions/ProfileCoverageAction.java
@@ -0,0 +1,157 @@
+package io.incquery.cameo.modelcoveragereport.actions;
+
+import io.incquery.cameo.modelcoveragereport.dtos.ProfileCoverageDTO;
+import com.nomagic.magicdraw.core.Application;
+import com.nomagic.magicdraw.core.Project;
+import com.nomagic.magicdraw.core.ProjectUtilities;
+import com.nomagic.magicdraw.uml.Finder;
+import com.nomagic.uml2.ext.jmi.helpers.StereotypesHelper;
+import com.nomagic.uml2.ext.magicdraw.classes.mdkernel.Element;
+import com.nomagic.uml2.ext.magicdraw.classes.mdkernel.Property;
+import com.nomagic.uml2.ext.magicdraw.classes.mdkernel.Slot;
+import com.nomagic.uml2.ext.magicdraw.classes.mdkernel.VisibilityKindEnum;
+import com.nomagic.uml2.ext.magicdraw.mdprofiles.Profile;
+import com.nomagic.uml2.ext.magicdraw.mdprofiles.Stereotype;
+import com.nomagic.uml2.ext.magicdraw.metadata.UMLPackage;
+
+import java.io.File;
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.stream.Collectors;
+
+public class ProfileCoverageAction extends CoverageReportAction {
+ private static final long serialVersionUID = 7959507763497894824L;
+ private transient Element selectedObjects;
+
+ public ProfileCoverageAction(Profile selectedObjects) {
+ super(selectedObjects.getName());
+ this.selectedObjects = selectedObjects;
+ }
+
+ @Override
+ public Result actionPerformed() {
+ Project project = Application.getInstance().getProject();
+ File outputDir = setupDestinationDirectory(project);
+ outputDir.mkdirs();
+
+ Collection stereotypes = getAllStereotypes(selectedObjects);
+ Collection filteredStereotypes = filterStereotpyes(stereotypes);
+ Collection propertysOfStereotyps = getPropertysOfStereotyps(stereotypes);
+ ProfileCoverageDTO profileCoverageBean = new ProfileCoverageDTO(filteredStereotypes.size(), propertysOfStereotyps.size());
+
+ Map> stereotypedElementsMap =
+ calculateStereotypedElementsMap(filteredStereotypes, project);
+ Map> propertyCoverageMap =
+ calculatePropertyMap(stereotypedElementsMap, propertysOfStereotyps);
+
+ calculateStereotypesCoverage(stereotypedElementsMap, profileCoverageBean);
+
+ calculatePropertyCoverage(propertyCoverageMap, profileCoverageBean);
+
+ serializeToJson(profileCoverageBean, outputDir);
+
+ return new Result(outputDir);
+ }
+
+ private Map> calculatePropertyMap(
+ Map> stereotypedElementsMap, Collection propertysOfStereotyps) {
+ Map> propertyCoverageMap = new HashMap<>();
+ for (Property property : propertysOfStereotyps) {
+ propertyCoverageMap.put(property, new HashSet<>());
+ }
+
+ for (Entry> stereotypedElements : stereotypedElementsMap.entrySet()) {
+ Stereotype stereo = stereotypedElements.getKey();
+ Collection currentProps = stereo.getMember().stream()
+ .filter(Property.class::isInstance).map(Property.class::cast)
+ .filter(prop -> prop.getVisibility() == VisibilityKindEnum.PUBLIC)
+ .collect(Collectors.toList());
+ for (Element stereotypedElement : stereotypedElements.getValue()) {
+ for (Property property : currentProps) {
+ // MD190sp4 version
+ Slot slot = StereotypesHelper.getSlot(stereotypedElement, stereo, property, false, false);
+ if (slot != null) {
+ propertyCoverageMap.get(property).add(slot);
+ }
+ // MD2021xr1 version
+// TaggedValue val = StereotypesHelper.getTaggedValue(stereotypedElement, property);
+// if(val != null && val.hasValue()) {
+// propertyCoverageMap.get(property).add(val);
+// }
+ }
+ }
+ }
+ return propertyCoverageMap;
+ }
+
+ private Map> calculateStereotypedElementsMap(
+ Collection filteredStereotypes, Project project) {
+ Map> stereotypedElementsMap = new HashMap<>();
+
+ for (Stereotype stereo : filteredStereotypes) {
+ stereotypedElementsMap.put(stereo, new HashSet<>());
+ }
+
+ project.getPrimaryModel().eAllContents().forEachRemaining(con -> {
+ if (con instanceof Element) {
+ Element elem = (Element) con;
+ if (elem.isEditable() || ProjectUtilities.isRemote( project.getPrimaryProject())) {
+ Collection foundStereotypes = StereotypesHelper
+ .getAllAssignedStereotypes(Collections.singletonList(elem));
+ for (Stereotype st : foundStereotypes) {
+ if (stereotypedElementsMap.containsKey(st)) {
+ stereotypedElementsMap.get(st).add(elem);
+ }
+ }
+ }
+ }
+
+ });
+ return stereotypedElementsMap;
+ }
+
+ private Collection getAllStereotypes(Element profile) {
+ return Finder.byTypeRecursively()
+ .find(profile, new Class[] { UMLPackage.eINSTANCE.getStereotype().getInstanceClass() }).stream()
+ .map(Stereotype.class::cast).collect(Collectors.toList());
+ }
+
+ private Collection filterStereotpyes(Collection stereotypes) {
+ return stereotypes.stream().filter(str -> !str.isAbstract()).collect(Collectors.toList());
+ }
+
+ private Collection getPropertysOfStereotyps(Collection stereotypes) {
+ return stereotypes.stream().flatMap(str -> str.getOwnedAttribute().stream())
+ .filter(prop -> prop.getVisibility() == VisibilityKindEnum.PUBLIC).collect(Collectors.toList());
+ }
+
+ public void calculateStereotypesCoverage(Map> stereotypedElementsMap, ProfileCoverageDTO profileCoverageBean) {
+ int coveredStereotypes = 0;
+ for (Entry> stereotypedElements : stereotypedElementsMap.entrySet()) {
+ String stereoName = stereotypedElements.getKey().getQualifiedName();
+ if (stereotypedElements.getValue().isEmpty()) {
+ profileCoverageBean.uncoveredStereotypes.add(stereoName);
+ } else {
+ coveredStereotypes++;
+ profileCoverageBean.coveredStereotypes.add(stereoName);
+ }
+ }
+ profileCoverageBean.numOfCoveredStereotypes = coveredStereotypes;
+ }
+
+ public void calculatePropertyCoverage(Map> propertyCoverageMap, ProfileCoverageDTO profileCoverageBean) {
+ int coveredPublicProperties = 0;
+ for (Entry> properties : propertyCoverageMap.entrySet()) {
+ String propName = properties.getKey().getQualifiedName();
+ if (properties.getValue().isEmpty()) {
+ profileCoverageBean.uncoveredPublicProperties.add(propName);
+ } else {
+ coveredPublicProperties++;
+ profileCoverageBean.coveredPublicProperties.add(propName);
+ }
+ }
+ profileCoverageBean.numOfCoveredPublicProperties = coveredPublicProperties;
+
+ }
+
+}
diff --git a/tools/io.incquery.cameo.model-coverage-report/src/main/java/io/incquery/cameo/modelcoveragereport/dtos/MetaModelCoverageDTO.java b/tools/io.incquery.cameo.model-coverage-report/src/main/java/io/incquery/cameo/modelcoveragereport/dtos/MetaModelCoverageDTO.java
new file mode 100644
index 0000000..775e9b2
--- /dev/null
+++ b/tools/io.incquery.cameo.model-coverage-report/src/main/java/io/incquery/cameo/modelcoveragereport/dtos/MetaModelCoverageDTO.java
@@ -0,0 +1,81 @@
+package io.incquery.cameo.modelcoveragereport.dtos;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeSet;
+
+import com.google.gson.annotations.SerializedName;
+import org.eclipse.emf.ecore.EStructuralFeature;
+
+public class MetaModelCoverageDTO {
+
+ @SerializedName(value = "numOfAllClasses")
+ public int allClasses;
+
+ @SerializedName(value = "numOfCoveredClasses")
+ public int numOfCoveredClasses;
+
+ @SerializedName(value = "numOfAllFeatures")
+ public int allFeatures;
+
+ @SerializedName(value = "numOfCoveredFeatures")
+ public int numOfCoveredFeatures;
+
+ @SerializedName(value = "numOfAllFilteredFeatures")
+ public int allFilteredFeatures;
+
+ @SerializedName(value = "numOfCoveredFilteredFeatures")
+ public int coveredFilteredFeatures;
+
+ @SerializedName(value = "numOfAllPureUMLFeatures")
+ public int allPureUMLFeatures;
+
+ @SerializedName(value = "numOfCoveredPureUMLFeatures")
+ public int coveredPureUMLFeatures;
+
+ @SerializedName(value = "coveredClasses")
+ public Collection coveredClasses;
+
+ @SerializedName(value = "uncoveredClasses")
+ public Collection uncoveredClasses;
+
+ @SerializedName(value = "coveredFeatures")
+ public Collection coveredFeatures;
+
+ public transient Map features;
+
+ @SerializedName(value = "uncoveredFeatures")
+ public Collection uncoveredFeatures;
+
+ @SerializedName(value = "uncoveredFilteredFeatures")
+ public Collection uncoveredFilteredFeatures;
+
+ @SerializedName(value = "uncoveredPureUMLFeatures")
+ public Collection uncoveredPureUMLFeatures;
+
+ public static MetaModelCoverageDTO getInitializedInstance(int allClasses, int allFeatures, int allFilteredFeatures, int allPureUMLFeatures) {
+ MetaModelCoverageDTO newBean = new MetaModelCoverageDTO();
+ newBean.allClasses = allClasses;
+ newBean.allFeatures = allFeatures;
+ newBean.allFilteredFeatures = allFilteredFeatures;
+ newBean.allPureUMLFeatures = allPureUMLFeatures;
+ newBean.features = new HashMap<>();
+ newBean.coveredClasses = new TreeSet<>();
+ newBean.uncoveredClasses = new TreeSet<>();
+ newBean.coveredFeatures = new TreeSet<>();
+ newBean.uncoveredFeatures = new TreeSet<>();
+ newBean.uncoveredFilteredFeatures = new TreeSet<>();
+ newBean.uncoveredPureUMLFeatures = new TreeSet<>();
+ return newBean;
+ }
+
+ @Override
+ public String toString() {
+ return "ModelCoverageBean [\n\t Number of all eClasses=" + allClasses + ",\n\t Number of covered classes="
+ + numOfCoveredClasses + ",\n\t Number of all eFeatures=" + allFeatures
+ + ",\n\t Number of all Filtered eFeatures=" + allFilteredFeatures + ",\n\t Number of covered eFeatures="
+ + numOfCoveredFeatures + ",\n\t List of uncovered classes=" + uncoveredClasses + ",\n\t uncoveredFeatures="
+ + uncoveredFeatures + "]";
+ }
+}
diff --git a/tools/io.incquery.cameo.model-coverage-report/src/main/java/io/incquery/cameo/modelcoveragereport/dtos/ProfileCoverageDTO.java b/tools/io.incquery.cameo.model-coverage-report/src/main/java/io/incquery/cameo/modelcoveragereport/dtos/ProfileCoverageDTO.java
new file mode 100644
index 0000000..da7598d
--- /dev/null
+++ b/tools/io.incquery.cameo.model-coverage-report/src/main/java/io/incquery/cameo/modelcoveragereport/dtos/ProfileCoverageDTO.java
@@ -0,0 +1,53 @@
+package io.incquery.cameo.modelcoveragereport.dtos;
+
+import java.util.Collection;
+import java.util.TreeSet;
+
+import com.google.gson.annotations.SerializedName;
+
+public class ProfileCoverageDTO {
+
+ @SerializedName(value = "numOfAllStereotypes")
+ public int allStereotypes;
+
+ @SerializedName(value = "numOfCoveredStereotypes")
+ public int numOfCoveredStereotypes;
+
+ @SerializedName(value = "numOfAllPublicProperties")
+ public int allPublicProperties;
+
+ @SerializedName(value = "numOfCoveredPublicProperties")
+ public int numOfCoveredPublicProperties;
+
+ @SerializedName(value = "coveredStereotypes")
+ public Collection coveredStereotypes;
+
+ @SerializedName(value = "uncoveredStereotypes")
+ public Collection uncoveredStereotypes;
+
+ @SerializedName(value = "coveredPublicProperties")
+ public Collection coveredPublicProperties;
+
+ @SerializedName(value = "uncoveredPublicProperties")
+ public Collection uncoveredPublicProperties;
+
+ public ProfileCoverageDTO(int allStereotypes, int allPublicProperties) {
+ super();
+ this.allStereotypes = allStereotypes;
+ this.allPublicProperties = allPublicProperties;
+ this.coveredStereotypes = new TreeSet<>();
+ this.uncoveredStereotypes = new TreeSet<>();
+ this.coveredPublicProperties = new TreeSet<>();
+ this.uncoveredPublicProperties = new TreeSet<>();
+ }
+
+ @Override
+ public String toString() {
+ return "ModelCoverageBean [\n\t Number of all Stereotypes=" + allStereotypes
+ + ",\n\t Number of covered Stereotypes=" + numOfCoveredStereotypes + ",\n\t Number of all Public Properties="
+ + allPublicProperties + ",\n\t Number of covered Public Properties=" + numOfCoveredPublicProperties
+ + ",\n\t List of uncovered Stereotypes=" + uncoveredStereotypes + ",\n\t uncoveredPublicProperties="
+ + uncoveredPublicProperties + "]";
+ }
+
+}