FlowContributionItem.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.editor;
import java.util.AbstractMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.TreeMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jface.viewers.ComboViewer;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.IEditorPart;
import org.osate.aadl2.ComponentImplementation;
import org.osate.aadl2.NamedElement;
import org.osate.aadl2.Subcomponent;
import org.osate.aadl2.instance.ComponentInstance;
import org.osate.ge.BusinessObjectContext;
import org.osate.ge.aadl2.internal.util.AadlClassifierUtil;
import org.osate.ge.aadl2.internal.util.AadlFlowSpecificationUtil;
import org.osate.ge.aadl2.internal.util.AadlFlowSpecificationUtil.FlowSegmentReference;
import org.osate.ge.aadl2.internal.util.AadlInstanceObjectUtil;
import org.osate.ge.internal.diagram.runtime.AgeDiagram;
import org.osate.ge.internal.diagram.runtime.DiagramElement;
import org.osate.ge.internal.diagram.runtime.DiagramNode;
import org.osate.ge.internal.services.ModelChangeNotifier;
import org.osate.ge.internal.services.ModelChangeNotifier.ChangeListener;
import org.osate.ge.internal.ui.editor.ComboContributionItem;
import org.osate.ge.internal.ui.editor.InternalDiagramEditor;
import org.osate.ge.internal.ui.util.UiUtil;
import org.osate.ge.query.ExecutableQuery;
import org.osate.ge.services.QueryService;
import org.osate.ge.swt.SwtUtil;
import com.google.common.base.Objects;
import com.google.common.base.Predicates;
public class FlowContributionItem extends ComboContributionItem {
public static final String WIDGET_ID_HIGHLIGHT_FLOW = "org.osate.ge.properties.HighlightFlow";
private static final String EMPTY_SELECTION_TXT = "<Flows>";
private static final String SELECTED_FLOW_PROPERTY_KEY = "org.osate.ge.ui.editor.selectedFlow";
private static final ExecutableQuery<Object> FLOW_CONTAINER_QUERY = ExecutableQuery
.create((rootQuery) -> rootQuery.descendants()
.filter((fa) -> fa.getBusinessObject() instanceof ComponentImplementation
|| fa.getBusinessObject() instanceof Subcomponent
|| fa.getBusinessObject() instanceof ComponentInstance));
private InternalDiagramEditor editor = null;
private final ShowFlowContributionItem showFlowContributionItem;
private final EditFlowContributionItem editFlowContributionItem;
private final DeleteFlowContributionItem deleteFlowContributionItem;
private final ModelChangeNotifier modelChangeNotifier;
private final ChangeListener modelChangeListener = new ChangeListener() {
@Override
public void afterModelChangeNotification() {
refresh();
}
};
public FlowContributionItem(final String id, final ShowFlowContributionItem showFlowImplElements,
final EditFlowContributionItem editFlowContributionItem,
final DeleteFlowContributionItem deleteFlowContributionItem,
final ModelChangeNotifier modelChangeNotifier) {
super(id);
this.showFlowContributionItem = showFlowImplElements;
this.editFlowContributionItem = editFlowContributionItem;
this.deleteFlowContributionItem = deleteFlowContributionItem;
this.modelChangeNotifier = modelChangeNotifier;
}
@Override
public boolean isDynamic() {
return true;
}
// Force a fixed width for the combo contribution items. Otherwise the sizes are often incorrect due to the dynamic nature of the control.
@Override
protected int computeWidth(Control control) {
return 310;
}
public final void setActiveEditor(final IEditorPart newEditor) {
if (editor != newEditor) {
setControlEnabled(newEditor != null);
if (newEditor == null) {
modelChangeNotifier.removeChangeListener(modelChangeListener);
} else if (editor == null) {
modelChangeNotifier.addChangeListener(modelChangeListener);
}
saveFlowSelection();
editor = newEditor instanceof InternalDiagramEditor ? (InternalDiagramEditor) newEditor : null;
refresh();
}
}
@Override
protected void onControlDisposed() {
saveFlowSelection();
super.onControlDisposed();
}
private void saveFlowSelection() {
// Save the current mode selection
final ComboViewer comboViewer = getComboViewer();
if (comboViewer != null && editor != null) {
final Object firstSelection = comboViewer.getStructuredSelection().getFirstElement();
@SuppressWarnings("unchecked")
final Map.Entry<String, HighlightableFlowInfo> entry = (Map.Entry<String, HighlightableFlowInfo>) firstSelection;
final String selectionStr = firstSelection != null ? (String) entry.getKey() : null;
editor.setPartProperty(SELECTED_FLOW_PROPERTY_KEY, selectionStr);
}
}
@Override
protected Control createControl(final Composite parent) {
final Control control = super.createControl(parent);
setControlEnabled(editor != null);
final ComboViewer comboViewer = getComboViewer();
comboViewer.getCombo().addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent e) {
final Object firstSelection = getComboViewer().getStructuredSelection().getFirstElement();
@SuppressWarnings("unchecked")
final Map.Entry<String, HighlightableFlowInfo> entry = (Map.Entry<String, HighlightableFlowInfo>) firstSelection;
showFlowContributionItem.updateShowFlowItem(entry.getValue());
editFlowContributionItem.updateEditFlowItem(entry.getValue());
deleteFlowContributionItem.updateDeleteFlowItem(entry.getValue());
}
});
comboViewer.setLabelProvider(new LabelProvider() {
@Override
public String getText(final Object element) {
@SuppressWarnings("unchecked")
final Map.Entry<String, HighlightableFlowInfo> entry = (Entry<String, HighlightableFlowInfo>) element;
return entry.getKey();
}
});
SwtUtil.setTestingId(comboViewer.getCombo(), WIDGET_ID_HIGHLIGHT_FLOW);
refresh(); // Populate the combo box
return control;
}
void refresh() {
final ComboViewer comboViewer = getComboViewer();
final Map<String, HighlightableFlowInfo> highlightableFlowElements = new TreeMap<>(
(o1, o2) -> o1.toLowerCase().compareTo(o2.toLowerCase()));
if (comboViewer != null) {
final Map.Entry<String, HighlightableFlowInfo> nullMapEntry = new AbstractMap.SimpleEntry<>(
getNullValueString(), new HighlightableFlowInfo(null, FlowSegmentState.COMPLETE));
highlightableFlowElements.put(nullMapEntry.getKey(), nullMapEntry.getValue());
Map.Entry<String, HighlightableFlowInfo> selectedValue = nullMapEntry;
final String selectedFlowName = editor == null ? null : editor.getPartProperty(SELECTED_FLOW_PROPERTY_KEY);
// Clear the combo box
comboViewer.setInput(null);
if (editor == null) {
return;
}
final AgeDiagram diagram = editor.getDiagram();
if (diagram != null) {
final QueryService queryService = ContributionUtil.getQueryService(editor);
if (queryService != null) {
// Determine which flows have elements contained in the diagram and whether the flow is partial.
queryService.getResults(FLOW_CONTAINER_QUERY, diagram, null)
.stream()
.flatMap(flowContainerQueryable -> {
if (flowContainerQueryable.getBusinessObject() instanceof ComponentInstance) {
return AadlInstanceObjectUtil
.getComponentInstance(flowContainerQueryable.getBusinessObjectContext())
.map(ci -> createFlowSegmentReferences(
flowContainerQueryable.getBusinessObjectContext(), ci))
.orElse(Stream.empty());
} else {
return AadlClassifierUtil
.getComponentImplementation(flowContainerQueryable.getBusinessObjectContext())
.map(ci -> createFlowSegmentReferences(
flowContainerQueryable.getBusinessObjectContext(), ci))
.orElse(Stream.empty());
}
}).map(HighlightableFlowInfo::create).filter(Predicates.notNull())
.forEachOrdered(highlightableFlowElement -> {
highlightableFlowElements.put(
getName(highlightableFlowElement.highlightableFlowElement),
highlightableFlowElement);
});
// Determine which value should be selected
final Optional<Entry<String, HighlightableFlowInfo>> tmpSelectedValue = highlightableFlowElements
.entrySet().stream().filter(entry -> entry.getKey().equalsIgnoreCase(selectedFlowName))
.findAny();
if (tmpSelectedValue.isPresent()) {
selectedValue = tmpSelectedValue.get();
}
comboViewer.setInput(highlightableFlowElements.entrySet());
}
}
showFlowContributionItem.updateShowFlowItem(selectedValue.getValue());
editFlowContributionItem.updateEditFlowItem(selectedValue.getValue());
deleteFlowContributionItem.updateDeleteFlowItem(selectedValue.getValue());
final StructuredSelection newSelection = new StructuredSelection(selectedValue);
if (!Objects.equal(newSelection, comboViewer.getSelection())) {
comboViewer.setSelection(newSelection);
onSelection(newSelection.getFirstElement());
}
}
}
private static Stream<FlowSegmentReference> createFlowSegmentReferences(
final BusinessObjectContext flowContainerBoc, final ComponentInstance ci) {
return ci.getEndToEndFlows().stream().filter(f -> f != null).distinct().map(flow -> {
return AadlFlowSpecificationUtil.createFlowSegmentReference(flow, flowContainerBoc);
});
}
private static Stream<FlowSegmentReference> createFlowSegmentReferences(
final BusinessObjectContext flowContainerBoc, final ComponentImplementation ci) {
return Stream
.concat(ci.getAllEndToEndFlows().stream(),
ci.getAllFlowImplementations().stream().map(fi -> fi.getSpecification()))
.filter(f -> f != null).distinct()
.map(flow -> AadlFlowSpecificationUtil.createFlowSegmentReference(flow, flowContainerBoc));
}
private static String getName(final FlowSegmentReference highlightableFlowElement) {
return UiUtil.getPathLabel((DiagramNode) highlightableFlowElement.container) + "::"
+ highlightableFlowElement.flowSegmentElement.getName();
}
// The state of the flow matches the combined state of the children.
private static FlowSegmentState getSegmentState(final FlowSegmentReference highlightableFlowElement) {
return getSegmentStateForChildren(AadlFlowSpecificationUtil.findChildren(highlightableFlowElement));
}
// Returns empty if:
// The stream is empty
//
// A flow is partial if:
// Any of it's children are partial
private static FlowSegmentState getSegmentStateForChildren(
final Stream<FlowSegmentReference> highlightableFlowElements) {
FlowSegmentState state = null;
for (final FlowSegmentReference highlightableFlowElement : highlightableFlowElements
.collect(Collectors.toList())) {
final FlowSegmentState childState = getChildSegmentState(highlightableFlowElement);
if ((state != null && state != childState) || childState == FlowSegmentState.PARTIAL) {
state = FlowSegmentState.PARTIAL;
break;
} else {
state = childState;
}
}
return state == null ? FlowSegmentState.EMPTY : state;
}
// A flow segment is complete if:
// It's children are empty, but the flow segment itself is found
// Otherwise, the state of the flow is that of the children as returned by getSegmentStateForChildren()
private static FlowSegmentState getChildSegmentState(final FlowSegmentReference highlightableFlowElement) {
if (highlightableFlowElement == null) {
return FlowSegmentState.EMPTY;
}
final FlowSegmentState childrenState = getSegmentStateForChildren(
AadlFlowSpecificationUtil.findChildren(highlightableFlowElement));
if (childrenState == FlowSegmentState.EMPTY
&& AadlFlowSpecificationUtil.findQueryable(highlightableFlowElement) != null) {
return FlowSegmentState.COMPLETE;
}
return childrenState;
}
@Override
protected void onSelection(final Object value) {
if (editor != null && !editor.isDisposed() && value != null) {
@SuppressWarnings("unchecked")
final Map.Entry<String, HighlightableFlowInfo> highlightableFlowsMapEntry = (Entry<String, HighlightableFlowInfo>) value;
final FlowSegmentReference highlightableFlowElement = highlightableFlowsMapEntry
.getValue().highlightableFlowElement;
NamedElement flowSegmentElement = null;
BusinessObjectContext container = null;
if (highlightableFlowElement != null) {
flowSegmentElement = highlightableFlowElement.flowSegmentElement;
container = highlightableFlowElement.container;
}
ContributionUtil.getColoringService(editor).setHighlightedFlow(flowSegmentElement, container);
}
}
@Override
protected String getNullValueString() {
return EMPTY_SELECTION_TXT;
}
public static class HighlightableFlowInfo {
private final FlowSegmentReference highlightableFlowElement;
private FlowSegmentState state;
public HighlightableFlowInfo(final FlowSegmentReference highlightableFlowElement) {
this(highlightableFlowElement, null);
}
public HighlightableFlowInfo(final FlowSegmentReference highlightableFlowElement,
final FlowSegmentState state) {
this.highlightableFlowElement = highlightableFlowElement;
this.state = state;
if (highlightableFlowElement != null
&& !(this.highlightableFlowElement.container instanceof DiagramElement)) {
throw new RuntimeException(
"Flow element container is not a diagram element: " + this.highlightableFlowElement.container);
}
}
public FlowSegmentState getState() {
if (state == null) {
this.state = getSegmentState(highlightableFlowElement);
}
return state;
}
public BusinessObjectContext getContainer() {
return highlightableFlowElement.container;
}
/**
* The container's type is checked in the constructor.
* @return the diagram element which contains the flow.
*/
public DiagramElement getDiagramElementContainer() {
return (DiagramElement) getContainer();
}
public static HighlightableFlowInfo create(final FlowSegmentReference fsr) {
return new HighlightableFlowInfo(fsr);
}
public NamedElement getFlowSegment() {
return highlightableFlowElement == null ? null : highlightableFlowElement.flowSegmentElement;
}
}
public static enum FlowSegmentState {
EMPTY, PARTIAL, COMPLETE
}
}