From ffe63d038649657ab2245a6dbb87a78ea18f6e79 Mon Sep 17 00:00:00 2001
From: Eddie Groh <groh@fortiss.org>
Date: Thu, 10 Aug 2023 18:15:42 +0200
Subject: [PATCH] Added automatic trigger for metric collection and executed
 large refactor and cleanup as well as added developer documentation

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

Signed-off-by: Eddie Groh <groh@fortiss.org>
---
 .../ui/ModelQualityExtractionMenu.java        |   4 +-
 .../ui/view/fx/ModelQualityFXController.java  |  13 +-
 .../html/developer/documentation.html         | 124 +++++-
 .../ext/quality/GraphMetricsProvider.java     | 321 ++++++++++++++
 .../quality/HierarchicElementProvider.java    |   2 +-
 .../tooling/ext/quality/data/MetricKey.java   |   2 +-
 .../quality/service/IModelQualityService.java |   4 +-
 .../quality/service/ModelQualityService.java  | 395 +++++-------------
 .../{io => storage}/CSVFileWriter.java        |  13 +-
 .../storage/ModelQualityStorageManager.java   |  43 ++
 10 files changed, 613 insertions(+), 308 deletions(-)
 create mode 100644 org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/GraphMetricsProvider.java
 rename org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/{io => storage}/CSVFileWriter.java (93%)
 create mode 100644 org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/storage/ModelQualityStorageManager.java

diff --git a/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/ModelQualityExtractionMenu.java b/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/ModelQualityExtractionMenu.java
index 31ae65d7e..e260d8d38 100644
--- a/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/ModelQualityExtractionMenu.java
+++ b/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/ModelQualityExtractionMenu.java
@@ -36,7 +36,7 @@ import org.fortiss.tooling.kernel.ui.extension.data.ContextMenuContextProvider;
 /**
  * Crates a context menu entry to generate the for the selected
  * 
- * @author munaro
+ * @author groh
  */
 public class ModelQualityExtractionMenu implements IContextMenuContributor {
 
@@ -81,7 +81,7 @@ public class ModelQualityExtractionMenu implements IContextMenuContributor {
 		@Override
 		public void run() {
 			ITopLevelElement top = IPersistencyService.getInstance().getTopLevelElementFor(this.fp);
-			IModelQualityService.getInstance().performMetricAnalysis(top);
+			IModelQualityService.getInstance().scheduleMetricCollection(top);
 		}
 	}
 }
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 4ffec83f3..a477b26f6 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
@@ -177,6 +177,11 @@ public class ModelQualityFXController extends CompositeFXControllerBase<SplitPan
 				Set<MetricKey> validKeys = new HashSet<>();
 				validKeys.addAll(node.getStoredDoubles().keySet());
 				validKeys.addAll(node.getStoredIntegers().keySet());
+				for(MetricTreeNode child : node.getChildren()) {
+					validKeys.addAll(child.getStoredDoubles().keySet());
+					validKeys.addAll(child.getStoredIntegers().keySet());
+				}
+
 				ObservableList<MetricKey> choices = choiceBox.getItems();
 				choices.retainAll(validKeys);
 				for(MetricKey key : validKeys) {
@@ -256,7 +261,7 @@ public class ModelQualityFXController extends CompositeFXControllerBase<SplitPan
 		}
 
 		double maxval = children.stream().map(p -> p.getValueAsDouble(key)).filter(p -> p != null)
-				.mapToDouble(p -> p).max().getAsDouble();
+				.mapToDouble(p -> p).max().orElse(0.0);
 
 		chartStyle.setUseIndividualAxisSegments(false);
 		chartStyle.setTitleStyle(new FontStyle("Verdana", 14, BLUE.brighter()));
@@ -264,10 +269,14 @@ public class ModelQualityFXController extends CompositeFXControllerBase<SplitPan
 		DataSeries elementData = new DataSeries("Data");
 		for(var child : node.getChildren()) {
 			Double value = child.getValueAsDouble(key);
+			String name = child.getName();
 			if(value == null) {
 				value = 0.0;
 			}
-			DoubleAxis testing = new DoubleAxis(child.getName(), 0.0, maxval);
+			if(name == null) {
+				name = "Impossible null";
+			}
+			DoubleAxis testing = new DoubleAxis(name, 0.0, maxval);
 
 			AxisStyle aStyle3Segs = new AxisStyle(SOLID_BLACK_1PT, BLACK_VERDANA_14PT, 3,
 					BLACK_VERDANA_8PT, new DecimalFormat("#.##"));
diff --git a/org.fortiss.tooling.ext.quality/html/developer/documentation.html b/org.fortiss.tooling.ext.quality/html/developer/documentation.html
index d8e07a13f..d97b0a20c 100644
--- a/org.fortiss.tooling.ext.quality/html/developer/documentation.html
+++ b/org.fortiss.tooling.ext.quality/html/developer/documentation.html
@@ -6,7 +6,129 @@
 <body lang="en-US" dir="ltr">
 <h1>Developer Documentation for <i>Quality Functionality
 (</i><i>org.fortiss.tooling.ext.quality</i><i>)</i></h1>
-<p>Lorem Ipsum</p>
+
+<p>Below is a list of all collected metrics. They are represented by the enum <code> org.fortiss.tooling.ext.quality.data.MetricKey</code></p>
+<p>They are categorized by the respective provider collecting the metric</p>
+
+<h2>HierarchicElementProvider</h2>
+<code>org.fortiss.tooling.ext.quality.HierarchicElementProvider</code>
+
+<h4>UNQIUE_ID</h4>
+<p><code>Integer</code>: This is an unique identifier for each element as specified in <code>org.fortiss.tooling.kernel.model.IIdLabeled</code>. </p>
+
+<h4>NUMBER_OF_CONNECTORS</h4>
+<p><code>Integer</code>: Number of connectors in the respective element</p>
+
+<h4>NUMBER_OF_CONTAINED_ELEMENTS</h4>
+<p><code>Integer</code>: Number of entries in the containedElements list.</p>
+
+<h4>NUMBER_OF_CONNECTIONS</h4>
+<p><code>Integer</code>: Number of connections in this element. List of aggregated connection model elements. Usually a aggregates all connections of its direct sub-structure.</p>
+
+<h4>NUMBER_OF_TOTAL_CONNECTORS</h4>
+<p><code>Integer</code>: Total number of connectors in this element and all contained elements</p>
+
+<h4>NUMBER_OF_TOTAL_ENTRY_CONNECTORS</h4>
+<p><code>Integer</code>: Sum of all connectors which implement <code>org.fortiss.tooling.base.model.base.EntryConnectorBase</code></p>
+
+<h4>NUMBER_OF_TOTAL_EXIT_CONNECTORS</h4>
+<p><code>Integer</code>: Sum of all connectors which implement <code>org.fortiss.tooling.base.model.base.ExitConnectorBase</code>. Usually the sum over this and <code>NUMBER_OF_TOTAL_ENTRY_CONNECTORS</code> should equal <code>NUMBER_OF_TOTAL_CONNECTORS</code></p>
+
+<h4>NUMBER_OF_TOTAL_ELEMENTS</h4>
+<p><code>Integer</code>: Total number of elements contained in this element. Includes the element for which this metric is recorded.</p>
+
+<h4>NUMBER_OF_TOTAL_COMMENTABLE_ELEMENTS</h4>
+<p><code>Integer</code>: Total number of elements implementing <code>org.fortiss.tooling.kernel.model.INamedCommentedElement</code> in this element. Includes the element for which this metric is recorded. Never exceeds <code>NUMBER_OF_TOTAL_ELEMENTS</code>.</p>
+
+<h4>NUMBER_OF_TOTAL_COMMENTED_ELEMENTS</h4>
+<p><code>Integer</code>: Total number of elements which are commentable and do not have <code>null</code> or an empty string as comment</p>
+
+<h4>NUMBER_OF_TOTAL_LEAF_ELEMENTS</h4>
+<p><code>Integer</code>: Total number of elements contained in this element which do not contain elements. Includes self</p>
+
+
+<h2>GraphMetricsProvider</h2>
+<code>org.fortiss.tooling.ext.quality.GraphMetricsProvider</code>
+
+
+<h4>BETWEENESS_CENTRALITY</h4>
+<p><code>Double</code>: Value of the betweenness centrality of this element embedded in a graph of consisting of all elements which are contained in the element which contains this element, as well as all neighbours of these elements</p>
+<p>The direction of the connections are respected, and it is assumed that all connections connect to a ExitConnectorBase or EntryConnectorBase</p>
+
+
+<h4>BETWEENESS_CENTRALITY_RECURSIVELY</h4>
+<p><code>Double</code>: Same as above, but all connections are resolved to leaf elements, i.e. elements which do not contain other elements</p>
+
+
+<h4>CLUSTERING_COEFFICIENT</h4>
+<p><code>Double</code>: Clustering coefficient of this element. Ignores connection direction and resolves all neighbours to leaf elements.</p>
+
+
+
+
+<h2>ComponentMetricProvider</h2>
+<code>org.fortiss.af3.component.quality.ComponentMetricProvider</code>
+
+<h4>NUMBER_OF_TOTAL_CODE_CONSTANTS</h4>
+<p><code>Integer</code>: Total number of constants in code specifications in this element and all contained elements</p>
+
+<h4>NUMBER_OF_TOTAL_CODE_CYCLOMATIC_COMPLEXITY</h4>
+<p><code>Integer</code>: The total cyclomatic complexity of all code specifications in this element and all contained elements</p>
+
+<h4>NUMBER_OF_TOTAL_CODE_LINES</h4>
+<p><code>Integer</code>: Total number of lines of code in all code specifications in this element and all contained elements</p>
+
+<h4>NUMBER_OF_TOTAL_CODE_COMMENTS</h4>
+<p><code>Integer</code>: Total number of comment blocks in all code specifications in this element and all contained elements</p>
+
+
+<h2>ComponentMetricProvider</h2>
+<code>org.fortiss.af3.component.quality.ComponentMetricProvider</code>
+
+
+<h2>DataDictonaryProvider</h2>
+<code>org.fortiss.af3.component.quality.DataDictonaryProvider</code>
+<p> Provides Metrics also collected by the HierarchicElementProvider, but for the DataDictionary.</p>
+
+
+<h2>SafetyMetricProvider</h2>
+<code>org.fortiss.af3.safety.quality.SafetyMetricProvider</code>
+
+<h4>SAFETY_LEVEL</h4>
+<p><code>String</code>: String representation of the assigned safety level of this element</p>
+
+
+<h2>AllocationTableProvider</h2>
+<code>org.fortiss.af3.task.quality.AllocationTableProvider</code>
+
+<h4>TASK_ALLOCATED_COMPONENTS</h4>
+<p><code>Integer</code>: Number of allocated Components on this Task. This metric can only be non-null for <code>org.fortiss.af3.task.model.Task</code> Elements</p>
+
+<h4>HARDWARE_ALLOCATED_TASKS</h4>
+<p><code>Integer</code>: Number of allocated Tasks on this ExecutionUnit. This metric can only be non-null for <code>org.fortiss.af3.platform.model.ExecutionUnit</code> Elements</p>
+
+
+
+<h2>ModelQualityService</h2>
+<code>org.fortiss.tooling.ext.quality.service.ModelQualityService</code>
+
+<h4>NESTING_LEVEL</h4>
+<p><code>Integer</code>: The depth of this element. Defined by the number of elements which have to be traversed to reach this element from the root</p>
+
+
+<h4>CONSTRAINT_VIOLATIONS_ERROR</h4>
+<p><code>Integer</code>: Number of constraint violations of the severity error which have this element as source</p>
+
+
+<h4>CONSTRAINT_VIOLATIONS_WARNING</h4>
+<p><code>Integer</code>: Number of constraint violations of the severity warning which have this element as source</p>
+
+
+
+
+
+
+
 
 </body>
 </html>
\ No newline at end of file
diff --git a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/GraphMetricsProvider.java b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/GraphMetricsProvider.java
new file mode 100644
index 000000000..bb73def50
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/GraphMetricsProvider.java
@@ -0,0 +1,321 @@
+/*-------------------------------------------------------------------------+
+| 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;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+import java.util.Stack;
+
+import org.eclipse.emf.common.util.EList;
+import org.fortiss.tooling.base.model.base.EntryConnectorBase;
+import org.fortiss.tooling.base.model.base.ExitConnectorBase;
+import org.fortiss.tooling.base.model.element.IConnection;
+import org.fortiss.tooling.base.model.element.IConnector;
+import org.fortiss.tooling.base.model.element.IHierarchicElement;
+import org.fortiss.tooling.ext.quality.data.MetricDataManager;
+import org.fortiss.tooling.ext.quality.data.MetricKey;
+import org.fortiss.tooling.kernel.utils.EcoreUtils;
+
+/**
+ * 
+ * @author groh
+ */
+public class GraphMetricsProvider {
+
+	/**
+	 * Creates a list of nodes contained in the graph centered around the provided element
+	 * It will contain all elements contained within the provided element as well as all elements
+	 * connected to the provided element
+	 * 
+	 * @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,
+			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<>();
+		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())) {
+			for(IConnection exitConnection : exitConnector.getOutgoing()) {
+				graphNodes.add(exitConnection.getTarget().getOwner());
+			}
+		}
+		// Collect all elements to which currentElement has a incoming connection
+		for(EntryConnectorBase entryConnector : EcoreUtils.pickInstanceOf(EntryConnectorBase.class,
+				scopeElement.getConnectors())) {
+			for(IConnection entryConnection : entryConnector.getIncoming()) {
+				graphNodes.add(entryConnection.getSource().getOwner());
+			}
+		}
+		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
+	 */
+	public 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
+	 * 
+	 * @param scopeElement
+	 *            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
+	 */
+	public static void calculateBetweennessCentrality(IHierarchicElement scopeElement,
+			MetricDataManager manager, boolean recursively) {
+
+		// Abort if no elements are found
+		if(!scopeElement.getContainedElements().isEmpty()) {
+
+			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) {
+				betweenness.put(elementNode, 0.0);
+			}
+
+			// Iterate over all collected nodes
+			for(IHierarchicElement elementNode : graphNodes) {
+
+				// Initialize all variables
+				Stack<IHierarchicElement> stack = new Stack<>();
+				Map<IHierarchicElement, List<IHierarchicElement>> pred = new HashMap<>();
+				Map<IHierarchicElement, Integer> dist = new HashMap<>();
+				Map<IHierarchicElement, Double> sigma = new HashMap<>();
+				Queue<IHierarchicElement> queue = new LinkedList<>();
+
+				for(IHierarchicElement v : graphNodes) {
+					pred.put(v, new ArrayList<>());
+					dist.put(v, -1);
+					sigma.put(v, 0.0);
+				}
+				sigma.put(elementNode, 1.0);
+				dist.put(elementNode, 0);
+				queue.add(elementNode);
+
+				while(!queue.isEmpty()) {
+					IHierarchicElement v = queue.remove();
+					stack.push(v);
+
+					// Iterate over all connectors going out from the element
+					for(ExitConnectorBase exitConnectors : EcoreUtils
+							.pickInstanceOf(ExitConnectorBase.class, v.getConnectors())) {
+						// Iterate over all connections
+						for(IConnection outgoingConnection : exitConnectors.getOutgoing()) {
+
+							Set<IHierarchicElement> targetElements = new HashSet<>();
+							if(recursively) {
+								recursivlyFollowOutgoingConnection(outgoingConnection.getTarget(),
+										targetElements);
+							} else {
+								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
+								// nodes outside of our graph
+								if(graphNodes.contains(targetElement)) {
+									if(dist.get(targetElement) < 0) {
+										queue.add(targetElement);
+										dist.put(targetElement, dist.get(v) + 1);
+									}
+									if(dist.get(targetElement) == dist.get(v) + 1) {
+										sigma.put(targetElement,
+												sigma.get(targetElement) + sigma.get(v));
+										pred.get(targetElement).add(v);
+									}
+								}
+							}
+						}
+					}
+				}
+				// Initialize delta
+				Map<IHierarchicElement, Double> delta = new HashMap<>();
+				for(IHierarchicElement v : graphNodes) {
+					delta.put(v, 0.0);
+				}
+
+				while(!stack.isEmpty()) {
+					IHierarchicElement w = stack.pop();
+					for(IHierarchicElement v : pred.get(w)) {
+						double c = (sigma.get(v) / sigma.get(w)) * (1.0 + delta.get(w));
+						delta.put(v, delta.get(v) + c);
+					}
+					if(!w.equals(elementNode)) {
+						betweenness.put(w, betweenness.get(w) + delta.get(w));
+					}
+				}
+			}
+			// Save metric
+			MetricKey key = recursively ? MetricKey.BETWEENESS_CENTRALITY_RECURSIVELY
+					: MetricKey.BETWEENESS_CENTRALITY;
+			for(IHierarchicElement child : scopeElement.getContainedElements()) {
+				manager.getTreeNodeLookupTable().get(child).getStoredDoubles().put(key,
+						betweenness.get(child));
+			}
+		}
+	}
+}
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 894d4fdc7..f98165974 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
@@ -78,7 +78,7 @@ public class HierarchicElementProvider implements IMetricProvider<IHierarchicEle
 
 		integers.put(MetricKey.NUMBER_OF_CONTAINED_ELEMENTS,
 				currentElement.getContainedElements().size());
-		integers.put(MetricKey.NUMBER_OF_CHANNELS, currentElement.getConnections().size());
+		integers.put(MetricKey.NUMBER_OF_CONNECTIONS, currentElement.getConnections().size());
 		// depth metrics
 		integers.put(MetricKey.NUMBER_OF_TOTAL_CONNECTORS, connectors.size());
 		var entry_connectors = EcoreUtils.pickInstanceOf(EntryConnectorBase.class, connectors);
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 827082829..2b24baa69 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
@@ -35,7 +35,7 @@ public enum MetricKey {
 	/** Key for the metric counting elements */
 	NUMBER_OF_CONTAINED_ELEMENTS(false),
 	/** Key for the metric counting channels */
-	NUMBER_OF_CHANNELS(false),
+	NUMBER_OF_CONNECTIONS(false),
 	/** Safety level of the element */
 	SAFETY_LEVEL(false, true),
 	/** How deeply nested the corresponding element is */
diff --git a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/service/IModelQualityService.java b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/service/IModelQualityService.java
index 025bc2e75..f355df420 100644
--- a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/service/IModelQualityService.java
+++ b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/service/IModelQualityService.java
@@ -34,6 +34,6 @@ public interface IModelQualityService {
 	<T extends EObject> void registerMetricProvider(IMetricProvider<T> provider,
 			Class<T> modelElementClass);
 
-	/** analyses the metrics and processes them */
-	void performMetricAnalysis(ITopLevelElement top);
+	/** Schedules an analysis of the metrics and processes them */
+	void scheduleMetricCollection(ITopLevelElement top);
 }
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 9ecfd8179..65d4ba8af 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
@@ -19,46 +19,49 @@ import static java.util.Collections.emptyList;
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedList;
 import java.util.List;
-import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Optional;
 import java.util.Queue;
-import java.util.Set;
-import java.util.Stack;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.stream.Collectors;
 
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.Job;
 import org.eclipse.emf.common.util.EList;
 import org.eclipse.emf.ecore.EObject;
-import org.fortiss.tooling.base.model.base.EntryConnectorBase;
-import org.fortiss.tooling.base.model.base.ExitConnectorBase;
-import org.fortiss.tooling.base.model.element.IConnection;
-import org.fortiss.tooling.base.model.element.IConnector;
 import org.fortiss.tooling.base.model.element.IHierarchicElement;
+import org.fortiss.tooling.ext.quality.GraphMetricsProvider;
 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.ext.quality.storage.CSVFileWriter;
+import org.fortiss.tooling.kernel.extension.data.IConstraintViolation;
+import org.fortiss.tooling.kernel.extension.data.IConstraintViolation.ESeverity;
 import org.fortiss.tooling.kernel.extension.data.ITopLevelElement;
+import org.fortiss.tooling.kernel.internal.ConstraintCheckerService;
 import org.fortiss.tooling.kernel.introspection.IIntrospectionDetailsItem;
 import org.fortiss.tooling.kernel.introspection.IIntrospectionItem;
 import org.fortiss.tooling.kernel.introspection.IIntrospectiveKernelService;
 import org.fortiss.tooling.kernel.model.IProjectRootElement;
 import org.fortiss.tooling.kernel.service.IKernelIntrospectionSystemService;
 import org.fortiss.tooling.kernel.service.IMigrationService;
+import org.fortiss.tooling.kernel.service.IPersistencyService;
 import org.fortiss.tooling.kernel.service.base.EObjectAwareServiceBase;
+import org.fortiss.tooling.kernel.service.listener.IPersistencyServiceListener;
 import org.fortiss.tooling.kernel.utils.EcoreUtils;
 
 /**
  * This class implements the {@link IMigrationService} interface.
  * 
  * @author blaschke
+ * @author groh
  */
 public class ModelQualityService extends EObjectAwareServiceBase<IMetricProvider<?>>
-		implements IIntrospectiveKernelService, IModelQualityService {
+		implements IIntrospectiveKernelService, IModelQualityService, IPersistencyServiceListener {
 
 	/** The singleton instance. */
 	private static final ModelQualityService INSTANCE = new ModelQualityService();
@@ -81,10 +84,43 @@ public class ModelQualityService extends EObjectAwareServiceBase<IMetricProvider
 	/** Static instance of the MetricDataManager */
 	public static MetricDataManager metricDataManagerInstance = new MetricDataManager();
 
+	/** Top-level elements queued to be processed by the metric collector. */
+	private final Queue<ITopLevelElement> queuedProcessableElements =
+			new ConcurrentLinkedQueue<ITopLevelElement>();
+
+	/** Stores the constraint checking job. */
+	private final Job metricCollectorJob = new Job("Collecting metrics ...") {
+
+		@Override
+		protected IStatus run(IProgressMonitor monitor) {
+
+			do {
+				if(monitor.isCanceled()) {
+					return Status.CANCEL_STATUS;
+				}
+
+				ITopLevelElement toBeProcessed = queuedProcessableElements.poll();
+				if(toBeProcessed == null) {
+					return Status.OK_STATUS;
+				}
+
+				performMetricCollection(toBeProcessed);
+			} while(true);
+		}
+	};
+
+	/** {@inheritDoc} */
+	@Override
+	public void scheduleMetricCollection(ITopLevelElement element) {
+		queuedProcessableElements.add(element);
+		metricCollectorJob.schedule();
+	}
+
 	/** {@inheritDoc} */
 	@Override
 	public void startService() {
 		IKernelIntrospectionSystemService.getInstance().registerService(this);
+		IPersistencyService.getInstance().addTopLevelElementListener(this);
 	}
 
 	/** Registers the migration provider with the service. */
@@ -108,9 +144,7 @@ public class ModelQualityService extends EObjectAwareServiceBase<IMetricProvider
 	 * @param topLvl
 	 *            the file project on which the analysis should be performed
 	 */
-	@Override
-	public void performMetricAnalysis(ITopLevelElement topLvl) {
-
+	private void performMetricCollection(ITopLevelElement topLvl) {
 		// Sort root elements, as some metrics are saved into trees from other root elements
 		// To avoid these problems, the elements have to be processed in a certain order
 
@@ -145,6 +179,29 @@ public class ModelQualityService extends EObjectAwareServiceBase<IMetricProvider
 				collectMetrics(rootNode, rootElement);
 			}
 
+			// Collect all violated constraints and group them by their respective source
+			List<IConstraintViolation<? extends EObject>> contraintViolations =
+					ConstraintCheckerService.getInstance()
+							.performAllConstraintChecksRecursively(rootElement);
+
+			for(Entry<? extends EObject, List<IConstraintViolation<? extends EObject>>> entry : contraintViolations
+					.stream().collect(Collectors.groupingBy(IConstraintViolation::getSource))
+					.entrySet()) {
+
+				MetricTreeNode node =
+						metricDataManagerInstance.getTreeNodeLookupTable().get(entry.getKey());
+
+				if(node != null) {
+					// Collect violations and store them in the error and warning metric
+					List<IConstraintViolation<? extends EObject>> violations = entry.getValue();
+					var integers = node.getStoredIntegers();
+					integers.put(MetricKey.CONSTRAINT_VIOLATIONS_ERROR, (int)violations.stream()
+							.filter(v -> v.getSeverity() == ESeverity.ERROR).count());
+					integers.put(MetricKey.CONSTRAINT_VIOLATIONS_WARNING, (int)violations.stream()
+							.filter(v -> v.getSeverity() == ESeverity.WARNING).count());
+				}
+			}
+
 			root_nodes.put(rootElement, rootNode);
 			metricDataManagerInstance.getTreeNodeLookupTable().put(rootElement, rootNode);
 		}
@@ -213,288 +270,10 @@ public class ModelQualityService extends EObjectAwareServiceBase<IMetricProvider
 
 			manager.getTreeNodeLookupTable().put(currentElement, node);
 
-			calculateBetweennessCentrality(currentElement, manager, false);
-			calculateBetweennessCentrality(currentElement, manager, true);
+			GraphMetricsProvider.calculateBetweennessCentrality(currentElement, manager, false);
+			GraphMetricsProvider.calculateBetweennessCentrality(currentElement, manager, true);
 			node.getStoredDoubles().put(MetricKey.CLUSTERING_COEFFICIENT,
-					calculateClusteringCoefficent(currentElement));
-		}
-	}
-
-	/**
-	 * Creates a list of nodes contained in the graph centered around the provided element
-	 * It will contain all elements contained within the provided element as well as all elements
-	 * connected to the provided element
-	 * 
-	 * @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,
-			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<>();
-		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())) {
-			for(IConnection exitConnection : exitConnector.getOutgoing()) {
-				graphNodes.add(exitConnection.getTarget().getOwner());
-			}
-		}
-		// Collect all elements to which currentElement has a incoming connection
-		for(EntryConnectorBase entryConnector : EcoreUtils.pickInstanceOf(EntryConnectorBase.class,
-				scopeElement.getConnectors())) {
-			for(IConnection entryConnection : entryConnector.getIncoming()) {
-				graphNodes.add(entryConnection.getSource().getOwner());
-			}
-		}
-		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
-	 * 
-	 * @param scopeElement
-	 *            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, boolean recursively) {
-
-		// Abort if no elements are found
-		if(!scopeElement.getContainedElements().isEmpty()) {
-
-			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) {
-				betweenness.put(elementNode, 0.0);
-			}
-
-			// Iterate over all collected nodes
-			for(IHierarchicElement elementNode : graphNodes) {
-
-				// Initialize all variables
-				Stack<IHierarchicElement> stack = new Stack<>();
-				Map<IHierarchicElement, List<IHierarchicElement>> pred = new HashMap<>();
-				Map<IHierarchicElement, Integer> dist = new HashMap<>();
-				Map<IHierarchicElement, Double> sigma = new HashMap<>();
-				Queue<IHierarchicElement> queue = new LinkedList<>();
-
-				for(IHierarchicElement v : graphNodes) {
-					pred.put(v, new ArrayList<>());
-					dist.put(v, -1);
-					sigma.put(v, 0.0);
-				}
-				sigma.put(elementNode, 1.0);
-				dist.put(elementNode, 0);
-				queue.add(elementNode);
-
-				while(!queue.isEmpty()) {
-					IHierarchicElement v = queue.remove();
-					stack.push(v);
-
-					// Iterate over all connectors going out from the element
-					for(ExitConnectorBase exitConnectors : EcoreUtils
-							.pickInstanceOf(ExitConnectorBase.class, v.getConnectors())) {
-						// Iterate over all connections
-						for(IConnection outgoingConnection : exitConnectors.getOutgoing()) {
-
-							Set<IHierarchicElement> targetElements = new HashSet<>();
-							if(recursively) {
-								recursivlyFollowOutgoingConnection(outgoingConnection.getTarget(),
-										targetElements);
-							} else {
-								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
-								// nodes outside of our graph
-								if(graphNodes.contains(targetElement)) {
-									if(dist.get(targetElement) < 0) {
-										queue.add(targetElement);
-										dist.put(targetElement, dist.get(v) + 1);
-									}
-									if(dist.get(targetElement) == dist.get(v) + 1) {
-										sigma.put(targetElement,
-												sigma.get(targetElement) + sigma.get(v));
-										pred.get(targetElement).add(v);
-									}
-								}
-							}
-						}
-					}
-				}
-				// Initialize delta
-				Map<IHierarchicElement, Double> delta = new HashMap<>();
-				for(IHierarchicElement v : graphNodes) {
-					delta.put(v, 0.0);
-				}
-
-				while(!stack.isEmpty()) {
-					IHierarchicElement w = stack.pop();
-					for(IHierarchicElement v : pred.get(w)) {
-						double c = (sigma.get(v) / sigma.get(w)) * (1.0 + delta.get(w));
-						delta.put(v, delta.get(v) + c);
-					}
-					if(!w.equals(elementNode)) {
-						betweenness.put(w, betweenness.get(w) + delta.get(w));
-					}
-				}
-			}
-			// Save metric
-			MetricKey key = recursively ? MetricKey.BETWEENESS_CENTRALITY_RECURSIVELY
-					: MetricKey.BETWEENESS_CENTRALITY;
-			for(IHierarchicElement child : scopeElement.getContainedElements()) {
-				manager.getTreeNodeLookupTable().get(child).getStoredDoubles().put(key,
-						betweenness.get(child));
-			}
+					GraphMetricsProvider.calculateClusteringCoefficent(currentElement));
 		}
 	}
 
@@ -571,4 +350,28 @@ public class ModelQualityService extends EObjectAwareServiceBase<IMetricProvider
 	public IIntrospectionDetailsItem getDetailsItem() {
 		return null;
 	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void topLevelElementLoaded(ITopLevelElement element) {
+		// Nothing done
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void topLevelElementAdded(ITopLevelElement element) {
+		// Nothing done
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void topLevelElementRemoved(ITopLevelElement element) {
+		// Nothing done
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void topLevelElementContentChanged(ITopLevelElement element) {
+		scheduleMetricCollection(element);
+	}
 }
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/storage/CSVFileWriter.java
similarity index 93%
rename from org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/io/CSVFileWriter.java
rename to org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/storage/CSVFileWriter.java
index 2598767c1..c7ef4a391 100644
--- 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/storage/CSVFileWriter.java
@@ -13,16 +13,16 @@
 | See the License for the specific language governing permissions and      |
 | limitations under the License.                                           |
 +--------------------------------------------------------------------------*/
-package org.fortiss.tooling.ext.quality.io;
+package org.fortiss.tooling.ext.quality.storage;
 
 import java.io.BufferedReader;
 import java.io.BufferedWriter;
+import java.io.File;
 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;
@@ -56,8 +56,15 @@ public class CSVFileWriter {
 			return;
 		}
 
+		if(!ModelQualityStorageManager.MODEL_QUALITY_PROJECT_DIR.exists()) {
+
+			ModelQualityStorageManager.MODEL_QUALITY_PROJECT_DIR.mkdirs();
+		}
+
 		// path to csv file
-		Path path = Paths.get("data_mega_cool.csv");
+		Path path =
+				new File(ModelQualityStorageManager.MODEL_QUALITY_PROJECT_DIR, "metric_logfile.csv")
+						.toPath();
 
 		List<String> allKeys = new ArrayList<>();
 		boolean createNewIndex = true;
diff --git a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/storage/ModelQualityStorageManager.java b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/storage/ModelQualityStorageManager.java
new file mode 100644
index 000000000..df5eddf73
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/storage/ModelQualityStorageManager.java
@@ -0,0 +1,43 @@
+/*-------------------------------------------------------------------------+
+| Copyright 2021 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.storage;
+
+import static org.eclipse.core.resources.ResourcesPlugin.getWorkspace;
+
+import java.io.File;
+
+import org.eclipse.core.resources.IWorkspaceRoot;
+
+/**
+ * Manages the local log of the model quality service.
+ *
+ * @author groh
+ */
+public class ModelQualityStorageManager {
+
+	/** The name of the general reuse (library) directory. */
+	public static final String AF3_QUALITY_DIRECTORY_NAME = "AF3-Model-Quality-Directory";
+
+	/** AF3 reuse library files extension */
+	public static final String AF3_MODEL_QUALITY_FILES_EXTENSION = "af3_mq";
+
+	/** The current workspace root. */
+	public static final IWorkspaceRoot WORKSPACE_ROOT = getWorkspace().getRoot();
+
+	/** The File handler of the reuse project. */
+	public static final File MODEL_QUALITY_PROJECT_DIR =
+			new File(WORKSPACE_ROOT.getLocation() + File.separator + AF3_QUALITY_DIRECTORY_NAME);
+}
-- 
GitLab