diff --git a/.gitignore b/.gitignore
index 763bc5ded238b70af005ecfed5fca311895d1e0a..8a621c8e4016a7303f39ad048b788fd50d0ba0e5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,4 +5,4 @@
 /target/
 *.rej
 .DS_Store
-._.DS_Store
+._.DS_Store
diff --git a/.gitmodules b/.gitmodules
index 6eabd0adfc781b97a5110fc9b00a4c8783258bda..ac46925eb1987978008571062fda3d7957c1b5c8 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -34,6 +34,12 @@
 [submodule "org.fortiss.variability/.settings"]
 	path = org.fortiss.variability/.settings
 	url = https://git.fortiss.org/af3/plugin-settings.git
+[submodule "org.fortiss.tooling.ext.quality/.settings"]
+	path = org.fortiss.tooling.ext.quality/.settings
+	url = https://git.fortiss.org/af3/plugin-settings.git
+[submodule "org.fortiss.tooling.ext.quality.ui/.settings"]
+	path = org.fortiss.tooling.ext.quality.ui/.settings
+        url = https://git.fortiss.org/af3/plugin-settings.git
 [submodule "org.fortiss.tooling.ext.reuse/.settings"]
 	path = org.fortiss.tooling.ext.reuse/.settings
 	url = https://git.fortiss.org/af3/plugin-settings.git
diff --git a/build.properties b/build.properties
new file mode 100644
index 0000000000000000000000000000000000000000..34d2e4d2dad529ceaeb953bfcdb63c51d69ffed2
--- /dev/null
+++ b/build.properties
@@ -0,0 +1,4 @@
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .
diff --git a/org.fortiss.tooling.ext.quality.ui/.classpath b/org.fortiss.tooling.ext.quality.ui/.classpath
new file mode 100644
index 0000000000000000000000000000000000000000..13ff42602618641083d7f5f8bfc6696892e9b0a8
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality.ui/.classpath
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
+	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="src" path="res"/>
+	<classpathentry kind="output" path="build"/>
+</classpath>
diff --git a/org.fortiss.tooling.ext.quality.ui/.project b/org.fortiss.tooling.ext.quality.ui/.project
new file mode 100644
index 0000000000000000000000000000000000000000..5ab5ccca9fd03df8e8b290633a0a1c5de0e5cd5b
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality.ui/.project
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.fortiss.tooling.ext.quality.ui</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.systemfocus.tooling.emfgeneration.git.EcoreBuilderGIT</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.ManifestBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.SchemaBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.systemfocus.tooling.codereview.builder.CodeReviewBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.systemfocus.tooling.codereview.builder.RemoveWarningsBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.systemfocus.tooling.codereview.builder.GuidelinesCheckBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.pde.PluginNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>org.eclipse.systemfocus.tooling.codereview.nature.CodeReviewNature</nature>
+	</natures>
+</projectDescription>
diff --git a/org.fortiss.tooling.ext.quality.ui/.settings b/org.fortiss.tooling.ext.quality.ui/.settings
new file mode 160000
index 0000000000000000000000000000000000000000..310d1c04f28f6252d5a02dd8fde1b76ae4a4da51
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality.ui/.settings
@@ -0,0 +1 @@
+Subproject commit 310d1c04f28f6252d5a02dd8fde1b76ae4a4da51
diff --git a/org.fortiss.tooling.ext.quality.ui/META-INF/MANIFEST.MF b/org.fortiss.tooling.ext.quality.ui/META-INF/MANIFEST.MF
new file mode 100644
index 0000000000000000000000000000000000000000..b0798135a5797ff8ce43acf2acd5c7c124499887
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality.ui/META-INF/MANIFEST.MF
@@ -0,0 +1,14 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Model Quality UI
+Bundle-SymbolicName: org.fortiss.tooling.ext.quality.ui;singleton:=true
+Bundle-Version: 2.23.0.qualifier
+Automatic-Module-Name: org.fortiss.tooling.ext.quality.ui
+Bundle-RequiredExecutionEnvironment: JavaSE-11
+Require-Bundle: org.fortiss.tooling.base.ui;visibility:=reexport,
+ org.fortiss.tooling.ext.quality;visibility:=reexport,
+ org.fortiss.tooling.spiderchart.ui;visibility:=reexport
+Export-Package: org.fortiss.tooling.ext.quality.ui.view.fx
+Bundle-Activator: org.fortiss.tooling.ext.quality.ui.ModelQualityUIActivator
+Bundle-ActivationPolicy: lazy
+Bundle-Vendor: fortiss GmbH
diff --git a/org.fortiss.tooling.ext.quality.ui/build.properties b/org.fortiss.tooling.ext.quality.ui/build.properties
new file mode 100644
index 0000000000000000000000000000000000000000..89e6a0c49c96d230bdef3865573cec7fe09f3624
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality.ui/build.properties
@@ -0,0 +1,9 @@
+# (c) 2023 fortiss GmbH
+bin.includes = .,\
+               META-INF/,\
+               plugin.xml,\
+               icons/
+source.. = src/,\
+           res/
+output.. = build/
+src.includes = icons/
diff --git a/org.fortiss.tooling.ext.quality.ui/html/developer/.ratings b/org.fortiss.tooling.ext.quality.ui/html/developer/.ratings
new file mode 100644
index 0000000000000000000000000000000000000000..1c6ee9d1b91ce3a08078c3c27854d70ba3135f75
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality.ui/html/developer/.ratings
@@ -0,0 +1 @@
+documentation.html aaa534ab30ef337c5e7bc75d37490eb6a5a4edd1 GREEN
diff --git a/org.fortiss.tooling.ext.quality.ui/html/developer/documentation.html b/org.fortiss.tooling.ext.quality.ui/html/developer/documentation.html
new file mode 100644
index 0000000000000000000000000000000000000000..aaa534ab30ef337c5e7bc75d37490eb6a5a4edd1
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality.ui/html/developer/documentation.html
@@ -0,0 +1,13 @@
+<html><body>
+<h1>Developer Documentation for <i>AF3 Model Quality</i> (org.fortiss.tooling.ext.quality.ui)</h1>
+
+<h2>Plugin description</h2>
+<p>This is the user interface part of the tooling.ext.quality plugin. It provides a simple view to display collected metrics</p>
+
+<h2>Package description</h2>
+<ul>
+<li><tt>quality.ui</tt>: main package with the UI plugin activator.</li>
+<li><tt>quality.ui.view.fx</tt>: implementation of quality library view (FX-based).</li>
+</ul>
+
+</body></html>
diff --git a/org.fortiss.tooling.ext.quality.ui/icons/metric.png b/org.fortiss.tooling.ext.quality.ui/icons/metric.png
new file mode 100644
index 0000000000000000000000000000000000000000..0822af9ca579b86080348b6520d12b0f93dab96c
Binary files /dev/null and b/org.fortiss.tooling.ext.quality.ui/icons/metric.png differ
diff --git a/org.fortiss.tooling.ext.quality.ui/plugin.properties b/org.fortiss.tooling.ext.quality.ui/plugin.properties
new file mode 100644
index 0000000000000000000000000000000000000000..63ff52c8a1cc1c3322ec47fd7707502614cfc530
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality.ui/plugin.properties
@@ -0,0 +1,4 @@
+<!-- (c) 2023 fortiss GmbH -->
+
+pluginName = Model Quality UI
+providerName = fortiss GmbH
diff --git a/org.fortiss.tooling.ext.quality.ui/plugin.xml b/org.fortiss.tooling.ext.quality.ui/plugin.xml
new file mode 100644
index 0000000000000000000000000000000000000000..25a97df238a3d86523d74dabc4fe8c3127c2da83
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality.ui/plugin.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse version="3.0"?>
+<!-- (c) 2021 fortiss GmbH -->
+
+<plugin>
+ <extension point="org.fortiss.tooling.kernel.ui.contextMenuContribution">
+      <contextMenuContribution
+            contributor="org.fortiss.tooling.ext.quality.ui.ModelQualityExtractionMenu">
+      </contextMenuContribution>
+   </extension>
+   <extension point="org.eclipse.ui.views">
+      <view
+            class="org.fortiss.tooling.ext.quality.ui.view.fx.ModelQualityFXViewPart"
+            icon="icons/metric.png"
+            id="org.fortiss.tooling.ext.quality.ui.metricsView"
+            name="Metrics"
+            restorable="true"
+            allowMultiple="false">
+      </view>
+   </extension>
+</plugin>
\ No newline at end of file
diff --git a/org.fortiss.tooling.ext.quality.ui/res/org/fortiss/tooling/ext/quality/ui/view/fx/MetricsViewFx.fxml b/org.fortiss.tooling.ext.quality.ui/res/org/fortiss/tooling/ext/quality/ui/view/fx/MetricsViewFx.fxml
new file mode 100644
index 0000000000000000000000000000000000000000..ebb44aa74b0b18837f664176ca3b4774c9ecc180
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality.ui/res/org/fortiss/tooling/ext/quality/ui/view/fx/MetricsViewFx.fxml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<?import javafx.scene.chart.BarChart?>
+<?import javafx.scene.chart.CategoryAxis?>
+<?import javafx.scene.chart.NumberAxis?>
+<?import javafx.scene.chart.PieChart?>
+<?import javafx.scene.chart.ScatterChart?>
+<?import javafx.scene.control.ChoiceBox?>
+<?import javafx.scene.control.SplitPane?>
+<?import javafx.scene.control.Tab?>
+<?import javafx.scene.control.TabPane?>
+<?import javafx.scene.layout.AnchorPane?>
+<?import javafx.scene.layout.BorderPane?>
+<?import javafx.scene.layout.HBox?>
+<?import javafx.scene.text.Text?>
+
+<SplitPane fx:id="metricsSplitPane" dividerPositions="0.5" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="-Infinity" orientation="VERTICAL" prefHeight="400.0" prefWidth="1035.0" xmlns="http://javafx.com/javafx/13.0.2" xmlns:fx="http://javafx.com/fxml/1">
+	<items>
+      <TabPane prefHeight="200.0" prefWidth="200.0" tabClosingPolicy="UNAVAILABLE">
+        <tabs>
+          <Tab text="Pie and Spider">
+            <content>
+              <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
+                     <children>
+                        <SplitPane dividerPositions="0.5" layoutX="56.0" layoutY="-19.0" prefHeight="134.0" prefWidth="1033.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
+                           <items>
+                              <BorderPane prefHeight="200.0" prefWidth="200.0">
+                                 <center>
+                                    <PieChart fx:id="pieChart" prefHeight="184.0" prefWidth="1033.0" BorderPane.alignment="CENTER" />
+                                 </center>
+                                 <top>
+                                    <ChoiceBox fx:id="childMetricChoiceBox" onAction="#onChildMetricChoiceBoxChange" prefHeight="24.0" prefWidth="270.0" BorderPane.alignment="CENTER" />
+                                 </top>
+                              </BorderPane>
+                              <BorderPane fx:id="borderPane" prefHeight="158.0" prefWidth="81.0" />
+                           </items>
+                        </SplitPane>
+                     </children>
+                  </AnchorPane>
+            </content>
+          </Tab>
+          <Tab text="Scatter and Bar">
+            <content>
+              <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
+                     <children>
+                        <SplitPane dividerPositions="0.5" prefHeight="336.0" prefWidth="1033.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
+                           <items>
+                              <BorderPane prefHeight="200.0" prefWidth="200.0">
+                                 <center>
+                                    <ScatterChart fx:id="scatterChart" prefHeight="322.0" prefWidth="540.0" BorderPane.alignment="CENTER">
+                                      <xAxis>
+                                        <NumberAxis side="BOTTOM" />
+                                      </xAxis>
+                                      <yAxis>
+                                        <NumberAxis side="LEFT" />
+                                      </yAxis>
+                                    </ScatterChart>
+                                 </center>
+                                 <top>
+                                    <HBox BorderPane.alignment="CENTER">
+                                       <children>
+                                          <ChoiceBox fx:id="yScatterMetricChoiceBox" onAction="#onScatterMetricChoiceBoxChange" prefHeight="24.0" prefWidth="270.0" />
+                                          <ChoiceBox fx:id="xScatterMetricChoiceBox" onAction="#onScatterMetricChoiceBoxChange" prefHeight="24.0" prefWidth="270.0" />
+                                       </children>
+                                    </HBox>
+                                 </top>
+                              </BorderPane>
+                              <BarChart fx:id="barChart">
+                                <xAxis>
+                                  <CategoryAxis side="BOTTOM" />
+                                </xAxis>
+                                <yAxis>
+                                  <NumberAxis side="LEFT" />
+                                </yAxis>
+                              </BarChart>
+                           </items>
+                        </SplitPane>
+                     </children>
+                  </AnchorPane>
+            </content>
+          </Tab>
+        </tabs>
+      </TabPane>
+      <Text fx:id="bottomText" strokeType="OUTSIDE" strokeWidth="0.0" text="Save model to refresh" />
+	</items>
+</SplitPane>
diff --git a/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/.ratings b/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/.ratings
new file mode 100644
index 0000000000000000000000000000000000000000..cbf0a8d86d8afb41340358f374a3dd30fb080d1e
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/.ratings
@@ -0,0 +1,2 @@
+ModelQualityExtractionMenu.java d9282c7df44aa9461ec662beb557e74589f6b05f GREEN
+ModelQualityUIActivator.java 2f47db82bc088072dbfe53f82d3ed417d77fd23c GREEN
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
new file mode 100644
index 0000000000000000000000000000000000000000..d9282c7df44aa9461ec662beb557e74589f6b05f
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/ModelQualityExtractionMenu.java
@@ -0,0 +1,91 @@
+/*-------------------------------------------------------------------------+
+| 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.ui;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static org.fortiss.tooling.kernel.ui.service.IContextMenuService.BOTTOM_MOST_MENU_SECTION_ID;
+
+import java.util.List;
+
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.ActionContributionItem;
+import org.eclipse.jface.action.IContributionItem;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.fortiss.tooling.ext.quality.service.IModelQualityService;
+import org.fortiss.tooling.kernel.extension.data.ITopLevelElement;
+import org.fortiss.tooling.kernel.service.IPersistencyService;
+import org.fortiss.tooling.kernel.ui.extension.IContextMenuContributor;
+import org.fortiss.tooling.kernel.ui.extension.data.ContextMenuContextProvider;
+
+/**
+ * Creates a context menu entry to extract metrics for the selected model.
+ * 
+ * @author groh
+ */
+public class ModelQualityExtractionMenu implements IContextMenuContributor {
+
+	/** String appearing in the context menu. */
+	private static final String MENU_NAME = "Extract metrics";
+
+	/** {@inheritDoc} */
+	@Override
+	public List<IContributionItem> getContributedItems(EObject selection,
+			ContextMenuContextProvider contextProvider) {
+
+		if(!(selection.eContainer() != null)) {
+			return emptyList();
+		}
+
+		// A null eContainer indicates the model root
+		return asList(new IContributionItem[] {
+				new ActionContributionItem(new MetricExtractionAction(selection))});
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public String getMenuSectionID() {
+		return BOTTOM_MOST_MENU_SECTION_ID;
+	}
+
+	/** Returns the icon that is visible in the context menu for this entry. */
+	protected ImageDescriptor getActionIcon() {
+		return ModelQualityUIActivator.getImageDescriptor("icons/metric.png");
+	}
+
+	/** Action for generating the set of . */
+	protected class MetricExtractionAction extends Action {
+
+		/** Model on which the action will be triggered. */
+		private final EObject model;
+
+		/** Constructor. */
+		public MetricExtractionAction(EObject model) {
+			super(MENU_NAME, getActionIcon());
+			this.model = model;
+		}
+
+		/** {@inheritDoc} */
+		@Override
+		public void run() {
+			ITopLevelElement top =
+					IPersistencyService.getInstance().getTopLevelElementFor(this.model);
+			IModelQualityService.getInstance().scheduleMetricCollection(top);
+		}
+	}
+}
diff --git a/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/ModelQualityUIActivator.java b/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/ModelQualityUIActivator.java
new file mode 100644
index 0000000000000000000000000000000000000000..2f47db82bc088072dbfe53f82d3ed417d77fd23c
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/ModelQualityUIActivator.java
@@ -0,0 +1,75 @@
+/*-------------------------------------------------------------------------+
+| 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.ui;
+
+import static org.eclipse.jface.resource.ResourceLocator.imageDescriptorFromBundle;
+
+import org.eclipse.core.runtime.Plugin;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.fortiss.tooling.ext.quality.service.IModelQualityService;
+import org.fortiss.tooling.ext.quality.ui.view.fx.ModelQualityFXViewPart;
+import org.osgi.framework.BundleContext;
+
+/**
+ * The activator class controls the plug-in life cycle.
+ * 
+ * @author blaschke
+ */
+public class ModelQualityUIActivator extends Plugin {
+
+	/** The plug-in ID. */
+	public static final String PLUGIN_ID = ModelQualityUIActivator.class.getPackage().getName(); // $NON-NLS-1$
+
+	/** The shared instance. */
+	private static ModelQualityUIActivator plugin;
+
+	/** {@inheritDoc} */
+	@Override
+	public void start(BundleContext context) throws Exception {
+		super.start(context);
+		plugin = this;
+		IModelQualityService.getInstance()
+				.registerMetricUpdateListener(ModelQualityFXViewPart.getMetricsFXController());
+		System.out.println("[Plugin] " + PLUGIN_ID + " started.");
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void stop(BundleContext context) throws Exception {
+		plugin = null;
+		super.stop(context);
+	}
+
+	/**
+	 * Returns the shared instance.
+	 *
+	 * @return The shared instance of {@link ModelQualityUIActivator}
+	 */
+	public static ModelQualityUIActivator getDefault() {
+		return plugin;
+	}
+
+	/**
+	 * Returns the image descriptor for the given icon file.
+	 *
+	 * @param path
+	 *            The path to the icon file of the image descriptor
+	 * @return The image descriptor
+	 */
+	public static ImageDescriptor getImageDescriptor(String path) {
+		return imageDescriptorFromBundle(PLUGIN_ID, path).orElse(null);
+	}
+}
diff --git a/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/view/fx/.ratings b/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/view/fx/.ratings
new file mode 100644
index 0000000000000000000000000000000000000000..988b0e6731a953fd80ba5a76c6065f0e87018e42
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/view/fx/.ratings
@@ -0,0 +1,3 @@
+IModelQualityViewPart.java 708f8089645df12098ea67190805cce343045d2e GREEN
+ModelQualityFXController.java c5577d2762eb916835386de5987109fc7899f1bc GREEN
+ModelQualityFXViewPart.java 9cfc7f60a86ea5b915c726b16712f8be7dec2c5f GREEN
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
new file mode 100644
index 0000000000000000000000000000000000000000..708f8089645df12098ea67190805cce343045d2e
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/view/fx/IModelQualityViewPart.java
@@ -0,0 +1,28 @@
+/*-------------------------------------------------------------------------+
+| 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.ui.view.fx;
+
+/**
+ * Interface for views displaying the Metrics view.
+ *
+ * @author groh
+ */
+public interface IModelQualityViewPart {
+
+	/** View ID for model quality views. */
+	// Keep in sync with plugin.xml
+	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
new file mode 100644
index 0000000000000000000000000000000000000000..c5577d2762eb916835386de5987109fc7899f1bc
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/view/fx/ModelQualityFXController.java
@@ -0,0 +1,458 @@
+/*-------------------------------------------------------------------------+
+| 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.ui.view.fx;
+
+import static java.util.stream.Collectors.toList;
+import static javafx.scene.paint.Color.BLUE;
+import static javafx.scene.paint.Color.DARKGRAY;
+import static javafx.scene.paint.Color.LIGHTGRAY;
+import static org.fortiss.tooling.common.ui.javafx.style.FontStyle.BLACK_VERDANA_10PT;
+import static org.fortiss.tooling.common.ui.javafx.style.FontStyle.BLACK_VERDANA_12PT;
+import static org.fortiss.tooling.common.ui.javafx.style.FontStyle.BLACK_VERDANA_14PT;
+import static org.fortiss.tooling.common.ui.javafx.style.FontStyle.BLACK_VERDANA_8PT;
+import static org.fortiss.tooling.common.ui.javafx.style.LineStyle.SOLID_BLACK_1PT;
+import static org.fortiss.tooling.kernel.ui.util.SelectionUtils.checkAndPickFirst;
+import static org.fortiss.tooling.kernel.utils.EcoreUtils.getFirstParentWithType;
+
+import java.text.DecimalFormat;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.ui.ISelectionListener;
+import org.eclipse.ui.IWorkbenchPart;
+import org.fortiss.tooling.common.ui.javafx.layout.CompositeFXControllerBase;
+import org.fortiss.tooling.common.ui.javafx.style.FillStyle;
+import org.fortiss.tooling.common.ui.javafx.style.FontStyle;
+import org.fortiss.tooling.common.ui.javafx.style.LineStyle;
+import org.fortiss.tooling.ext.quality.IMetricUpdateListener;
+import org.fortiss.tooling.ext.quality.data.MetricData;
+import org.fortiss.tooling.ext.quality.data.MetricKey;
+import org.fortiss.tooling.ext.quality.data.MetricTreeNode;
+import org.fortiss.tooling.ext.quality.service.IModelQualityService;
+import org.fortiss.tooling.kernel.model.IProjectRootElement;
+import org.fortiss.tooling.spiderchart.control.SpiderChartViewer;
+import org.fortiss.tooling.spiderchart.model.DataSeries;
+import org.fortiss.tooling.spiderchart.model.DoubleAxis;
+import org.fortiss.tooling.spiderchart.model.SpiderChart;
+import org.fortiss.tooling.spiderchart.style.AxisStyle;
+import org.fortiss.tooling.spiderchart.style.ChartStyle;
+import org.fortiss.tooling.spiderchart.style.DataSeriesStyle;
+import org.fortiss.tooling.spiderchart.style.LegendStyle;
+
+import javafx.application.Platform;
+import javafx.fxml.FXML;
+import javafx.scene.Node;
+import javafx.scene.chart.BarChart;
+import javafx.scene.chart.PieChart;
+import javafx.scene.chart.ScatterChart;
+import javafx.scene.chart.XYChart;
+import javafx.scene.control.ChoiceBox;
+import javafx.scene.control.Label;
+import javafx.scene.control.SplitPane;
+import javafx.scene.layout.BorderPane;
+import javafx.scene.paint.Color;
+import javafx.scene.text.Text;
+import javafx.util.Pair;
+
+/**
+ * FX Controller for the Metrics view.
+ *
+ * @author groh
+ */
+
+@SuppressWarnings("unchecked")
+public class ModelQualityFXController extends CompositeFXControllerBase<SplitPane, Node>
+		implements ISelectionListener, IMetricUpdateListener {
+
+	/**
+	 * Selection of colors for the overlays in the spider chart. The length of this array also
+	 * limits the depth of the {@link ModelQualityFXController#lastViewedElements} list.
+	 */
+	private static Color[] SPIDER_CHART_OVERLAY_COLORS = {Color.GREEN, Color.BLUE, Color.RED};
+
+	/** {@link ChoiceBox} for selecting the metric which shall be displayed. */
+	@FXML
+	private ChoiceBox<MetricKey> childMetricChoiceBox;
+
+	/** {@link PieChart} displaying the metrics. */
+	@FXML
+	private PieChart pieChart;
+
+	/** The bottom {@link BorderPane} for the spider chart. */
+	@FXML
+	private BorderPane borderPane;
+
+	/** {@link ChoiceBox} for selecting the metric displayed on the x axis of the scatter chart. */
+	@FXML
+	private ChoiceBox<MetricKey> xScatterMetricChoiceBox;
+
+	/** {@link ChoiceBox} for selecting the metric displayed on the y axis of the scatter chart. */
+	@FXML
+	private ChoiceBox<MetricKey> yScatterMetricChoiceBox;
+
+	/** A {@link ScatterChart} for displaying metrics. */
+	@FXML
+	private ScatterChart<Number, Number> scatterChart;
+
+	/** A {@link BarChart} for displaying metrics. */
+	@FXML
+	private BarChart<String, Number> barChart;
+
+	/** {@link Text} at the bottom of the view. */
+	@FXML
+	private Text bottomText;
+
+	/** Time format when displaying updating time. */
+	private final DateTimeFormatter timeFormat = DateTimeFormatter.ofPattern("HH:mm:ss");
+
+	/**
+	 * The last {@link EObject} which the user has clicked. Might be null when no or an invalid
+	 * object is selected.
+	 */
+	private EObject lastSelectedElement;
+
+	/** List of elements for which metrics were displayed. */
+	private List<Pair<MetricTreeNode, Set<MetricKey>>> lastViewedElements = new ArrayList<>();
+
+	/** {@inheritDoc} */
+	@Override
+	public String getFXMLLocation() {
+		return "MetricsViewFx.fxml";
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void initialize() {
+		// TODO (#4335)
+		childMetricChoiceBox.getItems().addAll(MetricKey.values());
+		xScatterMetricChoiceBox.getItems().addAll(MetricKey.values());
+		yScatterMetricChoiceBox.getItems().addAll(MetricKey.values());
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void selectionChanged(IWorkbenchPart part, ISelection selection) {
+
+		// Invalidate last selection, so if no new selection is found the charts are cleared
+		lastSelectedElement = null;
+
+		EObject selected = checkAndPickFirst(selection, EObject.class);
+
+		if(selected == null) {
+			updateCharts("Nothing selected.");
+			return;
+		}
+
+		IProjectRootElement currentRootElement =
+				selected instanceof IProjectRootElement ? (IProjectRootElement)selected
+						: getFirstParentWithType(selected, IProjectRootElement.class);
+
+		if(currentRootElement == null) {
+			updateCharts("Selection does not have a project root element.");
+			return;
+		}
+
+		MetricData metricData = IModelQualityService.getInstance().getMetricData();
+		var rootDataElement = metricData.getDataRootElementMap().get(currentRootElement);
+
+		if(rootDataElement == null) {
+			bottomText.setText(
+					"Save model or select \"Export Metrics\" in project options to generate metrics.");
+			updateCharts("No metrics available");
+			return;
+		}
+
+		bottomText.setText("Metrics last updated: " +
+				timeFormat.format(rootDataElement.getLastRefresh()) + ". Save model to refresh.");
+		lastSelectedElement = selected;
+		updateCharts("Selection does not have metric information.");
+	}
+
+	/** Triggered when the choice box changes. */
+	public void onChoiceBoxChange() {
+		updateCharts("Invalid selection or metrics not available.");
+	}
+
+	/** Triggered when the child metric choice box changes. */
+	public void onChildMetricChoiceBoxChange() {
+		updateCharts("Invalid selection or metrics not available.");
+	}
+
+	/** Triggered when one of the scatter choice boxes changes. */
+	public void onScatterMetricChoiceBoxChange() {
+		updateCharts("Invalid selection or metrics not available.");
+	}
+
+	/**
+	 * Updates the chart in the metric view.
+	 * 
+	 * @param message
+	 *            to display if metric is not available
+	 */
+	private void updateCharts(String message) {
+
+		if(lastSelectedElement != null) {
+			MetricData metricData = IModelQualityService.getInstance().getMetricData();
+			MetricTreeNode node = metricData.getTreeNodeLookupTable().get(lastSelectedElement);
+
+			if(node != null) {
+				if(!node.getChildren().isEmpty()) {
+
+					updatePieChart(node);
+					updateScatterChart(node);
+					updateBarChart(node);
+
+					updateLastViewedElements(node);
+					SpiderChartViewer viewer = buildSpiderChart();
+					borderPane.setCenter(viewer.getViewerPane());
+
+					// Return, otherwise the data will be cleared
+					return;
+				}
+				message = "Selection does not have contained elements.";
+			} else {
+				message = "Selection does not have metric information.";
+			}
+		} else {
+			// Nothing selected, so remove the bottom text
+			bottomText.setText("");
+		}
+		borderPane.setCenter(new Label(message));
+		pieChart.getData().clear();
+	}
+
+	/**
+	 * Updates the {@link #pieChart} with data from the provided {@link MetricTreeNode}.
+	 * 
+	 * @param node
+	 *            which provides the data for this chart
+	 */
+	private void updatePieChart(MetricTreeNode node) {
+		MetricKey selectedMetric = childMetricChoiceBox.getValue();
+		if(selectedMetric != null) {
+
+			// Create PieChart and add data
+			pieChart.getData().clear();
+			pieChart.setLegendVisible(false);
+			pieChart.setTitle("");
+			DecimalFormat format = new DecimalFormat("0.#");
+
+			for(var child : node.getChildren()) {
+				Double value = child.getValueAsDouble(selectedMetric);
+				if(value != null) {
+					PieChart.Data slice =
+							new PieChart.Data(child.getName() + ": " + format.format(value), value);
+					pieChart.getData().add(slice);
+				}
+			}
+		}
+	}
+
+	/**
+	 * Updates the {@link #scatterChart} with data from the provided {@link MetricTreeNode}.
+	 * 
+	 * @param node
+	 *            which provides the data for this chart
+	 */
+	private void updateScatterChart(MetricTreeNode node) {
+		MetricKey xScatterKey = xScatterMetricChoiceBox.getValue();
+		MetricKey yScatterKey = yScatterMetricChoiceBox.getValue();
+		if(xScatterKey != null && yScatterKey != null) {
+
+			scatterChart.getXAxis().setLabel(xScatterKey.toString());
+			scatterChart.getYAxis().setLabel(yScatterKey.toString());
+			scatterChart.getData().clear();
+
+			for(var child : node.getChildren()) {
+				Double xValue = child.getValueAsDouble(xScatterKey);
+				Double yValue = child.getValueAsDouble(yScatterKey);
+
+				if(xValue != null && yValue != null) {
+					// Add data to the chart
+					XYChart.Series<Number, Number> dataSeries = new XYChart.Series<>();
+					dataSeries.setName(child.getName());
+					dataSeries.getData().add(new XYChart.Data<>(xValue, yValue));
+					scatterChart.getData().add(dataSeries);
+				}
+			}
+		}
+	}
+
+	/**
+	 * Updates the {@link #barChart} with data from the provided {@link MetricTreeNode}.
+	 * 
+	 * @param node
+	 *            which provides the data for this chart
+	 */
+	private void updateBarChart(MetricTreeNode node) {
+		MetricKey yScatterKey = yScatterMetricChoiceBox.getValue();
+		if(yScatterKey != null) {
+
+			barChart.getYAxis().setLabel(yScatterKey.toString());
+			barChart.getData().clear();
+
+			for(var child : node.getChildren()) {
+				Double yValue = child.getValueAsDouble(yScatterKey);
+
+				if(yValue != null) {
+					// Add data to the chart
+					XYChart.Series<String, Number> dataSeries = new XYChart.Series<>();
+					dataSeries.setName(child.getName());
+					dataSeries.getData().add(new XYChart.Data<>(child.getName(), yValue));
+					barChart.getData().add(dataSeries);
+				}
+			}
+		}
+	}
+
+	/**
+	 * Creates a SpiderChart using the elements in
+	 * {@link ModelQualityFXController#lastViewedElements}. A set of keys which are present in all
+	 * elements in the list is created, and then these elements are overlaid.
+	 */
+	private SpiderChartViewer buildSpiderChart() {
+		// Calculate the intersection of the keys, i.e. a set of keys which have a non-null value in
+		// all elements of the lastViewedElements list
+
+		// lastViewedElements is never empty due to the fact, that updateLastViewedElements() is
+		// always executed before in updateCharts(), which ensure that there is at least one element
+		Set<MetricKey> keysIntersection = lastViewedElements.get(0).getValue();
+		for(var p : lastViewedElements) {
+			keysIntersection.retainAll(p.getValue());
+		}
+
+		// Create DataSeries for all elements
+		List<Pair<MetricTreeNode, DataSeries>> dataSeriesList =
+				lastViewedElements.stream().map(pair -> {
+					MetricTreeNode key = pair.getKey();
+					String name = key.getName();
+					return new Pair<>(key, (name == null) ? new DataSeries("No name found")
+							: new DataSeries(name));
+				}).collect(toList());
+
+		// Create SpiderChart and set basic styling
+		SpiderChart spiderChart = new SpiderChart();
+		spiderChart.setLegendLabel("Legend");
+		ChartStyle chartStyle = new ChartStyle(true, true, true);
+
+		chartStyle.setUseIndividualAxisSegments(false);
+		chartStyle.setTitleStyle(new FontStyle("Verdana", 14, BLUE.brighter()));
+
+		// Iterate over all keys, and add data points
+		for(MetricKey key : keysIntersection) {
+			// Calculate maximumValue for the range of the axis
+			Double maxmiumValue = lastViewedElements.stream()
+					.mapToDouble(p -> p.getKey().getValueAsDouble(key)).max().getAsDouble();
+
+			if(maxmiumValue == 0.0) {
+				// Don't display metrics where all values are zero
+				continue;
+			}
+
+			// Create Axis and apply Styling
+			DoubleAxis doubleAxis = new DoubleAxis(key.toString(), 0.0, maxmiumValue);
+			AxisStyle aStyle3Segs = new AxisStyle(SOLID_BLACK_1PT, BLACK_VERDANA_14PT, 3,
+					BLACK_VERDANA_8PT, new DecimalFormat("#.##"));
+			chartStyle.setAxisStyle(doubleAxis, aStyle3Segs);
+
+			spiderChart.addAxis(doubleAxis);
+			// Add the data point for this axis to all elements
+			for(var pair : dataSeriesList) {
+				pair.getValue().setPoint(doubleAxis, pair.getKey().getValueAsDouble(key));
+			}
+		}
+
+		// Iterate over all dataSeries and add them to the spider chart
+		int i = 0;
+		for(var pair : dataSeriesList) {
+			spiderChart.addData(pair.getValue());
+			// Assign different colors to different data series
+			// updateLastViewedElements() ensures that the are are at most three elements in
+			// updateLastViewedElements, from which dataSeriesList is derived above.
+			Color color = SPIDER_CHART_OVERLAY_COLORS[i];
+			LineStyle olive1pt = new LineStyle(color);
+			FillStyle oliveFill = new FillStyle(color, 0.25);
+			DataSeriesStyle style = new DataSeriesStyle(olive1pt, oliveFill, true, true,
+					BLACK_VERDANA_10PT, 7, new DecimalFormat("#.#"));
+			chartStyle.setDataSeriesStyle(pair.getValue(), style);
+			i++;
+		}
+
+		// Configure legend
+		LegendStyle legendStyle = new LegendStyle(false, 5, BLACK_VERDANA_12PT);
+		chartStyle.setLegendStyle(legendStyle);
+		chartStyle.setAxisSegments(4);
+		chartStyle.setBackgroundFillStyle(new FillStyle(LIGHTGRAY));
+		chartStyle.setBackgroundLineStyle(new LineStyle(DARKGRAY));
+
+		SpiderChartViewer viewer = new SpiderChartViewer(spiderChart, chartStyle);
+		return viewer;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void metricUpdate(IProjectRootElement updatedElement) {
+		Platform.runLater(() -> {
+			if(lastSelectedElement == null) {
+				// If nothing is selected, there is nothing to update
+				return;
+			}
+
+			MetricData metricData = IModelQualityService.getInstance().getMetricData();
+			var rootDataElement = metricData.getDataRootElementMap().get(updatedElement);
+			if(rootDataElement != null) {
+				bottomText.setText("Metrics last updated: " +
+						timeFormat.format(rootDataElement.getLastRefresh()) +
+						". Save model to refresh.");
+			} else {
+				bottomText.setText(
+						"Save model or select \"Export Metrics\" in project options to generate metrics.");
+			}
+			updateCharts("Selection does not have metric information.");
+		});
+	}
+
+	/**
+	 * Updates the {@link ModelQualityFXController#lastViewedElements} variable. It will add the
+	 * provided {@link MetricTreeNode} in the back of the list, and if the list is too long it will
+	 * remove the first element.
+	 * 
+	 * @param node
+	 *            which was selected
+	 */
+	private void updateLastViewedElements(MetricTreeNode node) {
+
+		// Create set of keys which store values in the node
+		Set<MetricKey> keys = new HashSet<>();
+		keys.addAll(node.getDoubleMetrics().keySet());
+		keys.addAll(node.getIntegerMetrics().keySet());
+
+		// Check if the node is already part of the list, and skip adding it if it is
+		if(lastViewedElements.stream().allMatch(p -> p.getKey() != node)) {
+			lastViewedElements.add(new Pair<>(node, keys));
+		}
+
+		// Limit the amount of elements in the list to the amount of colors
+		if(lastViewedElements.size() > SPIDER_CHART_OVERLAY_COLORS.length) {
+			lastViewedElements.remove(0);
+		}
+	}
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..9cfc7f60a86ea5b915c726b16712f8be7dec2c5f
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality.ui/src/org/fortiss/tooling/ext/quality/ui/view/fx/ModelQualityFXViewPart.java
@@ -0,0 +1,56 @@
+/*-------------------------------------------------------------------------+
+| 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.ui.view.fx;
+
+import org.eclipse.ui.part.ViewPart;
+import org.fortiss.tooling.common.ui.javafx.AF3FXViewPart;
+
+import javafx.scene.Scene;
+
+/**
+ * {@link ViewPart} for the FX implementation of the metrics view.
+ *
+ * @author groh
+ */
+public class ModelQualityFXViewPart extends AF3FXViewPart implements IModelQualityViewPart {
+
+	/** The FX Controller for this view. */
+	private static final ModelQualityFXController VIEW_CONTROLLER = new ModelQualityFXController();
+
+	/** Constructor. */
+	public ModelQualityFXViewPart() throws Exception {
+		super(VIEW_CONTROLLER, null);
+	}
+
+	/**
+	 * Returns the initialized {@link ModelQualityFXController} for the quality
+	 * version of the {@link AF3FXViewPart}.
+	 *
+	 * @return The view controller for the metrics view
+	 */
+	public static ModelQualityFXController getMetricsFXController() {
+		return VIEW_CONTROLLER;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected Scene createFxScene() {
+		Scene scene = super.createFxScene();
+		getSite().getWorkbenchWindow().getSelectionService().addSelectionListener(VIEW_CONTROLLER);
+
+		return scene;
+	}
+}
diff --git a/org.fortiss.tooling.ext.quality/.classpath b/org.fortiss.tooling.ext.quality/.classpath
new file mode 100644
index 0000000000000000000000000000000000000000..1a2f6f5de91bf0eb28fe3bf922046cd5055e3627
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
+	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="output" path="build"/>
+</classpath>
diff --git a/org.fortiss.tooling.ext.quality/.project b/org.fortiss.tooling.ext.quality/.project
new file mode 100644
index 0000000000000000000000000000000000000000..fa567514166b5b95143adf3d2c9e60ef00c77caf
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality/.project
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.fortiss.tooling.ext.quality</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.systemfocus.tooling.emfgeneration.git.EcoreBuilderGIT</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.ManifestBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.SchemaBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.systemfocus.tooling.codereview.builder.CodeReviewBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.systemfocus.tooling.codereview.builder.RemoveWarningsBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.systemfocus.tooling.codereview.builder.GuidelinesCheckBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.pde.PluginNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>org.eclipse.systemfocus.tooling.codereview.nature.CodeReviewNature</nature>
+	</natures>
+</projectDescription>
diff --git a/org.fortiss.tooling.ext.quality/.settings b/org.fortiss.tooling.ext.quality/.settings
new file mode 160000
index 0000000000000000000000000000000000000000..310d1c04f28f6252d5a02dd8fde1b76ae4a4da51
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality/.settings
@@ -0,0 +1 @@
+Subproject commit 310d1c04f28f6252d5a02dd8fde1b76ae4a4da51
diff --git a/org.fortiss.tooling.ext.quality/META-INF/MANIFEST.MF b/org.fortiss.tooling.ext.quality/META-INF/MANIFEST.MF
new file mode 100644
index 0000000000000000000000000000000000000000..730b39560b4c2ad54dd97284b1747ed12c47830e
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality/META-INF/MANIFEST.MF
@@ -0,0 +1,18 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Model Quality
+Bundle-SymbolicName: org.fortiss.tooling.ext.quality;singleton:=true
+Bundle-Version: 2.23.0.qualifier
+Automatic-Module-Name: org.fortiss.tooling.ext.quality
+Bundle-RequiredExecutionEnvironment: JavaSE-11
+Bundle-Vendor: fortiss GmbH
+Bundle-Activator: org.fortiss.tooling.ext.quality.ModelQualityActivator
+Require-Bundle: org.eclipse.core.runtime,
+ org.eclipse.emf.ecore;visibility:=reexport,
+ org.fortiss.tooling.base;visibility:=reexport,
+ org.fortiss.tooling.kernel;visibility:=reexport
+Export-Package: org.fortiss.tooling.ext.quality,
+ org.fortiss.tooling.ext.quality.data,
+ org.fortiss.tooling.ext.quality.service
+Bundle-ActivationPolicy: lazy
+
diff --git a/org.fortiss.tooling.ext.quality/build.properties b/org.fortiss.tooling.ext.quality/build.properties
new file mode 100644
index 0000000000000000000000000000000000000000..d45ffd2c580f438c11f0287a88f92b4df1e0cac5
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality/build.properties
@@ -0,0 +1,8 @@
+# (c) 2023 fortiss GmbH
+
+bin.includes = .,\
+               META-INF/,\
+               plugin.properties,\
+               plugin.xml
+source.. = src/
+output.. = build/
diff --git a/org.fortiss.tooling.ext.quality/html/developer/.ratings b/org.fortiss.tooling.ext.quality/html/developer/.ratings
new file mode 100644
index 0000000000000000000000000000000000000000..424f339e671bc7ed76131d24d7a85be5620d3670
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality/html/developer/.ratings
@@ -0,0 +1 @@
+documentation.html b66c2bfb495047a8e4b680b02231ab2fdb34cec3 GREEN
diff --git a/org.fortiss.tooling.ext.quality/html/developer/documentation.html b/org.fortiss.tooling.ext.quality/html/developer/documentation.html
new file mode 100644
index 0000000000000000000000000000000000000000..b66c2bfb495047a8e4b680b02231ab2fdb34cec3
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality/html/developer/documentation.html
@@ -0,0 +1,161 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<html>
+<head>
+</head>
+
+<body lang="en-US" dir="ltr">
+
+<h1>Developer Documentation for <i>Quality Functionality
+(</i><i>org.fortiss.tooling.ext.quality</i><i>)</i></h1>
+
+<h2>Metrics List </h2>
+<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>
+
+<table border="1">
+    <thead>
+        <tr>
+            <th>Provider</th>
+            <th>Qualified Name</th>
+            <th>Metric</th>
+            <th>Type</th>
+            <th>Description</th>
+        </tr>
+    </thead>
+    <tbody>
+        <tr>
+            <td>HierarchicElementMetricProvider</td>
+            <td>org.fortiss.tooling.ext.quality.HierarchicElementMetricProvider</td>
+            <td>UNIQUE_ID</td>
+            <td>Integer</td>
+            <td>This is an unique identifier for each element as specified in org.fortiss.tooling.kernel.model.IIdLabeled.</td>
+        </tr>
+        <tr>
+            <td>HierarchicElementMetricProvider</td>
+            <td>org.fortiss.tooling.ext.quality.HierarchicElementMetricProvider</td>
+            <td>NUMBER_OF_CONNECTORS</td>
+            <td>Integer</td>
+            <td>Number of connectors in the respective element.</td>
+        </tr>
+        <tr>
+            <td>HierarchicElementMetricProvider</td>
+            <td>org.fortiss.tooling.ext.quality.HierarchicElementMetricProvider</td>
+            <td>NUMBER_OF_CONTAINED_ELEMENTS</td>
+            <td>Integer</td>
+            <td>Number of entries in the containedElements list.</td>
+        </tr>
+        <tr>
+            <td>HierarchicElementMetricProvider</td>
+            <td>org.fortiss.tooling.ext.quality.HierarchicElementMetricProvider</td>
+            <td>NUMBER_OF_CONNECTIONS</td>
+            <td>Integer</td>
+            <td>Number of connections in this element. List of aggregated connection model elements. Usually aggregates all connections of its direct sub-structure.</td>
+        </tr>
+        <tr>
+            <td>HierarchicElementMetricProvider</td>
+            <td>org.fortiss.tooling.ext.quality.HierarchicElementMetricProvider</td>
+            <td>NUMBER_OF_TOTAL_CONNECTORS</td>
+            <td>Integer</td>
+            <td>Total number of connectors in this element and all contained elements.</td>
+        </tr>
+        <tr>
+            <td>HierarchicElementMetricProvider</td>
+            <td>org.fortiss.tooling.ext.quality.HierarchicElementMetricProvider</td>
+            <td>NUMBER_OF_TOTAL_ENTRY_CONNECTORS</td>
+            <td>Integer</td>
+            <td>Sum of all connectors which implement org.fortiss.tooling.base.model.base.EntryConnectorBase.</td>
+        </tr>
+        <tr>
+            <td>HierarchicElementMetricProvider</td>
+            <td>org.fortiss.tooling.ext.quality.HierarchicElementMetricProvider</td>
+            <td>NUMBER_OF_TOTAL_EXIT_CONNECTORS</td>
+            <td>Integer</td>
+            <td>Sum of all connectors which implement org.fortiss.tooling.base.model.base.ExitConnectorBase. Usually, the sum over this and NUMBER_OF_TOTAL_ENTRY_CONNECTORS should equal NUMBER_OF_TOTAL_CONNECTORS.</td>
+        </tr>
+        <tr>
+            <td>HierarchicElementMetricProvider</td>
+            <td>org.fortiss.tooling.ext.quality.HierarchicElementMetricProvider</td>
+            <td>NUMBER_OF_TOTAL_ELEMENTS</td>
+            <td>Integer</td>
+            <td>Total number of elements contained in this element. Includes the element for which this metric is recorded.</td>
+        </tr>
+        <tr>
+            <td>HierarchicElementMetricProvider</td>
+            <td>org.fortiss.tooling.ext.quality.HierarchicElementMetricProvider</td>
+            <td>NUMBER_OF_TOTAL_COMMENTABLE_ELEMENTS</td>
+            <td>Integer</td>
+            <td>Total number of elements implementing org.fortiss.tooling.kernel.model.INamedCommentedElement in this element. Includes the element for which this metric is recorded. Never exceeds NUMBER_OF_TOTAL_ELEMENTS.</td>
+        </tr>
+        <tr>
+            <td>HierarchicElementMetricProvider</td>
+            <td>org.fortiss.tooling.ext.quality.HierarchicElementMetricProvider</td>
+            <td>NUMBER_OF_TOTAL_COMMENTED_ELEMENTS</td>
+            <td>Integer</td>
+            <td>Total number of elements which are commentable and do not have null or an empty string as a comment.</td>
+        </tr>
+        <tr>
+            <td>HierarchicElementMetricProvider</td>
+            <td>org.fortiss.tooling.ext.quality.HierarchicElementMetricProvider</td>
+            <td>NUMBER_OF_TOTAL_LEAF_ELEMENTS</td>
+            <td>Integer</td>
+            <td>Total number of elements contained in this element which do not contain elements. Includes self.</td>
+        </tr>
+        <tr>
+            <td>GraphMetricsProvider</td>
+            <td>org.fortiss.tooling.ext.quality.GraphMetricsProvider</td>
+            <td>BETWEENESS_CENTRALITY</td>
+            <td>Double</td>
+            <td>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 neighbors of these elements.</td>
+        </tr>
+        <tr>
+            <td>GraphMetricsProvider</td>
+            <td>org.fortiss.tooling.ext.quality.GraphMetricsProvider</td>
+            <td>BETWEENESS_CENTRALITY_RECURSIVELY</td>
+            <td>Double</td>
+            <td>Same as above, but all connections are resolved to leaf elements, i.e. elements which do not contain other elements.</td>
+        </tr>
+        <tr>
+            <td>GraphMetricsProvider</td>
+            <td>org.fortiss.tooling.ext.quality.GraphMetricsProvider</td>
+            <td>CLUSTERING_COEFFICIENT</td>
+            <td>Double</td>
+            <td>Clustering coefficient of this element. Ignores connection direction and resolves all neighbors to leaf elements.</td>
+        </tr>
+        <tr>
+            <td>ModelQualityService</td>
+            <td>org.fortiss.tooling.ext.quality.service.ModelQualityService</td>
+            <td>NESTING_LEVEL</td>
+            <td>Integer</td>
+            <td>The depth of this element. Defined by the number of elements which have to be traversed to reach this element from the root.</td>
+        </tr>
+        <tr>
+            <td>ModelQualityService</td>
+            <td>org.fortiss.tooling.ext.quality.service.ModelQualityService</td>
+            <td>CONSTRAINT_VIOLATIONS_ERROR</td>
+            <td>Integer</td>
+            <td>Number of constraint violations of the severity error which have this element as source.</td>
+        </tr>
+        <tr>
+            <td>ModelQualityService</td>
+            <td>org.fortiss.tooling.ext.quality.service.ModelQualityService</td>
+            <td>CONSTRAINT_VIOLATIONS_WARNING</td>
+            <td>Integer</td>
+            <td>Number of constraint violations of the severity warning which have this element as source.</td>
+        </tr>                   
+    </tbody>
+</table>
+
+<h2></h2>
+
+The included packages are:
+<ul>
+<li><i>org.fortiss.tooling.ext.quality</i>: containing the GraphMetrics and HierarchicElement providers. The other providers are therefore located on the
+necessary plugin level to support the extraction of meta information from other elements. All metric providers implement the IMetricProvider interface.</li>
+<li><i>org.fortiss.tooling.ext.quality.data</i>: contains the classes to handle the data structure and map of metrics. It manages the data during runtime
+ so the visualizations can receive the necessary data.</li>
+<li><i>org.fortiss.tooling.ext.quality.service</i>: The interface and implementation of the registered  service.</li>
+<li><i>org.fortiss.tooling.ext.quality.storage</i>: The classes here manage the csv extraction to archive the metrics.</li>
+</ul>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/org.fortiss.tooling.ext.quality/plugin.properties b/org.fortiss.tooling.ext.quality/plugin.properties
new file mode 100644
index 0000000000000000000000000000000000000000..c18cbbb30532298ac0bbdeb6f0fc43f0d544a09f
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality/plugin.properties
@@ -0,0 +1,4 @@
+<!-- (c) 2023 fortiss GmbH -->
+
+pluginName = Model Quality
+providerName = fortiss GmbH
diff --git a/org.fortiss.tooling.ext.quality/plugin.xml b/org.fortiss.tooling.ext.quality/plugin.xml
new file mode 100644
index 0000000000000000000000000000000000000000..5535690d3778e11156a22397241826e5d5766009
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality/plugin.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse version="3.4"?>
+<plugin>
+
+</plugin>
diff --git a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/.ratings b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/.ratings
new file mode 100644
index 0000000000000000000000000000000000000000..44fde8c402ed4300f3e45b7cf81b5dff1f3389ce
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/.ratings
@@ -0,0 +1,5 @@
+GraphMetricsProvider.java ce70c838b887df0bdf776f462b23cf594c700822 GREEN
+HierarchicElementMetricProvider.java 966b3aeef92475034fc35e68ba5344688988b88a GREEN
+IMetricProvider.java 99fc8993b0e65b2f8757978eeb0481d912f5608c GREEN
+IMetricUpdateListener.java c24dc7c0f282623bbf1eefac1fbbb6752c97ddf0 GREEN
+ModelQualityActivator.java 6d5f0794aa48670cf45fb405bd7641bce6c1fec4 GREEN
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 0000000000000000000000000000000000000000..ce70c838b887df0bdf776f462b23cf594c700822
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/GraphMetricsProvider.java
@@ -0,0 +1,371 @@
+/*-------------------------------------------------------------------------+
+| 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 static org.fortiss.tooling.kernel.utils.EcoreUtils.pickInstanceOf;
+
+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.MetricData;
+import org.fortiss.tooling.ext.quality.data.MetricKey;
+import org.fortiss.tooling.ext.quality.data.MetricTreeNode;
+
+/**
+ * Supplies methods to compute certain graph metrics.
+ * 
+ * @author groh
+ */
+
+// TODO (#4332) Restructure GraphMetricsProvider
+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) {
+		// 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 : 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 : 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 : pickInstanceOf(ExitConnectorBase.class,
+				element.getConnectors())) {
+			recursivlyFollowOutgoingConnection(exitConnectors, neighbors);
+		}
+		for(EntryConnectorBase entryConnectors : pickInstanceOf(EntryConnectorBase.class,
+				element.getConnectors())) {
+			recursivlyFollowIncomingConnection(entryConnectors, neighbors);
+		}
+		return neighbors;
+	}
+
+	/**
+	 * Calculates the clustering coefficient based on the neighborhood.
+	 * See, e.g., https://en.wikipedia.org/wiki/Clustering_coefficient
+	 * Clustering coefficient C for a node V is counting the triangles divided by the "number of
+	 * neighbors of "V" times "number of neighbors of V - 1" which results in the potential number
+	 * of maximum links between the nodes
+	 * 
+	 * @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).
+		}
+		// Iterate over neighbors and get their neighbors and check if they are also our neighbor in
+		// which case we have a triangle
+		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. See, e.g., https://en.wikipedia.org/wiki/Betweenness_centrality
+	 * 
+	 * @param scopeElement
+	 *            the scope of this calculation
+	 * @param metricData
+	 *            location to save the metrics
+	 * @param recursively
+	 *            if true, all calculation will be carried out on leaf elements
+	 *            if false, it is only computing the centrality with the children
+	 *            which means the inner circle.
+	 */
+	public static void calculateBetweennessCentrality(IHierarchicElement scopeElement,
+			MetricData metricData, boolean recursively) {
+
+		// Abort if no elements are found
+		if(scopeElement.getContainedElements().isEmpty()) {
+			return;
+		}
+
+		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 with standard values
+			Stack<IHierarchicElement> stack = new Stack<>();
+			Map<IHierarchicElement, List<IHierarchicElement>> predecessors = new HashMap<>();
+			Map<IHierarchicElement, Integer> distance = new HashMap<>();
+			Map<IHierarchicElement, Double> dependency = new HashMap<>();
+			Queue<IHierarchicElement> queue = new LinkedList<>();
+
+			for(IHierarchicElement v : graphNodes) {
+				predecessors.put(v, new ArrayList<>());
+				distance.put(v, -1);
+				dependency.put(v, 0.0);
+			}
+			dependency.put(elementNode, 1.0);
+			distance.put(elementNode, 0);
+			queue.add(elementNode);
+
+			processCentrality(queue, stack, predecessors, distance, dependency, graphNodes,
+					elementNode, recursively);
+
+			// 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 : predecessors.get(w)) {
+					double c = (dependency.get(v) / dependency.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
+		saveMetric(scopeElement, metricData, recursively, betweenness);
+	}
+
+	/** Saving the metrics for each contained element of an {@link IHierarchicElement}. */
+	private static void saveMetric(IHierarchicElement scopeElement, MetricData metricData,
+			boolean recursively, Map<IHierarchicElement, Double> betweenness) {
+		MetricKey key = recursively ? MetricKey.BETWEENESS_CENTRALITY_RECURSIVELY
+				: MetricKey.BETWEENESS_CENTRALITY;
+		for(IHierarchicElement child : scopeElement.getContainedElements()) {
+			MetricTreeNode node = metricData.getTreeNodeLookupTable().get(child);
+			node.getDoubleMetrics().put(key, betweenness.get(child));
+		}
+	}
+
+	/**
+	 * Performs intermediate calculations for the betweenness centrality algorithm.
+	 * 
+	 * @param queue
+	 *            of nodes for Breadth-First Search.
+	 * @param stack
+	 *            of nodes for Breadth-First Search.
+	 * @param predecessor
+	 *            maps to lists used to keep track of the predecessors of nodes in the shortest
+	 *            paths.
+	 * @param distance
+	 *            map used to store the shortest distances from the source node to other nodes.
+	 * @param dependency
+	 *            map used to calculate and store the dependency values for nodes in the network.
+	 *            These values are part of the calculation for betweenness centrality.
+	 * @param graphNodes
+	 *            list of nodes
+	 * @param scopeElement
+	 *            the scope of this calculation
+	 * @param recursively
+	 *            if true, all calculation will be carried out on leaf elements
+	 */
+	private static void processCentrality(Queue<IHierarchicElement> queue,
+			Stack<IHierarchicElement> stack,
+			Map<IHierarchicElement, List<IHierarchicElement>> predecessor,
+			Map<IHierarchicElement, Integer> distance, Map<IHierarchicElement, Double> dependency,
+			Set<IHierarchicElement> graphNodes, IHierarchicElement scopeElement,
+			boolean recursively) {
+
+		while(!queue.isEmpty()) {
+			IHierarchicElement v = queue.remove();
+			stack.push(v);
+
+			// Iterate over all connectors going out from the element
+			for(ExitConnectorBase exitConnectors : 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)) {
+							var targetDistance = distance.get(targetElement);
+							var vDistance = distance.get(v);
+							// first start of distance calculation from v to the target Element
+							if(targetDistance < 0) {
+								queue.add(targetElement);
+								distance.put(targetElement, vDistance + 1);
+							} else if(targetDistance == vDistance + 1) {
+								dependency.put(targetElement,
+										dependency.get(targetElement) + dependency.get(v));
+								predecessor.get(targetElement).add(v);
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+}
diff --git a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/HierarchicElementMetricProvider.java b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/HierarchicElementMetricProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..966b3aeef92475034fc35e68ba5344688988b88a
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/HierarchicElementMetricProvider.java
@@ -0,0 +1,91 @@
+/*-------------------------------------------------------------------------+
+| 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 static org.fortiss.tooling.kernel.utils.EcoreUtils.pickInstanceOf;
+
+import org.fortiss.tooling.base.model.base.EntryConnectorBase;
+import org.fortiss.tooling.base.model.base.ExitConnectorBase;
+import org.fortiss.tooling.base.model.element.IHierarchicElement;
+import org.fortiss.tooling.ext.quality.data.MetricKey;
+import org.fortiss.tooling.ext.quality.data.MetricTreeNode;
+import org.fortiss.tooling.kernel.model.IIdLabeled;
+import org.fortiss.tooling.kernel.model.INamedCommentedElement;
+
+/**
+ * {@link IMetricProvider} to collect various metrics from an {@link IHierarchicElement}.
+ * 
+ * @author groh
+ */
+public class HierarchicElementMetricProvider implements IMetricProvider<IHierarchicElement> {
+	/** {@inheritDoc} */
+	@Override
+	public void collectMetrics(MetricTreeNode node, IHierarchicElement currentElement) {
+
+		var intMetrics = node.getIntegerMetrics();
+		// Only collect these metrics if the name is null, in which case they have not been
+		// collected before
+		// This is used in some cases to combine metrics from two different IHierarchicElement
+		if(node.getName() == null) {
+
+			// Metrics are always set to default value, as these metric are combined later
+			if(currentElement instanceof INamedCommentedElement) {
+				intMetrics.put(MetricKey.NUMBER_OF_TOTAL_COMMENTABLE_ELEMENTS, 1);
+				INamedCommentedElement nce = (INamedCommentedElement)currentElement;
+				String comment = nce.getComment();
+				if(comment != null && comment != "") {
+					intMetrics.put(MetricKey.NUMBER_OF_TOTAL_COMMENTED_ELEMENTS, 1);
+				}
+				node.setName(nce.getName());
+			} else {
+				intMetrics.put(MetricKey.NUMBER_OF_TOTAL_COMMENTABLE_ELEMENTS, 0);
+				intMetrics.put(MetricKey.NUMBER_OF_TOTAL_COMMENTED_ELEMENTS, 0);
+				node.setName("No Name existant");
+			}
+
+			if(currentElement instanceof IIdLabeled) {
+				intMetrics.put(MetricKey.UNIQUE_ID, ((IIdLabeled)currentElement).getId());
+			} else {
+				intMetrics.put(MetricKey.UNIQUE_ID, -1);
+			}
+		}
+
+		// connector metrics
+		var connectors = currentElement.getConnectors();
+		intMetrics.put(MetricKey.NUMBER_OF_CONNECTORS, connectors.size());
+		intMetrics.put(MetricKey.NUMBER_OF_CONTAINED_ELEMENTS,
+				currentElement.getContainedElements().size());
+		intMetrics.put(MetricKey.NUMBER_OF_CONNECTIONS, currentElement.getConnections().size());
+
+		// depth metrics
+		intMetrics.put(MetricKey.NUMBER_OF_TOTAL_CONNECTORS, connectors.size());
+
+		var entry_connectors = pickInstanceOf(EntryConnectorBase.class, connectors);
+		var exit_connectors = pickInstanceOf(ExitConnectorBase.class, connectors);
+		intMetrics.put(MetricKey.NUMBER_OF_TOTAL_ENTRY_CONNECTORS, entry_connectors.size());
+		intMetrics.put(MetricKey.NUMBER_OF_TOTAL_EXIT_CONNECTORS, exit_connectors.size());
+
+		intMetrics.put(MetricKey.NUMBER_OF_TOTAL_ELEMENTS, 1);
+
+		if(currentElement.getContainedElements().isEmpty()) {
+			intMetrics.put(MetricKey.NUMBER_OF_TOTAL_LEAF_ELEMENTS, 0);
+		} else {
+			intMetrics.put(MetricKey.NUMBER_OF_TOTAL_LEAF_ELEMENTS, 1);
+		}
+
+		// graph metrics
+	}
+}
diff --git a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/IMetricProvider.java b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/IMetricProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..99fc8993b0e65b2f8757978eeb0481d912f5608c
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/IMetricProvider.java
@@ -0,0 +1,33 @@
+/*-------------------------------------------------------------------------+
+| 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 org.eclipse.emf.ecore.EObject;
+import org.fortiss.tooling.ext.quality.data.MetricTreeNode;
+import org.fortiss.tooling.ext.quality.service.IModelQualityService;
+import org.fortiss.tooling.kernel.service.base.IEObjectAware;
+
+/**
+ * Interface for all metric providers.
+ * They are called by {@link IModelQualityService} to collect metrics on model elements.
+ * 
+ * @author blaschke
+ */
+public interface IMetricProvider<C extends EObject> extends IEObjectAware<EObject> {
+
+	/** Applies the IMetricProvider to the given model element. */
+	void collectMetrics(MetricTreeNode node, C element);
+}
diff --git a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/IMetricUpdateListener.java b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/IMetricUpdateListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..c24dc7c0f282623bbf1eefac1fbbb6752c97ddf0
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/IMetricUpdateListener.java
@@ -0,0 +1,33 @@
+/*-------------------------------------------------------------------------+
+| 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 org.fortiss.tooling.kernel.model.IProjectRootElement;
+
+/**
+ * Listener getting called when metrics update
+ * 
+ * @author groh
+ */
+public interface IMetricUpdateListener {
+	/**
+	 * Callback for metric update. This method will get called every time the metrics are updated.
+	 * 
+	 * @param updatedElement
+	 *            the corresponding root element which was updated
+	 */
+	public void metricUpdate(IProjectRootElement updatedElement);
+}
diff --git a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/ModelQualityActivator.java b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/ModelQualityActivator.java
new file mode 100644
index 0000000000000000000000000000000000000000..6d5f0794aa48670cf45fb405bd7641bce6c1fec4
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/ModelQualityActivator.java
@@ -0,0 +1,59 @@
+package org.fortiss.tooling.ext.quality;
+
+/*-------------------------------------------------------------------------+
+| 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.                                           |
++--------------------------------------------------------------------------*/
+
+import org.eclipse.core.runtime.Plugin;
+import org.fortiss.tooling.base.model.element.IHierarchicElement;
+import org.fortiss.tooling.ext.quality.service.IModelQualityService;
+import org.osgi.framework.BundleContext;
+
+/**
+ * The activator class controls the plug-in life cycle.
+ * 
+ * @author blaschke
+ */
+public class ModelQualityActivator extends Plugin {
+	/** The plug-in ID. */
+	public static final String PLUGIN_ID = ModelQualityActivator.class.getPackage().getName(); // $NON-NLS-1$
+
+	/** The shared instance. */
+	private static ModelQualityActivator plugin;
+
+	/** {@inheritDoc} */
+	@Override
+	public void start(BundleContext context) throws Exception {
+		super.start(context);
+		plugin = this;
+		System.out.println("[Plugin] " + PLUGIN_ID + " started.");
+		IModelQualityService mqs = IModelQualityService.getInstance();
+		mqs.startService();
+		mqs.registerMetricProvider(new HierarchicElementMetricProvider(),
+				IHierarchicElement.class);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void stop(BundleContext context) throws Exception {
+		plugin = null;
+		super.stop(context);
+	}
+
+	/** Returns the shared instance. */
+	public static ModelQualityActivator getDefault() {
+		return plugin;
+	}
+}
diff --git a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/.ratings b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/.ratings
new file mode 100644
index 0000000000000000000000000000000000000000..a66b729711906e63b7594c05e1e5bca1c281ef70
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/.ratings
@@ -0,0 +1,4 @@
+DataRootElement.java 7b655236c155e3a4473301aa8c3ecfe769cf071c GREEN
+MetricData.java b3c1a395cca29ced5845b789d76ee2c965ec66f6 GREEN
+MetricKey.java d78026f0c68aeeff0cc758bbd76fbb84da65afd6 GREEN
+MetricTreeNode.java 88ddbdfc62fb8efee71aa3ee44257d1a1a582fe6 GREEN
diff --git a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/DataRootElement.java b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/DataRootElement.java
new file mode 100644
index 0000000000000000000000000000000000000000..7b655236c155e3a4473301aa8c3ecfe769cf071c
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/DataRootElement.java
@@ -0,0 +1,77 @@
+/*-------------------------------------------------------------------------+
+| 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.time.LocalDateTime;
+
+import org.fortiss.tooling.kernel.extension.data.ITopLevelElement;
+
+/**
+ * Storage element corresponding to a {@Link IProjectRootElement}. It stores the last refresh time
+ * as {@link LocalDateTime}, its respective {@link ITopLevelElement} and the {@link MetricTreeNode}
+ * storing the data.
+ * 
+ * @author groh
+ */
+public class DataRootElement {
+	/** The last time a metric tree was refreshed. */
+	private LocalDateTime lastRefresh;
+
+	/** The last time the metric tree was refreshed. */
+	private ITopLevelElement topLevelElement;
+
+	/** Root {@link MetricTreeNode} for the metric tree containing all data. */
+	private MetricTreeNode rootNode;
+
+	/** Constructor. */
+	public DataRootElement(LocalDateTime lastRefresh, ITopLevelElement topLevelElement,
+			MetricTreeNode rootNode) {
+		super();
+		this.lastRefresh = lastRefresh;
+		this.topLevelElement = topLevelElement;
+		this.rootNode = rootNode;
+	}
+
+	/** Getter for {@link #lastRefresh}. */
+	public LocalDateTime getLastRefresh() {
+		return lastRefresh;
+	}
+
+	/** Setter for {@link #lastRefresh}. */
+	public void setLastRefresh(LocalDateTime lastRefresh) {
+		this.lastRefresh = lastRefresh;
+	}
+
+	/** Getter for {@link #topLevelElement}. */
+	public ITopLevelElement getTopLevelElement() {
+		return topLevelElement;
+	}
+
+	/** Setter for {@link #topLevelElement}. */
+	public void setTopLevelElement(ITopLevelElement topLevelElement) {
+		this.topLevelElement = topLevelElement;
+	}
+
+	/** Getter for {@link #rootNode}. */
+	public MetricTreeNode getRootNode() {
+		return rootNode;
+	}
+
+	/** Setter for {@link #rootNode}. */
+	public void setRootNode(MetricTreeNode metricTreeNode) {
+		this.rootNode = metricTreeNode;
+	}
+}
diff --git a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/MetricData.java b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/MetricData.java
new file mode 100644
index 0000000000000000000000000000000000000000..b3c1a395cca29ced5845b789d76ee2c965ec66f6
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/MetricData.java
@@ -0,0 +1,52 @@
+/*-------------------------------------------------------------------------+
+| 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.eclipse.emf.ecore.EObject;
+import org.fortiss.tooling.kernel.model.IProjectRootElement;
+
+/**
+ * Class storing the metrics computed for the all suitable elements.
+ * 
+ * @author groh
+ */
+public class MetricData {
+
+	/** A map to lookup the corresponding tree node for any {@link EObject}. */
+	private Map<EObject, MetricTreeNode> treeNodeLookupTable;
+
+	/** A map to lookup the corresponding root tree node for a project. */
+	private Map<IProjectRootElement, DataRootElement> dataRootElementMap;
+
+	/** Constructor. */
+	public MetricData() {
+		dataRootElementMap = new HashMap<>();
+		treeNodeLookupTable = new HashMap<>();
+	}
+
+	/** Getter for {@link #treeNodeLookupTable}. */
+	public Map<EObject, MetricTreeNode> getTreeNodeLookupTable() {
+		return treeNodeLookupTable;
+	}
+
+	/** Getter for {@link #dataRootElementMap}. */
+	public Map<IProjectRootElement, DataRootElement> getDataRootElementMap() {
+		return dataRootElementMap;
+	}
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..d78026f0c68aeeff0cc758bbd76fbb84da65afd6
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/MetricKey.java
@@ -0,0 +1,183 @@
+/*-------------------------------------------------------------------------+
+| 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.ArrayList;
+import java.util.List;
+
+/**
+ * Enum class representing a key which can be used to store a respective metric.
+ * Metrics provided by the HierarchicElementProvider.
+ * 
+ * @author groh
+ */
+public enum MetricKey {
+
+	/** Unique ID of the element */
+	UNIQUE_ID(false),
+
+	/** Key for the metric counting ports. */
+	NUMBER_OF_CONNECTORS(false),
+	/** Key for the metric counting elements. */
+	NUMBER_OF_CONTAINED_ELEMENTS(false),
+	/** Key for the metric counting channels. */
+	NUMBER_OF_CONNECTIONS(false),
+	// TODO (#4333):
+	/** Safety level of the element. */
+	SAFETY_LEVEL(false, true),
+	/** How deeply nested the corresponding element is. */
+	NESTING_LEVEL(false),
+	/** The betweenness centrality of the corresponding element. */
+	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),
+
+	/** How many constraints this element violates with severity error. */
+	CONSTRAINT_VIOLATIONS_ERROR(false),
+	/** How many constraints this element violates with severity warning. */
+	CONSTRAINT_VIOLATIONS_WARNING(false),
+	// TODO(#4333) Remove knowledge about af3 from the kernel.
+	/** Number of allocated components to this task. */
+	TASK_ALLOCATED_COMPONENTS(false),
+	// TODO (#4333): Remove knowledge about af3 from the kernel.
+	/** How many constraints this element violates with severity warning. */
+	EXECUTION_UNIT_ALLOCATED_TASKS(false),
+
+	// Metrics which are collected over all children nodes
+	/** Key for the metric counting the total amount of ports contained. */
+	NUMBER_OF_TOTAL_CONNECTORS(true),
+	/** Key for the metric counting the total amount of input ports contained. */
+	NUMBER_OF_TOTAL_ENTRY_CONNECTORS(true),
+	/** Key for the metric counting the total amount of output ports contained. */
+	NUMBER_OF_TOTAL_EXIT_CONNECTORS(true),
+	/** Key for the metric counting the total amount of elements contained. */
+	NUMBER_OF_TOTAL_ELEMENTS(true),
+	/** Key for the metric counting the total amount of elements which can be commented. */
+	NUMBER_OF_TOTAL_COMMENTABLE_ELEMENTS(true),
+	/** Key for the metric counting the total amount of elements which are commented. */
+	NUMBER_OF_TOTAL_COMMENTED_ELEMENTS(true),
+	/**
+	 * Key for the metric counting the total amount of elements which do not contain other elements.
+	 */
+	NUMBER_OF_TOTAL_LEAF_ELEMENTS(true),
+	/**
+	 * Key for the metric counting of the total amount of constant values in all code
+	 * specifications.
+	 */
+	NUMBER_OF_TOTAL_CODE_CONSTANTS(true),
+	/** Cyclomatic complexity number as sum over all components. */
+	NUMBER_OF_TOTAL_CODE_CYCLOMATIC_COMPLEXITY(true),
+	/** Counts the Lines of Code (LoC) in the component and its sub-components. */
+	NUMBER_OF_TOTAL_CODE_LINES(true),
+	/** Representation of the total amount of comment blocks in all code specifications. */
+	NUMBER_OF_TOTAL_CODE_COMMENTS(true);
+
+	/** Metrics which are a combination from the data of multiple nodes */
+	private static List<MetricKey> collectorKeys = new ArrayList<>();
+
+	/** Getter for {@link #collectorKeys}. */
+	public static List<MetricKey> getCollectorKeys() {
+		if(collectorKeys == null) {
+			collectorKeys = new ArrayList<>();
+			for(MetricKey key : MetricKey.values()) {
+				if(key.isCollectorKey) {
+					collectorKeys.add(key);
+				}
+			}
+		}
+		return collectorKeys;
+	}
+
+	/** Used for populating the {@link MetricKey#getCollectorKeys()} method. */
+	private boolean isCollectorKey;
+
+	// TODO (4336): In MetricTreeNode, there are int, double, and String metrics. However, here are
+	// only doubles and strings. What about marking the type of a MetricKey with a
+	// Class<? extends Number> field? In the constructor, you can check that only the admissible
+	// types Integer, Double, String are passed.
+	/** 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;
+
+	/**
+	 * Constructor for regular integer metric keys.
+	 * 
+	 * @param isCollectorKey
+	 *            decides if this is a key returned by {@link MetricKey#getCollectorKeys()}
+	 */
+	private MetricKey(boolean isCollectorKey) {
+		this(isCollectorKey, false);
+	}
+
+	/**
+	 * Constructor for metric key storing a double.
+	 * 
+	 * @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;
+	}
+
+	/**
+	 * Constructor for metric key storing a string.
+	 * 
+	 * @param isCollectorKey
+	 *            decides if this is a key returned by {@link MetricKey#getCollectorKeys()}
+	 * @param isStringValue
+	 *            marks if the value associated with this key is a string
+	 */
+	private MetricKey(boolean isCollectorKey, boolean isStringValue) {
+		this.isCollectorKey = isCollectorKey;
+		this.isStringValue = isStringValue;
+		this.isDoubleValue = false;
+	}
+
+	/**
+	 * If this value is true, it should be expected that the value corresponding to this key should
+	 * be a string and be stored in an appropriate place.
+	 * 
+	 * @return if the value associated with this key is a string
+	 */
+	public boolean isStringValue() {
+		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;
+	}
+
+	/** Get all collector keys. */
+	static {
+		collectorKeys = getCollectorKeys();
+	}
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..88ddbdfc62fb8efee71aa3ee44257d1a1a582fe6
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/data/MetricTreeNode.java
@@ -0,0 +1,119 @@
+/*-------------------------------------------------------------------------+
+| 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.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+/**
+ * A general class for storing metric data in a hierarchical tree structure.
+ * <p>
+ * Allows for efficient storage and retrieval of data.
+ * 
+ * @author groh
+ */
+public class MetricTreeNode {
+
+	/** stores the children of this tree node */
+	private List<MetricTreeNode> children;
+
+	/** Map containing all metrics which are a Integer */
+	private Map<MetricKey, Integer> integerMetrics;
+
+	/** Map containing all metrics which are a Double */
+	private Map<MetricKey, Double> doubleMetrics;
+
+	/** Map containing all metrics which are a String */
+	private Map<MetricKey, String> stringMetrics;
+
+	/** name of the element which is associated with this data */
+	private String name;
+
+	/** Constructor. */
+	public MetricTreeNode() {
+		children = new ArrayList<>();
+		integerMetrics = new HashMap<>();
+		doubleMetrics = new HashMap<>();
+		stringMetrics = new HashMap<>();
+	}
+
+	/** Getter for {@link #children}. */
+	public List<MetricTreeNode> getChildren() {
+		return children;
+	}
+
+	/** Getter for {@link #name}. */
+	public String getName() {
+		return name;
+	}
+
+	/** Setter for {@link #name}. */
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	/** Getter for {@link #integerMetrics}. */
+	public Map<MetricKey, Integer> getIntegerMetrics() {
+		return integerMetrics;
+	}
+
+	/** Getter for {@link #doubleMetrics}. */
+	public Map<MetricKey, Double> getDoubleMetrics() {
+		return doubleMetrics;
+	}
+
+	/** Getter for {@link #stringMetrics}. */
+	public Map<MetricKey, String> getStringMetrics() {
+		return stringMetrics;
+	}
+
+	/**
+	 * 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 doubleMetrics.get(key);
+		}
+		Integer val = integerMetrics.get(key);
+		return val == null ? null : val.doubleValue();
+	}
+
+	/**
+	 * Traverses the tree in post-order.
+	 * 
+	 * @param consumer
+	 *            which is applied to each node
+	 */
+	public void traverseTree(Consumer<MetricTreeNode> consumer) {
+
+		// traverse all children
+		for(var child : getChildren()) {
+			child.traverseTree(consumer);
+		}
+
+		// apply consumer to self
+		consumer.accept(this);
+	}
+}
diff --git a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/service/.ratings b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/service/.ratings
new file mode 100644
index 0000000000000000000000000000000000000000..8f58ff0101b51c912f7b21b9e889a52ed5bdb52e
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/service/.ratings
@@ -0,0 +1,2 @@
+IModelQualityService.java db237db72cf1a0905fa82b1dfd799f830d092a10 GREEN
+ModelQualityService.java 6337c64d1a948fecd938726c88b9fc7d6d7ae147 GREEN
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
new file mode 100644
index 0000000000000000000000000000000000000000..db237db72cf1a0905fa82b1dfd799f830d092a10
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/service/IModelQualityService.java
@@ -0,0 +1,59 @@
+/*-------------------------------------------------------------------------+
+| 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.service;
+
+import org.eclipse.emf.ecore.EObject;
+import org.fortiss.tooling.ext.quality.IMetricProvider;
+import org.fortiss.tooling.ext.quality.IMetricUpdateListener;
+import org.fortiss.tooling.ext.quality.data.MetricData;
+import org.fortiss.tooling.kernel.extension.data.ITopLevelElement;
+
+/**
+ * A service for performing metric extraction and analysis.
+ * 
+ * @author groh
+ */
+public interface IModelQualityService {
+	/** Returns the service instance. */
+	static IModelQualityService getInstance() {
+		return ModelQualityService.getInstance();
+	}
+
+	/** Starting the service. */
+	void startService();
+
+	/** Registers the metric provider with the service. */
+	<T extends EObject> void registerMetricProvider(IMetricProvider<T> provider,
+			Class<T> modelElementClass);
+
+	/** Schedules an analysis of the metrics and processes them. */
+	void scheduleMetricCollection(ITopLevelElement top);
+
+	/** Returns the {@link MetricData} for the Service. */
+	MetricData getMetricData();
+
+	/**
+	 * Registers the provided {@link IMetricUpdateListener} to be called when the metrics are
+	 * updated.
+	 * 
+	 * @param listener
+	 *            to be registered
+	 */
+	void registerMetricUpdateListener(IMetricUpdateListener listener);
+
+	/** Creating the quality folder to store the metrics in. */
+	void createQualityFolder();
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..6337c64d1a948fecd938726c88b9fc7d6d7ae147
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/service/ModelQualityService.java
@@ -0,0 +1,419 @@
+/*-------------------------------------------------------------------------+
+| 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.service;
+
+import static java.util.Collections.emptyList;
+import static java.util.Collections.sort;
+import static java.util.stream.Collectors.groupingBy;
+import static org.fortiss.tooling.kernel.utils.KernelModelElementUtils.getRootElements;
+
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import org.eclipse.core.runtime.CoreException;
+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.element.IHierarchicElement;
+import org.fortiss.tooling.ext.quality.GraphMetricsProvider;
+import org.fortiss.tooling.ext.quality.IMetricProvider;
+import org.fortiss.tooling.ext.quality.IMetricUpdateListener;
+import org.fortiss.tooling.ext.quality.data.DataRootElement;
+import org.fortiss.tooling.ext.quality.data.MetricData;
+import org.fortiss.tooling.ext.quality.data.MetricKey;
+import org.fortiss.tooling.ext.quality.data.MetricTreeNode;
+import org.fortiss.tooling.ext.quality.storage.CSVFileWriter;
+import org.fortiss.tooling.ext.quality.storage.ModelQualityStorageManager;
+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.IPersistencyService;
+import org.fortiss.tooling.kernel.service.base.EObjectAwareServiceBase;
+import org.fortiss.tooling.kernel.service.listener.IPersistencyServiceListener;
+
+/**
+ * This class implements the {@link IModelQualityService}.
+ * 
+ * @author blaschke
+ * @author groh
+ */
+/* package */ class ModelQualityService
+		extends EObjectAwareServiceBase<IMetricProvider<? extends EObject>>
+		implements IIntrospectiveKernelService, IModelQualityService, IPersistencyServiceListener {
+
+	/** The singleton instance. */
+	private static final ModelQualityService INSTANCE = new ModelQualityService();
+
+	/** Returns singleton instance of the service. */
+	/* package */ static ModelQualityService getInstance() {
+		return INSTANCE;
+	}
+
+	/** Collected metrics. */
+	private MetricData metricDataContainer = new MetricData();
+
+	/** List of {@link IMetricUpdateListener} which are called when the metrics are updated */
+	private List<IMetricUpdateListener> metricUpdateListeners = new ArrayList<>();
+
+	/** Top-level elements queued to be processed by the metric collector. */
+	private final Queue<ITopLevelElement> queuedProcessableElements =
+			new ConcurrentLinkedQueue<ITopLevelElement>();
+
+	/** Stores the metrics collector job. */
+	private final Job metricCollectorJob = new Job("Collecting metrics...") {
+
+		@Override
+		protected IStatus run(IProgressMonitor monitor) {
+			// Using a do-while-true loop with internal termination in order to ensure that the
+			// monitor is checked for cancellation at least once.
+			do {
+				if(monitor.isCanceled()) {
+					return Status.CANCEL_STATUS;
+				}
+				ITopLevelElement toBeProcessed = queuedProcessableElements.poll();
+				if(toBeProcessed == null) {
+					return Status.OK_STATUS;
+				}
+				try {
+					performMetricCollection(toBeProcessed);
+				} catch(NoSuchAlgorithmException e) {
+					e.printStackTrace();
+				} catch(IOException e) {
+					e.printStackTrace();
+				} catch(CoreException e) {
+					e.printStackTrace();
+				}
+			} while(true);
+		}
+	};
+
+	/** {@inheritDoc} */
+	@Override
+	public void scheduleMetricCollection(ITopLevelElement element) {
+		queuedProcessableElements.add(element);
+		// The Eclipse Job ensures that at most one instance of the metrics collection is executed.
+		// When a new element is added to the processing queue while a job is active, this current
+		// job will run longer in order to process it.
+		metricCollectorJob.schedule();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void registerMetricUpdateListener(IMetricUpdateListener listener) {
+		this.metricUpdateListeners.add(listener);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void startService() {
+		IKernelIntrospectionSystemService.getInstance().registerService(this);
+		IPersistencyService.getInstance().addTopLevelElementListener(this);
+	}
+
+	/** Registers the metric provider with the service. */
+	@Override
+	public <T extends EObject> void registerMetricProvider(IMetricProvider<T> provider,
+			Class<T> modelElementClass) {
+		addHandler(modelElementClass, provider);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public String getIntrospectionDescription() {
+		return getIntrospectionLabel() +
+				"\n\nThis service allows to track the evolution of model metrics." +
+				"\n The service determines the current metrics of a model and stores" +
+				"\n them in a CSV file for later analysis of the modeling history." +
+				"\n\nThe service extension point is 'not existent'.";
+	}
+
+	/**
+	 * Collects all metrics for all elements in the provided {@link ITopLevelElement}.
+	 * 
+	 * @param topLevelElement
+	 *            the {@link ITopLevelElement} on which the analysis should be performed
+	 * @throws IOException
+	 * @throws NoSuchAlgorithmException
+	 * @throws CoreException
+	 */
+	private void performMetricCollection(ITopLevelElement topLevelElement)
+			throws NoSuchAlgorithmException, IOException, CoreException {
+
+		List<IProjectRootElement> rootElements = new ArrayList<>(
+				getRootElements(topLevelElement.getRootModelElement(), IProjectRootElement.class));
+
+		// TODO(#XXX): Remove below workaround that makes assumptions on the overall structure of
+		// the model, and its implementation.
+		String allocationTableClassName =
+				"org.fortiss.af3.allocation.model.impl.AllocationTableCollectionImpl";
+		sort(rootElements, (r1, r2) -> {
+			if(allocationTableClassName.equals(r1.getClass().getName())) {
+				return 1;
+			} else if(allocationTableClassName.equals(r2.getClass().getName())) {
+				return -1;
+			}
+			return 0;
+		});
+
+		var timestamp = LocalDateTime.now();
+		Map<IProjectRootElement, DataRootElement> updatedElements = new HashMap<>();
+		for(IProjectRootElement rootElement : rootElements) {
+			MetricTreeNode rootNode = new MetricTreeNode();
+			if(rootElement instanceof IHierarchicElement) {
+				IHierarchicElement firstElement = (IHierarchicElement)rootElement;
+				recursivlyCollectMetrics(rootNode, firstElement, 0);
+			} else {
+				// Search for provider on ProjectRootElement
+				collectMetrics(rootNode, rootElement);
+			}
+
+			// Collect all violated constraints and group them by their respective source
+			List<IConstraintViolation<? extends EObject>> contraintViolations =
+					ConstraintCheckerService.getInstance()
+							.performAllConstraintChecksRecursively(rootElement);
+
+			// Add the violated constraints to the corresponding tree node
+			for(Entry<? extends EObject, List<IConstraintViolation<? extends EObject>>> entry : contraintViolations
+					.stream().collect(groupingBy(IConstraintViolation::getSource)).entrySet()) {
+
+				MetricTreeNode node =
+						metricDataContainer.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.getIntegerMetrics();
+					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());
+				}
+			}
+
+			// building the data Root Element
+			DataRootElement dataRootElement =
+					new DataRootElement(timestamp, topLevelElement, rootNode);
+			metricDataContainer.getDataRootElementMap().put(rootElement, dataRootElement);
+			updatedElements.put(rootElement, dataRootElement);
+			metricDataContainer.getTreeNodeLookupTable().put(rootElement, rootNode);
+			metricUpdateListeners.forEach(l -> l.metricUpdate(rootElement));
+		}
+		// Store all currently saved metrics to the CSV file
+		CSVFileWriter.metricExtractionToCSV(updatedElements);
+	}
+
+	/**
+	 * Collect metrics for the given {@link IHierarchicElement} and all its contained elements.
+	 * 
+	 * @param node
+	 *            to which the data is collected
+	 * @param currentElement
+	 *            element to collect the data from
+	 * @param recursionLevel
+	 *            parameter which indicates the current recursion level, used for metric
+	 */
+	private void recursivlyCollectMetrics(MetricTreeNode node, IHierarchicElement currentElement,
+			int recursionLevel) {
+
+		collectMetrics(node, currentElement);
+		node.getIntegerMetrics().put(MetricKey.NESTING_LEVEL, recursionLevel);
+
+		// Check if any of the specifications is an IHierarchicElement
+		// This is for example the case for AF3 StateAutomatons
+		Optional<IHierarchicElement> hierarchicElementOptional = currentElement.getSpecifications()
+				.stream().filter(s -> s instanceof IHierarchicElement)
+				.map(s -> (IHierarchicElement)s).findAny();
+		if(hierarchicElementOptional.isPresent()) {
+			// Do not collect statistics for the current element, but instead collect them for the
+			// specification
+			IHierarchicElement specificationElement = hierarchicElementOptional.get();
+			EList<IHierarchicElement> containedElements =
+					specificationElement.getContainedElements();
+			if(containedElements.size() == 1) {
+				// Add reference from the element to this, so the lookups works as expected
+				metricDataContainer.getTreeNodeLookupTable().put(specificationElement, node);
+				// There is exactly one contained element. Skip the specification element to get a
+				// more useful tree structure
+				recursivlyCollectMetrics(node, containedElements.get(0), recursionLevel);
+			} else {
+				recursivlyCollectMetrics(node, specificationElement, recursionLevel);
+			}
+			// Add reference from the element to this, so the lookups works as expected
+			metricDataContainer.getTreeNodeLookupTable().put(currentElement, node);
+		} else {
+			// Iterate over all children and merge values
+			var nodeDoubles = node.getDoubleMetrics();
+			var nodeIntegers = node.getIntegerMetrics();
+			for(IHierarchicElement containedElement : currentElement.getContainedElements()) {
+				MetricTreeNode child = new MetricTreeNode();
+				node.getChildren().add(child);
+				recursivlyCollectMetrics(child, containedElement, recursionLevel + 1);
+
+				for(MetricKey key : MetricKey.getCollectorKeys()) {
+					var childDoubles = child.getDoubleMetrics();
+					var childIntegers = child.getIntegerMetrics();
+					if(childDoubles.containsKey(key)) {
+						nodeDoubles.merge(key, childDoubles.get(key), Double::sum);
+					}
+					if(childIntegers.containsKey(key)) {
+						nodeIntegers.merge(key, childIntegers.get(key), Integer::sum);
+					}
+				}
+			}
+
+			metricDataContainer.getTreeNodeLookupTable().put(currentElement, node);
+
+			// TODO (#4332) Restructure GraphMetricsProvider in quality Plugin
+			GraphMetricsProvider.calculateBetweennessCentrality(currentElement, metricDataContainer,
+					false);
+			GraphMetricsProvider.calculateBetweennessCentrality(currentElement, metricDataContainer,
+					true);
+			node.getDoubleMetrics().put(MetricKey.CLUSTERING_COEFFICIENT,
+					GraphMetricsProvider.calculateClusteringCoefficent(currentElement));
+		}
+	}
+
+	/**
+	 * Collect all metrics provided by any {@link IMetricProvider}} for the given object and saves
+	 * them in the {@link MetricTreeNode}.
+	 * 
+	 * @param node
+	 *            to which the data is collected
+	 * @param object
+	 *            object to collect the data from
+	 */
+	@SuppressWarnings("unchecked")
+	private void collectMetrics(MetricTreeNode node, EObject object) {
+
+		List<IMetricProvider<? extends EObject>> providers = getAllMetricProviders(object);
+
+		for(IMetricProvider<? extends EObject> provider : providers) {
+			// TODO (#4331)
+			((IMetricProvider<EObject>)provider).collectMetrics(node, object);
+		}
+	}
+
+	/** Get all suitable {@link IMetricProvider} for the given input. */
+	private List<IMetricProvider<? extends EObject>> getAllMetricProviders(EObject input) {
+		List<IMetricProvider<?>> providers = new ArrayList<>();
+		for(Entry<Class<?>, List<IMetricProvider<?>>> migEntry : handlerMap.entrySet()) {
+			if(migEntry.getKey().isAssignableFrom(input.getClass())) {
+				providers.addAll(migEntry.getValue());
+			}
+		}
+		return providers;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected String getExtensionPointName() {
+		return null;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected String getConfigurationElementName() {
+		return null;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	protected String getHandlerClassAttribute() {
+		return null;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public String getIntrospectionLabel() {
+		return "Metric Service";
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public List<IIntrospectionItem> getIntrospectionItems() {
+		return emptyList();
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public boolean showInIntrospectionNavigation() {
+		return true;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public IIntrospectionDetailsItem getDetailsItem() {
+		return null;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void topLevelElementLoaded(ITopLevelElement element) {
+		// Schedule metric collection when loading projects
+		scheduleMetricCollection(element);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void topLevelElementAdded(ITopLevelElement element) {
+		// Schedule the metric collection for a newly added project
+		scheduleMetricCollection(element);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void topLevelElementRemoved(ITopLevelElement element) {
+		// Nothing done so we keep the data
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void topLevelElementContentChanged(ITopLevelElement element) {
+		scheduleMetricCollection(element);
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public MetricData getMetricData() {
+		return metricDataContainer;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void createQualityFolder() {
+		if(!ModelQualityStorageManager.MODEL_QUALITY_PROJECT_DIR.exists()) {
+			ModelQualityStorageManager.MODEL_QUALITY_PROJECT_DIR.mkdirs();
+		}
+	}
+}
diff --git a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/storage/.ratings b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/storage/.ratings
new file mode 100644
index 0000000000000000000000000000000000000000..d2dd8e585384f28b05f0694781597259fec8af02
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/storage/.ratings
@@ -0,0 +1,2 @@
+CSVFileWriter.java ba986ec60099bf94472f9e80cba10c01c85bc73f GREEN
+ModelQualityStorageManager.java c5a6783f51f596663bf836f98abf2b23130fe180 GREEN
diff --git a/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/storage/CSVFileWriter.java b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/storage/CSVFileWriter.java
new file mode 100644
index 0000000000000000000000000000000000000000..ba986ec60099bf94472f9e80cba10c01c85bc73f
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/storage/CSVFileWriter.java
@@ -0,0 +1,252 @@
+/*-------------------------------------------------------------------------+
+| 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.storage;
+
+import static java.util.stream.Collectors.toList;
+import static org.apache.commons.io.FilenameUtils.getBaseName;
+
+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.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+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.eclipse.core.runtime.NullProgressMonitor;
+import org.fortiss.tooling.ext.quality.data.DataRootElement;
+import org.fortiss.tooling.ext.quality.data.MetricKey;
+import org.fortiss.tooling.ext.quality.service.IModelQualityService;
+import org.fortiss.tooling.kernel.extension.data.ITopLevelElement;
+import org.fortiss.tooling.kernel.model.IProjectRootElement;
+
+/**
+ * Allows the export of metrics to a CSV file.
+ * 
+ * @author groh
+ */
+public class CSVFileWriter {
+
+	/** Suffix of the file in which the data is written. */
+	private static final String CSV = ".csv";
+
+	/**
+	 * Method to write the data into CSV files. For each project, one file is built. If it already
+	 * exists, it uses to the existing keys in the file as dimensions, to help that the log file
+	 * always remains consistent.
+	 * 
+	 * TODO (#4334) Newly added {@link MetricKey}s are not added in the next export
+	 * 
+	 * @param map
+	 *            to save into CSV file
+	 * @throws IOException
+	 * @throws NoSuchAlgorithmException
+	 * 
+	 */
+	public static void metricExtractionToCSV(Map<IProjectRootElement, DataRootElement> map)
+			throws NoSuchAlgorithmException, IOException {
+
+		// check if there is any data to export
+		if(map == null || map.isEmpty()) {
+			return;
+		}
+
+		IModelQualityService.getInstance().createQualityFolder();
+
+		// Map is not empty due to check above
+		DataRootElement firstValue = map.entrySet().iterator().next().getValue();
+		ITopLevelElement topLevelElement = firstValue.getTopLevelElement();
+		String projectName = topLevelElement.getSaveableName();
+
+		var fileBaseName = getBaseName(projectName).toString();
+		// TODO (#4333)
+		Path path = new File(ModelQualityStorageManager.MODEL_QUALITY_PROJECT_DIR,
+				fileBaseName.concat(CSV)).toPath();
+
+		// Metric extraction is triggered when model files are saved. Therefore, the topLevelElement
+		// should never be dirty here. In order to be on the safe side, conditionally save model.
+		// save before using the saved file to produce the hash
+		if(topLevelElement.isDirty()) {
+			try {
+				topLevelElement.doSave(new NullProgressMonitor());
+				// Avoid duplicated metric extraction (the save operation will trigger another one).
+				return;
+			} catch(Exception e) {
+				e.printStackTrace();
+			}
+			return;
+		}
+		// TODO (#4333)
+		Path modelFile = new File(ModelQualityStorageManager.MODEL_PROJECT_DIR,
+				topLevelElement.getSaveableName()).toPath();
+		String projectHash = computeGitObjectHash(modelFile);
+
+		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) {
+				// In case there is an error, writeMetricsToFile() will provide the default keys.
+				// Therefore this exception can be ignored (and just logged).
+				e.printStackTrace();
+			}
+		}
+		writeMetricsToFile(map, allKeys, path, createNewIndex, projectName, projectHash);
+	}
+
+	/**
+	 * Function to write the specified metrics into a CSV file.
+	 * 
+	 * @param map
+	 *            of the metrics which are written into the file
+	 * @param allKeys
+	 *            a list of keys which will be extracted from the provided map
+	 * @param path
+	 *            location of the file
+	 * @param createNewIndex
+	 *            if a index should be written before writing any values
+	 * @param projectName
+	 *            the name of the base fileProject for this map currently in the focus
+	 * @param projectHash
+	 *            the model file hash value saved for version identification
+	 */
+	private static void writeMetricsToFile(Map<IProjectRootElement, DataRootElement> map,
+			List<String> allKeys, Path path, boolean createNewIndex, String projectName,
+			String projectHash) {
+
+		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("nv_timestamp");
+				allKeys.add("nv_project_name");
+				allKeys.add("nv_project_hash");
+				allKeys.add("nv_root_element_name");
+				allKeys.add("nv_name");
+				allKeys.add("nv_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(toList()));
+
+				// Write first line
+				writer.write(String.join(",", allKeys));
+				writer.write("\n");
+			}
+
+			// Remove first 6 keys as they are not provided by the provider
+			List<String> valueKeys = allKeys.subList(6, allKeys.size());
+
+			LocalDateTime dateTime = LocalDateTime.now();
+			DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
+			String formattedDateTime = dateTime.format(formatter);
+
+			map.forEach((rootElement, dataRootElement) -> {
+				// iterate over all project roots
+				String rootName = rootElement.getClass().getSimpleName();
+				var rootNode = dataRootElement.getRootNode();
+				rootNode.traverseTree(node -> {
+					var integers = node.getIntegerMetrics();
+					var doubles = node.getDoubleMetrics();
+					var strings = node.getStringMetrics();
+					// collect IDs of children
+					String children_ids = node.getChildren().stream()
+							.map(n -> n.getIntegerMetrics().get(MetricKey.UNIQUE_ID))
+							.map(id -> id.toString()).reduce((a, b) -> a + " " + b).orElse("");
+
+					// These 6 data entries are just concatenated in front
+					var startStream = Stream.of(formattedDateTime, projectName, projectHash,
+							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);
+								} else 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();
+		}
+	}
+
+	/**
+	 * Return the Git object hash for the contents of the given file:
+	 * <p>
+	 * {@code SHA-1("blob <size>\0"+<contents>).asHexString()}.
+	 */
+	private static String computeGitObjectHash(Path filePath)
+			throws NoSuchAlgorithmException, IOException {
+
+		try(BufferedReader reader = new BufferedReader(new FileReader(filePath.toString()))) {
+			// Read file line-wise with UNIX line endings
+			StringBuilder inputBuilder = new StringBuilder();
+			do {
+				String line = reader.readLine();
+				if(line == null) {
+					break;
+				}
+				inputBuilder.append(line);
+				inputBuilder.append("\n");
+			} while(true);
+
+			// Construct pre-image (input to hash function) according to Git specification
+			String fileContents = inputBuilder.toString();
+			// fileContexts.length() does not return the correct length on some platforms (e.g.,
+			// Linux)
+			int n = fileContents.getBytes().length;
+			String preImage = "blob " + n + "\0" + fileContents;
+
+			// Compute hash and convert it to a hex string
+			MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
+			byte[] digest = sha1.digest(preImage.getBytes());
+
+			StringBuilder resultBuilder = new StringBuilder();
+			for(byte b : digest) {
+				resultBuilder.append(String.format("%02x", b & 0xff));
+			}
+			return resultBuilder.toString();
+		}
+	}
+}
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 0000000000000000000000000000000000000000..c5a6783f51f596663bf836f98abf2b23130fe180
--- /dev/null
+++ b/org.fortiss.tooling.ext.quality/src/org/fortiss/tooling/ext/quality/storage/ModelQualityStorageManager.java
@@ -0,0 +1,56 @@
+/*-------------------------------------------------------------------------+
+| 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.storage;
+
+import static org.eclipse.core.resources.ResourcesPlugin.getWorkspace;
+
+import java.io.File;
+
+import org.eclipse.core.resources.IWorkspaceRoot;
+
+// TODO (#4333) Exclude the constants to the corresponding packages. The suggested
+// resolution is to distribute the information to other classes.
+/**
+ * Manages the directory of the CSV files.
+ *
+ * @author groh
+ */
+public class ModelQualityStorageManager {
+
+	// TODO(#4333): Move to org.fortiss.af3.project (e.g., create sister class of AF3Project (e.g.,
+	// AF3Metrics), and configure the IModelQualityService from there with the directory name, etc.)
+	/** The name of the general quality plugin directory. */
+	public static final String AF3_QUALITY_DIRECTORY_NAME = "AF3-Model-Quality-Directory";
+
+	// TODO(#4333): Remove! The single source of truth for this is
+	// org.fortiss.af3.project.utils.FileUtils
+	/** The name of the general project directory. */
+	public static final String AF3_PROJECT_DIRECTORY_NAME = "AF3-Project-Directory";
+
+	// TODO(#4333): Remove (unneeded magic)
+	/** The current workspace root. */
+	public static final IWorkspaceRoot WORKSPACE_ROOT = getWorkspace().getRoot();
+
+	// TODO(#4333): Remove, see suggested AF3Metrics class above
+	/** {@link File} representing the directory where all data is saved. */
+	public static final File MODEL_QUALITY_PROJECT_DIR =
+			new File(WORKSPACE_ROOT.getLocation() + File.separator + AF3_QUALITY_DIRECTORY_NAME);
+
+	// TODO(#4333): Remove, see suggested AF3Metrics class above
+	/** {@link File} representing the directory where all data is saved. */
+	public static final File MODEL_PROJECT_DIR =
+			new File(WORKSPACE_ROOT.getLocation() + File.separator + AF3_PROJECT_DIRECTORY_NAME);
+}
diff --git a/org.fortiss.tooling.ext.reuse.ui/META-INF/MANIFEST.MF b/org.fortiss.tooling.ext.reuse.ui/META-INF/MANIFEST.MF
index 3ac20b12b06029da3be9348c33fd7e907a26c895..4b56e3fb91e617b0800f29f8334b67a34e0800ac 100644
--- a/org.fortiss.tooling.ext.reuse.ui/META-INF/MANIFEST.MF
+++ b/org.fortiss.tooling.ext.reuse.ui/META-INF/MANIFEST.MF
@@ -1,7 +1,7 @@
 Manifest-Version: 1.0
 Automatic-Module-Name: org.fortiss.tooling.ext.reuse.ui
 Bundle-ManifestVersion: 2
-Bundle-Name: AF3 Reuse UI
+Bundle-Name: Reuse UI
 Bundle-SymbolicName: org.fortiss.tooling.ext.reuse.ui;singleton:=true
 Bundle-Version: 2.23.0.qualifier
 Bundle-Activator: org.fortiss.tooling.ext.reuse.ui.ToolingReuseUIActivator
diff --git a/org.fortiss.tooling.ext.reuse.ui/plugin.properties b/org.fortiss.tooling.ext.reuse.ui/plugin.properties
index 819b9e6b4946973e4235133269aadc2023bd697e..765418afb2d5ce5fcc99657511aa380e85dddff2 100644
--- a/org.fortiss.tooling.ext.reuse.ui/plugin.properties
+++ b/org.fortiss.tooling.ext.reuse.ui/plugin.properties
@@ -1,4 +1,4 @@
 # (c) 2021 fortiss GmbH
 
-pluginName = AF3 Reuse UI
+pluginName = Reuse UI
 providerName = fortiss GmbH
diff --git a/org.fortiss.tooling.ext.reuse/META-INF/MANIFEST.MF b/org.fortiss.tooling.ext.reuse/META-INF/MANIFEST.MF
index 63c7277ad71516a7358c66a25e93bfe90674a069..4a9be6e18fea7f812456e4aeaa9e22e5d2f3ffe8 100644
--- a/org.fortiss.tooling.ext.reuse/META-INF/MANIFEST.MF
+++ b/org.fortiss.tooling.ext.reuse/META-INF/MANIFEST.MF
@@ -1,11 +1,11 @@
 Manifest-Version: 1.0
 Automatic-Module-Name: org.fortiss.tooling.ext.reuse
 Bundle-ManifestVersion: 2
-Bundle-Name: %pluginName
+Bundle-Name: Reuse
 Bundle-SymbolicName: org.fortiss.tooling.ext.reuse;singleton:=true
 Bundle-Version: 2.23.0.qualifier
 Bundle-ClassPath: .
-Bundle-Vendor: %providerName
+Bundle-Vendor: fortiss GmbH
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-11
 Bundle-ActivationPolicy: lazy
diff --git a/org.fortiss.tooling.ext.reuse/plugin.properties b/org.fortiss.tooling.ext.reuse/plugin.properties
index df6c054c8323107d456037cd5393a9f100b0b845..324d8736b02455e0b7a14e7dc7861cf82f8064b8 100644
--- a/org.fortiss.tooling.ext.reuse/plugin.properties
+++ b/org.fortiss.tooling.ext.reuse/plugin.properties
@@ -1,4 +1,4 @@
 <!-- (c) 2021 fortiss GmbH -->
 
-pluginName = AF3 Reuse
+pluginName = Reuse
 providerName = fortiss GmbH
diff --git a/src/org/fortiss/tooling/ext/quality/ui/AF3QualityUIActivator.java b/src/org/fortiss/tooling/ext/quality/ui/AF3QualityUIActivator.java
new file mode 100644
index 0000000000000000000000000000000000000000..4f51b3dce8b7c9f051324ad57044b2620cf88f46
--- /dev/null
+++ b/src/org/fortiss/tooling/ext/quality/ui/AF3QualityUIActivator.java
@@ -0,0 +1,65 @@
+/*-------------------------------------------------------------------------+
+| Copyright 2022 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.ui;
+
+import static org.eclipse.jface.resource.ResourceLocator.imageDescriptorFromBundle;
+
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.ui.plugin.AbstractUIPlugin;
+import org.osgi.framework.BundleContext;
+
+/**
+ * The activator class controls the plug-in life cycle.
+ * 
+ * @author Blaschke
+ */
+public class AF3QualityUIActivator extends AbstractUIPlugin {
+
+	/** The plug-in ID. */
+	public static final String PLUGIN_ID = AF3QualityUIActivator.class.getPackage().getName(); // $NON-NLS-1$
+
+	/** The shared instance. */
+	private static AF3QualityUIActivator plugin;
+
+	/** Constructor. */
+	public AF3QualityUIActivator() {
+		// do nothing
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void start(BundleContext context) throws Exception {
+		super.start(context);
+		plugin = this;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public void stop(BundleContext context) throws Exception {
+		plugin = null;
+		super.stop(context);
+	}
+
+	/** Returns the shared instance. */
+	public static AF3QualityUIActivator getDefault() {
+		return plugin;
+	}
+
+	/** Returns the image descriptor for the given icon file. */
+	public static ImageDescriptor getImageDescriptor(String path) {
+		return imageDescriptorFromBundle(PLUGIN_ID, path).orElse(null);
+	}
+}
diff --git a/src/org/fortiss/tooling/ext/quality/ui/MetricExportMenu.java b/src/org/fortiss/tooling/ext/quality/ui/MetricExportMenu.java
new file mode 100644
index 0000000000000000000000000000000000000000..56264ea19292b6365984a710af5629043245f55a
--- /dev/null
+++ b/src/org/fortiss/tooling/ext/quality/ui/MetricExportMenu.java
@@ -0,0 +1,75 @@
+/*-------------------------------------------------------------------------+
+| Copyright 2022 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.ui;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.jface.action.IContributionItem;
+import org.fortiss.tooling.ext.quality.AF3QualityActivator;
+import org.fortiss.tooling.kernel.ui.extension.IContextMenuMultiSelectionContributor;
+import org.fortiss.tooling.kernel.ui.extension.data.ContextMenuContextProvider;
+
+import com.sun.javafx.iio.common.ImageDescriptor;
+
+/**
+ * Context menu entry to automatic layout {@link IHierarchicElement}-based models.
+ * 
+ * @author blaschke
+ */
+public class MetricExportMenu implements IContextMenuMultiSelectionContributor {
+
+	/** Returns the icon that is visible in the context menu for this entry. */
+	protected ImageDescriptor getActionIcon() {
+		return AF3QualityUIActivator.getImageDescriptor("icons/MetricIcon.png");
+	}
+
+		/** {@inheritDoc} */
+		@Override
+		public void run() {
+			try(PrintStream ps_final = new PrintStream("test")) {
+				AF3QualityActivator extractor = new AF3QualityActivator();
+				String enterpriseArchitectureFile = extractor.transformFileProjectToSysML("enterpriseArchitect");
+				ps_final.println(enterpriseArchitectureFile);
+			} catch(Exception ex) {
+				ex.printStackTrace();
+				showError("Unable to save the SysML xmi architecture file", ex.getMessage());
+			}
+		}
+	
+	/** {@inheritDoc} */
+	@Override
+	public String getMenuSectionID() {
+		return null;
+	}
+
+	@Override
+	public List<IContributionItem> getContributedItems(EObject selection, ContextMenuContextProvider contextProvider) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+	@Override
+	public List<IContributionItem> getContributedItems(List<EObject> selection,
+			ContextMenuContextProvider contextProvider) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+}