blob: d8a5d99b22fcd7df5b65d306199108d391cecd42 [file] [log] [blame]
/* Copyright (c) 2019 Robert Bosch GmbH and others.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Robert Bosch GmbH - initial API and implementation
********************************************************************************
*/
package org.eclipse.app4mc.amalthea.model;
import java.util.Set;
import org.eclipse.emf.common.util.BasicDiagnostic;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.DiagnosticChain;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.jdt.annotation.NonNull;
/**
* This class provides static methods that implement validations (invariants) of the Amalthea EMF model.
* <p>
* The methods are called from several generated model objects.
*/
public class AmaltheaValidations {
// Suppress default constructor
private AmaltheaValidations() {
throw new IllegalStateException("Utility class");
}
private static final String DIAGNOSTIC_SOURCE = "org.eclipse.app4mc.amalthea.model";
private static final int NO_INDEX = -1;
private static final AmaltheaPackage PACKAGE = AmaltheaPackage.eINSTANCE;
public static boolean validateInvariants(MinAvgMaxStatistic obj, DiagnosticChain diagnostics) {
boolean isValid = obj.getMin() <= obj.getAvg() && obj.getAvg() <= obj.getMax();
if (!isValid) {
addError(obj, PACKAGE.getMinAvgMaxStatistic_Avg(), "MinAvgMaxStatistic: constraint min <= avg <= max violated", diagnostics);
}
return isValid;
}
// ******** Interval bounds + Average (if available) ********
public static boolean validateInvariants(TimeInterval obj, DiagnosticChain diagnostics) {
return checkMinAvgMax(obj, diagnostics, obj.getLowerBound(), getAverage(obj), obj.getUpperBound());
}
public static boolean validateInvariants(DiscreteValueInterval obj, DiagnosticChain diagnostics) {
return checkMinAvgMax(obj, diagnostics, toDouble(obj.getLowerBound()), getAverage(obj), toDouble(obj.getUpperBound()));
}
public static boolean validateInvariants(ContinuousValueInterval obj, DiagnosticChain diagnostics) {
return checkMinAvgMax(obj, diagnostics, obj.getLowerBound(), getAverage(obj), obj.getUpperBound());
}
// ******** Truncated distribution bounds (mean is not limited) ********
public static boolean validateInvariants(TruncatedTimeDistribution obj, DiagnosticChain diagnostics) {
return checkMinAvgMax(obj, diagnostics, obj.getLowerBound(), null, obj.getUpperBound());
}
public static boolean validateInvariants(TruncatedDiscreteValueDistribution obj, DiagnosticChain diagnostics) {
return checkMinAvgMax(obj, diagnostics, toDouble(obj.getLowerBound()), null, toDouble(obj.getUpperBound()));
}
public static boolean validateInvariants(TruncatedContinuousValueDistribution obj, DiagnosticChain diagnostics) {
return checkMinAvgMax(obj, diagnostics, obj.getLowerBound(), null, obj.getUpperBound());
}
// private methods to get the given average / mean value
private static Time getAverage(TimeInterval obj) {
Time avg = null;
if (obj instanceof TimeStatistics) {
avg = ((TimeStatistics) obj).getAverage();
}
if (obj instanceof TimeWeibullEstimatorsDistribution) {
avg = ((TimeWeibullEstimatorsDistribution) obj).getAverage();
}
return avg;
}
private static Double getAverage(DiscreteValueInterval obj) {
Double avg = null;
if (obj instanceof DiscreteValueStatistics) {
avg = ((DiscreteValueStatistics) obj).getAverage();
}
if (obj instanceof DiscreteValueWeibullEstimatorsDistribution) {
avg = ((DiscreteValueWeibullEstimatorsDistribution) obj).getAverage();
}
return avg;
}
private static Double getAverage(ContinuousValueInterval obj) {
Double avg = null;
if (obj instanceof ContinuousValueStatistics) {
avg = ((ContinuousValueStatistics) obj).getAverage();
}
if (obj instanceof ContinuousValueWeibullEstimatorsDistribution) {
avg = ((ContinuousValueWeibullEstimatorsDistribution) obj).getAverage();
}
return avg;
}
private static Double toDouble(Long value) {
if (value == null) return null;
return value.doubleValue();
}
private static <T extends Comparable<T>> boolean checkMinAvgMax(EObject obj, DiagnosticChain diagnostics, T min, T avg, T max) {
boolean isValid = true;
if (min != null && max != null && min.compareTo(max) > 0) {
addError(obj, null, obj.eClass().getName() + ": lower bound > upper bound", diagnostics);
isValid = false;
}
if (min != null && avg != null && min.compareTo(avg) > 0) {
addError(obj, null, obj.eClass().getName() + ": lower bound > average", diagnostics);
isValid = false;
}
if (avg != null && max != null && avg.compareTo(max) > 0) {
addError(obj, null, obj.eClass().getName() + ": average > upper bound", diagnostics);
isValid = false;
}
return isValid;
}
// ******** IReferable name (empty or duplicate) ********
public static boolean validateInvariants(IReferable obj, DiagnosticChain diagnostics) {
String shortName = obj.getName();
if (shortName == null || shortName.isEmpty()) {
addError(obj, PACKAGE.getINamed_Name(), obj.eClass().getName() + ": missing name", diagnostics);
return false;
}
Set<? extends @NonNull IReferable> duplicates = AmaltheaIndex.getElements(obj, shortName, obj.getClass());
if (duplicates.size() > 1) {
String longName = obj.getQualifiedName();
if (duplicates.stream().anyMatch(e -> e != obj && longName.equals(e.getQualifiedName()))) {
addError(obj, PACKAGE.getINamed_Name(), obj.eClass().getName() + ": duplicate name", diagnostics);
return false;
}
}
return true;
}
// ******** Modes and Local Modes ********
public static boolean validateInvariants(LocalModeLabel obj, DiagnosticChain diagnostics) {
return checkModeAndValue(obj, PACKAGE.getLocalModeLabel_DefaultValue(), obj.getMode(), obj.getDefaultValue(), diagnostics);
}
public static boolean validateInvariants(LocalModeValue obj, DiagnosticChain diagnostics) {
if (obj.getLabel() != null && obj.getValueSource() != null) {
LocalModeLabel label = obj.getLabel();
ILocalModeValueSource source = obj.getValueSource();
if ((label.isEnum() && source.isNumeric()) || (label.isNumeric() && source.isEnum())) {
addError(obj, PACKAGE.getLocalModeValue_ValueSource(), obj.eClass().getName() + ": incompatible types", diagnostics);
return false;
}
return checkModeCompatibility(obj, PACKAGE.getLocalModeValue_ValueSource(), label.getMode(), source.getMode(), diagnostics);
}
return true;
}
public static boolean validateInvariants(ModeLabel obj, DiagnosticChain diagnostics) {
return checkModeAndValue(obj, PACKAGE.getModeLabel_InitialValue(), obj.getMode(), obj.getInitialValue(), diagnostics);
}
public static boolean validateInvariants(ModeValue obj, DiagnosticChain diagnostics) {
ModeLabel modeLabel = obj.getLabel();
if (modeLabel == null) {
// This case is already handled by standard EMF validations (definition ModeLabel[1])
// Manual diagnostics entry would be:
// addError(obj, PACKAGE.getModeValue_Label(), obj.eClass().getName() + ": missing mode label", diagnostics)
return false;
}
return checkModeAndValue(obj, PACKAGE.getModeValue_Value(), modeLabel.getMode(), obj.getValue(), diagnostics);
}
public static boolean validateInvariants(ModeLabelAccess obj, DiagnosticChain diagnostics) {
ModeLabel modeLabel = obj.getData();
if (modeLabel == null) {
// This case is already handled by standard EMF validations (definition ModeLabel[1])
// Manual diagnostics entry would be:
// addError(obj, PACKAGE.getModeLabelAccess_Data(), obj.eClass().getName() + ": missing mode label", diagnostics)
return false;
}
if (obj.getAccess() == ModeLabelAccessEnum.SET) {
return checkModeAndValue(obj, PACKAGE.getModeLabelAccess_Value(), modeLabel.getMode(), obj.getValue(), diagnostics);
}
return true;
}
public static boolean validateInvariants(ModeLabelAssignment obj, DiagnosticChain diagnostics) {
if (obj.getGlobalLabel() != null && obj.getLocalLabel() != null) {
ModeLabel label1 = obj.getGlobalLabel();
LocalModeLabel label2 = obj.getLocalLabel();
return checkModeCompatibility(obj, PACKAGE.getModeLabelAssignment_LocalLabel(), label1.getMode(), label2.getMode(), diagnostics);
}
return true;
}
public static boolean validateInvariants(ModeLabelCondition obj, DiagnosticChain diagnostics) {
if (obj.getLabel1() != null && obj.getLabel2() != null) {
ModeLabel label1 = obj.getLabel1();
ModeLabel label2 = obj.getLabel2();
return checkModeCompatibility(obj, PACKAGE.getModeLabelAssignment_LocalLabel(), label1.getMode(), label2.getMode(), diagnostics);
}
return true;
}
private static boolean checkModeAndValue(EObject obj, EStructuralFeature feature, Mode mode, String value, DiagnosticChain diagnostics) {
if (mode == null) {
addWarning(obj, feature, obj.eClass().getName() + ": unknown value type (mode)", diagnostics);
return false;
}
if (value == null) {
if (feature.getLowerBound() != 0) {
addError(obj, feature, obj.eClass().getName() + ": missing value", diagnostics);
return false;
} else {
// value does not have to be set
return true;
}
}
String trimmedValue = value.trim();
if (mode instanceof EnumMode) {
final EnumMode enumMode = (EnumMode) mode;
if (enumMode.getLiteral(trimmedValue) == null) {
addError(obj, feature, "The " + feature.getName() + " '" + trimmedValue + "' is not a valid literal of Enum Mode \"" + mode.getName() + "\"", diagnostics);
return false;
}
}
if (mode instanceof NumericMode) {
boolean isInteger = trimmedValue.matches("-?\\d+");
if (!isInteger) {
addError(obj, feature, "The " + feature.getName() + " '" + trimmedValue + "' is not an integer", diagnostics);
return false;
}
}
return true;
}
private static boolean checkModeCompatibility(EObject obj, EStructuralFeature feature, Mode mode1, Mode mode2, DiagnosticChain diagnostics) {
if (mode1 != null && mode2 != null && !mode1.equals(mode2)) {
addError(obj, feature, obj.eClass().getName() + ": incompatible modes", diagnostics);
return false;
}
return true;
}
// private helper methods
private static void addWarning(EObject object, EStructuralFeature feature, String message, DiagnosticChain diagnostics) {
if (diagnostics != null) {
diagnostics.add(createIssue(Diagnostic.WARNING, object, feature, message));
}
}
private static void addError(EObject object, EStructuralFeature feature, String message, DiagnosticChain diagnostics) {
if (diagnostics != null) {
diagnostics.add(createIssue(Diagnostic.ERROR, object, feature, message));
}
}
private static BasicDiagnostic createIssue(int severity, EObject object, EStructuralFeature feature, String message) {
return new BasicDiagnostic(
severity,
DIAGNOSTIC_SOURCE,
NO_INDEX,
message,
new Object [] { object, feature });
}
}