CreateFlowImplementationTool.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.tools;

import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.eclipse.core.runtime.Adapters;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.TitleAreaDialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.layout.RowData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Shell;
import org.osate.aadl2.Aadl2Factory;
import org.osate.aadl2.Aadl2Package;
import org.osate.aadl2.ComponentImplementation;
import org.osate.aadl2.Context;
import org.osate.aadl2.Feature;
import org.osate.aadl2.FlowElement;
import org.osate.aadl2.FlowEnd;
import org.osate.aadl2.FlowImplementation;
import org.osate.aadl2.FlowKind;
import org.osate.aadl2.FlowSegment;
import org.osate.aadl2.FlowSpecification;
import org.osate.aadl2.ModeFeature;
import org.osate.aadl2.NamedElement;
import org.osate.aadl2.Subcomponent;
import org.osate.ge.BusinessObjectContext;
import org.osate.ge.aadl2.internal.util.AgeAadlUtil;
import org.osate.ge.aadl2.ui.internal.tools.FlowDialogUtil.SegmentData;
import org.osate.ge.graphics.Color;
import org.osate.ge.internal.diagram.runtime.DiagramElement;
import org.osate.ge.internal.services.AadlModificationService;
import org.osate.ge.internal.services.AadlModificationService.Modification;
import org.osate.ge.internal.services.ColoringService;
import org.osate.ge.internal.services.ReferenceService;
import org.osate.ge.internal.services.UiService;
import org.osate.ge.internal.ui.editor.InternalDiagramEditor;
import org.osate.ge.internal.ui.handlers.AgeHandlerUtil;
import org.osate.ge.internal.ui.tools.ActivatedEvent;
import org.osate.ge.internal.ui.tools.DeactivatedEvent;
import org.osate.ge.internal.ui.tools.SelectionChangedEvent;
import org.osate.ge.internal.ui.tools.Tool;
import org.osate.ge.internal.ui.tools.ToolUtil;
import org.osate.ge.internal.ui.util.ContextHelpUtil;
import org.osate.ge.internal.ui.util.UiUtil;

public class CreateFlowImplementationTool implements Tool {
	private final ArrayList<SegmentData> segmentSelections = new ArrayList<>();
	private final ArrayList<BusinessObjectContext> modeFeatureSelections = new ArrayList<>();
	private final ReferenceService referenceService;
	private ColoringService.Coloring coloring = null;
	private CreateFlowImplementationDialog createFlowImplDlg;

	public CreateFlowImplementationTool(final InternalDiagramEditor editor, final DiagramElement container,
			final FlowImplementation flowImpl) {
		final UiService uiService = Objects.requireNonNull(Adapters.adapt(editor, UiService.class),
				"ui service must not be null");
		this.referenceService = Objects.requireNonNull(Adapters.adapt(editor, ReferenceService.class),
				"unable to retrieve reference service");
		final Display display = Display.getCurrent();
		createFlowImplDlg = new CreateFlowImplementationDialog(display.getActiveShell(), flowImpl, uiService);

		// Populate segments
		final FlowSpecification flowSpec = flowImpl.getSpecification();
		segmentSelections.add(
				new SegmentData(FlowToolUtil.findOrCreateBusinessObjectContext(referenceService, container, flowSpec)));

		final FlowEnd inEnd = flowImpl.getInEnd();
		if (inEnd != null) {
			segmentSelections.add(FlowToolUtil.createSegmentData(referenceService, container, inEnd));
		}

		flowImpl.getOwnedFlowSegments()
		.stream()
		.map(flowSegment -> FlowToolUtil.createSegmentData(referenceService, container, flowSegment))
		.forEachOrdered(segmentSelections::add);

		final FlowEnd outEnd = flowImpl.getOutEnd();
		if (outEnd != null) {
			segmentSelections.add(FlowToolUtil.createSegmentData(referenceService, container, outEnd));
		}

		// Populate in mode features
		flowImpl.getInModeOrTransitions()
		.stream()
		.forEachOrdered(modeFeature -> modeFeatureSelections
				.add(FlowToolUtil.findOrCreateBusinessObjectContext(referenceService, container, modeFeature)));
	}

	public CreateFlowImplementationTool(final InternalDiagramEditor editor) {
		final Display display = Display.getCurrent();
		final UiService uiService = Objects.requireNonNull(Adapters.adapt(editor, UiService.class),
				"ui service must not be null");
		this.referenceService = Objects.requireNonNull(Adapters.adapt(editor, ReferenceService.class),
				"unable to retrieve reference service");
		createFlowImplDlg = new CreateFlowImplementationDialog(display.getActiveShell(), null, uiService);
	}

	@Override
	public void activated(final ActivatedEvent ctx) {
		final UiService uiService = ctx.getUiService();
		try {
			ctx.getSelectedBoc().ifPresent(selectedBoc -> {
				final AadlModificationService aadlModService = ctx.getAadlModificatonService();
				final ColoringService coloringService = ctx.getColoringService();

				// Check if we are trying to implement a spec for which no implementation is allowed
				boolean forbidden = false;
				var selected = selectedBoc.getBusinessObject();
				if (selected instanceof FlowSpecification) {
					var fs = (FlowSpecification) selected;
					forbidden = noImpl(fs.getAllInEnd()) || noImpl(fs.getAllOutEnd());
				}

				// Check for existing errors and warnings
				final Set<Diagnostic> diagnostics = ToolUtil.getAllReferencedPackageDiagnostics(selectedBoc);
				// Do not allow tool activation if there are errors in the models
				final Set<Diagnostic> errors = FlowDialogUtil.getErrors(diagnostics);
				if (forbidden) {
					Display.getDefault().asyncExec(() -> {
						MessageDialog.openError(Display.getDefault().getActiveShell(), "Error",
								"Cannot implement a flow specification that reaches more than two levels into a feature group");
					});
				} else if (!errors.isEmpty()) {
					Display.getDefault()
					.asyncExec(() -> new FlowDialogUtil.ErrorDialog("The Create Flow Implementation", errors)
							.open());
				} else {
					coloring = coloringService.adjustColors();

					// Create and update based on current selection
					createFlowImplDlg.create();
					if (segmentSelections.isEmpty() && modeFeatureSelections.isEmpty()) {
						update(Collections.singletonList(selectedBoc), true);
					} else {
						final Iterator<SegmentData> segmentIt = segmentSelections.iterator();
						if (segmentIt.hasNext()) {
							// Set color for flow spec
							setColor(segmentIt.next().getBoc(), Color.ORANGE.darker());

							// Set color for flow segments
							while (segmentIt.hasNext()) {
								setColor(segmentIt.next().getBoc(), Color.MAGENTA.darker());
							}
						}

						// Set color for in mode and mode transitions
						for (Iterator<BusinessObjectContext> modeFeatureIt = modeFeatureSelections
								.iterator(); modeFeatureIt
								.hasNext(); setColor(modeFeatureIt.next(), Color.MAGENTA.brighter())) {
						}
					}

					if (createFlowImplDlg.open() == Window.OK && createFlowImplDlg != null) {
						final BusinessObjectContext ownerBoc = createFlowImplDlg.getOwnerBoc().orElse(null);
						// Create a new flow impl based on selections
						final FlowImplementation flowImpl = createFlowImplDlg.createFlow(ownerBoc);

						createFlowImplDlg.getFlowComponentImplementation(ownerBoc).ifPresent(ownerCi -> {
							// Modifications to perform
							final List<AadlModificationService.Modification<? extends NamedElement, ? extends NamedElement>> modifications = new ArrayList<>();
							if (createFlowImplDlg.flowImplToEdit != null) {
								// Editing existing flow impl
								final FlowImplementation flowImplToEdit = createFlowImplDlg.flowImplToEdit;

								// Copy owned property associations from old flow impl to new flow impl and remove old flow impl
								modifications.add(Modification.create(flowImplToEdit, fi -> {
									flowImpl.getOwnedPropertyAssociations()
									.addAll(EcoreUtil.copyAll(fi.getOwnedPropertyAssociations()));
									EcoreUtil.remove(fi);
								}));
							}

							// Add new flow impl
							modifications.add(Modification.create(ownerCi, ci -> {
								ci.getOwnedFlowImplementations().add(flowImpl);
								ci.setNoFlows(false);
							}));

							// Perform modifications
							aadlModService.modify(modifications);
						});
					}
				}

			});
		} finally {
			uiService.deactivateActiveTool();
		}
	}

	private boolean noImpl(FlowEnd end) {
		return end != null && end.getContext() != null && end.getContext().getContext() != null;
	}

	private void setColor(final Object o, final Color color) {
		if (o instanceof DiagramElement) {
			final DiagramElement de = (DiagramElement) o;
			coloring.setForeground(de, color);
		} else if (o instanceof SegmentData) {
			final SegmentData segmentData = (SegmentData) o;
			final BusinessObjectContext boc = segmentData.getBoc();

			// Do not remove color from duplicate segments
			if (color == null) {
				final Iterator<SegmentData> it = segmentSelections.iterator();
				if (it.hasNext()) {
					// Check if boc matches first segment to see if boc
					// should be orange to show the implementations flow specification
					final boolean isImplsFlowSpec = it.next().getBoc() == boc;

					// Keep color if it matches a segment
					while (it.hasNext()) {
						if (it.next().getBoc() == boc) {
							return;
						}
					}

					if (isImplsFlowSpec) {
						// Set orange to show flow specification
						setColor(boc, Color.ORANGE.darker());
						return;
					}
				}
			}

			setColor(boc, color);
		}
	}

	/**
	 * Update the diagram and tool dialog
	 * @param selectedBocs - the selected bocs
	 * @param isInit - whether the selected bocs are the inital selection when the tool was activated
	 */
	private void update(final List<BusinessObjectContext> selectedBocs, final boolean isInit) {
		if (createFlowImplDlg != null) {
			if (createFlowImplDlg.getShell() != null && !createFlowImplDlg.getShell().isDisposed()
					&& createFlowImplDlg.elementSelectionDlg == null) {
				// Handle selection for creating flow segments and mode features
				if (selectedBocs.size() > 1) {
					createFlowImplDlg.setMultipleElementsSelected(true);
				} else if (selectedBocs.size() == 1) {
					createFlowImplDlg.setMultipleElementsSelected(false);
					createFlowImplDlg.addSelectedElement(selectedBocs.get(0), isInit);
				}
			} else if (createFlowImplDlg.elementSelectionDlg != null
					&& createFlowImplDlg.elementSelectionDlg.getShell() != null
					&& !createFlowImplDlg.elementSelectionDlg.getShell().isDisposed()
					&& createFlowImplDlg.elementSelectionDlg.getShell().isVisible()) {
				// Handle selection for editing flow segments and mode features
				final CreateFlowImplementationDialog.SelectSegmentOrModeFeatureDialog elementSelectionDlg = createFlowImplDlg.elementSelectionDlg;
				if (selectedBocs.size() > 1) {
					elementSelectionDlg
					.setErrorMessage("Multiple elements are selected. " + elementSelectionDlg.getMessage());
					elementSelectionDlg.setSelection(null);
				} else if (selectedBocs.size() == 1) {
					final BusinessObjectContext selectedBoc = selectedBocs.get(0);
					elementSelectionDlg.setErrorMessage(null);
					elementSelectionDlg.setSelection(selectedBoc);
				}
			}
		}
	}

	@Override
	public void deactivated(final DeactivatedEvent ctx) {
		// Dispose of the coloring object
		if (coloring != null) {
			coloring.dispose();
			coloring = null;
		}

		// Close the dialog
		if (createFlowImplDlg != null) {
			if (createFlowImplDlg.elementSelectionDlg != null) {
				createFlowImplDlg.elementSelectionDlg.close();
				createFlowImplDlg.elementSelectionDlg = null;
			}
			createFlowImplDlg.close();
			createFlowImplDlg = null;
		}

		segmentSelections.clear();
		modeFeatureSelections.clear();
	}

	@Override
	public void selectionChanged(SelectionChangedEvent ctx) {
		update(ctx.getSelectedBocs(), false);
	}

	private class CreateFlowImplementationDialog extends TitleAreaDialog {
		private final Aadl2Package pkg = Aadl2Factory.eINSTANCE.getAadl2Package();
		private final FlowImplementation flowImplToEdit;
		private final UiService uiService;
		private TableViewer errorTableViewer;
		private Composite flowComposite;
		private FlowImplementation flowImpl;

		private SelectSegmentOrModeFeatureDialog elementSelectionDlg;

		private boolean multipleElementsSelected = false;
		private boolean isValid = true;

		CreateFlowImplementationDialog(final Shell parentShell, final FlowImplementation flowImplToEdit,
				final UiService uiService) {
			super(parentShell);
			this.flowImplToEdit = flowImplToEdit;
			this.uiService = uiService;
			this.setHelpAvailable(true);
			setShellStyle(SWT.CLOSE | SWT.MODELESS | SWT.BORDER | SWT.TITLE | SWT.RESIZE);
		}

		private void setMultipleElementsSelected(final boolean multipleElementsSelected) {
			this.multipleElementsSelected = multipleElementsSelected;
			updateMessage();
		}

		/**
		 * Determine message based on currently selected element
		 */
		private String getDirectionMessage() {
			String msg = "";
			final FlowSpecification fs = flowImpl.getSpecification();
			if (fs == null) {
				msg = "Select the flow specification to implement.";
			} else if (isValid) {
				msg = "Select flow segments or select the OK button to create the flow implementation.";
			} else {
				final FlowKind kind = fs.getKind();
				if (kind == FlowKind.SOURCE && flowImpl.getOutEnd() == null) {
					msg = "Select an ending feature or flow segment.";
				} else if (kind == FlowKind.SINK && flowImpl.getInEnd() == null) {
					msg = "Select a starting feature or flow segment.";
				} else if (kind == FlowKind.PATH) {
					if (flowImpl.getInEnd() == null) {
						msg = "Select a starting feature or flow segment.";
					} else if (flowImpl.getOutEnd() == null) {
						msg = "Select an ending feature or flow segment.";
					}
				}
			}

			if (msg.isEmpty()) {
				msg = "Select a flow segment.";
			}

			return msg += "\nOptionally, select modes or mode transitions.";
		}

		private void updateMessage() {
			final String msg = getDirectionMessage();
			String error = null;

			if (multipleElementsSelected) {
				error = "Multiple elements selected.  Select a single element. ";
			} else if (!segmentSelections.isEmpty() && !isValid) {
				// Show error if ending feature has been selected for flow paths and sources.
				final FlowSpecification fs = flowImpl.getSpecification();
				if (fs.getKind() != FlowKind.SINK && flowImpl.getOutEnd() != null) {
					error = "Invalid Flow Implementation.  ";
				}
			}

			if (error == null) {
				setErrorMessage(null);
				setMessage(msg);
			} else {
				setErrorMessage(error + " " + msg);
			}
		}

		/**
		 * Create a Flow Implementation based on user selections
		 */
		private FlowImplementation createFlow(final BusinessObjectContext ownerBoc) {
			// Create a flow implementation based on the state of the dialog
			final FlowImplementation flowImpl = (FlowImplementation) pkg.getEFactoryInstance()
					.create(pkg.getFlowImplementation());
			getFlowComponentImplementationBoc(ownerBoc).ifPresent(flowImplOwnerBoc -> {
				final Iterator<SegmentData> it = segmentSelections.iterator();
				if (it.hasNext()) {
					final FlowSpecification fs = (FlowSpecification) it.next().getBoc().getBusinessObject();
					flowImpl.setSpecification(fs);
					flowImpl.setKind(fs.getKind());
					while (it.hasNext()) {
						final BusinessObjectContext segment = it.next().getBoc();
						final Object bo = segment.getBusinessObject();
						if (bo instanceof Feature) {
							if ((fs.getKind() == FlowKind.PATH || fs.getKind() == FlowKind.SINK)
									&& flowImpl.getInEnd() == null) {
								final FlowEnd inEnd = flowImpl.createInEnd();
								inEnd.setFeature((Feature) bo);
								final Context ctx = ToolUtil.findContextExcludeOwner(segment, flowImplOwnerBoc);
								if (ctx != null) {
									final FlowEnd context = inEnd.createContext();
									context.setFeature((Feature) ctx);
								}
							} else if (flowImpl.getOutEnd() == null) {
								final FlowEnd outEnd = flowImpl.createOutEnd();
								outEnd.setFeature((Feature) bo);
								final Context ctx = ToolUtil.findContextExcludeOwner(segment, flowImplOwnerBoc);
								if (ctx != null) {
									final FlowEnd context = outEnd.createContext();
									context.setFeature((Feature) ctx);
								}
							}
						} else {
							final FlowSegment newFlowSegment = flowImpl.createOwnedFlowSegment();
							newFlowSegment.setContext(ToolUtil.findContextExcludeOwner(segment, flowImplOwnerBoc));
							newFlowSegment.setFlowElement((FlowElement) bo);
						}
					}
				}
			});

			modeFeatureSelections.forEach(modeFeatureBoc -> flowImpl.getInModeOrTransitions()
					.add((ModeFeature) modeFeatureBoc.getBusinessObject()));

			return flowImpl;
		}

		/**
		 * Add qualified element to build flow implementation
		 * @param boc - the business object context for the selected element
		 * @param isInit - whether the boc is the initial selection when tool is activated
		 */
		private void addSelectedElement(final BusinessObjectContext boc, final boolean isInit) {
			if (!modeFeatureSelections.contains(boc) && boc.getBusinessObject() instanceof NamedElement) {
				if (getShell().isVisible() || isInit) {
					// Decide whether to add the element to the selection list
					final Object bo = boc.getBusinessObject();
					if (bo instanceof ModeFeature) {
						addModeSelection(boc, Color.MAGENTA.brighter());
					} else if (flowImpl.getSpecification() == null && bo instanceof FlowSpecification) {
						addSegmentSelection(new SegmentData(boc), segmentSelections.size(), Color.ORANGE.darker());

						final FlowSpecification fs = (FlowSpecification) bo;
						if (boc.getParent() instanceof DiagramElement) {
							final DiagramElement container = (DiagramElement) boc.getParent();
							final FlowEnd inEnd = fs.getAllInEnd();
							if (inEnd != null
									&& !(inEnd.getContext() != null && inEnd.getContext().getContext() != null)) {
								addSegmentSelection(FlowToolUtil.createSegmentData(referenceService, container, inEnd),
										segmentSelections.size(), Color.MAGENTA.darker());
							}
						}
					} else {
						final FlowSpecification fs = flowImpl.getSpecification();
						if (fs != null) {
							final int size = segmentSelections.size();
							if (fs.getKind() == FlowKind.PATH) {
								if (bo instanceof Feature) {
									if (flowImpl.getInEnd() == null) {
										addSegmentSelection(new SegmentData(boc), 1, Color.MAGENTA.darker());
									} else if (flowImpl.getOutEnd() == null) {
										addSegmentSelection(new SegmentData(boc), size, Color.MAGENTA.darker());
									}
								} else if (bo instanceof FlowElement) {
									int insertIndex = flowImpl.getOutEnd() == null ? size : (size - 1);
									addSegmentSelection(new SegmentData(boc), insertIndex, Color.MAGENTA.darker());
								}
							} else if (fs.getKind() == FlowKind.SINK) {
								if (bo instanceof Feature) {
									if (flowImpl.getInEnd() == null) {
										addSegmentSelection(new SegmentData(boc), 1, Color.MAGENTA.darker());
									}
								} else if (bo instanceof FlowElement) {
									addSegmentSelection(new SegmentData(boc), size, Color.MAGENTA.darker());
								}
							} else {
								if (bo instanceof Feature && flowImpl.getOutEnd() == null) {
									addSegmentSelection(new SegmentData(boc), size, Color.MAGENTA.darker());
								} else if (bo instanceof FlowElement) {
									final int insertIndex = flowImpl.getOutEnd() == null ? size : (size - 1);
									addSegmentSelection(new SegmentData(boc), insertIndex, Color.MAGENTA.darker());
								}
							}
						}
					}
				}
			}
		}

		private void addModeSelection(final BusinessObjectContext boc, final Color color) {
			modeFeatureSelections.add(boc);
			setColor(boc, color);
			updateDialog();
		}

		/**
		 * Add the segment to the list of segments
		 * @param segmentData the segment to add
		 * @param index the index to insert the segment into the segment list
		 * @param color the color to set the diagram element
		 */
		private void addSegmentSelection(final SegmentData segmentData, final int index, final Color color) {
			segmentSelections.add(index, segmentData);
			setColor(segmentData.getBoc(), color);
			updateDialog();
		}

		private void updateDialog() {
			setErrorMessage(null);
			// Update the UI
			updateWidgets();
		}

		private boolean isFlowImplValid(final FlowImplementation fi) {
			if (fi.getSpecification() == null) {
				return false;
			}

			Set<Diagnostic> diagnostics;
			final Optional<ComponentImplementation> optCi = getFlowComponentImplementation(getOwnerBoc().orElse(null));

			if (!optCi.isPresent()) {
				diagnostics = Collections.emptySet();
				setDefaultTitle();
			} else {
				final ComponentImplementation ci = optCi.get();
				// Update title
				setTitle("Creating Flow Implementation in: " + ci.getQualifiedName());
				diagnostics = ToolUtil.getModificationDiagnostics(ci, (resourceSet) -> {
					final ComponentImplementation objectToModify = (ComponentImplementation) resourceSet
							.getEObject(EcoreUtil.getURI(ci), true);
					objectToModify.setNoFlows(false);
					objectToModify.getOwnedFlowImplementations().add(fi);
					return objectToModify;
				});
			}

			// Errors to show to user
			final Set<Diagnostic> dialogDiagnostics = diagnostics.stream().filter(diagnostic -> {
				final String errorMsg = diagnostic.getMessage();
				return errorMsg == null || !errorMsg.contains("Serialization Error");
			}).collect(Collectors.toSet());

			// Update error table
			FlowDialogUtil.setInput(errorTableViewer, dialogDiagnostics);

			return !diagnostics.stream()
					.filter(diagnostic -> diagnostic.getSeverity() == Diagnostic.ERROR)
					.findAny()
					.isPresent();
		}

		private Optional<ComponentImplementation> getFlowComponentImplementation(final BusinessObjectContext owner) {
			return getFlowBocToComponentImplementation(owner).map(entry -> entry.getValue());
		}

		private Optional<BusinessObjectContext> getFlowComponentImplementationBoc(final BusinessObjectContext owner) {
			return getFlowBocToComponentImplementation(owner).map(entry -> entry.getKey());
		}

		private Optional<SimpleEntry<BusinessObjectContext, ComponentImplementation>> getFlowBocToComponentImplementation(
				final BusinessObjectContext owner) {
			if (owner == null) {
				return Optional.empty();
			}

			final Object bo = owner.getBusinessObject();
			final ComponentImplementation ci;
			if (bo instanceof ComponentImplementation) {
				ci = ComponentImplementation.class.cast(bo);
			} else if (bo instanceof Subcomponent) {
				ci = Subcomponent.class.cast(bo).getComponentImplementation();
			} else {
				return Optional.empty();
			}

			return Optional.of(new SimpleEntry<BusinessObjectContext, ComponentImplementation>(owner, ci));
		}

		private void updateWidgets() {
			for (final Control child : flowComposite.getChildren()) {
				child.dispose();
			}

			final BusinessObjectContext ownerBoc = createFlowImplDlg.getOwnerBoc().orElse(null);
			flowImpl = createFlow(ownerBoc);

			isValid = isFlowImplValid(flowImpl);
			getButton(IDialogConstants.OK_ID).setEnabled(isValid);

			final Iterator<SegmentData> segmentIt = segmentSelections.iterator();
			if (segmentIt.hasNext()) {
				SegmentData segmentData = segmentIt.next();
				BusinessObjectContext boc = segmentData.getBoc();
				if (flowImplToEdit != null) {
					createFlowSpecLabel((FlowSpecification) boc.getBusinessObject());
				} else {
					final FlowSpecification fs = (FlowSpecification) boc.getBusinessObject();
					createSegmentButton(AgeAadlUtil.getRootName(fs), segmentData,
							(selectedBo) -> selectedBo instanceof FlowSpecification);
					createFlowText(fs);
				}

				while (segmentIt.hasNext()) {
					segmentData = segmentIt.next();
					boc = segmentData.getBoc();

					final StringBuilder segmentNameBuilder = new StringBuilder();
					final Context context = ToolUtil.findContextExcludeOwner(boc, ownerBoc);
					if (context != null) {
						segmentNameBuilder.append(AgeAadlUtil.getRootName(context));
						segmentNameBuilder.append(".");
					}

					segmentNameBuilder.append(AgeAadlUtil.getRootName((NamedElement) boc.getBusinessObject()));
					createSegmentButton(segmentNameBuilder.toString(), segmentData, getType(boc));
					if (segmentIt.hasNext()) {
						// If segment is not last, add an arrow
						FlowDialogUtil.createArrowText(flowComposite);
					}
				}
			}

			if (!modeFeatureSelections.isEmpty()) {
				FlowDialogUtil.createInModeText(flowComposite);
				final Iterator<BusinessObjectContext> modeFeatureIt = modeFeatureSelections.iterator();
				final BusinessObjectContext boc = modeFeatureIt.next();

				createModeFeatureButton("(" + ((NamedElement) boc.getBusinessObject()).getName()
						+ (modeFeatureIt.hasNext() ? "," : ")"), boc);

				while (modeFeatureIt.hasNext()) {
					final BusinessObjectContext mode = modeFeatureIt.next();
					createModeFeatureButton(
							((NamedElement) mode.getBusinessObject()).getName() + (modeFeatureIt.hasNext() ? "," : ")"),
							mode);
				}
			}

			flowComposite.layout(true, true);
			updateMessage();
		}

		private Function<Object, Boolean> getType(final Object bo) {
			final Function<Object, Boolean> isQualifiedType;
			if (bo instanceof ModeFeature) {
				isQualifiedType = (selectedBo) -> selectedBo instanceof ModeFeature;
			} else if (bo instanceof Feature) {
				isQualifiedType = (selectedBo) -> selectedBo instanceof Feature;
			} else {
				isQualifiedType = (selectedBo) -> selectedBo instanceof FlowElement;
			}

			return isQualifiedType;
		}

		private void createFlowSpecLabel(final FlowSpecification fs) {
			final StyledText flowSpecLabel = new StyledText(flowComposite, SWT.NONE);
			flowSpecLabel.setBackground(flowComposite.getBackground());
			flowSpecLabel.setLayoutData(new RowData());
			final String flowSpecName = AgeAadlUtil.getRootName(fs);
			final String flowKind = fs.getKind().getName();
			flowSpecLabel.setText(flowSpecName + " :  flow " + flowKind);
			flowSpecLabel.setStyleRange(new StyleRange(flowSpecName.length() + 4, 5 + flowKind.length(),
					Display.getCurrent().getSystemColor(SWT.COLOR_DARK_RED), null, SWT.BOLD));
		}

		private void createFlowText(final FlowSpecification fs) {
			final StyledText label = new StyledText(flowComposite, SWT.NONE);
			label.setBackground(flowComposite.getBackground());
			label.setLayoutData(new RowData());
			final String flowKind = fs.getKind().getName();
			label.setText(" :  flow " + flowKind);
			label.setStyleRange(new StyleRange(4, 5 + flowKind.length(),
					Display.getCurrent().getSystemColor(SWT.COLOR_DARK_RED), null, SWT.BOLD));
		}

		private void createSegmentButton(final String name, final SegmentData segmentData,
				final Function<Object, Boolean> isQualifiedType) {
			final Button segmentBtn = new Button(flowComposite, SWT.FLAT);
			segmentBtn.setText(name);
			segmentBtn.setLayoutData(new RowData());
			final Menu editMenu = new Menu(segmentBtn);

			createSegmentReplaceButton(editMenu, segmentData, isQualifiedType);
			final MenuItem insertBeforeMenuItem = createInsertSegmentButton(editMenu, "Insert Before", segmentData,
					false);
			final MenuItem insertAfterMenuItem = createInsertSegmentButton(editMenu, "Insert After", segmentData, true);

			final MenuItem remove = new MenuItem(editMenu, SWT.NONE);
			remove.setText("Remove");
			remove.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(final SelectionEvent e) {
					segmentSelections.remove(segmentData);
					setColor(segmentData, null);
					uiService.clearSelection();
					createFlowImplDlg.updateWidgets();
				}
			});

			segmentBtn.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(final SelectionEvent e) {
					final Point location = segmentBtn.getLocation();
					editMenu.setLocation(Display.getCurrent()
							.map(createFlowImplDlg.flowComposite, null,
									new Point(location.x, location.y + segmentBtn.getSize().y)));
					// Only allow replacing for impl's flow spec and in/out end features
					if (segmentSelections.indexOf(segmentData) == 0) {
						insertBeforeMenuItem.setEnabled(false);
						insertAfterMenuItem.setEnabled(false);
						remove.setEnabled(false);
					} else {
						final FlowEnd inEnd = flowImpl.getInEnd();
						if (inEnd != null && inEnd.getFeature() == segmentData.getBoc().getBusinessObject()) {
							insertBeforeMenuItem.setEnabled(false);
							insertAfterMenuItem.setEnabled(false);
						}

						final FlowEnd outEnd = flowImpl.getOutEnd();
						if (outEnd != null && outEnd.getFeature() == segmentData.getBoc().getBusinessObject()) {
							insertBeforeMenuItem.setEnabled(false);
							insertAfterMenuItem.setEnabled(false);
						}
					}

					editMenu.setVisible(true);
				};
			});
		}

		private MenuItem createInsertSegmentButton(final Menu newMenu, final String text, final SegmentData segmentData,
				final boolean isInsertAfter) {
			final MenuItem insertMenuItem = new MenuItem(newMenu, SWT.PUSH);
			insertMenuItem.setText(text);
			insertMenuItem.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(final SelectionEvent e) {
					elementSelectionDlg = new SelectSegmentOrModeFeatureDialog(createFlowImplDlg.getShell(),
							"Select Element to Insert", getType(segmentData.getBoc()));
					createFlowImplDlg.setSegment(segmentData, isInsertAfter);
				}
			});

			return insertMenuItem;
		}

		private void createSegmentReplaceButton(final Menu menu, final SegmentData segmentData,
				final Function<Object, Boolean> isQualifiedType) {
			final MenuItem replace = new MenuItem(menu, SWT.NONE);
			replace.setText("Replace");
			replace.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(final SelectionEvent e) {
					elementSelectionDlg = new SelectSegmentOrModeFeatureDialog(createFlowImplDlg.getShell(),
							"Select Replacement Element", isQualifiedType);
					createFlowImplDlg.setSegment(Optional.of(() -> {
						segmentSelections.remove(segmentData);
						setColor(segmentData, null);
					}), segmentData, false);
				}
			});
		}

		private void createModeFeatureButton(final String text, final BusinessObjectContext boc) {
			final Button createModeFeatureBtn = new Button(flowComposite, SWT.FLAT);
			createModeFeatureBtn.setText(text);
			createModeFeatureBtn.setEnabled(true);
			createModeFeatureBtn.setLayoutData(new RowData());

			final Menu menu = new Menu(createModeFeatureBtn);
			final MenuItem replaceMenuItem = new MenuItem(menu, SWT.PUSH);
			replaceMenuItem.setText("Replace");

			replaceMenuItem.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(final SelectionEvent e) {
					elementSelectionDlg = new SelectSegmentOrModeFeatureDialog(createFlowImplDlg.getShell(),
							"Select Replacement Element", getType(boc));
					createFlowImplDlg.setSegment(Optional.of(() -> {
						modeFeatureSelections.remove(boc);
						if (boc instanceof DiagramElement) {
							coloring.setForeground((DiagramElement) boc, null);
						}
					}), boc, false);
				}
			});

			createInsertModeFeatureMenuItem(menu, "Insert Before", boc, false);
			createInsertModeFeatureMenuItem(menu, "Insert After", boc, true);

			final MenuItem removeMenuItem = new MenuItem(menu, SWT.PUSH);
			removeMenuItem.setText("Remove");
			removeMenuItem.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(final SelectionEvent e) {
					modeFeatureSelections.remove(boc);
					if (boc instanceof DiagramElement) {
						coloring.setForeground((DiagramElement) boc, null);
					}

					createFlowImplDlg.updateWidgets();
				}

			});

			createModeFeatureBtn.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(final SelectionEvent e) {
					final Point location = createModeFeatureBtn.getLocation();
					menu.setLocation(Display.getCurrent()
							.map(createFlowImplDlg.flowComposite, null,
									new Point(location.x, location.y + createModeFeatureBtn.getSize().y)));
					menu.setVisible(true);
				};
			});
		}

		private void createInsertModeFeatureMenuItem(final Menu menu, final String text,
				final BusinessObjectContext boc, final boolean isInsertAfter) {
			final MenuItem insertMenuItem = new MenuItem(menu, SWT.PUSH);
			insertMenuItem.setText(text);
			insertMenuItem.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(final SelectionEvent e) {
					elementSelectionDlg = new SelectSegmentOrModeFeatureDialog(createFlowImplDlg.getShell(),
							"Select Element to Insert", (selectedBo) -> selectedBo instanceof ModeFeature);
					createFlowImplDlg.setSegment(boc, isInsertAfter);
				}
			});
		}

		private void setSegment(final SegmentData segmentData, final boolean isInsertAfter) {
			setSegment(Optional.empty(), segmentData, isInsertAfter);
		}

		private void setSegment(final Optional<Runnable> opRunnable, final SegmentData segmentData,
				final boolean isInsertAfter) {
			try {
				createFlowImplDlg.getShell().setVisible(false);
				if (elementSelectionDlg.open() == Window.OK && elementSelectionDlg != null
						&& createFlowImplDlg != null) {
					final Optional<BusinessObjectContext> optBoc = getReplacementBoc();
					if (optBoc.isPresent()) {
						final BusinessObjectContext selectedBoc = optBoc.get();
						int insertIndex = segmentSelections.indexOf(segmentData);
						if (isInsertAfter) {
							insertIndex++;
						}

						if (insertIndex == 0) {
							// Replacing flow specification
							final FlowSpecification fs = (FlowSpecification) segmentData.getBoc().getBusinessObject();
							final FlowKind flowKind = fs.getKind();
							final FlowSpecification replacementFlowSpec = (FlowSpecification) selectedBoc
									.getBusinessObject();
							final FlowKind replacementFlowKind = replacementFlowSpec.getKind();
							if (flowKind != replacementFlowKind) {
								// Move in/out features based on replacement kind
								if (flowKind == FlowKind.SINK) {
									if (replacementFlowKind == FlowKind.SOURCE && flowImpl.getInEnd() != null) {
										// Move in feature to out feature
										moveFeature(1, segmentSelections.size());
									}
								} else if (flowKind == FlowKind.SOURCE) {
									if (flowImpl.getOutEnd() != null) {
										// Move out feature to in feature
										moveFeature(segmentSelections.size() - 1, 1);
									}
								} else { // Path
									if (replacementFlowKind == FlowKind.SINK) {
										if (flowImpl.getOutEnd() != null) {
											// Remove out end feature
											removeFeature(segmentSelections.size() - 1);
										}
									} else { // Source
										if (flowImpl.getInEnd() != null) {
											// Remove in end feature
											removeFeature(1);
										}
									}
								}
							}

							// Set color for flow spec
							setColor(selectedBoc, Color.ORANGE.darker());
						} else {
							// Set color for segments
							setColor(selectedBoc, Color.MAGENTA.darker());
						}

						// Add new segment
						segmentSelections.add(insertIndex, new SegmentData(selectedBoc));

						opRunnable.ifPresent(runnable -> runnable.run());
					}

					updateWidgets();
				}
			} finally {
				elementSelectionDlg = null;
				createFlowImplDlg.getShell().setVisible(true);
			}
		}

		private void removeFeature(final int featureIndex) {
			final SegmentData featureToRemove = segmentSelections.get(featureIndex);
			if (featureToRemove.getBoc() instanceof DiagramElement) {
				coloring.setForeground((DiagramElement) featureToRemove.getBoc(), null);
			}
			segmentSelections.remove(featureToRemove);
		}

		private void moveFeature(final int movingFrom, final int movingTo) {
			final SegmentData bocToMove = segmentSelections.get(movingFrom);
			segmentSelections.remove(bocToMove);
			segmentSelections.add(movingTo, bocToMove);
		}

		private void setSegment(final BusinessObjectContext boc, final boolean isInsertAfter) {
			setSegment(Optional.empty(), boc, isInsertAfter);
		}

		private void setSegment(final Optional<Runnable> consumer, final BusinessObjectContext boc,
				final boolean isInsertAfter) {
			try {
				createFlowImplDlg.getShell().setVisible(false);
				if (elementSelectionDlg.open() == Window.OK && elementSelectionDlg != null
						&& createFlowImplDlg != null) {
					final Optional<BusinessObjectContext> optBoc = getReplacementBoc();
					if (optBoc.isPresent()) {
						final BusinessObjectContext selectedBoc = optBoc.get();
						int insertIndex = modeFeatureSelections.indexOf(boc);
						if (isInsertAfter) {
							insertIndex++;
						}
						modeFeatureSelections.add(insertIndex, selectedBoc);
						setColor(selectedBoc, Color.MAGENTA.brighter());
						consumer.ifPresent(con -> con.run());
					}

					updateWidgets();
				}
			} finally {
				elementSelectionDlg = null;
				createFlowImplDlg.getShell().setVisible(true);
			}
		}

		private class SelectSegmentOrModeFeatureDialog extends TitleAreaDialog {
			private final String title;
			private final Function<Object, Boolean> isQualifiedType;
			private Label selectionLabel;

			public SelectSegmentOrModeFeatureDialog(final Shell parentShell, final String title,
					final Function<Object, Boolean> isQualifiedType) {
				super(parentShell);
				this.title = title;
				this.isQualifiedType = isQualifiedType;
				setShellStyle(SWT.CLOSE | SWT.PRIMARY_MODAL | SWT.BORDER | SWT.TITLE | SWT.RESIZE);
			}

			@Override
			public void create() {
				super.create();
				setTitle(title);
				setMessage("Select element from diagram or outline view.");
				final Button okBtn = getButton(IDialogConstants.OK_ID);

				final List<BusinessObjectContext> bocs = AgeHandlerUtil.getSelectedBusinessObjectContexts();
				if (bocs.size() == 1 && bocs.get(0).getBusinessObject() instanceof NamedElement) {
					final NamedElement selectedElement = (NamedElement) bocs.get(0).getBusinessObject();
					okBtn.setEnabled(isQualifiedType.apply(selectedElement));
					selectionLabel.setText(AgeAadlUtil.getRootName(selectedElement));
					setErrorMessage(null);
				} else {
					okBtn.setEnabled(false);
					setErrorMessage("Invalid selection.\n " + getMessage());
					selectionLabel.setText("<Invalid>");
				}
			}

			@Override
			protected void configureShell(final Shell newShell) {
				super.configureShell(newShell);
				newShell.setText("Element Selection");
				newShell.setLocation(
						UiUtil.getOffsetRectangleLocation(Display.getCurrent().getActiveShell().getBounds(), 50, 50));
				newShell.setSize(400, 200);
				newShell.setMinimumSize(400, 200);
			}

			public void setSelection(final BusinessObjectContext selection) {
				updateDialog(selection);
				getShell().layout(true, true);
			}

			private void updateDialog(final BusinessObjectContext boc) {
				final Object bo = boc.getBusinessObject();
				final boolean isEnabled = !modeFeatureSelections.contains(boc)
						&& isQualifiedType.apply(boc.getBusinessObject());
				final Button okBtn = getButton(IDialogConstants.OK_ID);
				if (okBtn != null) {
					okBtn.setEnabled(isEnabled);
				}

				if (bo instanceof NamedElement) {
					selectionLabel.setText(AgeAadlUtil.getRootName((NamedElement) bo));
					setErrorMessage(null);
				}
			}

			@Override
			protected Control createDialogArea(final Composite parent) {
				final Composite composite = new Composite(parent, SWT.NONE);
				final GridLayout layout = new GridLayout();
				layout.marginHeight = 0;
				layout.marginWidth = 0;
				layout.verticalSpacing = 0;
				layout.horizontalSpacing = 5;
				layout.numColumns = 2;
				layout.marginLeft = 10;
				layout.marginTop = 8;
				composite.setLayout(layout);

				composite.setLayoutData(GridDataFactory.fillDefaults().grab(true, true).create());
				composite.setFont(parent.getFont());

				final Label label = new Label(composite, SWT.NONE);
				label.setText("Selected Element: ");
				label.setLayoutData(
						GridDataFactory.fillDefaults().grab(false, true).align(SWT.CENTER, SWT.CENTER).create());

				selectionLabel = new Label(composite, SWT.NONE);
				final List<BusinessObjectContext> bocs = AgeHandlerUtil.getSelectedBusinessObjectContexts();
				if (bocs.size() == 1 && bocs.get(0).getBusinessObject() instanceof NamedElement) {
					selectionLabel.setText(AgeAadlUtil.getRootName((NamedElement) bocs.get(0).getBusinessObject()));
				} else {
					selectionLabel.setText("<Invalid>");
				}

				selectionLabel.setLayoutData(
						GridDataFactory.fillDefaults().grab(false, true).align(SWT.CENTER, SWT.CENTER).create());

				return composite;
			}
		}

		private Optional<BusinessObjectContext> getFirstSelection() {
			if (segmentSelections.size() == 0) {
				return Optional.empty();
			}

			return Optional.ofNullable(segmentSelections.get(0).getBoc());
		}

		/**
		 * Returns the business object context for the owner of the new flow implementation
		 * @return
		 */
		private Optional<BusinessObjectContext> getOwnerBoc() {
			return getFirstSelection().map(boc -> boc.getParent());
		}

		@Override
		protected void configureShell(final Shell newShell) {
			super.configureShell(newShell);
			newShell.setText("Flow Implementation Tool");
			newShell.setLocation(
					UiUtil.getOffsetRectangleLocation(Display.getCurrent().getActiveShell().getBounds(), 50, 50));
			newShell.setSize(800, 400);
			newShell.setMinimumSize(300, 215);
		}

		@Override
		public void create() {
			super.create();
			setDefaultTitle();
			ContextHelpUtil.setHelp(getShell(), ContextHelpUtil.FLOW_IMPL_TOOL);
			updateWidgets();
		}

		private void setDefaultTitle() {
			setTitle("Creating Flow Implementation");
		}

		@Override
		protected Control createDialogArea(final Composite parent) {
			final Composite area = FlowDialogUtil.createFlowArea(parent, SWT.NONE);
			flowComposite = FlowDialogUtil.createFlowComposite(area);
			errorTableViewer = FlowDialogUtil.createErrorTableViewer(new Composite(area, SWT.NONE));
			return flowComposite;
		}

		@Override
		protected Control createButtonBar(final Composite parent) {
			final Composite buttonBar = new Composite(parent, SWT.NONE);
			final GridLayout buttonBarLayout = new GridLayout();
			buttonBarLayout.numColumns = 3;
			buttonBar.setLayout(buttonBarLayout);
			final GridData buttonBarData = new GridData(SWT.FILL, SWT.BOTTOM, true, false);
			buttonBar.setLayoutData(buttonBarData);
			buttonBar.setFont(parent.getFont());

			final Composite editButtonBar = new Composite(buttonBar, SWT.NONE);
			editButtonBar.setFont(parent.getFont());
			editButtonBar.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
			final GridLayout editButtonBarLayout = new GridLayout();
			editButtonBarLayout.numColumns = 3;
			editButtonBar.setLayout(editButtonBarLayout);

			final Control buttonControl = super.createButtonBar(buttonBar);
			buttonControl.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
			final Button okBtn = getButton(IDialogConstants.OK_ID);
			okBtn.setEnabled(false);
			return buttonBar;
		}

		private Optional<BusinessObjectContext> getReplacementBoc() {
			final List<BusinessObjectContext> selectedBocs = AgeHandlerUtil.getSelectedBusinessObjectContexts();
			if (selectedBocs.size() == 1) {
				return selectedBocs.stream().filter(boc -> boc.getBusinessObject() instanceof NamedElement).findAny();
			}

			return Optional.empty();
		}
	}
}