| /* 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 }); |
| } |
| |
| } |