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