From 1879e1b2d3cf9e1e343093b64e86ffaee499b141 Mon Sep 17 00:00:00 2001
From: Eddie Groh <groh@fortiss.org>
Date: Mon, 7 Aug 2023 09:54:09 +0200
Subject: [PATCH] Cleanup and refactor of MetricTreeNode

Issue-Ref: 4310
Issue-Url: https://git.fortiss.org/af3/af3/-/issues/4310

Signed-off-by: Eddie Groh <groh@fortiss.org>
---
 .../ui/view/fx/IModelQualityViewPart.java     |   4 +-
 .../ui/view/fx/ModelQualityFXController.java  |  44 ++-
 .../ui/view/fx/ModelQualityFXViewPart.java    |   1 -
 .../ext/quality/AF3QualityActivator.java      |   1 +
 .../quality/HierarchicElementProvider.java    |  34 +-
 .../data/HierarchicalMetricDataContainer.java |  54 ---
 .../quality/data/IMetricDataContainer.java    |  25 --
 .../tooling/ext/quality/data/MetricKey.java   |  48 ++-
 .../ext/quality/data/MetricTreeNode.java      |  65 ++--
 .../tooling/ext/quality/io/CSVFileWriter.java | 141 ++++++++
 .../quality/service/ModelQualityService.java  | 312 ++++++++++--------
 11 files changed, 428 insertions(+), 301 deletions(-)
 delete mode 100644 org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/HierarchicalMetricDataContainer.java
 delete mode 100644 org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/IMetricDataContainer.java
 create mode 100644 org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/io/CSVFileWriter.java

diff --git a/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/view/fx/IModelQualityViewPart.java b/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/view/fx/IModelQualityViewPart.java
index c8fc7399e..f730a8d37 100644
--- a/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/view/fx/IModelQualityViewPart.java
+++ b/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/view/fx/IModelQualityViewPart.java
@@ -22,7 +22,7 @@ package org.fortiss.tooling.ext.quality.ui.view.fx;
  */
 public interface IModelQualityViewPart {
 
-	/** View ID for reuse library views. */
+	/** View ID for model quality views. */
 	// Keep in sync with plugin.xml
-	public static String REUSE_LIBRARY_VIEW_ID = "org.fortiss.tooling.ext.quality.ui.metricsView";
+	public static String MODEL_QUALITY_VIEW_ID = "org.fortiss.tooling.ext.quality.ui.metricsView";
 }
diff --git a/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/view/fx/ModelQualityFXController.java b/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/view/fx/ModelQualityFXController.java
index 589d7843f..ea331dd49 100644
--- a/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/view/fx/ModelQualityFXController.java
+++ b/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/view/fx/ModelQualityFXController.java
@@ -183,21 +183,22 @@ public class ModelQualityFXController extends CompositeFXControllerBase<SplitPan
 
 				if(!node.getChildren().isEmpty()) {
 
-					SpiderChartViewer viewer = buildSpiderChart(node);
-					borderPane.setCenter(viewer.getViewerPane());
-
-					// Create PieChart and add data
-					pieChart.getData().clear();
-					for(var child : node.getChildren()) {
-						Double value = child.getStoredMetrics().get(choiceBox.getValue());
-						if(value != null) {
-							PieChart.Data slice = new PieChart.Data(child.getName(), value);
-							pieChart.getData().add(slice);
-							pieChart.setLegendVisible(false);
-							pieChart.setTitle("");
+					if(choiceBox.getValue() != null) {
+						SpiderChartViewer viewer = buildSpiderChart(node, choiceBox.getValue());
+						borderPane.setCenter(viewer.getViewerPane());
+
+						// Create PieChart and add data
+						pieChart.getData().clear();
+						for(var child : node.getChildren()) {
+							Double value = child.getValueAsDouble(choiceBox.getValue());
+							if(value != null) {
+								PieChart.Data slice = new PieChart.Data(child.getName(), value);
+								pieChart.getData().add(slice);
+								pieChart.setLegendVisible(false);
+								pieChart.setTitle("");
+							}
 						}
 					}
-
 					// scatterChart
 					scatterChart.getXAxis().setLabel("NUMBER_OF_TOTAL_LEAF_ELEMENTS");
 					scatterChart.getYAxis().setLabel("NUMBER_OF_TOTAL_CYCLOMATIC_COMPLEXITY");
@@ -208,9 +209,9 @@ public class ModelQualityFXController extends CompositeFXControllerBase<SplitPan
 
 					for(var child : node.getChildren()) {
 
-						Double leafs = child.getStoredMetrics()
+						Integer leafs = child.getStoredIntegers()
 								.get(MetricKey.NUMBER_OF_TOTAL_LEAF_ELEMENTS);
-						Double complexity = child.getStoredMetrics()
+						Integer complexity = child.getStoredIntegers()
 								.get(MetricKey.NUMBER_OF_TOTAL_CODE_CYCLOMATIC_COMPLEXITY);
 
 						if(leafs != null && complexity != null) {
@@ -238,9 +239,9 @@ public class ModelQualityFXController extends CompositeFXControllerBase<SplitPan
 	 *            of which the data will be visualized
 	 * @return {@link SpiderChartViewer} with the matching metrics
 	 */
-	private static SpiderChartViewer buildSpiderChart(MetricTreeNode node) {
+	private static SpiderChartViewer buildSpiderChart(MetricTreeNode node, MetricKey key) {
 		SpiderChart spiderChart = new SpiderChart();
-		spiderChart.setTitle("Amount of Ports");
+		// spiderChart.setTitle("Amount of Ports");
 		spiderChart.setLegendLabel("Legend");
 		ChartStyle chartStyle = new ChartStyle(true, true, true);
 
@@ -250,10 +251,8 @@ public class ModelQualityFXController extends CompositeFXControllerBase<SplitPan
 			return null;
 		}
 
-		double maxval = children.stream()
-				.mapToDouble(
-						p -> p.getStoredMetrics().get(MetricKey.NUMBER_OF_TOTAL_EXIT_CONNECTORS))
-				.max().getAsDouble();
+		double maxval =
+				children.stream().mapToDouble(p -> p.getValueAsDouble(key)).max().getAsDouble();
 
 		chartStyle.setUseIndividualAxisSegments(false);
 		chartStyle.setTitleStyle(new FontStyle("Verdana", 14, BLUE.brighter()));
@@ -267,8 +266,7 @@ public class ModelQualityFXController extends CompositeFXControllerBase<SplitPan
 			chartStyle.setAxisStyle(testing, aStyle3Segs);
 
 			spiderChart.addAxis(testing);
-			elementData.setPoint(testing,
-					child.getStoredMetrics().get(MetricKey.NUMBER_OF_TOTAL_EXIT_CONNECTORS));
+			elementData.setPoint(testing, child.getValueAsDouble(key));
 		}
 		spiderChart.addData(elementData);
 
diff --git a/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/view/fx/ModelQualityFXViewPart.java b/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/view/fx/ModelQualityFXViewPart.java
index 182cd2459..cb30f69cc 100644
--- a/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/view/fx/ModelQualityFXViewPart.java
+++ b/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/view/fx/ModelQualityFXViewPart.java
@@ -58,5 +58,4 @@ public class ModelQualityFXViewPart extends AF3FXViewPart implements IModelQuali
 
 		return scene;
 	}
-
 }
diff --git a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/AF3QualityActivator.java b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/AF3QualityActivator.java
index 3c8aaf351..353c3d99f 100644
--- a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/AF3QualityActivator.java
+++ b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/AF3QualityActivator.java
@@ -40,6 +40,7 @@ public class AF3QualityActivator extends Plugin {
 		super.start(context);
 		plugin = this;
 		System.out.println("[Plugin] " + PLUGIN_ID + " started.");
+
 		ModelQualityService.getInstance().startService();
 		IModelQualityService.getInstance().registerMetricProvider(new HierarchicElementProvider(),
 				IHierarchicElement.class);
diff --git a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/HierarchicElementProvider.java b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/HierarchicElementProvider.java
index 16b732c97..894d4fdc7 100644
--- a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/HierarchicElementProvider.java
+++ b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/HierarchicElementProvider.java
@@ -54,41 +54,41 @@ public class HierarchicElementProvider implements IMetricProvider<IHierarchicEle
 	@Override
 	public void collectMetrics(MetricTreeNode node, IHierarchicElement currentElement) {
 
-		var metrics = node.getStoredMetrics();
+		var integers = node.getStoredIntegers();
 		if(node.getName() == null) {
 			boolean isElementCommentable = currentElement instanceof INamedCommentedElement;
-			metrics.put(MetricKey.NUMBER_OF_TOTAL_COMMENTABLE_ELEMENTS,
-					isElementCommentable ? 1.0 : 0.0);
+			integers.put(MetricKey.NUMBER_OF_TOTAL_COMMENTABLE_ELEMENTS,
+					isElementCommentable ? 1 : 0);
 			String comment = isElementCommentable
 					? ((INamedCommentedElement)currentElement).getComment() : null;
-			metrics.put(MetricKey.NUMBER_OF_TOTAL_COMMENTED_ELEMENTS,
-					comment != null && comment != "" ? 1.0 : 0.0);
+			integers.put(MetricKey.NUMBER_OF_TOTAL_COMMENTED_ELEMENTS,
+					comment != null && comment != "" ? 1 : 0);
 			if(isElementCommentable) {
 				node.setName(((INamedCommentedElement)currentElement).getName());
 			} else {
 				node.setName("Unnamable Element");
 			}
 
-			metrics.put(MetricKey.UNQIUE_ID, (currentElement instanceof IIdLabeled)
-					? ((IIdLabeled)currentElement).getId() : -1.0);
+			integers.put(MetricKey.UNQIUE_ID, (currentElement instanceof IIdLabeled)
+					? ((IIdLabeled)currentElement).getId() : -1);
 		}
 
 		var connectors = currentElement.getConnectors();
-		metrics.put(MetricKey.NUMBER_OF_CONNECTORS, (double)connectors.size());
+		integers.put(MetricKey.NUMBER_OF_CONNECTORS, connectors.size());
 
-		metrics.put(MetricKey.NUMBER_OF_CONTAINED_ELEMENTS,
-				(double)currentElement.getContainedElements().size());
-		metrics.put(MetricKey.NUMBER_OF_CHANNELS, (double)currentElement.getConnections().size());
+		integers.put(MetricKey.NUMBER_OF_CONTAINED_ELEMENTS,
+				currentElement.getContainedElements().size());
+		integers.put(MetricKey.NUMBER_OF_CHANNELS, currentElement.getConnections().size());
 		// depth metrics
-		metrics.put(MetricKey.NUMBER_OF_TOTAL_CONNECTORS, (double)connectors.size());
+		integers.put(MetricKey.NUMBER_OF_TOTAL_CONNECTORS, connectors.size());
 		var entry_connectors = EcoreUtils.pickInstanceOf(EntryConnectorBase.class, connectors);
 		var exit_connectors = EcoreUtils.pickInstanceOf(ExitConnectorBase.class, connectors);
-		metrics.put(MetricKey.NUMBER_OF_TOTAL_ENTRY_CONNECTORS, (double)entry_connectors.size());
-		metrics.put(MetricKey.NUMBER_OF_TOTAL_EXIT_CONNECTORS, (double)exit_connectors.size());
+		integers.put(MetricKey.NUMBER_OF_TOTAL_ENTRY_CONNECTORS, entry_connectors.size());
+		integers.put(MetricKey.NUMBER_OF_TOTAL_EXIT_CONNECTORS, exit_connectors.size());
 
-		metrics.put(MetricKey.NUMBER_OF_TOTAL_ELEMENTS, 1.0);
+		integers.put(MetricKey.NUMBER_OF_TOTAL_ELEMENTS, 1);
 
-		metrics.put(MetricKey.NUMBER_OF_TOTAL_LEAF_ELEMENTS,
-				currentElement.getContainedElements().size() == 0 ? 1.0 : 0.0);
+		integers.put(MetricKey.NUMBER_OF_TOTAL_LEAF_ELEMENTS,
+				currentElement.getContainedElements().size() == 0 ? 1 : 0);
 	}
 }
diff --git a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/HierarchicalMetricDataContainer.java b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/HierarchicalMetricDataContainer.java
deleted file mode 100644
index f75a4d67c..000000000
--- a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/HierarchicalMetricDataContainer.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*-------------------------------------------------------------------------+
-| Copyright 2023 fortiss GmbH                                              |
-|                                                                          |
-| 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.                                           |
-+--------------------------------------------------------------------------*/
-package org.fortiss.tooling.ext.quality.data;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import org.fortiss.tooling.ext.quality.HierarchicElementProvider;
-
-/**
- * Class for storing data generated by the {@link HierarchicElementProvider}
- * 
- * @author groh
- */
-public class HierarchicalMetricDataContainer implements IMetricDataContainer {
-
-	/** Map containing all metrics which are a double */
-	private Map<String, Double> storedMetrics = new HashMap<>();
-
-	/** name of the element which is associated with this data */
-	private String name;
-
-	/**
-	 * @return the name of the element which is associated with this data
-	 */
-	public String getName() {
-		return name;
-	}
-
-	/** Set the name */
-	public void setName(String name) {
-		this.name = name;
-	}
-
-	/**
-	 * @return the stored metrics map
-	 */
-	public Map<String, Double> getStoredMetrics() {
-		return storedMetrics;
-	}
-}
diff --git a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/IMetricDataContainer.java b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/IMetricDataContainer.java
deleted file mode 100644
index 7cbd00479..000000000
--- a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/IMetricDataContainer.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*-------------------------------------------------------------------------+
-| Copyright 2023 fortiss GmbH                                              |
-|                                                                          |
-| 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.                                           |
-+--------------------------------------------------------------------------*/
-package org.fortiss.tooling.ext.quality.data;
-
-/**
- * Interface which is implemented by all
- * 
- * @author groh
- */
-public interface IMetricDataContainer {
-	// Nothing here yet
-}
diff --git a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/MetricKey.java b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/MetricKey.java
index 3d494172e..99c514431 100644
--- a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/MetricKey.java
+++ b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/MetricKey.java
@@ -41,23 +41,32 @@ public enum MetricKey {
 	/** How deeply nested the corresponding element is */
 	NESTING_LEVEL(false),
 	/** The betweenness centrality of the corresponding element */
-	BETWEENESS_CENTRALITY(false),
+	BETWEENESS_CENTRALITY(false, 0.0),
+	/**
+	 * The betweenness centrality of the corresponding element considering other elements
+	 * recursively
+	 */
+	BETWEENESS_CENTRALITY_RECURSIVELY(false, 0.0),
+	/**
+	 * The clustering coefficient of the element considering its neighbors
+	 */
+	CLUSTERING_COEFFICIENT(false, 0.0),
 
 	//
 	// Metrics which are collected over all children nodes
 	//
 	/** Key for the metric counting the total amount of ports contained */
-	NUMBER_OF_TOTAL_CONNECTORS(false),
+	NUMBER_OF_TOTAL_CONNECTORS(true),
 	/** Key for the metric counting the total amount of input ports contained */
-	NUMBER_OF_TOTAL_ENTRY_CONNECTORS(false),
+	NUMBER_OF_TOTAL_ENTRY_CONNECTORS(true),
 	/** Key for the metric counting the total amount of output ports contained */
-	NUMBER_OF_TOTAL_EXIT_CONNECTORS(false),
+	NUMBER_OF_TOTAL_EXIT_CONNECTORS(true),
 	/** Key for the metric counting the total amount of elements contained */
-	NUMBER_OF_TOTAL_ELEMENTS(false),
+	NUMBER_OF_TOTAL_ELEMENTS(true),
 	/** Key for the metric counting the total amount of elements which can be commented */
-	NUMBER_OF_TOTAL_COMMENTABLE_ELEMENTS(false),
+	NUMBER_OF_TOTAL_COMMENTABLE_ELEMENTS(true),
 	/** Key for the metric counting the total amount of elements which are commented */
-	NUMBER_OF_TOTAL_COMMENTED_ELEMENTS(false),
+	NUMBER_OF_TOTAL_COMMENTED_ELEMENTS(true),
 	/**
 	 * Key for the metric counting the total amount of elements which do not contain other elements
 	 */
@@ -92,6 +101,8 @@ public enum MetricKey {
 	private boolean isCollectorKey;
 	/** Determines if the corresponding value to this key is a string or not */
 	private boolean isStringValue;
+	/** Determines if the corresponding value to this key is a integer or not */
+	private boolean isDoubleValue;
 
 	/**
 	 * Basic constructor
@@ -103,6 +114,18 @@ public enum MetricKey {
 		this(isCollectorKey, false);
 	}
 
+	/**
+	 * @param isCollectorKey
+	 *            decides if this is a key returned by {@link MetricKey#getCollectorKeys()}
+	 * @param isDoubleValue
+	 *            regardless of argument will make the key to a double key
+	 */
+	private MetricKey(boolean isCollectorKey, double isDoubleValue) {
+		this.isCollectorKey = isCollectorKey;
+		this.isStringValue = false;
+		this.isDoubleValue = true;
+	}
+
 	/**
 	 * @param isCollectorKey
 	 *            decides if this is a key returned by {@link MetricKey#getCollectorKeys()}
@@ -112,6 +135,7 @@ public enum MetricKey {
 	private MetricKey(boolean isCollectorKey, boolean isStringValue) {
 		this.isCollectorKey = isCollectorKey;
 		this.isStringValue = isStringValue;
+		this.isDoubleValue = false;
 	}
 
 	/**
@@ -124,6 +148,16 @@ public enum MetricKey {
 		return isStringValue;
 	}
 
+	/**
+	 * If this value is true, it should be expected that the value corresponding to this key should
+	 * be a integer and be stored in an appropriate place
+	 * 
+	 * @return if the value associated with this key is a integer
+	 */
+	public boolean isDoubleValue() {
+		return isDoubleValue;
+	}
+
 	static {
 		for(MetricKey key : MetricKey.values()) {
 			if(key.isCollectorKey) {
diff --git a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/MetricTreeNode.java b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/MetricTreeNode.java
index cc3b43951..25be67105 100644
--- a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/MetricTreeNode.java
+++ b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/MetricTreeNode.java
@@ -21,8 +21,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.function.Consumer;
 
-import org.fortiss.tooling.ext.quality.IMetricProvider;
-
 /**
  * A general class for storing all kinds of metric data in a hierarchical tree structure.
  * 
@@ -35,22 +33,13 @@ public class MetricTreeNode {
 	/** stores the children of this tree node */
 	private List<MetricTreeNode> children;
 
-	/**
-	 * This map stores a relationship between a {@link IMetricProvider} and
-	 * {@link IMetricDataContainer}.
-	 * 
-	 * This allows all {@linkplain IMetricProvider} to deposit data inside this structure, and
-	 * quickly retrieve it for processing
-	 * 
-	 * The stored data can then also be accessed by other methods, for displaying information,
-	 * creating statistics, etc.
-	 */
-	private Map<Class<? extends IMetricProvider<?>>, IMetricDataContainer> dataContainers;
+	/** Map containing all metrics which are a Integer */
+	private Map<MetricKey, Integer> storedIntegers;
 
-	/** Map containing all metrics which are a double */
-	private Map<MetricKey, Double> storedMetrics;
+	/** Map containing all metrics which are a Double */
+	private Map<MetricKey, Double> storedDoubles;
 
-	/** Map containing all metrics which are a double */
+	/** Map containing all metrics which are a String */
 	private Map<MetricKey, String> storedStrings;
 
 	/** name of the element which is associated with this data */
@@ -61,8 +50,8 @@ public class MetricTreeNode {
 	 */
 	public MetricTreeNode() {
 		children = new ArrayList<>();
-		dataContainers = new HashMap<>();
-		storedMetrics = new HashMap<>();
+		storedIntegers = new HashMap<>();
+		storedDoubles = new HashMap<>();
 		storedStrings = new HashMap<>();
 	}
 
@@ -73,15 +62,6 @@ public class MetricTreeNode {
 		return children;
 	}
 
-	/**
-	 * See {@link MetricTreeNode#dataContainers}
-	 * 
-	 * @return the map storing all DataContainers in this node
-	 */
-	public Map<Class<? extends IMetricProvider<?>>, IMetricDataContainer> getDataContainers() {
-		return dataContainers;
-	}
-
 	/**
 	 * @return the name of the element which is associated with this data
 	 */
@@ -95,19 +75,42 @@ public class MetricTreeNode {
 	}
 
 	/**
-	 * @return the stored metrics map
+	 * @return the stored Integer map
 	 */
-	public Map<MetricKey, Double> getStoredMetrics() {
-		return storedMetrics;
+	public Map<MetricKey, Integer> getStoredIntegers() {
+		return storedIntegers;
 	}
 
 	/**
-	 * @return the stored strings map
+	 * @return the stored Doubles map
+	 */
+	public Map<MetricKey, Double> getStoredDoubles() {
+		return storedDoubles;
+	}
+
+	/**
+	 * @return the stored Strings map
 	 */
 	public Map<MetricKey, String> getStoredStrings() {
 		return storedStrings;
 	}
 
+	/**
+	 * Allows to retrieve allows to retrieve the value of a key which may be stored as double or integer type.
+	 * In the latter case the value will automatically be converted into a double
+	 * 
+	 * @param key
+	 *            for which the value shall be retrieved
+	 * @return the respective integer or double
+	 */
+	public Double getValueAsDouble(MetricKey key) {
+		if(key.isDoubleValue()) {
+			return storedDoubles.get(key);
+		}
+		Integer val = storedIntegers.get(key);
+		return val == null ? null : val.doubleValue();
+	}
+
 	/**
 	 * Traverses the tree in post-order
 	 * 
diff --git a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/io/CSVFileWriter.java b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/io/CSVFileWriter.java
new file mode 100644
index 000000000..2598767c1
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/io/CSVFileWriter.java
@@ -0,0 +1,141 @@
+/*-------------------------------------------------------------------------+
+| Copyright 2023 fortiss GmbH                                              |
+|                                                                          |
+| 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.                                           |
++--------------------------------------------------------------------------*/
+package org.fortiss.tooling.ext.quality.io;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.fortiss.tooling.ext.quality.data.MetricKey;
+import org.fortiss.tooling.ext.quality.data.MetricTreeNode;
+import org.fortiss.tooling.kernel.model.IProjectRootElement;
+
+/**
+ * 
+ * @author groh
+ */
+public class CSVFileWriter {
+
+	/**
+	 * Method to write the data into a csv
+	 * 
+	 * @param map
+	 *            to convert into csv
+	 * 
+	 */
+	public static void metricExtractionToCSV(Map<IProjectRootElement, MetricTreeNode> map) {
+
+		// check if there is any data to export
+		if(map == null || map.isEmpty()) {
+			return;
+		}
+
+		// path to csv file
+		Path path = Paths.get("data_mega_cool.csv");
+
+		List<String> allKeys = new ArrayList<>();
+		boolean createNewIndex = true;
+
+		if(Files.exists(path)) {
+			// Read first line and try to extract the keys from the already existing file
+			try(BufferedReader reader = new BufferedReader(new FileReader(path.toFile()))) {
+				String line = reader.readLine();
+				if(line != null) {
+					allKeys = Arrays.asList(line.split(","));
+					createNewIndex = false;
+				}
+			} catch(IOException e) {
+				e.printStackTrace();
+			}
+		}
+
+		try(var writer = new BufferedWriter(new FileWriter(path.toFile(), true))) {
+
+			if(createNewIndex) {
+				// Create new index and write it into the first line of the file
+				allKeys.add("timestamp");
+				allKeys.add("root_name");
+				allKeys.add("name");
+				allKeys.add("children");
+				// Collect all MetricKeys, convert them to a string and append them to the allKeys
+				// list
+				allKeys.addAll(Arrays.stream(MetricKey.values()).map(MetricKey::toString)
+						.map(String::toLowerCase).collect(Collectors.toList()));
+
+				// Write first line
+				writer.write(String.join(",", allKeys));
+				writer.write("\n");
+			}
+			// Remove first 4 keys as they are not provided by the provider
+			List<String> valueKeys = allKeys.subList(4, allKeys.size());
+
+			LocalDateTime dateTime = LocalDateTime.now();
+			DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
+			String formattedDateTime = dateTime.format(formatter);
+
+			map.forEach((rootProject, rootNode) -> {
+				// iterate over all project roots
+				String rootName = rootProject.getName();
+
+				rootNode.traverseTree(node -> {
+					var integers = node.getStoredIntegers();
+					var doubles = node.getStoredDoubles();
+					var strings = node.getStoredStrings();
+					// collect IDs of children
+					String children_ids = node.getChildren().stream()
+							.map(n -> n.getStoredIntegers().get(MetricKey.UNQIUE_ID))
+							.map(id -> id.toString()).reduce((a, b) -> a + " " + b).orElse("");
+
+					// These 4 data entries are just concatenated in front
+					var startStream =
+							Stream.of(formattedDateTime, rootName, node.getName(), children_ids);
+					// Collect all values from provider in the correct order and combine
+					var values = Stream.concat(startStream, valueKeys.stream()
+							.map(String::toUpperCase).map(MetricKey::valueOf).map(k -> {
+								if(k.isStringValue())
+									return strings.get(k);
+								if(k.isDoubleValue())
+									return doubles.get(k);
+								return integers.get(k);
+							}).map(String::valueOf)).collect(Collectors.joining(","));
+
+					try {
+						// write values
+						writer.write(values);
+						writer.write("\n");
+					} catch(IOException e) {
+						e.printStackTrace();
+					}
+				});
+			});
+		} catch(IOException e) {
+			e.printStackTrace();
+		}
+	}
+}
diff --git a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/service/ModelQualityService.java b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/service/ModelQualityService.java
index 2447695e8..5a8ec1f22 100644
--- a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/service/ModelQualityService.java
+++ b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/service/ModelQualityService.java
@@ -17,18 +17,7 @@ package org.fortiss.tooling.ext.quality.service;
 
 import static java.util.Collections.emptyList;
 
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.FileReader;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.time.LocalDateTime;
-import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedList;
@@ -39,8 +28,6 @@ import java.util.Optional;
 import java.util.Queue;
 import java.util.Set;
 import java.util.Stack;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 import org.eclipse.emf.common.util.EList;
 import org.eclipse.emf.ecore.EObject;
@@ -53,6 +40,7 @@ import org.fortiss.tooling.ext.quality.IMetricProvider;
 import org.fortiss.tooling.ext.quality.data.MetricDataManager;
 import org.fortiss.tooling.ext.quality.data.MetricKey;
 import org.fortiss.tooling.ext.quality.data.MetricTreeNode;
+import org.fortiss.tooling.ext.quality.io.CSVFileWriter;
 import org.fortiss.tooling.kernel.extension.data.ITopLevelElement;
 import org.fortiss.tooling.kernel.introspection.IIntrospectionDetailsItem;
 import org.fortiss.tooling.kernel.introspection.IIntrospectionItem;
@@ -141,7 +129,7 @@ public class ModelQualityService extends EObjectAwareServiceBase<IMetricProvider
 			}
 		}
 		// Store all currently saved metrics to the csv
-		metricExtractionToCSV(metricDataManagerInstance.getRootNodes());
+		CSVFileWriter.metricExtractionToCSV(metricDataManagerInstance.getRootNodes());
 	}
 
 	/**
@@ -158,7 +146,7 @@ public class ModelQualityService extends EObjectAwareServiceBase<IMetricProvider
 		var manager = metricDataManagerInstance;
 
 		collectMetrics(node, currentElement);
-		node.getStoredMetrics().put(MetricKey.NESTING_LEVEL, (double)recursionLevel);
+		node.getStoredIntegers().put(MetricKey.NESTING_LEVEL, recursionLevel);
 
 		// Check if any of the specifications is an IHierarchicElement
 		// This is for example the case with an StateAutomaton
@@ -183,24 +171,32 @@ public class ModelQualityService extends EObjectAwareServiceBase<IMetricProvider
 			manager.getHierarchicLookupTable().put(currentElement, node);
 		} else {
 
-			// Iterate over all children and
+			// Iterate over all children and merge values
+			var nodeDoubles = node.getStoredDoubles();
+			var nodeIntegers = node.getStoredIntegers();
 			for(IHierarchicElement containedElement : currentElement.getContainedElements()) {
 				MetricTreeNode child = new MetricTreeNode();
 				node.getChildren().add(child);
-				recursivlyCollectMetrics(child, containedElement, recursionLevel++);
+				recursivlyCollectMetrics(child, containedElement, recursionLevel + 1);
 
 				for(MetricKey key : MetricKey.getCollectorKeys()) {
-					var childMetrics = child.getStoredMetrics();
-					if(childMetrics.containsKey(key)) {
-						node.getStoredMetrics().merge(key, child.getStoredMetrics().get(key),
-								Double::sum);
+					var childDoubles = child.getStoredDoubles();
+					var childIntegers = child.getStoredIntegers();
+					if(childDoubles.containsKey(key)) {
+						nodeDoubles.merge(key, childDoubles.get(key), Double::sum);
+					}
+					if(childIntegers.containsKey(key)) {
+						nodeIntegers.merge(key, childIntegers.get(key), Integer::sum);
 					}
 				}
 			}
 
 			manager.getHierarchicLookupTable().put(currentElement, node);
 
-			calculateBetweennessCentrality(currentElement, manager);
+			calculateBetweennessCentrality(currentElement, manager, false);
+			calculateBetweennessCentrality(currentElement, manager, true);
+			node.getStoredDoubles().put(MetricKey.CLUSTERING_COEFFICIENT,
+					calculateClusteringCoefficent(currentElement));
 		}
 	}
 
@@ -211,17 +207,23 @@ public class ModelQualityService extends EObjectAwareServiceBase<IMetricProvider
 	 * 
 	 * @param scopeElement
 	 *            element defining the scope of the graph
+	 * @param recursively
+	 *            if true, everything is resolved to leaf elements
 	 * @return node list
 	 */
-	private static Set<IHierarchicElement> getLocalGraphView(IHierarchicElement scopeElement) {
+	private static Set<IHierarchicElement> getLocalGraphView(IHierarchicElement scopeElement,
+			boolean recursively) {
 		// Create a view consisting of all nodes inside this element, as well as all elements
 		// connected to this element
 
 		// This ensures that elements inside this element communicating with elements outside
 		// this element are properly recognized
 		Set<IHierarchicElement> graphNodes = new HashSet<>();
-		graphNodes.addAll(scopeElement.getContainedElements());
-
+		if(recursively) {
+			recursivelyGetLeafElements(scopeElement, graphNodes);
+		} else {
+			graphNodes.addAll(scopeElement.getContainedElements());
+		}
 		// Collect all elements to which currentElement has a outgoing connection
 		for(ExitConnectorBase exitConnector : EcoreUtils.pickInstanceOf(ExitConnectorBase.class,
 				scopeElement.getConnectors())) {
@@ -239,6 +241,122 @@ public class ModelQualityService extends EObjectAwareServiceBase<IMetricProvider
 		return graphNodes;
 	}
 
+	/**
+	 * Get all leafs inside the provided element
+	 * 
+	 * @param element
+	 *            the element containing leafs
+	 * @param allElements
+	 *            list where the leafs are saved
+	 */
+	private static void recursivelyGetLeafElements(IHierarchicElement element,
+			Set<IHierarchicElement> allElements) {
+		allElements.add(element);
+		for(IHierarchicElement containedElement : element.getContainedElements()) {
+			recursivelyGetLeafElements(containedElement, allElements);
+		}
+	}
+
+	/**
+	 * Follows outgoing connections until the connector has no further outgoing connections and then
+	 * saves the owner of this connector to the provided list
+	 * 
+	 * @param connector
+	 *            starting point for search
+	 * @param allElements
+	 *            list where the owners are collected
+	 */
+	private static void recursivlyFollowOutgoingConnection(IConnector connector,
+			Set<IHierarchicElement> allElements) {
+
+		EList<IConnection> outgoingConnections = connector.getOutgoing();
+
+		if(outgoingConnections.isEmpty()) {
+			allElements.add(connector.getOwner());
+			return;
+		}
+
+		for(IConnection outgoingConnection : outgoingConnections) {
+			recursivlyFollowOutgoingConnection(outgoingConnection.getTarget(), allElements);
+		}
+	}
+
+	/**
+	 * Follows incoming connections until the connector has no further incoming connections and
+	 * then
+	 * saves the owner of this connector to the provided list
+	 * 
+	 * @param connector
+	 *            starting point for search
+	 * @param allElements
+	 *            list where the owners are collected
+	 */
+	private static void recursivlyFollowIncomingConnection(IConnector connector,
+			Set<IHierarchicElement> allElements) {
+
+		EList<IConnection> incomingConnections = connector.getIncoming();
+
+		if(incomingConnections.isEmpty()) {
+			allElements.add(connector.getOwner());
+			return;
+		}
+
+		for(IConnection incomingConnection : incomingConnections) {
+			recursivlyFollowIncomingConnection(incomingConnection.getSource(), allElements);
+		}
+	}
+
+	/**
+	 * Collects all neighbors regardless of the direction of connection
+	 * 
+	 * @param element
+	 *            of which the neighbors should be collected
+	 * @return set of the neighbors
+	 */
+	private static Set<IHierarchicElement> getUndirectedNeighbors(IHierarchicElement element) {
+		Set<IHierarchicElement> neighbors = new HashSet<>();
+		for(ExitConnectorBase exitConnectors : EcoreUtils.pickInstanceOf(ExitConnectorBase.class,
+				element.getConnectors())) {
+			recursivlyFollowOutgoingConnection(exitConnectors, neighbors);
+		}
+		for(EntryConnectorBase entryConnectors : EcoreUtils.pickInstanceOf(EntryConnectorBase.class,
+				element.getConnectors())) {
+			recursivlyFollowIncomingConnection(entryConnectors, neighbors);
+		}
+		return neighbors;
+	}
+
+	/**
+	 * Calculates the clustering coefficient based on the neighborhood
+	 * 
+	 * @param element
+	 *            of which the coefficient shall be calculated
+	 * @return value of the coefficient
+	 */
+	private static double calculateClusteringCoefficent(IHierarchicElement element) {
+
+		Set<IHierarchicElement> neighbors = getUndirectedNeighbors(element);
+
+		int totalEdges = neighbors.size();
+		if(totalEdges < 2) {
+			return 0.0; // If there are fewer than 2 neighbors, the coefficient is undefined (or 0).
+		}
+
+		int triangles = 0;
+		for(IHierarchicElement neighbor : neighbors) {
+
+			Set<IHierarchicElement> neighborNeighbors = getUndirectedNeighbors(neighbor);
+
+			for(IHierarchicElement secondNeighbor : neighbors) {
+				if(secondNeighbor != neighbor && neighborNeighbors.contains(secondNeighbor)) {
+					triangles++;
+				}
+			}
+		}
+		double clusteringCoefficient = (double)triangles / (totalEdges * (totalEdges - 1));
+		return clusteringCoefficient;
+	}
+
 	/**
 	 * Calculates and saves the betweenness centrality of all elements contained inside the provided
 	 * scopeElement
@@ -247,16 +365,16 @@ public class ModelQualityService extends EObjectAwareServiceBase<IMetricProvider
 	 *            the scope of this calculation
 	 * @param manager
 	 *            location to save the metrics
+	 * @param recursively
+	 *            if true, all calculation will be carried out on leaf elements
 	 */
 	private static void calculateBetweennessCentrality(IHierarchicElement scopeElement,
-			MetricDataManager manager) {
+			MetricDataManager manager, boolean recursively) {
 
 		// Abort if no elements are found
 		if(!scopeElement.getContainedElements().isEmpty()) {
 
-			// Calculate betweenness centrality
-
-			Set<IHierarchicElement> graphNodes = getLocalGraphView(scopeElement);
+			Set<IHierarchicElement> graphNodes = getLocalGraphView(scopeElement, recursively);
 			// Initialize the betweenness values for all nodes to zero
 			Map<IHierarchicElement, Double> betweenness = new HashMap<>();
 			for(IHierarchicElement elementNode : graphNodes) {
@@ -292,24 +410,28 @@ public class ModelQualityService extends EObjectAwareServiceBase<IMetricProvider
 						// Iterate over all connections
 						for(IConnection outgoingConnection : exitConnectors.getOutgoing()) {
 
-							List<IHierarchicElement> targetElements = new ArrayList<>();
-							IHierarchicElement primaryTargetElement =
-									outgoingConnection.getTarget().getOwner();
-							// When we have a connection to the scopeElement, we have a connection
-							// leaving the local scope
-							if(scopeElement == primaryTargetElement) {
-								// Continue searching for outgoing connections on the target
-								// Connector
-								IConnector targetConnector = outgoingConnection.getTarget();
-								for(IConnection secondaryOutgoingConnection : targetConnector
-										.getOutgoing()) {
-									targetElements.add(
-											secondaryOutgoingConnection.getTarget().getOwner());
-								}
+							Set<IHierarchicElement> targetElements = new HashSet<>();
+							if(recursively) {
+								recursivlyFollowOutgoingConnection(outgoingConnection.getTarget(),
+										targetElements);
 							} else {
-								targetElements.add(primaryTargetElement);
+								IHierarchicElement primaryTargetElement =
+										outgoingConnection.getTarget().getOwner();
+								// When we have a connection to the scopeElement, we have a
+								// connection leaving the local scope
+								if(scopeElement == primaryTargetElement) {
+									// Continue searching for outgoing connections on the target
+									// Connector
+									IConnector targetConnector = outgoingConnection.getTarget();
+									for(IConnection secondaryOutgoingConnection : targetConnector
+											.getOutgoing()) {
+										targetElements.add(
+												secondaryOutgoingConnection.getTarget().getOwner());
+									}
+								} else {
+									targetElements.add(primaryTargetElement);
+								}
 							}
-
 							for(IHierarchicElement targetElement : targetElements) {
 
 								// Check if element is in node list, to avoid creating data for
@@ -346,11 +468,12 @@ public class ModelQualityService extends EObjectAwareServiceBase<IMetricProvider
 					}
 				}
 			}
-
 			// Save metric
+			MetricKey key = recursively ? MetricKey.BETWEENESS_CENTRALITY_RECURSIVELY
+					: MetricKey.BETWEENESS_CENTRALITY;
 			for(IHierarchicElement child : scopeElement.getContainedElements()) {
-				manager.getHierarchicLookupTable().get(child).getStoredMetrics()
-						.put(MetricKey.BETWEENESS_CENTRALITY, betweenness.get(child));
+				manager.getHierarchicLookupTable().get(child).getStoredDoubles().put(key,
+						betweenness.get(child));
 			}
 		}
 	}
@@ -428,97 +551,4 @@ public class ModelQualityService extends EObjectAwareServiceBase<IMetricProvider
 	public IIntrospectionDetailsItem getDetailsItem() {
 		return null;
 	}
-
-	/**
-	 * Method to write the data into a csv
-	 * 
-	 * @param map
-	 *            to convert into csv
-	 * 
-	 */
-	public void metricExtractionToCSV(Map<IProjectRootElement, MetricTreeNode> map) {
-
-		// check if there is any data to export
-		if(map == null || map.isEmpty()) {
-			return;
-		}
-
-		// path to csv file
-		Path path = Paths.get("data_mega_cool.csv");
-
-		List<String> allKeys = new ArrayList<>();
-		boolean createNewIndex = true;
-
-		if(Files.exists(path)) {
-			// Read first line and try to extract the keys from the already existing file
-			try(BufferedReader reader = new BufferedReader(new FileReader(path.toFile()))) {
-				String line = reader.readLine();
-				if(line != null) {
-					allKeys = Arrays.asList(line.split(","));
-					createNewIndex = false;
-				}
-			} catch(IOException e) {
-				e.printStackTrace();
-			}
-		}
-
-		try(var writer = new BufferedWriter(new FileWriter(path.toFile(), true))) {
-
-			if(createNewIndex) {
-				// Create new index and write it into the first line of the file
-				allKeys.add("timestamp");
-				allKeys.add("root_name");
-				allKeys.add("name");
-				allKeys.add("children");
-				// Collect all MetricKeys, convert them to a string and append them to the allKeys
-				// list
-				allKeys.addAll(Arrays.stream(MetricKey.values()).map(MetricKey::toString)
-						.map(String::toLowerCase).collect(Collectors.toList()));
-
-				// Write first line
-				writer.write(String.join(",", allKeys));
-				writer.write("\n");
-			}
-			// Remove first 4 keys as they are not provided by the provider
-			List<String> valueKeys = allKeys.subList(4, allKeys.size());
-
-			LocalDateTime dateTime = LocalDateTime.now();
-			DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
-			String formattedDateTime = dateTime.format(formatter);
-
-			map.forEach((rootProject, rootNode) -> {
-				// iterate over all project roots
-				String rootName = rootProject.getName();
-
-				rootNode.traverseTree(node -> {
-					var metrics = node.getStoredMetrics();
-					var strings = node.getStoredStrings();
-					// collect IDs of children
-					String children_ids = node.getChildren().stream()
-							.map(n -> n.getStoredMetrics().get(MetricKey.UNQIUE_ID))
-							.map(id -> id.toString()).reduce((a, b) -> a + " " + b).orElse("");
-
-					// These 4 data entries are just concatenated in front
-					var startStream =
-							Stream.of(formattedDateTime, rootName, node.getName(), children_ids);
-					// Collect all values from provider in the correct order and combine
-					var values = Stream.concat(startStream,
-							valueKeys.stream().map(String::toUpperCase).map(MetricKey::valueOf)
-									.map(k -> k.isStringValue() ? strings.get(k) : metrics.get(k))
-									.map(String::valueOf))
-							.collect(Collectors.joining(","));
-
-					try {
-						// write values
-						writer.write(values);
-						writer.write("\n");
-					} catch(IOException e) {
-						e.printStackTrace();
-					}
-				});
-			});
-		} catch(IOException e) {
-			e.printStackTrace();
-		}
-	}
 }
-- 
GitLab