DefaultBusinessObjectTreeUpdater.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.internal.diagram.runtime.updating;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.xtext.resource.IEObjectDescription;
import org.osate.aadl2.Aadl2Package;
import org.osate.aadl2.ClassifierValue;
import org.osate.aadl2.Property;
import org.osate.aadl2.instance.InstanceObject;
import org.osate.aadl2.instance.InstanceReferenceValue;
import org.osate.aadl2.util.Aadl2Util;
import org.osate.ge.BusinessObjectContext;
import org.osate.ge.ContentFilter;
import org.osate.ge.DiagramType;
import org.osate.ge.FundamentalContentFilter;
import org.osate.ge.RelativeBusinessObjectReference;
import org.osate.ge.aadl2.internal.aadlproperties.AadlPropertyResolutionResults;
import org.osate.ge.aadl2.internal.aadlproperties.AadlPropertyResolver;
import org.osate.ge.aadl2.internal.aadlproperties.AadlPropertyUtil;
import org.osate.ge.aadl2.internal.aadlproperties.PropertyResult;
import org.osate.ge.aadl2.internal.aadlproperties.PropertyResult.NullReason;
import org.osate.ge.aadl2.internal.aadlproperties.PropertyValueUtil;
import org.osate.ge.aadl2.internal.aadlproperties.ReferenceValueWithContext;
import org.osate.ge.aadl2.internal.model.AgePropertyValue;
import org.osate.ge.aadl2.internal.model.PropertyValueGroup;
import org.osate.ge.aadl2.ui.AadlModelAccessUtil;
import org.osate.ge.internal.GraphicalEditorException;
import org.osate.ge.internal.diagram.runtime.DiagramConfiguration;
import org.osate.ge.internal.model.BusinessObjectProxy;
import org.osate.ge.internal.model.EmbeddedBusinessObject;
import org.osate.ge.internal.services.ExtensionRegistryService;
import org.osate.ge.internal.services.ProjectProvider;
import org.osate.ge.internal.services.ProjectReferenceService;
import org.osate.ge.internal.util.BusinessObjectProviderHelper;
import org.osate.ge.internal.util.ContentFilterUtil;
import org.osate.ge.internal.util.DiagramTypeUtil;
import org.osate.ge.services.QueryService;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
/**
* A {@link BusinessObjectTreeUpdater} which creates a tree which contains nodes based for business objects provided by registered business object
* providers. Uses {@link FundamentalContentFilter} instances and {@link ContentFilter} instances specified by the {@link DiagramType} to determine
* whether a node should be created for a business object. Diagram type content filters are only used for nodes which have not had their default
* children populated.
*
* Diagrams which have a context business object specified will only contain the specified business object or embedded business objects as children of the
* root. Diagrams which do not have a context may include any business objects which are returned by the business object providers when using the current
* IProject as the root business object context.
*/
public class DefaultBusinessObjectTreeUpdater implements BusinessObjectTreeUpdater {
private final ProjectProvider projectProvider;
private final ExtensionRegistryService extService;
private final ProjectReferenceService refService;
private final QueryService queryService;
private final BusinessObjectNodeFactory nodeFactory;
/**
* Creates a new instance
* @param projectProvider a project provider which returns the project in which diagram is located.
* @param extService the extension registry
* @param refService the reference service
* @param queryService the query service
* @param nodeFactory the node factory used to create new {@link BusinessObjectNode} instances
*/
public DefaultBusinessObjectTreeUpdater(final ProjectProvider projectProvider, final ExtensionRegistryService extService,
final ProjectReferenceService refService, final QueryService queryService,
final BusinessObjectNodeFactory nodeFactory) {
this.projectProvider = Objects.requireNonNull(projectProvider, "projectProvider must not be null");
this.extService = Objects.requireNonNull(extService, "extService must not be null");
this.refService = Objects.requireNonNull(refService, "refService must not be null");
this.queryService = Objects.requireNonNull(queryService, "queryService must not be null");
this.nodeFactory = Objects.requireNonNull(nodeFactory, "nodeFactory must not be null");
}
@Override
public BusinessObjectNode updateTree(final DiagramConfiguration configuration, final BusinessObjectNode tree) {
// Refresh Child Nodes
final BusinessObjectProviderHelper bopHelper = new BusinessObjectProviderHelper(extService);
final BusinessObjectNode newRoot = nodeFactory.create(null, UUID.randomUUID(), null, Completeness.UNKNOWN);
final Map<RelativeBusinessObjectReference, Object> boMap;
// If the context business object is non-null, then only one business object may exist at the root of the resulting tree and it must be the context
// business object.
// This restriction prevents the need to retrieve all packages as potential top level business objects.
// Determine what business objects are required based on the diagram configuration
if (configuration.getContextBoReference() == null) {
// Get potential top level business objects from providers
// A simple business object context which is used as the root BOC for contextless diagrams. It has no parent and used the current
// project as the business object.
final BusinessObjectContext contextlessRootBoc = new BusinessObjectContext() {
@Override
public Collection<? extends BusinessObjectContext> getChildren() {
return Collections.emptyList();
}
@Override
public BusinessObjectContext getParent() {
return null;
}
@Override
public Object getBusinessObject() {
return projectProvider.getProject();
}
};
final Collection<Object> potentialRootBusinessObjects = bopHelper
.getChildBusinessObjects(contextlessRootBoc);
// Determine the root business objects
final Set<RelativeBusinessObjectReference> existingRootBranches = tree.getChildrenMap().keySet();
// Content filters are not supported for the root of diagrams.
final ImmutableSet<ContentFilter> rootContentFilters = ImmutableSet.of();
boMap = getChildBusinessObjects(potentialRootBusinessObjects, existingRootBranches, rootContentFilters);
// Contextless diagrams are always considered complete
newRoot.setCompleteness(Completeness.COMPLETE);
// This is needed so the configure diagram dialog, etc will know which project should be used to
// retrieve root objects.
// Set the root of the BO tree to the project
newRoot.setBusinessObject(projectProvider.getProject());
} else {
// Get the context business object
Object contextBo = refService.resolve(configuration.getContextBoReference());
if (contextBo == null) {
final String contextLabel = refService.getLabel(configuration.getContextBoReference());
throw new GraphicalEditorException("Unable to find context business object: "
+ contextLabel);
}
// Require the use of the business object specified in the diagram along with any other business objects which are already in the diagram.
final RelativeBusinessObjectReference relativeReference = refService.getRelativeReference(contextBo);
if (relativeReference == null) {
throw new GraphicalEditorException(
"Unable to build relative reference for context business object: " + contextBo);
}
boMap = new HashMap<>();
boMap.put(relativeReference, contextBo);
newRoot.setCompleteness(Completeness.COMPLETE);
}
// Add embedded business objects to the child BO map
addEmbeddedBusinessObjectsToBoMap(tree.getChildrenMap().values(), boMap);
// Populate the new tree
final Map<RelativeBusinessObjectReference, BusinessObjectNode> oldNodes = tree.getChildrenMap();
createNodes(configuration.getDiagramType(), bopHelper, boMap, oldNodes, newRoot);
// Build set of the names of all properties which are enabled
final Set<String> enabledPropertyNames = new HashSet<>(configuration.getEnabledAadlPropertyNames());
enabledPropertyNames.add("communication_properties::timing"); // Add properties which are always enabled regardless of configuration setting
// Get the property objects
final Set<Property> enabledProperties = getPropertiesByLowercasePropertyNames(enabledPropertyNames);
// Process properties. This is done after everything else since properties may need to refer to other nodes.
final AadlPropertyResolver propertyResolver = new AadlPropertyResolver(newRoot);
processProperties(propertyResolver, newRoot, tree, enabledProperties);
return newRoot;
}
private Set<Property> getPropertiesByLowercasePropertyNames(final Set<String> lcPropertyNames) {
final ResourceSet resourceSet = new ResourceSetImpl();
final Set<Property> properties = new HashSet<>();
for (final IEObjectDescription desc : AadlModelAccessUtil.getAllEObjectsByType(projectProvider.getProject(),
Aadl2Package.eINSTANCE.getProperty())) {
final String lowercasePropertyName = desc.getName().toString("::").toLowerCase();
if (lcPropertyNames.contains(lowercasePropertyName)) {
EObject property = desc.getEObjectOrProxy();
property = EcoreUtil.resolve(property, resourceSet);
if (!Aadl2Util.isNull(property)) {
properties.add((Property) property);
}
}
}
return properties;
}
private void processProperties(final AadlPropertyResolver pr, final BusinessObjectNode node,
final BusinessObjectNode oldNode, final Collection<Property> properties) {
final Deque<Integer> indicesStack = new ArrayDeque<Integer>();
final Multimap<BusinessObjectNode, AgePropertyValue> dstToValues = HashMultimap.create();
processProperties(pr, node, oldNode, properties, indicesStack, dstToValues);
}
private void processProperties(final AadlPropertyResolver pr, final BusinessObjectNode node,
final BusinessObjectNode oldNode, final Collection<Property> properties, final Deque<Integer> indicesStack,
final Multimap<BusinessObjectNode, AgePropertyValue> dstToValues) {
for (final Property property : properties) {
// Get values from processed property associations and create property value objects while grouping them by destination.
final PropertyResult result = PropertyResult.getProcessedPropertyValue(pr, queryService, node, property);
if (result != null) {
// Don't show undefined or inherited property values
if (result.nullReason != NullReason.UNDEFINED) {
indicesStack.clear();
createPropertyValues(node, property, result, indicesStack, null, dstToValues);
}
}
// For reference properties, do the same for unprocessed references.
if (AadlPropertyUtil.isReferenceOrListReferenceType(property.getType())) {
// Create property values which reference children which are not included in the diagram. These are stored as property associations
// which have not been fully processed
// The key is the path to the element is applies to
final Map<String, PropertyResult> unprocessedReferencesResult = PropertyResult
.getIncompletelyProcessedReferencePropertyValues(pr, queryService, node, property);
if (!unprocessedReferencesResult.isEmpty()) {
// Convert to property result value objects and place in the multimap based on destination.
for (final Entry<String, PropertyResult> propertyResultEntry : unprocessedReferencesResult
.entrySet()) {
indicesStack.clear();
final String appliesToDescendantRef = propertyResultEntry.getKey();
createPropertyValues(node, property, propertyResultEntry.getValue(), indicesStack,
appliesToDescendantRef, dstToValues);
}
}
}
// Iterate over unique destinations and create business objects for groups.
for (final BusinessObjectNode dst : dstToValues.keySet()) {
final Collection<AgePropertyValue> dstPropertyValues = dstToValues.get(dst);
// Create the Property Value Group business object
final UUID dstId = dst == null ? null : dst.getId();
final PropertyValueGroup pvg = new PropertyValueGroup(property, dstId, dstPropertyValues);
// Create the business object node for the property value group
final RelativeBusinessObjectReference propRelRef = Objects
.requireNonNull(refService.getRelativeReference(pvg), "unable to get relative reference");
final BusinessObjectNode oldPropNode = oldNode == null ? null : oldNode.getChild(propRelRef);
final UUID propNodeId = oldPropNode == null ? UUID.randomUUID() : oldPropNode.getId();
new BusinessObjectNode(node, propNodeId, propRelRef, pvg, Completeness.COMPLETE, true);
}
dstToValues.clear(); // Clear map for the next use.
}
for (final BusinessObjectNode child : node.getChildren()) {
final BusinessObjectNode oldChild = oldNode == null ? null : oldNode.getChild(child.getRelativeReference());
processProperties(pr, child, oldChild, properties, indicesStack, dstToValues);
}
}
private void createPropertyValues(final BusinessObjectNode node, final Property property, final PropertyResult pr,
final Deque<Integer> indicesStack, final String appliesToDescendantRef,
final Multimap<BusinessObjectNode, AgePropertyValue> dstToPropertyValues) {
createPropertyValues(node, property, pr, pr.value, indicesStack, appliesToDescendantRef, dstToPropertyValues);
}
private void createPropertyValues(final BusinessObjectNode node, final Property property, final PropertyResult pr,
final Object value, final Deque<Integer> indicesStack, final String appliesToDescendantRef,
final Multimap<BusinessObjectNode, AgePropertyValue> dstToPropertyValues) {
if (value instanceof List) {
@SuppressWarnings("unchecked")
final List<Object> valueList = ((List<Object>) value);
int idx = 0;
for (final Object innerValue : valueList) {
indicesStack.addLast(idx);
createPropertyValues(node, property, pr, innerValue, indicesStack, appliesToDescendantRef,
dstToPropertyValues);
indicesStack.removeLast();
idx++;
}
} else {
final BusinessObjectNode dst;
boolean fullyResolved = true;
if (value instanceof ReferenceValueWithContext) {
final AadlPropertyResolutionResults rr = ((ReferenceValueWithContext) value).resolve(node,
queryService);
dst = (BusinessObjectNode) rr.dst;
fullyResolved = !rr.isPartial;
} else if (value instanceof ClassifierValue) {
dst = (BusinessObjectNode) PropertyValueUtil.getReferencedClassifier(node, (ClassifierValue) value,
queryService);
} else if (value instanceof InstanceReferenceValue) {
final InstanceReferenceValue irv = (InstanceReferenceValue) value;
final InstanceObject referencedInstanceObject = irv.getReferencedInstanceObject();
final AadlPropertyResolutionResults rr = PropertyValueUtil.getReferencedInstanceObject(node,
referencedInstanceObject, queryService);
dst = (BusinessObjectNode) rr.dst;
fullyResolved = !rr.isPartial;
} else {
dst = null;
}
dstToPropertyValues.put(dst, new AgePropertyValue(pr, indicesStack, appliesToDescendantRef, fullyResolved));
}
}
private void createNodes(final DiagramType diagramType, final BusinessObjectProviderHelper bopHelper,
final Map<RelativeBusinessObjectReference, Object> newBoMap,
final Map<RelativeBusinessObjectReference, BusinessObjectNode> oldNodeMap,
final BusinessObjectNode parentNode) {
for (final Entry<RelativeBusinessObjectReference, Object> childEntry : newBoMap.entrySet()) {
// Create node
final Object childBo = childEntry.getValue();
final RelativeBusinessObjectReference childRelReference = childEntry.getKey();
createNode(diagramType, bopHelper, oldNodeMap, parentNode, childBo, childRelReference);
}
}
private void createNode(final DiagramType diagramType, final BusinessObjectProviderHelper bopHelper,
final Map<RelativeBusinessObjectReference, BusinessObjectNode> oldNodeMap,
final BusinessObjectNode parentNode, final Object bo, final RelativeBusinessObjectReference relReference) {
// Get the node which is in the input tree from the old node map
final BusinessObjectNode oldNode = oldNodeMap.get(relReference);
// Create the node
final ImmutableSet<ContentFilter> contentFilters = oldNode == null
|| !oldNode.getDefaultChildrenHaveBeenPopulated() ? getDefaultContentFilters(diagramType, bo)
: ImmutableSet.of();
final UUID id = oldNode == null || oldNode.getId() == null ? UUID.randomUUID() : oldNode.getId();
final BusinessObjectNode newNode = nodeFactory.create(parentNode, id, bo, Completeness.UNKNOWN);
// Determine the business objects for which nodes in the tree should be created.
final Map<RelativeBusinessObjectReference, BusinessObjectNode> childOldNodes = oldNode == null
? Collections.emptyMap()
: oldNode.getChildrenMap();
final Collection<Object> childBusinessObjectsFromProviders = bopHelper.getChildBusinessObjects(newNode);
final Map<RelativeBusinessObjectReference, Object> childBoMap = getChildBusinessObjects(
childBusinessObjectsFromProviders, childOldNodes.keySet(), contentFilters);
// Update the business objects before considering embedded business objects
newNode.setCompleteness(childBusinessObjectsFromProviders.size() == childBoMap.size() ? Completeness.COMPLETE
: Completeness.INCOMPLETE);
addEmbeddedBusinessObjectsToBoMap(childOldNodes.values(), childBoMap);
createNodes(diagramType, bopHelper, childBoMap, childOldNodes, newNode);
}
private static void addEmbeddedBusinessObjectsToBoMap(final Collection<BusinessObjectNode> childOldNodes,
final Map<RelativeBusinessObjectReference, Object> boMap) {
// Add embedded business objects to the child BO map
for (final BusinessObjectNode childOldNode : childOldNodes) {
if (childOldNode.getBusinessObject() instanceof EmbeddedBusinessObject) {
boMap.put(childOldNode.getRelativeReference(), childOldNode.getBusinessObject());
}
}
}
private ImmutableSet<ContentFilter> getDefaultContentFilters(final DiagramType diagramType, final Object bo) {
return DiagramTypeUtil.getApplicableDefaultContentFilters(diagramType, bo, extService);
}
// Create a map between relative references and business objects for every business object which should be used as a child BO
// Create entries for all potential business objects which pass the filter or are in the forcedRefs set.
// Do not include objects unless relative reference exists
private Map<RelativeBusinessObjectReference, Object> getChildBusinessObjects(
final Collection<Object> potentialBusinessObjects, final Set<RelativeBusinessObjectReference> forcedRefs,
final ImmutableSet<ContentFilter> contentFilters) {
final Map<RelativeBusinessObjectReference, Object> results = new HashMap<>();
for (final Object potentialBusinessObject : potentialBusinessObjects) {
final RelativeBusinessObjectReference relativeReference = refService
.getRelativeReference(potentialBusinessObject);
if (relativeReference != null) {
if (forcedRefs.contains(relativeReference) || extService.isFundamental(potentialBusinessObject)
|| ContentFilterUtil.passesAnyContentFilter(potentialBusinessObject, contentFilters)) {
// Special handling of proxies. Only resolve them if they are needed
Object resolvedBo = potentialBusinessObject;
if (potentialBusinessObject instanceof BusinessObjectProxy) {
final BusinessObjectProxy proxy = (BusinessObjectProxy) potentialBusinessObject;
resolvedBo = proxy.resolve(refService);
}
results.put(relativeReference, resolvedBo);
}
}
}
return results;
}
}