Skip to content
Snippets Groups Projects
Commit 4ef727a3 authored by Simon Barner's avatar Simon Barner
Browse files

Merge branch '4310' into 'master'

Setting up Metric extraction plugin for AF3 :  Issue 4310

See merge request !210
parents b41ae985 105a0ee4
No related branches found
No related tags found
1 merge request!210Setting up Metric extraction plugin for AF3 : Issue 4310
Showing
with 1054 additions and 5 deletions
/*-------------------------------------------------------------------------+
| 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);
}
}
IModelQualityService.java db237db72cf1a0905fa82b1dfd799f830d092a10 GREEN
ModelQualityService.java 6337c64d1a948fecd938726c88b9fc7d6d7ae147 GREEN
/*-------------------------------------------------------------------------+
| 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();
}
/*-------------------------------------------------------------------------+
| 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();
}
}
}
CSVFileWriter.java ba986ec60099bf94472f9e80cba10c01c85bc73f GREEN
ModelQualityStorageManager.java c5a6783f51f596663bf836f98abf2b23130fe180 GREEN
/*-------------------------------------------------------------------------+
| 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();
}
}
}
/*-------------------------------------------------------------------------+
| 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);
}
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
......
# (c) 2021 fortiss GmbH
pluginName = AF3 Reuse UI
pluginName = Reuse UI
providerName = fortiss GmbH
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
......
<!-- (c) 2021 fortiss GmbH -->
pluginName = AF3 Reuse
pluginName = Reuse
providerName = fortiss GmbH
/*-------------------------------------------------------------------------+
| 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);
}
}
/*-------------------------------------------------------------------------+
| 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;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment