BusinessObjectNode.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.Map;
import java.util.Objects;
import java.util.UUID;

import org.osate.ge.BusinessObjectContext;
import org.osate.ge.RelativeBusinessObjectReference;
import org.osate.ge.internal.diagram.runtime.DiagramElement;

/**
 * Node in the business object tree. See {@link org.osate.ge.internal.diagram.runtime.updating}
 *
 */
public class BusinessObjectNode implements BusinessObjectContext {
	private BusinessObjectNode parent;
	private final UUID id;
	private final RelativeBusinessObjectReference relativeReference;
	private Object bo;
	private Map<RelativeBusinessObjectReference, BusinessObjectNode> children;
	private Completeness completeness = Completeness.UNKNOWN;
	private boolean defaultChildrenHaveBeenPopulated;

	/**
	 * Creates a new instance
	 * @param parent the parent of the node. The new node will be added as a child of this node.
	 * @param id unique identifier for the node. If the node is created based on a {@link DiagramElement}, this usually matches its ID.
	 * @param relativeReference the relative reference for the node's business object. May only be null for root nodes.
	 * @param bo the business object. May only be null for root nodes.
	 * @param completeness the completeness state of the node.
	 * @param defaultChildrenHaveBeenPopulated whether the child diagram elements which have been added to the diagram by default have already been added to this node.
	 */
	public BusinessObjectNode(final BusinessObjectNode parent,
			final UUID id,
			final RelativeBusinessObjectReference relativeReference,
			final Object bo,
			final Completeness completeness, final boolean defaultChildrenHaveBeenPopulated) {
		this.parent = parent;
		this.id = Objects.requireNonNull(id, "id must not be null");
		this.relativeReference = relativeReference;
		this.bo = bo;
		this.completeness = Objects.requireNonNull(completeness, "completeness must not be null");
		this.defaultChildrenHaveBeenPopulated = defaultChildrenHaveBeenPopulated;


		if(parent != null) {
			parent.addChild(this);
		}
	}

	/**
	 * Returns the relative reference for the node's business object.
	 * @return the relative reference for the node's business object.
	 */
	public final RelativeBusinessObjectReference getRelativeReference() {
		return relativeReference;
	}

	@Override
	public final BusinessObjectNode getParent() {
		return parent;
	}

	@Override
	public final Object getBusinessObject() {
		return bo;
	}

	/**
	 * Sets the node's business object
	 * @param value the new business object
	 */
	public final void setBusinessObject(final Object value) {
		this.bo = value;
	}

	/**
	 * Returns the node's unique identifier
	 * @return the node's unique identifier
	 * @see org.osate.ge.internal.diagram.runtime.DiagramElement#getId()
	 */
	public final UUID getId() {
		return id;
	}

	/**
	 * Returns the node's completeness state
	 * @return the node's completeness state
	 * @see org.osate.ge.internal.diagram.runtime.DiagramElement#getCompleteness()
	 */
	public final Completeness getCompleteness() {
		return completeness;
	}

	/**
	 * Set the node's completeness state
	 * @param value the new completeness state
	 * @see #getCompleteness()
	 */
	public final void setCompleteness(final Completeness value) {
		this.completeness = Objects.requireNonNull(value, "value must not be null");
	}

	/**
	 * Returns an unmodifiable collection containing the node's children.
	 * @return an unmodifiable collection containing the node's children. Will never return null.
	 */
	@Override
	public final Collection<BusinessObjectNode> getChildren() {
		return children == null ? Collections.emptyList() : Collections.unmodifiableCollection(children.values());
	}

	/**
	 * Returns an unmodifiable mapping between the business object of child nodes to the child nodes.
	 * @return an unmodifiable mapping between the business object of child nodes to the child nodes. Will never return null.
	 */
	public final Map<RelativeBusinessObjectReference, BusinessObjectNode> getChildrenMap() {
		return children == null ? Collections.emptyMap() : Collections.unmodifiableMap(children);
	}

	/**
	 * Returns the child business object node with the specified relative reference
	 * @param ref the relative reference of the child to return
	 * @return the child business object node with the specified reference. Returns null if such a node was not found.
	 */
	public final BusinessObjectNode getChild(final RelativeBusinessObjectReference ref) {
		if(children == null) {
			return null;
		}

		return children.get(ref);
	}

	private void addChild(final BusinessObjectNode node) {
		Objects.requireNonNull(node.relativeReference, "relativeReference must not be null");

		if(children == null) {
			children = new HashMap<>();
		}

		if (children.containsKey(node.relativeReference)) {
			throw new IllegalArgumentException("Node already has a child with reference: " + node.relativeReference);
		}

		children.put(node.relativeReference, node);
	}

	/**
	 * Removes the node from the tree. Removes the node from the parent and then sets parent to null.
	 */
	public void remove() {
		Objects.requireNonNull(relativeReference, "relativeReference must not be null");

		if (parent != null) {
			parent.children.remove(relativeReference);
			parent = null;
		}
	}

	/**
	 * Returns whether the node has not had its default children populated. This is usually true for new nodes to allow the tree updater to add
	 * children based on the content filters provided by the diagram type.
	 * @return whether the node has not had its default children populated
	 */
	public final boolean getDefaultChildrenHaveBeenPopulated() {
		return defaultChildrenHaveBeenPopulated;
	}

	/**
	 * Copies the node. The new node will be the root of a new tree
	 * @return the new node
	 * @see #copy(BusinessObjectNode)
	 */
	public BusinessObjectNode copy() {
		return copy(null);
	}

	/**
	 * Copies the node. The new node will be a child of the specified parent. The id of the node will be preserved.
	 * Id's are intended to be globally unique.
	 * @param newParent
	 * @return the new node
	 */
	private BusinessObjectNode copy(final BusinessObjectNode newParent) {
		final BusinessObjectNode newNode = new BusinessObjectNode(newParent, id, relativeReference, bo, completeness,
				defaultChildrenHaveBeenPopulated);
		for(final BusinessObjectNode child : getChildren()) {
			child.copy(newNode);
		}

		return newNode;
	}

	/**
	 * Looks for a node in the tree which has the same relative reference path as the specified search node.
	 * The path is a sequence of relative references which specify the path from the root to the node.
	 * @param tree the root node of the tree in which to search
	 * @param searchNode the node for which use to build the sequence of relative references to navigate
	 * @return the node in the tree. Returns null if the node could not be found.
	 */
	public static BusinessObjectNode findNodeByRelativeReferences(final BusinessObjectNode tree, final BusinessObjectNode searchNode) {
		// Build path to node we want to find
		final Deque<RelativeBusinessObjectReference> path = new ArrayDeque<RelativeBusinessObjectReference>();
		for(BusinessObjectNode t = searchNode; t.getParent() != null; t = t.getParent()) {
			path.push(t.relativeReference);
		}

		// Pop the path from the stack and find it in the specified tree
		BusinessObjectNode t = tree;
		while(t != null && !path.isEmpty()) {
			final RelativeBusinessObjectReference pathSegment = path.pop();
			t = t.getChild(pathSegment);
		}

		return t;
	}
}