InstanceLinkingService.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.aadl2.instance.textual.linking;
import static org.eclipse.xtext.EcoreUtil2.getContainerOfType;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Stream;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.xtext.linking.impl.DefaultLinkingService;
import org.eclipse.xtext.linking.impl.IllegalNodeException;
import org.eclipse.xtext.naming.IQualifiedNameConverter;
import org.eclipse.xtext.naming.QualifiedName;
import org.eclipse.xtext.nodemodel.INode;
import org.eclipse.xtext.resource.impl.ResourceDescriptionsProvider;
import org.osate.aadl2.Aadl2Package;
import org.osate.aadl2.BasicProperty;
import org.osate.aadl2.BasicPropertyAssociation;
import org.osate.aadl2.BehavioredImplementation;
import org.osate.aadl2.Classifier;
import org.osate.aadl2.ClassifierFeature;
import org.osate.aadl2.ComponentClassifier;
import org.osate.aadl2.ComponentImplementation;
import org.osate.aadl2.ComponentType;
import org.osate.aadl2.EnumerationType;
import org.osate.aadl2.FeatureGroupType;
import org.osate.aadl2.ModeTransition;
import org.osate.aadl2.NumberType;
import org.osate.aadl2.PropertyAssociation;
import org.osate.aadl2.RangeType;
import org.osate.aadl2.RecordType;
import org.osate.aadl2.RecordValue;
import org.osate.aadl2.UnitsType;
import org.osate.aadl2.instance.ComponentInstance;
import org.osate.aadl2.instance.FeatureInstance;
import org.osate.aadl2.instance.InstanceObject;
import org.osate.aadl2.instance.InstancePackage;
import org.osate.aadl2.instance.SystemInstance;
import org.osate.aadl2.modelsupport.util.AadlUtil;
import com.google.inject.Inject;
public class InstanceLinkingService extends DefaultLinkingService {
private static final InstancePackage INSTANCE_PACKAGE = InstancePackage.eINSTANCE;
private static final Aadl2Package AADL2_PACKAGE = Aadl2Package.eINSTANCE;
private final ResourceDescriptionsProvider rdp;
private final IQualifiedNameConverter qualifiedNameConverter;
@Inject
public InstanceLinkingService(ResourceDescriptionsProvider rdp, IQualifiedNameConverter qualifiedNameConverter) {
this.rdp = rdp;
this.qualifiedNameConverter = qualifiedNameConverter;
}
@Override
public List<EObject> getLinkedObjects(EObject context, EReference ref, INode node) throws IllegalNodeException {
var crossRefString = getCrossRefNodeAsString(node).replaceAll("\\s", "");
EObject result = null;
if (crossRefString != null && !crossRefString.isEmpty()) {
var qName = qualifiedNameConverter.toQualifiedName(crossRefString);
if (INSTANCE_PACKAGE.getSystemInstance_ComponentImplementation().equals(ref)) {
result = getExportedObject(context, AADL2_PACKAGE.getComponentImplementation(),
qName.getFirstSegment());
} else if (INSTANCE_PACKAGE.getFeatureInstance_Feature().equals(ref)) {
result = getClassifierFeature(context, AADL2_PACKAGE.getClassifier(), qName, classifier -> {
if (classifier instanceof ComponentType component) {
return component.getOwnedFeatures();
} else if (classifier instanceof FeatureGroupType featureGroup) {
return featureGroup.getOwnedFeatures();
} else {
return Collections.emptyList();
}
});
} else if (INSTANCE_PACKAGE.getComponentInstance_InMode().equals(ref)) {
var container = getContainerOfType(context.eContainer(), ComponentInstance.class);
if (container != null) {
result = container.getModeInstances()
.stream()
.filter(mode -> mode.getName().equals(qName.getFirstSegment()))
.findFirst()
.orElse(null);
}
} else if (INSTANCE_PACKAGE.getComponentInstance_Classifier().equals(ref)) {
result = getExportedObject(context, AADL2_PACKAGE.getClassifier(), qName.getFirstSegment());
} else if (INSTANCE_PACKAGE.getComponentInstance_Subcomponent().equals(ref)) {
result = getClassifierFeature(context, AADL2_PACKAGE.getComponentImplementation(), qName,
ComponentImplementation::getOwnedSubcomponents);
} else if (INSTANCE_PACKAGE.getConnectionInstance_Source().equals(ref)
|| INSTANCE_PACKAGE.getConnectionInstance_Destination().equals(ref)
|| INSTANCE_PACKAGE.getConnectionReference_Source().equals(ref)
|| INSTANCE_PACKAGE.getConnectionReference_Destination().equals(ref)
|| INSTANCE_PACKAGE.getFlowSpecificationInstance_Source().equals(ref)
|| INSTANCE_PACKAGE.getFlowSpecificationInstance_Destination().equals(ref)
|| INSTANCE_PACKAGE.getEndToEndFlowInstance_FlowElement().equals(ref)
|| INSTANCE_PACKAGE.getSystemOperationMode_CurrentMode().equals(ref)
|| INSTANCE_PACKAGE.getModeTransitionInstance_Trigger().equals(ref)) {
result = getInstanceObject(context, qName, ref);
} else if (INSTANCE_PACKAGE.getConnectionInstance_InSystemOperationMode().equals(ref)
|| INSTANCE_PACKAGE.getEndToEndFlowInstance_InSystemOperationMode().equals(ref)
|| AADL2_PACKAGE.getModalElement_InMode().equals(ref)) {
result = getContainerOfType(context, SystemInstance.class).getSystemOperationModes()
.stream()
.filter(som -> som.getName().equals(qName.getFirstSegment()))
.findFirst()
.orElse(null);
} else if (INSTANCE_PACKAGE.getConnectionInstance_InModeTransition().equals(ref)
|| INSTANCE_PACKAGE.getFlowSpecificationInstance_InModeTransition().equals(ref)) {
result = guardedGet(getContainerOfType(context, ComponentInstance.class).getModeTransitionInstances(),
toIndex(qName.getFirstSegment()));
} else if (INSTANCE_PACKAGE.getConnectionReference_Connection().equals(ref)) {
result = this.<ComponentImplementation> getClassifierFeature(context,
AADL2_PACKAGE.getComponentImplementation(), qName,
ComponentImplementation::getOwnedConnections);
} else if (INSTANCE_PACKAGE.getConnectionReference_Context().equals(ref)) {
if (qName.getFirstSegment().equals("parent")) {
result = getContainerOfType(context, ComponentInstance.class);
} else {
result = getInstanceObject(context, qName, ref);
}
} else if (INSTANCE_PACKAGE.getFlowSpecificationInstance_InMode().equals(ref)
|| INSTANCE_PACKAGE.getModeTransitionInstance_Source().equals(ref)
|| INSTANCE_PACKAGE.getModeTransitionInstance_Destination().equals(ref)) {
result = getContainerOfType(context, ComponentInstance.class)
.getModeInstances()
.stream()
.filter(mode -> mode.getName().equals(qName.getFirstSegment()))
.findFirst()
.orElse(null);
} else if (INSTANCE_PACKAGE.getFlowSpecificationInstance_FlowSpecification().equals(ref)) {
result = getClassifierFeature(context, AADL2_PACKAGE.getComponentType(), qName,
ComponentType::getOwnedFlowSpecifications);
} else if (INSTANCE_PACKAGE.getEndToEndFlowInstance_EndToEndFlow().equals(ref)) {
result = getClassifierFeature(context, AADL2_PACKAGE.getComponentImplementation(), qName,
ComponentImplementation::getOwnedEndToEndFlows);
} else if (INSTANCE_PACKAGE.getModeInstance_Parent().equals(ref)) {
var container = getContainerOfType(getContainerOfType(context, ComponentInstance.class).eContainer(),
ComponentInstance.class);
if (container != null) {
result = container.getModeInstances()
.stream()
.filter(mode -> mode.getName().equals(qName.getFirstSegment()))
.findFirst()
.orElse(null);
}
} else if (INSTANCE_PACKAGE.getModeInstance_Mode().equals(ref)) {
result = getClassifierFeature(context, AADL2_PACKAGE.getComponentClassifier(), qName,
ComponentClassifier::getOwnedModes);
} else if (INSTANCE_PACKAGE.getModeTransitionInstance_ModeTransition().equals(ref)) {
var segments = splitDeclarative(qName.getFirstSegment());
if (segments.size() == 2) {
var classifier = getExportedObject(context, AADL2_PACKAGE.getComponentClassifier(),
segments.get(0));
if (classifier != null) {
var resolved = (ComponentClassifier) EcoreUtil.resolve(classifier, context);
if (segments.get(1).startsWith("transition#")) {
result = getUnnamedTransition(resolved, qName.getLastSegment());
} else {
result = resolved.getOwnedModeTransitions()
.stream()
.filter(transition -> transition.getName().equals(segments.get(1)))
.findFirst()
.orElse(null);
}
}
}
} else if (AADL2_PACKAGE.getPropertyAssociation_Property().equals(ref)) {
result = getExportedObject(context, AADL2_PACKAGE.getProperty(), qName.getFirstSegment());
} else if (INSTANCE_PACKAGE.getPropertyAssociationInstance_PropertyAssociation().equals(ref)) {
var segments = splitDeclarative(qName.getFirstSegment());
if (segments.size() == 2) {
var classifier = getExportedObject(context, AADL2_PACKAGE.getClassifier(), segments.get(0));
if (classifier != null) {
var resolved = (Classifier) EcoreUtil.resolve(classifier, context);
result = guardedGet(resolved.getOwnedPropertyAssociations(), toIndex(segments.get(1)));
}
} else if (segments.size() == 3) {
var classifier = getExportedObject(context, AADL2_PACKAGE.getClassifier(), segments.get(0));
if (classifier != null) {
var resolved = EcoreUtil.resolve(classifier, context);
ClassifierFeature element = null;
if (resolved instanceof FeatureGroupType featureGroup) {
element = featureGroup.getOwnedFeatures()
.stream()
.filter(feature -> feature.getName().equals(segments.get(1)))
.findFirst()
.orElse(null);
} else if (resolved instanceof ComponentClassifier component
&& segments.get(1).startsWith("transition#")) {
element = getUnnamedTransition(component, segments.get(1));
} else if (resolved instanceof ComponentType type) {
element = Stream
.of(type.getOwnedFeatures(), type.getOwnedFlowSpecifications(),
type.getOwnedModes(), type.getOwnedModeTransitions())
.flatMap(Collection::stream)
.filter(member -> member.getName().equals(segments.get(1)))
.findFirst()
.orElse(null);
} else if (resolved instanceof ComponentImplementation impl) {
element = Stream
.of(impl.getOwnedModes(), impl.getOwnedSubcomponents(), impl.getOwnedConnections(),
impl.getOwnedEndToEndFlows(), impl.getOwnedModeTransitions())
.flatMap(Collection::stream)
.filter(member -> member.getName().equals(segments.get(1)))
.findFirst()
.orElse(null);
}
if (element != null) {
result = guardedGet(element.getOwnedPropertyAssociations(), toIndex(segments.get(2)));
}
}
}
} else if (AADL2_PACKAGE.getContainmentPathElement_NamedElement().equals(ref)) {
result = getClassifierFeature(context, AADL2_PACKAGE.getClassifier(), qName, classifier -> {
if (classifier instanceof FeatureGroupType featureGroup) {
return Stream.of(featureGroup.getOwnedPrototypes(), featureGroup.getOwnedFeatureGroups())
.flatMap(Collection::stream)
.toList();
} else if (classifier instanceof ComponentType type) {
return Stream.of(getComponentClassifierReferenceElements(type), type.getOwnedFeatureGroups())
.flatMap(Collection::stream)
.toList();
} else if (classifier instanceof BehavioredImplementation impl) {
return Stream
.of(getImplReferenceElements(impl), impl.getOwnedSubprogramCallSequences(),
impl.getSubprogramCalls())
.flatMap(Collection::stream)
.toList();
} else if (classifier instanceof ComponentImplementation impl) {
return getImplReferenceElements(impl);
} else {
return Collections.emptyList();
}
});
} else if (INSTANCE_PACKAGE.getInstanceReferenceValue_ReferencedInstanceObject().equals(ref)) {
result = getInstanceObject(getContainerOfType(context, SystemInstance.class), qName, ref);
} else if (AADL2_PACKAGE.getClassifierValue_Classifier().equals(ref)) {
result = getExportedObject(context, AADL2_PACKAGE.getClassifier(), qName.getFirstSegment());
} else if (AADL2_PACKAGE.getNamedValue_NamedValue().equals(ref)) {
if (qName.getFirstSegment().contains("::")) {
result = getExportedObject(context, AADL2_PACKAGE.getProperty(), qName.getFirstSegment());
if (result == null) {
result = getExportedObject(context, AADL2_PACKAGE.getPropertyConstant(),
qName.getFirstSegment());
}
} else {
var property = getProperty(context);
if (property != null) {
var type = AadlUtil.getBasePropertyType(property.getPropertyType());
if (type instanceof EnumerationType enumType) {
result = enumType.getOwnedLiterals()
.stream()
.filter(literal -> literal.getName().equals(qName.getFirstSegment()))
.findFirst()
.orElse(null);
}
}
}
} else if (AADL2_PACKAGE.getNumberValue_Unit().equals(ref)) {
var property = getProperty(context);
if (property != null) {
var type = AadlUtil.getBasePropertyType(property.getPropertyType());
UnitsType unitsType = null;
if (type instanceof NumberType numberType) {
unitsType = numberType.getUnitsType();
} else if (type instanceof RangeType rangeType) {
unitsType = rangeType.getNumberType().getUnitsType();
}
if (unitsType != null) {
result = unitsType.getOwnedLiterals()
.stream()
.filter(unit -> unit.getName().equals(qName.getFirstSegment()))
.findFirst()
.orElse(null);
}
}
} else if (AADL2_PACKAGE.getBasicPropertyAssociation_Property().equals(ref)) {
var property = getProperty(getContainerOfType(context, RecordValue.class));
if (property != null) {
if (AadlUtil.getBasePropertyType(property.getPropertyType()) instanceof RecordType recordType) {
result = recordType.getOwnedFields()
.stream()
.filter(field -> field.getName().equals(qName.getFirstSegment()))
.findFirst()
.orElse(null);
}
}
}
}
if (result != null) {
return List.of(result);
} else {
return Collections.emptyList();
}
}
private EObject getExportedObject(EObject context, EClass type, String name) {
var qName = QualifiedName.create(name.split("::"));
var exported = rdp.getResourceDescriptions(context.eResource()).getExportedObjects(type, qName, false);
for (var obj : exported) {
return obj.getEObjectOrProxy();
}
return null;
}
private <T extends Classifier> ClassifierFeature getClassifierFeature(EObject context, EClass type,
QualifiedName qName, Function<T, List<? extends ClassifierFeature>> featureGetter) {
var segments = splitDeclarative(qName.getFirstSegment());
if (segments.size() == 2) {
var exported = getExportedObject(context, type, segments.get(0));
if (exported != null) {
@SuppressWarnings("unchecked")
var classifier = (T) EcoreUtil.resolve(exported, context);
for (var feature : featureGetter.apply(classifier)) {
if (segments.get(1).equals(feature.getName())) {
return feature;
}
}
}
}
return null;
}
private static List<String> splitDeclarative(String s) {
var firstColon = s.indexOf(':', s.lastIndexOf("::") + 2);
if (firstColon == -1) {
return List.of(s);
} else if (firstColon == s.length() - 1) {
return List.of(s.substring(0, firstColon));
} else {
var splitArray = s.substring(firstColon + 1).split(":");
return Stream.concat(Stream.of(s.substring(0, firstColon)), Arrays.stream(splitArray)).toList();
}
}
private static InstanceObject getInstanceObject(EObject context, QualifiedName qName, EReference ref) {
InstanceObject element = getContainerOfType(context, ComponentInstance.class);
for (var segment : qName.getFirstSegment().split("\\.")) {
if (element != null) {
element = getNextObject(element, segment);
}
}
if (element != null && ref.getEReferenceType().isSuperTypeOf(element.eClass())) {
return element;
} else {
return null;
}
}
private static InstanceObject getNextObject(InstanceObject container, String segment) {
var bracketIndex = segment.indexOf('[');
if (bracketIndex != -1) {
var requestedName = segment.substring(0, bracketIndex);
var requestedIndices = Arrays
.stream(segment.substring(bracketIndex + 1, segment.length() - 1).split("\\]\\["))
.map(Long::parseLong)
.toList();
if (container instanceof FeatureInstance feature && requestedIndices.size() == 1) {
return feature.getFeatureInstances()
.stream()
.filter(innerFeature -> innerFeature.getIndex() == requestedIndices.get(0)
&& innerFeature.getName().equals(requestedName))
.findFirst()
.orElse(null);
} else if (container instanceof ComponentInstance component) {
var features = component.getFeatureInstances()
.stream()
.filter(feature -> feature.getIndex() == requestedIndices.get(0));
var components = component.getComponentInstances()
.stream()
.filter(innerComponent -> innerComponent.getIndices().equals(requestedIndices));
return Stream.of(features, components)
.flatMap(Function.identity())
.filter(member -> member.getName().equals(requestedName))
.findFirst()
.orElse(null);
} else {
return null;
}
} else if (container instanceof FeatureInstance feature) {
return feature.getFeatureInstances()
.stream()
.filter(innerFeature -> innerFeature.getIndex() == 0 && innerFeature.getName().equals(segment))
.findFirst()
.orElse(null);
} else if (container instanceof ComponentInstance component) {
if (segment.startsWith("connection#")) {
return guardedGet(component.getConnectionInstances(), toIndex(segment));
} else {
var features = component.getFeatureInstances().stream().filter(feature -> feature.getIndex() == 0);
var components = component.getComponentInstances()
.stream()
.filter(innerComponent -> innerComponent.getIndices().isEmpty());
return Stream
.of(features, components, component.getFlowSpecifications().stream(),
component.getEndToEndFlows().stream(), component.getModeInstances().stream(),
component.getModeTransitionInstances().stream())
.flatMap(Function.identity())
.filter(member -> member.getName().equals(segment))
.findFirst()
.orElse(null);
}
} else {
return null;
}
}
private static <T> T guardedGet(List<T> list, int index) {
if (index < list.size()) {
return list.get(index);
} else {
return null;
}
}
private static int toIndex(String s) {
return Integer.parseInt(s.substring(s.indexOf('#') + 1));
}
private static ModeTransition getUnnamedTransition(ComponentClassifier classifier, String indexString) {
var transition = guardedGet(classifier.getOwnedModeTransitions(), toIndex(indexString));
if (transition != null && transition.getName() == null) {
return transition;
} else {
return null;
}
}
private static BasicProperty getProperty(EObject context) {
var bpa = getContainerOfType(context, BasicPropertyAssociation.class);
if (bpa != null) {
return bpa.getProperty();
} else {
return getContainerOfType(context, PropertyAssociation.class).getProperty();
}
}
private static List<? extends ClassifierFeature> getComponentClassifierReferenceElements(
ComponentClassifier classifier) {
return Stream.of(classifier.getOwnedPrototypes(), classifier.getOwnedModeTransitions())
.flatMap(Collection::stream)
.filter(element -> element.getName() != null)
.toList();
}
private static List<? extends ClassifierFeature> getImplReferenceElements(ComponentImplementation impl) {
return Stream
.of(getComponentClassifierReferenceElements(impl), impl.getOwnedSubcomponents(),
impl.getOwnedInternalFeatures(), impl.getOwnedProcessorFeatures())
.flatMap(Collection::stream)
.toList();
}
}