CreateAadlConnectionPaletteCommand.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.ge.aadl2.ui.internal.palette;
import java.util.LinkedList;
import java.util.Optional;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.osate.aadl2.Aadl2Package;
import org.osate.aadl2.AccessCategory;
import org.osate.aadl2.AccessConnection;
import org.osate.aadl2.AccessConnectionEnd;
import org.osate.aadl2.BusAccess;
import org.osate.aadl2.ComponentImplementation;
import org.osate.aadl2.ConnectedElement;
import org.osate.aadl2.ConnectionEnd;
import org.osate.aadl2.Context;
import org.osate.aadl2.DataAccess;
import org.osate.aadl2.Feature;
import org.osate.aadl2.FeatureConnectionEnd;
import org.osate.aadl2.FeatureGroupConnectionEnd;
import org.osate.aadl2.ParameterConnectionEnd;
import org.osate.aadl2.PortConnectionEnd;
import org.osate.aadl2.Subcomponent;
import org.osate.aadl2.SubprogramAccess;
import org.osate.aadl2.SubprogramCall;
import org.osate.aadl2.SubprogramCallSequence;
import org.osate.aadl2.SubprogramGroupAccess;
import org.osate.aadl2.SubprogramSubcomponent;
import org.osate.ge.BusinessObjectContext;
import org.osate.ge.StringUtil;
import org.osate.ge.aadl2.AadlCategories;
import org.osate.ge.aadl2.internal.AadlImages;
import org.osate.ge.aadl2.internal.AadlNamingUtil;
import org.osate.ge.aadl2.internal.util.AadlConnectionUtil;
import org.osate.ge.aadl2.internal.util.AgeAadlUtil;
import org.osate.ge.aadl2.ui.AadlOperationBuilder;
import org.osate.ge.operations.Operation;
import org.osate.ge.operations.StepResultBuilder;
import org.osate.ge.palette.BasePaletteCommand;
import org.osate.ge.palette.CanStartConnectionContext;
import org.osate.ge.palette.CreateConnectionPaletteCommand;
import org.osate.ge.palette.GetCreateConnectionOperationContext;
public class CreateAadlConnectionPaletteCommand extends BasePaletteCommand implements CreateConnectionPaletteCommand {
private final EClass connectionType;
public CreateAadlConnectionPaletteCommand(final EClass connectionType) {
super(StringUtil.camelCaseToUser(connectionType.getName()),
AadlCategories.CONNECTIONS,
AadlImages.getImage(connectionType));
this.connectionType = connectionType;
}
@Override
public boolean canStartConnection(final CanStartConnectionContext ctx) {
return ctx.getSource().getBusinessObject(ConnectionEnd.class).map(srcConnectionEnd -> {
// Perform type specific connection start connection validity check
final Class<?> connectionEndType = getConnectionEndType(connectionType);
if (connectionEndType == null || !connectionEndType.isInstance(srcConnectionEnd)) {
return false;
}
return true;
}).orElse(false);
}
@Override
public Optional<Operation> getOperation(final GetCreateConnectionOperationContext ctx) {
final BusinessObjectContext ownerBoc = getOwnerBoc(ctx.getSource(), ctx.getDestination());
if (ownerBoc == null) {
return Optional.empty();
}
if (!canCreate(ownerBoc, ctx.getSource(), ctx.getDestination())) {
return Optional.empty();
}
return Optional.ofNullable(
buildCreateOperation(ownerBoc, ctx.getSource(), ctx.getDestination()));
}
private boolean canCreate(final BusinessObjectContext ownerBoc, final BusinessObjectContext srcBoc,
final BusinessObjectContext dstBoc) {
// Get the connection elements for the source and destination
final ConnectedElement srcConnectedElement = getConnectedElementForBusinessObjectContext(srcBoc, connectionType,
false, ownerBoc);
if (srcConnectedElement == null) {
return false;
}
final ConnectedElement dstConnectedElement = getConnectedElementForBusinessObjectContext(dstBoc, connectionType,
!(srcConnectedElement.getContext() instanceof Subcomponent), ownerBoc);
// Ensure they are valid and are not the same
if (dstConnectedElement == null || EcoreUtil.equals(srcConnectedElement, dstConnectedElement)) {
return false;
}
// Don't allow connecting two features owned by the same classifier
if (!(srcConnectedElement.getContext() instanceof Subcomponent
|| srcConnectedElement.getContext() instanceof SubprogramCall)
&& srcConnectedElement.getConnectionEnd() instanceof Feature
&& !(dstConnectedElement.getContext() instanceof Subcomponent
|| dstConnectedElement.getContext() instanceof SubprogramCall)
&& dstConnectedElement.getConnectionEnd() instanceof Feature) {
return false;
}
final Class<?> connectionEndType = getConnectionEndType(connectionType);
if (connectionEndType == null || !connectionEndType.isInstance(dstConnectedElement.getConnectionEnd())) {
return false;
}
return ownerBoc == null ? false : getClassifierOpBuilder().canBuildOperation(ownerBoc.getBusinessObject());
}
public Operation buildCreateOperation(final BusinessObjectContext ownerBoc, final BusinessObjectContext srcBoc,
final BusinessObjectContext dstBoc) {
return Operation.createWithBuilder(createOp -> {
// Create the subcomponent
getClassifierOpBuilder().buildOperation(createOp, ownerBoc.getBusinessObject())
.modifyPreviousResult(owner -> {
// Create the appropriate type of connection object
final org.osate.aadl2.Connection newAadlConnection = AadlConnectionUtil.createConnection(owner,
connectionType);
if (newAadlConnection == null) {
return null;
}
// Reset the no connections flag
owner.setNoConnections(false);
// Set the source and destination
final ConnectedElement src = getConnectedElementForBusinessObjectContext(srcBoc, connectionType,
false, ownerBoc);
newAadlConnection.setSource(src);
final ConnectedElement dst = getConnectedElementForBusinessObjectContext(dstBoc, connectionType,
!(src.getContext() instanceof Subcomponent), ownerBoc);
newAadlConnection.setDestination(dst);
// Determine the name for the new connection
final String newConnectionName = AadlNamingUtil.buildUniqueIdentifier(owner, "new_connection");
newAadlConnection.setName(newConnectionName);
// Set type of access connection
if (newAadlConnection instanceof AccessConnection) {
final AccessConnection ac = (AccessConnection) newAadlConnection;
if (src.getConnectionEnd() instanceof SubprogramAccess
|| dst.getConnectionEnd() instanceof SubprogramAccess) {
ac.setAccessCategory(AccessCategory.SUBPROGRAM);
} else if (src.getConnectionEnd() instanceof SubprogramGroupAccess
|| dst.getConnectionEnd() instanceof SubprogramGroupAccess) {
ac.setAccessCategory(AccessCategory.SUBPROGRAM_GROUP);
} else if (src.getConnectionEnd() instanceof BusAccess
|| dst.getConnectionEnd() instanceof BusAccess) {
ac.setAccessCategory(AccessCategory.BUS);
} else if (src.getConnectionEnd() instanceof DataAccess
|| dst.getConnectionEnd() instanceof DataAccess) {
ac.setAccessCategory(AccessCategory.DATA);
}
}
return StepResultBuilder.create().showNewBusinessObject(ownerBoc, newAadlConnection).build();
});
});
}
private static AadlOperationBuilder<ComponentImplementation> getClassifierOpBuilder() {
return AadlOperationBuilder.componentImplementations();
}
/**
* Retrieves the appropriate interface for connection ends for the current connection type
* @return
*/
private Class<?> getConnectionEndType(final EClass connectionType) {
final Class<?> connectionEndType;
final Aadl2Package p = Aadl2Package.eINSTANCE;
if (connectionType == p.getAccessConnection()) {
connectionEndType = AccessConnectionEnd.class;
} else if (connectionType == p.getFeatureConnection()) {
connectionEndType = FeatureConnectionEnd.class;
} else if (connectionType == p.getFeatureGroupConnection()) {
connectionEndType = FeatureGroupConnectionEnd.class;
} else if (connectionType == p.getParameterConnection()) {
connectionEndType = ParameterConnectionEnd.class;
} else if (connectionType == p.getPortConnection()) {
connectionEndType = PortConnectionEnd.class;
} else {
connectionEndType = null;
}
return connectionEndType;
}
/**
* Builds a ConnectedElement for the specific business object.
* @param boc
* @param connectionType
* @param disableNesting
* @param limitBoc is an optional business object context which limits the path of the connected element
* @return
*/
private ConnectedElement getConnectedElementForBusinessObjectContext(final BusinessObjectContext boc,
final EClass connectionType, final boolean disableNesting, final BusinessObjectContext limitBoc) {
final Object bo = boc.getBusinessObject();
if (!(bo instanceof ConnectionEnd)) {
return null;
}
// Build a list of business objects which make up the path to the connection end.
boolean pathIncludesSubcomponent = false;
final LinkedList<Object> path = new LinkedList<>();
for (BusinessObjectContext tmp = boc; tmp != null && tmp != limitBoc
&& tmp.getBusinessObject() != null; tmp = tmp.getParent()) {
if (tmp.getBusinessObject() instanceof ComponentImplementation
|| tmp.getBusinessObject() instanceof SubprogramCallSequence) {
break;
} else if (tmp.getBusinessObject() instanceof Subcomponent) {
if (pathIncludesSubcomponent) {
// If two subcomponents are encountered then the element is nested and is inaccessible.
return null;
}
pathIncludesSubcomponent = true;
}
path.add(0, tmp.getBusinessObject());
}
if (path.size() == 0) {
return null;
}
final Object[] pathObjects = path.toArray();
final Object firstBo = pathObjects[0];
final boolean allowNested = !disableNesting
&& connectionType == Aadl2Package.eINSTANCE.getFeatureConnection();
// If nesting is not allowed, then the number of objects must be at most 2.
if (!allowNested && pathObjects.length > 2) {
return null;
}
final ConnectedElement ce = AgeAadlUtil.getAadl2Factory().createConnectedElement();
// Add the context
int i = 0;
if (pathObjects.length > 1) {
if (!(firstBo instanceof Context)) {
return null;
}
ce.setContext((Context) firstBo);
i++;
}
// Add the first connection end
if (!(pathObjects[i] instanceof ConnectionEnd)) {
return null;
}
ce.setConnectionEnd((ConnectionEnd) pathObjects[i]);
i++;
// Add other segments
ConnectedElement tmp = ce;
for (; i < pathObjects.length; i++) {
tmp = tmp.createNext();
if (!(pathObjects[i] instanceof ConnectionEnd)) {
return null;
}
tmp.setConnectionEnd((ConnectionEnd) pathObjects[i]);
}
// SubprogramSubcomponent elements are never a valid context
if (ce.getContext() instanceof SubprogramSubcomponent) {
return null;
}
return ce;
}
private static BusinessObjectContext getOwnerBoc(final BusinessObjectContext srcBoc,
final BusinessObjectContext dstBoc) {
// Search for an appropriate owner. To be appropriate the owner BOC must be a component implementation or a subcomponent which is reachable from both
// the source and destination BOC.
int subcomponentsChecked1 = 0;
BusinessObjectContext temp1 = srcBoc.getParent();
while (temp1 != null) {
final Object bo1 = temp1.getBusinessObject();
if (bo1 instanceof Subcomponent || bo1 instanceof ComponentImplementation) {
BusinessObjectContext temp2 = dstBoc.getParent();
int subcomponentsChecked2 = 0;
while (temp2 != null) {
final Object bo2 = temp2.getBusinessObject();
if (bo2 instanceof Subcomponent || bo2 instanceof ComponentImplementation) {
if (temp1 == temp2) {
return temp1;
}
if (bo2 instanceof Subcomponent) {
subcomponentsChecked2++;
// Stop checking at second subcomponent
if (subcomponentsChecked2 >= 2) {
break;
}
} else if (bo2 instanceof ComponentImplementation) {
// Stop checking at component implementations
break;
}
}
temp2 = temp2.getParent();
}
if (temp1.getBusinessObject() instanceof Subcomponent) {
subcomponentsChecked1++;
// Stop checking at second subcomponent
if (subcomponentsChecked1 >= 2) {
break;
}
} else if (bo1 instanceof ComponentImplementation) {
// Stop checking at component implementations
break;
}
}
temp1 = temp1.getParent();
}
return null;
}
}