CheckBindingConstraints.java
/**
* Copyright (c) 2004-2025 Carnegie Mellon University and others. (see Contributors file).
* All Rights Reserved.
*
* NO WARRANTY. ALL MATERIAL IS FURNISHED ON AN "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY
* KIND, EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE
* OR MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT
* MAKE ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.
*
* 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
*
* Created, in part, with funding and support from the United States Government. (see Acknowledgments file).
*
* This program includes and/or can make use of certain third party source code, object code, documentation and other
* files ("Third Party Software"). The Third Party Software that is used by this program is dependent upon your system
* configuration. By using this program, You agree to comply with any and all relevant Third Party Software terms and
* conditions contained in any such Third Party Software or separate license file distributed with such Third Party
* Software. The parties who own the Third Party Software ("Third Party Licensors") are intended third party benefici-
* aries to this license with respect to the terms applicable to their Third Party Software. Third Party Software li-
* censes only apply to the Third Party Software and not any other portion of this program or this program as a whole.
*/
package org.osate.analysis.architecture.handlers;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.xtext.xbase.lib.StringExtensions;
import org.osate.aadl2.Classifier;
import org.osate.aadl2.ComponentCategory;
import org.osate.aadl2.ComponentClassifier;
import org.osate.aadl2.Element;
import org.osate.aadl2.EnumerationLiteral;
import org.osate.aadl2.NamedElement;
import org.osate.aadl2.contrib.deployment.DeploymentProperties;
import org.osate.aadl2.instance.ComponentInstance;
import org.osate.aadl2.instance.ConnectionInstance;
import org.osate.aadl2.instance.FeatureCategory;
import org.osate.aadl2.instance.FeatureInstance;
import org.osate.aadl2.instance.InstanceObject;
import org.osate.aadl2.instance.SystemInstance;
import org.osate.aadl2.instance.SystemOperationMode;
import org.osate.aadl2.modelsupport.modeltraversal.SOMIterator;
import org.osate.aadl2.modelsupport.util.AadlUtil;
import org.osate.aadl2.util.Aadl2Util;
import org.osate.ui.dialogs.Dialog;
import org.osate.ui.handlers.AaxlReadOnlyHandlerAsJob;
import org.osate.xtext.aadl2.properties.util.GetProperties;
public class CheckBindingConstraints extends AaxlReadOnlyHandlerAsJob {
@Override
protected String getMarkerType() {
return "org.osate.analysis.architecture.CheckBindingConstraintsObjectMarker";
}
@Override
protected void doAaxlAction(IProgressMonitor monitor, Element root) {
if (root instanceof ComponentInstance) {
SubMonitor subMonitor = SubMonitor.convert(monitor).checkCanceled();
SystemInstance si = ((ComponentInstance) root).getSystemInstance();
if (si != null) {
List<Issue> issues = runAnalysis(subMonitor, si);
issues.forEach(issue -> error(issue.target, issue.message));
if (issues.isEmpty()) {
getShell().getDisplay().asyncExec(() -> MessageDialog.openInformation(getShell(),
"Check Binding Constraints", "No problems found"));
} else {
getShell().getDisplay().asyncExec(() -> MessageDialog.openError(getShell(),
"Check Binding Constraints", issues.size() + " problem(s) found"));
}
}
} else {
Dialog.showWarning(getActionName(), "Please invoke command on an instance model");
}
}
@Override
protected String getActionName() {
return "Check binding constraints";
}
public static List<Issue> runAnalysis(IProgressMonitor monitor, SystemInstance si) {
List<Issue> issuesList = new ArrayList<>();
SubMonitor subMonitor = SubMonitor.convert(monitor, 100);
SubMonitor loopMonitor = subMonitor.split(100).setWorkRemaining(si.getSystemOperationModes().size());
for (SOMIterator somIter = new SOMIterator(si); somIter.hasNext();) {
SystemOperationMode som = somIter.next();
SubMonitor iterationMonitor = loopMonitor.split(1);
iterationMonitor.setWorkRemaining(8);
// Processor binding
SubMonitor processorChild = iterationMonitor.split(2);
subMonitor.setTaskName("Getting Processor Binding Components");
processorChild.split(1);
List<ComponentInstance> processorBindingComponents = getComponents(monitor, si, ComponentCategory.THREAD,
ComponentCategory.VIRTUAL_PROCESSOR, ComponentCategory.DEVICE).collect(Collectors.toList());
processorChild.setTaskName("Checking Processor Bindings");
processorChild.split(1);
issuesList.addAll(checkBindingConstraints(processorBindingComponents.stream(), "processor",
GetProperties::getActualProcessorBinding, GetProperties::getAllowedProcessorBinding,
GetProperties::getAllowedProcessorBindingClass, som));
// Dispatch Protocol
subMonitor.setTaskName("Checking Dispatch Protocols");
issuesList.addAll(checkDispatchProtocol(processorBindingComponents.stream(), som));
// Memory binding
subMonitor.setTaskName("Getting Memory Components");
SubMonitor memrChild = iterationMonitor.split(2);
memrChild.split(1);
Stream<ComponentInstance> memoryBindingComponents = getComponents(monitor, si, ComponentCategory.THREAD,
ComponentCategory.DEVICE, ComponentCategory.DATA, ComponentCategory.SUBPROGRAM,
ComponentCategory.PROCESSOR, ComponentCategory.PROCESS, ComponentCategory.VIRTUAL_PROCESSOR);
subMonitor.setTaskName("Checking Memory Bindings");
memrChild.split(1);
Stream<FeatureInstance> memoryBindingFeatures = getFeatures(monitor, si, FeatureCategory.DATA_PORT,
FeatureCategory.EVENT_DATA_PORT);
Stream<InstanceObject> memoryBindingElements = Stream.concat(memoryBindingComponents,
memoryBindingFeatures);
issuesList.addAll(
checkBindingConstraints(memoryBindingElements, "memory", GetProperties::getActualMemoryBinding,
GetProperties::getAllowedMemoryBinding, GetProperties::getAllowedMemoryBindingClass, som));
// Connection binding (only handles connection and virtual bus)
subMonitor.setTaskName("Checking Connection Bindings");
Stream<ComponentInstance> connectionBindingComponents = getComponents(monitor, si,
ComponentCategory.VIRTUAL_BUS);
Stream<ConnectionInstance> connectionBindingConnections = getConnections(monitor, si);
List<InstanceObject> connectionBindingElements = Stream
.concat(connectionBindingComponents, connectionBindingConnections).collect(Collectors.toList());
issuesList.addAll(checkBindingConstraints(connectionBindingElements.stream(), "connection",
GetProperties::getActualConnectionBinding, GetProperties::getAllowedConnectionBinding,
GetProperties::getAllowedConnectionBindingClass, som));
// Connection Quality of Service
subMonitor.setTaskName("Checking Connection Quality of Services");
issuesList.addAll(checkRequiredAndProvided(connectionBindingElements.stream(),
GetProperties::getRequiredConnectionQualityOfService, "Required_Connection_Quality_Of_Service",
GetProperties::getProvidedConnectionQualityOfService, qos -> qos.getName(), som));
// Virtual Bus Class
subMonitor.setTaskName("Checking Virtual Bus Bindings");
SubMonitor busChild = iterationMonitor.split(1);
Function<ComponentInstance, Collection<ComponentClassifier>> getProvidedVBClass = boundElement -> {
Stream<ComponentClassifier> providedProperty = GetProperties.getProvidedVirtualBusClass(boundElement)
.stream();
Stream<ComponentClassifier> providedBySubcomponent = boundElement.getComponentInstances().stream()
.map(subcomponent -> subcomponent.getClassifier());
return Stream.concat(providedProperty, providedBySubcomponent).collect(Collectors.toSet());
};
issuesList.addAll(checkRequiredAndProvided(connectionBindingElements.stream(),
GetProperties::getRequiredVirtualBusClass, "Required_Virtual_Bus_Class", getProvidedVBClass,
vbClass -> vbClass.getName(), som));
busChild.setWorkRemaining(0);
}
subMonitor.setWorkRemaining(0);
return issuesList;
}
private static <T extends InstanceObject> List<Issue> checkBindingConstraints(Stream<T> bindingElements,
String bindingType, Function<T, List<ComponentInstance>> getActualBinding,
Function<T, List<ComponentInstance>> getAllowedBinding,
Function<T, List<Classifier>> getAllowedBindingClass, SystemOperationMode som) {
return bindingElements.flatMap(element -> {
Set<ComponentInstance> actualBinding = Collections
.unmodifiableSet(new HashSet<>(getActualBinding.apply(element)));
Set<ComponentInstance> allowedBinding = Collections
.unmodifiableSet(new HashSet<>(getAllowedBinding.apply(element)));
Set<Classifier> allowedBindingClasses = Collections
.unmodifiableSet(new HashSet<>(getAllowedBindingClass.apply(element)));
Stream<Issue> issues = Stream.empty();
if (!actualBinding.isEmpty() && !allowedBindingClasses.isEmpty()) {
Set<ComponentInstance> modifiableActual = new HashSet<>(actualBinding);
modifiableActual.removeIf(boundElement -> {
ComponentClassifier boundElementClassifier = boundElement.getClassifier();
return boundElementClassifier != null && allowedBindingClasses.stream()
.anyMatch(allowedClass -> AadlUtil.isSubClassifier(allowedClass, boundElementClassifier));
});
String propertyName = "Allowed_" + StringExtensions.toFirstUpper(bindingType) + "_Binding_Class";
issues = Stream.concat(issues,
createErrorsForBindings(modifiableActual, element, bindingType, som, propertyName));
}
if (!actualBinding.isEmpty() && !allowedBinding.isEmpty()) {
Set<ComponentInstance> modifiableActual = new HashSet<>(actualBinding);
modifiableActual.removeAll(allowedBinding);
String propertyName = "Allowed_" + StringExtensions.toFirstUpper(bindingType) + "_Binding";
issues = Stream.concat(issues,
createErrorsForBindings(modifiableActual, element, bindingType, som, propertyName));
}
return issues;
}).collect(Collectors.toList());
}
private static List<Issue> checkDispatchProtocol(Stream<ComponentInstance> bindingElements,
SystemOperationMode som) {
return bindingElements.flatMap(element -> {
EnumerationLiteral dispatchProtocol = GetProperties.getDispatchProtocol(element);
if (dispatchProtocol != null) {
return GetProperties.getActualProcessorBinding(element).stream().map(boundElement -> {
List<EnumerationLiteral> allowedDispatchProtocol = GetProperties
.getAllowedDispatchProtocol(boundElement);
if (!allowedDispatchProtocol.isEmpty() && !allowedDispatchProtocol.contains(dispatchProtocol)) {
StringBuilder message = new StringBuilder(getTitle(element));
message.append(" '");
message.append(element.getName());
message.append("' has a Dispatch_Protocol '");
message.append(dispatchProtocol.getName());
if (!Aadl2Util.isNoModes(som)) {
message.append("' in mode '");
message.append(som.getName());
}
message.append("' which is not allowed by '");
message.append(boundElement.getName());
message.append("'");
return Optional.of(new Issue(element, message.toString()));
} else {
return Optional.<Issue> empty();
}
}).filter(issue -> issue.isPresent()).map(issue -> issue.get());
} else {
return Stream.empty();
}
}).collect(Collectors.toList());
}
private static <T> List<Issue> checkRequiredAndProvided(Stream<InstanceObject> bindingElements,
Function<NamedElement, List<T>> getRequired, String requiredPropertyName,
Function<ComponentInstance, Collection<T>> getProvided, Function<T, String> getName,
SystemOperationMode som) {
return bindingElements.flatMap(element -> {
Set<T> required = Collections.unmodifiableSet(new HashSet<>(getRequired.apply(element)));
if (!required.isEmpty()) {
return DeploymentProperties.getActualConnectionBinding(element).orElse(Collections.emptyList()).stream()
.flatMap(boundElement -> {
Set<T> missingSet = new HashSet<>(required);
missingSet.removeAll(getProvided.apply((ComponentInstance) boundElement));
return missingSet.stream().map(missing -> {
StringBuilder message = new StringBuilder(getTitle(element));
message.append(" '");
message.append(element.getName());
message.append("' has a ");
message.append(requiredPropertyName);
message.append(" '");
message.append(getName.apply(missing));
if (!Aadl2Util.isNoModes(som)) {
message.append("' in mode '");
message.append(som.getName());
}
message.append("' which is not provided by '");
message.append(boundElement.getName());
message.append("'");
return new Issue(element, message.toString());
});
});
} else {
return Stream.empty();
}
}).collect(Collectors.toList());
}
private static Stream<Issue> createErrorsForBindings(Set<ComponentInstance> actualBinding, InstanceObject element,
String bindingType, SystemOperationMode som, String propertyName) {
return actualBinding.stream().map(boundElement -> {
StringBuilder message = new StringBuilder(getTitle(element));
message.append(" '");
message.append(element.getName());
message.append("' has a ");
message.append(bindingType);
message.append(" binding to '");
message.append(boundElement.getName());
if (!Aadl2Util.isNoModes(som)) {
message.append("' in mode '");
message.append(som.getName());
}
message.append("' which is not allowed by the property ");
message.append(propertyName);
return new Issue(element, message.toString());
});
}
private static String getTitle(InstanceObject element) {
if (element instanceof ComponentInstance) {
return StringExtensions.toFirstUpper(((ComponentInstance) element).getCategory().getName());
} else if (element instanceof FeatureInstance) {
return ((FeatureInstance) element).getCategory().getName();
} else if (element instanceof ConnectionInstance) {
return "Connection";
} else {
throw new AssertionError("Unhandled condition: " + element);
}
}
private static Stream<ComponentInstance> getComponents(IProgressMonitor monitor, SystemInstance instance,
ComponentCategory... categories) {
Stream<EObject> stream = streamWhileNotCanceled(monitor, instance);
Stream<ComponentInstance> components = filterByType(stream, ComponentInstance.class);
if (categories.length == 0) {
return components;
} else {
EnumSet<ComponentCategory> categoriesSet = EnumSet.copyOf(Arrays.asList(categories));
return components.filter(element -> categoriesSet.contains(element.getCategory()));
}
}
private static Stream<FeatureInstance> getFeatures(IProgressMonitor monitor, SystemInstance instance,
FeatureCategory... categories) {
Stream<EObject> stream = streamWhileNotCanceled(monitor, instance);
Stream<FeatureInstance> features = filterByType(stream, FeatureInstance.class);
if (categories.length == 0) {
return features;
} else {
EnumSet<FeatureCategory> categoriesSet = EnumSet.copyOf(Arrays.asList(categories));
return features.filter(element -> categoriesSet.contains(element.getCategory()));
}
}
private static Stream<ConnectionInstance> getConnections(IProgressMonitor monitor, SystemInstance instance) {
Stream<EObject> stream = streamWhileNotCanceled(monitor, instance);
return filterByType(stream, ConnectionInstance.class);
}
private static Stream<EObject> streamWhileNotCanceled(IProgressMonitor monitor, SystemInstance instance) {
return StreamSupport.stream(new Spliterators.AbstractSpliterator<EObject>(Long.MAX_VALUE, Spliterator.ORDERED) {
private final Iterator<EObject> allContentsIter = instance.eAllContents();
@Override
public boolean tryAdvance(Consumer<? super EObject> action) {
if (!monitor.isCanceled() && allContentsIter.hasNext()) {
action.accept(allContentsIter.next());
return true;
} else {
return false;
}
}
}, false);
}
private static <T> Stream<T> filterByType(Stream<?> unfiltered, Class<T> type) {
return unfiltered.filter(element -> type.isInstance(element)).map(element -> type.cast(element));
}
public static class Issue {
public final InstanceObject target;
public final String message;
private Issue(InstanceObject target, String message) {
this.target = target;
this.message = message;
}
}
}