ShowElementsInModeHandler.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.handlers;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Queue;
import java.util.UUID;
import java.util.stream.Collectors;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.runtime.Adapters;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.handlers.HandlerUtil;
import org.osate.aadl2.FeatureGroup;
import org.osate.aadl2.ModalElement;
import org.osate.aadl2.Mode;
import org.osate.aadl2.ModeFeature;
import org.osate.aadl2.ModeTransition;
import org.osate.aadl2.NamedElement;
import org.osate.aadl2.instance.InstanceObject;
import org.osate.aadl2.instance.ModeInstance;
import org.osate.aadl2.instance.ModeTransitionInstance;
import org.osate.ge.BusinessObjectContext;
import org.osate.ge.RelativeBusinessObjectReference;
import org.osate.ge.aadl2.internal.util.AadlInstanceObjectUtil;
import org.osate.ge.aadl2.internal.util.AadlModalElementUtil;
import org.osate.ge.internal.diagram.runtime.AgeDiagram;
import org.osate.ge.internal.diagram.runtime.layout.DiagramElementLayoutUtil;
import org.osate.ge.internal.diagram.runtime.layout.LayoutInfoProvider;
import org.osate.ge.internal.diagram.runtime.updating.BusinessObjectNode;
import org.osate.ge.internal.diagram.runtime.updating.BusinessObjectTreeUpdater;
import org.osate.ge.internal.diagram.runtime.updating.Completeness;
import org.osate.ge.internal.diagram.runtime.updating.DiagramToBusinessObjectTreeConverter;
import org.osate.ge.internal.diagram.runtime.updating.DiagramUpdater;
import org.osate.ge.internal.services.ActionExecutor.ExecutionMode;
import org.osate.ge.internal.services.ExtensionRegistryService;
import org.osate.ge.internal.services.ProjectReferenceService;
import org.osate.ge.internal.ui.editor.InternalDiagramEditor;
import org.osate.ge.internal.ui.handlers.AgeHandlerUtil;
import org.osate.ge.internal.util.BusinessObjectProviderHelper;

public class ShowElementsInModeHandler extends AbstractHandler {
	private ProjectReferenceService referenceService;

	@Override
	public void setEnabled(final Object evaluationContext) {
		// Modal element selected
		setBaseEnabled(AgeHandlerUtil.getSelectedBusinessObjectContexts().stream()
				.filter(de -> (isModal(de.getBusinessObject()))).findAny().isPresent());
	}

	@Override
	public Object execute(final ExecutionEvent event) throws ExecutionException {
		final InternalDiagramEditor editor = getDiagramEditor(event);
		referenceService = Objects.requireNonNull(Adapters.adapt(editor, ProjectReferenceService.class),
				"Unable to retrieve reference service");
		final ExtensionRegistryService extService = Objects.requireNonNull(Adapters.adapt(editor, ExtensionRegistryService.class),
				"Unable to retrieve extension service");
		final BusinessObjectProviderHelper bopHelper = new BusinessObjectProviderHelper(extService);
		final BusinessObjectTreeUpdater boTreeUpdater = editor.getBoTreeUpdater();
		final BusinessObjectNode boTree = getBoTree(editor, boTreeUpdater);
		final List<BusinessObjectContext> selectedModes = AgeHandlerUtil.getSelectedBusinessObjectContexts().stream()
				.filter(de -> isModal(de.getBusinessObject())).collect(Collectors.toList());
		for (final BusinessObjectContext selectedMode : selectedModes) {
			enableInModeNodes(bopHelper, boTree, selectedMode);
		}

		final AgeDiagram diagram = editor.getDiagram();
		final DiagramUpdater diagramUpdater = editor.getDiagramUpdater();
		final LayoutInfoProvider layoutInfoProvider = Objects.requireNonNull(
				Adapters.adapt(editor, LayoutInfoProvider.class), "Unable to retrieve layout info provider");
		editor.getActionExecutor().execute("Show Elements In Mode", ExecutionMode.NORMAL, () -> {
			// Update the diagram
			diagramUpdater.updateDiagram(diagram, boTree);

			// Update layout
			diagram.modify("Layout Incrementally",
					m -> DiagramElementLayoutUtil.layoutIncrementally(diagram, m, layoutInfoProvider));

			return null;
		});

		return null;
	}

	private void enableInModeNodes(final BusinessObjectProviderHelper bopHelper, final BusinessObjectNode boTree,
			final BusinessObjectContext selectedMode) {
		final BusinessObjectNode parentNode = getParentNode(boTree, selectedMode.getParent());
		bopHelper.getChildBusinessObjects(parentNode).forEach(childBo -> {
			if (childBo instanceof ModalElement) {
				final ModeFeature modeFeature = getModeFeature(selectedMode);
				final List<ModeFeature> modeFeatures = AadlModalElementUtil
						.getAllInModesOrTransitions((ModalElement) childBo);
				// Enable if ModalElement is in selected mode
				enableInModeChild(bopHelper, modeFeatures, childBo, modeFeature, parentNode);
			} else if (childBo instanceof InstanceObject) {
				final ModalElement modalElement = AadlInstanceObjectUtil.getModalElement((InstanceObject) childBo);
				if (modalElement != null) {
					final List<ModeFeature> modeFeatures = AadlModalElementUtil
							.getAllInModesOrTransitions(modalElement);
					// Get ModeFeature from ModeInstance or ModeTransitionInstance
					final ModeFeature modeFeature = getModeFeature(selectedMode);
					// Enable if ModalElement is in selected mode
					enableInModeChild(bopHelper, modeFeatures, childBo, modeFeature, parentNode);
				} else if (childBo instanceof NamedElement
						&& !(childBo instanceof ModeInstance || childBo instanceof ModeTransitionInstance)) {
					// Child with no mode should be enabled
					enableChild(bopHelper, selectedMode, childBo, parentNode);
				}
			} else if (childBo instanceof NamedElement && !(childBo instanceof ModeFeature)) {
				// Child with no mode should be enabled
				enableChild(bopHelper, selectedMode, childBo, parentNode);
			}
		});
	}

	/**
	 * Get the mode feature of the selected BOC
	 */
	private static ModeFeature getModeFeature(final BusinessObjectContext selectedBoc) {
		if (selectedBoc.getBusinessObject() instanceof InstanceObject) {
			final Object modeFeatureInstance = selectedBoc.getBusinessObject();
			return modeFeatureInstance instanceof ModeInstance ? ((ModeInstance) modeFeatureInstance).getMode()
					: ((ModeTransitionInstance) modeFeatureInstance).getModeTransition();
		}

		return (ModeFeature) selectedBoc.getBusinessObject();
	}

	private static boolean isModal(final Object ob) {
		return Mode.class.isInstance(ob) || ModeInstance.class.isInstance(ob) || ModeTransition.class.isInstance(ob)
				|| ModeTransitionInstance.class.isInstance(ob);
	}

	private void enableInModeChild(final BusinessObjectProviderHelper bopHelper, final List<ModeFeature> inModeFeatures,
			final Object child, final ModeFeature selectedModeFeature, final BusinessObjectNode parentNode) {
		if (inModeFeatures.isEmpty() || inModeFeatures.contains(selectedModeFeature)) {
			enableChild(bopHelper, selectedModeFeature, child, parentNode);
		}
	}

	/**
	 * Get the selected mode parent node and create ancestor nodes if necessary
	 */
	private BusinessObjectNode getParentNode(BusinessObjectNode parent, BusinessObjectContext selectedModeParent) {
		final Queue<Object> ancestors = Collections.asLifoQueue(new LinkedList<Object>());
		while (selectedModeParent.getBusinessObject() != null) {
			ancestors.add(selectedModeParent.getBusinessObject());
			selectedModeParent = selectedModeParent.getParent();
		}

		// Find ancestors to parent
		for (final Object ancestor : ancestors) {
			parent = getAncestor(ancestor, parent);
		}

		return parent;
	}

	private BusinessObjectNode getAncestor(final Object ancestor, final BusinessObjectNode parent) {
		final RelativeBusinessObjectReference ref = getRelativeBusinessObjectReference(ancestor);
		final BusinessObjectNode node = parent.getChild(ref);
		// Enable ancestor node if necessary
		return node == null ? createNode(parent, ref, ancestor) : node;
	}

	private static InternalDiagramEditor getDiagramEditor(final ExecutionEvent event) {
		final IEditorPart activeEditor = HandlerUtil.getActiveEditor(event);
		if (!(activeEditor instanceof InternalDiagramEditor)) {
			throw new RuntimeException("Unexpected editor: " + activeEditor);
		}

		return (InternalDiagramEditor) activeEditor;
	}

	/**
	 * Enable child node if necessary
	 */
	private void enableChild(final BusinessObjectProviderHelper bopHelper, final Object selectedMode,
			final Object bo,
			final BusinessObjectNode container) {
		final RelativeBusinessObjectReference ref = getRelativeBusinessObjectReference(bo);
		final BusinessObjectNode childNode = container.getChild(ref);
		if (childNode == null) {
			final BusinessObjectNode node = createNode(container, ref, bo);
			// Show in mode feature group children
			if (bo instanceof FeatureGroup) {
				enableChildren(bopHelper, selectedMode, node);
			}
		} else if (childNode.getBusinessObject() instanceof FeatureGroup) {
			enableChildren(bopHelper, selectedMode, childNode);
		}
	}

	private void enableChildren(final BusinessObjectProviderHelper bopHelper, final Object selectedMode,
			final BusinessObjectNode node) {
		for (final Object childBo : bopHelper.getChildBusinessObjects(node)) {
			final RelativeBusinessObjectReference ref = getRelativeBusinessObjectReference(childBo);
			final BusinessObjectNode childNode = node.getChild(ref);
			if (childNode == null) {
				if (childBo instanceof ModalElement) {
					final ModalElement modalElement = (ModalElement) childBo;
					final List<ModeFeature> inModeFeatures = AadlModalElementUtil
							.getAllInModesOrTransitions(modalElement);
					if (inModeFeatures.isEmpty() || inModeFeatures.contains(selectedMode)) {
						enableChild(bopHelper, selectedMode, childBo, node);
					}
				} else {
					enableChild(bopHelper, selectedMode, childBo, node);
				}
				enableChildren(bopHelper, selectedMode, node.getChild(ref));
			}
		}
	}

	private static BusinessObjectNode createNode(final BusinessObjectNode container,
			final RelativeBusinessObjectReference ref, final Object bo) {
		return new BusinessObjectNode(container, UUID.randomUUID(), ref, bo, Completeness.UNKNOWN, false);
	}

	private static BusinessObjectNode getBoTree(final InternalDiagramEditor editor, final BusinessObjectTreeUpdater boTreeUpdater) {
		final BusinessObjectNode boTree = DiagramToBusinessObjectTreeConverter
				.createBusinessObjectNode(editor.getDiagram());
		return boTreeUpdater.updateTree(editor.getDiagram().getConfiguration(), boTree);
	}

	private RelativeBusinessObjectReference getRelativeBusinessObjectReference(final Object bo) {
		return referenceService.getRelativeReference(bo);
	}
}