BusLoadModelBuilder.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.resource.budgets.busload;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.osate.aadl2.ComponentCategory;
import org.osate.aadl2.Element;
import org.osate.aadl2.instance.ComponentInstance;
import org.osate.aadl2.instance.ConnectionInstance;
import org.osate.aadl2.instance.ConnectionInstanceEnd;
import org.osate.aadl2.instance.InstanceObject;
import org.osate.aadl2.instance.SystemInstance;
import org.osate.aadl2.instance.SystemOperationMode;
import org.osate.aadl2.modelsupport.modeltraversal.ForAllElement;
import org.osate.analysis.resource.budgets.internal.models.busload.Broadcast;
import org.osate.analysis.resource.budgets.internal.models.busload.Bus;
import org.osate.analysis.resource.budgets.internal.models.busload.BusLoadModel;
import org.osate.analysis.resource.budgets.internal.models.busload.BusOrVirtualBus;
import org.osate.analysis.resource.budgets.internal.models.busload.BusloadFactory;
import org.osate.analysis.resource.budgets.internal.models.busload.Connection;
import org.osate.analysis.resource.budgets.internal.models.busload.VirtualBus;
import org.osate.contribution.sei.sei.Sei;
import org.osate.xtext.aadl2.properties.util.InstanceModelUtil;
final class BusLoadModelBuilder {
private final Map<ComponentInstance, Bus> buses = new HashMap<>();
private final Map<ComponentInstance, VirtualBus> virtualBuses = new HashMap<>();
private final SystemInstance systemInstance;
private final SystemOperationMode som;
private BusLoadModelBuilder(final SystemInstance systemInstance, final SystemOperationMode som) {
this.systemInstance = systemInstance;
this.som = som;
}
public static BusLoadModel buildModel(final SystemInstance systemInstance, final SystemOperationMode som) {
return new BusLoadModelBuilder(systemInstance, som).build();
}
// ==== Wrap instance model elements with analysis model elements ====
private Bus getBus(final ComponentInstance ci) {
if (ci.getCategory() != ComponentCategory.BUS) {
throw new IllegalArgumentException("Component instance is not a bus");
}
Bus bus = buses.get(ci);
if (bus == null) {
bus = BusloadFactory.eINSTANCE.createBus();
bus.setBusInstance(ci);
buses.put(ci, bus);
}
return bus;
}
private VirtualBus getVirtualBus(final ComponentInstance ci) {
if (ci.getCategory() != ComponentCategory.VIRTUAL_BUS) {
throw new IllegalArgumentException("Component instance is not a virtual bus");
}
VirtualBus vb = virtualBuses.get(ci);
if (vb == null) {
vb = BusloadFactory.eINSTANCE.createVirtualBus();
vb.setBusInstance(ci);
virtualBuses.put(ci, vb);
}
return vb;
}
private static Connection getConnection(final ConnectionInstance connInstance) {
final Connection c = BusloadFactory.eINSTANCE.createConnection();
c.setConnectionInstance(connInstance);
return c;
}
// ==== Methods to build the model ====
public BusLoadModel build() {
final BusLoadModel model = BusloadFactory.eINSTANCE.createBusLoadModel();
final ForAllElement mal = new ForAllElement() {
@Override
protected void process(final Element obj) {
final ComponentInstance ci = (ComponentInstance) obj;
final ComponentCategory cat = ci.getCategory();
if (cat == ComponentCategory.BUS) {
addBus(model, ci, som);
} else if (cat == ComponentCategory.VIRTUAL_BUS) {
addVirtualBus(model, ci, som);
}
}
};
mal.processPreOrderComponentInstance(systemInstance);
return model;
}
private void addBus(final BusLoadModel model, final ComponentInstance bus, final SystemOperationMode som) {
final Bus theBus = getBus(bus);
model.getRootBuses().add(theBus);
addBusOrVirtualBus(model, theBus, bus, som);
}
private void addVirtualBus(final BusLoadModel model, final ComponentInstance vb,
final SystemOperationMode som) {
final VirtualBus theVirtualBus = getVirtualBus(vb);
// Node will attached to the model by the (virtual) bus that it is bound to
addBusOrVirtualBus(model, theVirtualBus, vb, som);
}
private void addBusOrVirtualBus(final BusLoadModel model, final BusOrVirtualBus bus, final ComponentInstance ci,
final SystemOperationMode som) {
final boolean isBroadcast = Sei.getBroadcastProtocol(ci).orElse(false);
List<ConnectionInstance> budgetedConnections = InstanceModelUtil.getBoundConnections(ci);
List<ComponentInstance> budgetedVBs = InstanceModelUtil.getBoundVirtualBuses(ci);
// Make sure the connections exist in the current mode
budgetedConnections = filterInMode(budgetedConnections, som);
/*
* If the bus is broadcast only, send a message from each source feature once. So we only keep one connection
* for each source feature.
*/
if (isBroadcast) {
budgetedConnections = findBroadcasts(budgetedConnections, bus);
}
// Make sure the virtual buses exist in the current mode
budgetedVBs = filterInMode(budgetedVBs, som);
budgetedConnections.forEach(connInstance -> bus.getBoundConnections().add(getConnection(connInstance)));
budgetedVBs.forEach(vb -> bus.getBoundVirtualBuses().add(getVirtualBus(vb)));
}
private static <E extends InstanceObject> List<E> filterInMode(final List<E> instanceObjects,
final SystemOperationMode som) {
final List<E> result = new ArrayList<>();
for (final E io : instanceObjects) {
if (io.isActive(som)) {
result.add(io);
}
}
return result;
}
private static final class FeatureComparator implements Comparator<ConnectionInstanceEnd> {
public static final FeatureComparator INSTANCE = new FeatureComparator();
private FeatureComparator() {
super();
}
@Override
public int compare(final ConnectionInstanceEnd o1, final ConnectionInstanceEnd o2) {
final String name1 = o1.getInstanceObjectPath();
final String name2 = o2.getInstanceObjectPath();
return name1.compareTo(name2);
}
}
/*
* Build go through the list of connections and sort based on the connection source. For each
* group, create a new Broadcast child of the bus. Returns the list of remaining individual connections.
*/
private static List<ConnectionInstance> findBroadcasts(final List<ConnectionInstance> connections,
final BusOrVirtualBus bus) {
// Linked list is better for removals, below
final List<ConnectionInstance> nonBroadcast = new LinkedList<>(connections);
// Group all the connections by their source feature
final Map<ConnectionInstanceEnd, List<ConnectionInstance>> broadcastGroups = new TreeMap<>(
FeatureComparator.INSTANCE);
for (final ConnectionInstance ci : connections) {
final ConnectionInstanceEnd src = ci.getSource();
List<ConnectionInstance> list = broadcastGroups.get(src);
if (list == null) {
list = new ArrayList<ConnectionInstance>();
broadcastGroups.put(src, list);
}
list.add(ci);
}
/*
* Create a Broadcast object for each group with size > 1, and then remove the
* connections from the noBroadcast list. We do this, instead of building a new
* list with the groups of size 1 so that the order of the list remains deterministic
* and not influenced by hashing in the map.
*
* We should have alphabetical order for the broadcast groups (from the TreeMap)
* and declarative order for the singletons.
*/
for (final Map.Entry<ConnectionInstanceEnd, List<ConnectionInstance>> group : broadcastGroups.entrySet()) {
final ConnectionInstanceEnd groupSource = group.getKey();
final List<ConnectionInstance> groupedConnections = group.getValue();
if (groupedConnections.size() > 1) {
nonBroadcast.removeAll(groupedConnections);
final Broadcast broadcast = BusloadFactory.eINSTANCE.createBroadcast();
broadcast.setSource(groupSource);
groupedConnections.forEach(connInstance -> broadcast.getConnections().add(getConnection(connInstance)));
bus.getBoundBroadcasts().add(broadcast);
}
}
return nonBroadcast;
}
}