DiagramElement.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;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

import org.osate.ge.GraphicalConfiguration;
import org.osate.ge.RelativeBusinessObjectReference;
import org.osate.ge.businessobjecthandling.BusinessObjectHandler;
import org.osate.ge.graphics.Dimension;
import org.osate.ge.graphics.Graphic;
import org.osate.ge.graphics.Point;
import org.osate.ge.graphics.Style;
import org.osate.ge.internal.diagram.runtime.updating.Completeness;
import org.osate.ge.internal.query.RelativeReferenceProvider;

import com.google.common.collect.ImmutableList;

/**
 * A node in the {@link AgeDiagram} tree which represents a single business object. Instances are displayed
 * graphically in the editor.
 * All non-root {@link DiagramNode} instances which make up an {@link AgeDiagram} are instances of this class.
 */
public class DiagramElement extends DiagramNode implements RelativeReferenceProvider {
	private final DiagramNode container;

	private final UUID id;
	private Object bo;
	private BusinessObjectHandler boHandler;
	private RelativeBusinessObjectReference boRelReference;
	private Completeness completeness = Completeness.UNKNOWN;
	private final DiagramElementCollection children = new DiagramElementCollection();
	private String labelName;
	private String uiName;
	private GraphicalConfiguration graphicalConfig; // Required after initialization.
	private Style style = Style.EMPTY; // Will never be null

	private Point position; // Optional. Relative to container.

	// Shape Specific
	private Dimension size; // Optional
	private DockArea dockArea; // Optional

	// Connection Specific
	private ImmutableList<Point> bendpoints; // Optional. Diagram coordinate system. A null value indicates that the bendpoints have not been configured.
	private Point connectionPrimaryLabelPosition; // Optional. Position of the connection label. Relative to the midpoint of the connection.

	/**
	 * Creates a new instance
	 * It is intended that bo and boHandler will not be null except for during the diagram loading process.
	 * Once the diagram is updated, these fields should be non-null
	 * @param container the container for the diagram element. The diagram element will not be added to the container as part of this
	 * constructor. Diagram elements are added to their container using {@link DiagramModification#addElement(DiagramElement)}.
	 * @param bo the business object to which the diagram element is related.
	 * @param boHandler the handler for the business object
	 * @param boRelReference the relative reference for the business object. Must not be null.
	 * @param id a unique identifier used to distinguish diagram elements from others in the same diagram. Must not be null.
	 */
	public DiagramElement(final DiagramNode container, final Object bo, final BusinessObjectHandler boHandler,
			final RelativeBusinessObjectReference boRelReference, final UUID id) {
		this.container = container;
		this.bo = bo;
		this.boHandler = boHandler;
		this.boRelReference = Objects.requireNonNull(boRelReference, "boRelReference must not be null");
		this.id = Objects.requireNonNull(id, "id must not be null");
	}

	@Override
	public final DiagramNode getParent() {
		return container;
	}

	@Override
	public final Collection<DiagramElement> getChildren() {
		return Collections.unmodifiableCollection(children);
	}

	@Override
	public final DiagramElementCollection getModifiableChildren() {
		return children;
	}

	@Override
	public final DiagramElement getChildByRelativeReference(final RelativeBusinessObjectReference ref) {
		return children.getByRelativeReference(ref);
	}

	/**
	 * Returns the identifier for the diagram element. Unique within a diagram.
	 * @return the identifier for the diagram element. Unique within a diagram.
	 */
	public final UUID getId() {
		return id;
	}

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

	/**
	 * Returns whether the diagram element is complete
	 * @return whether the diagram element is complete. Will not return null.
	 */
	public final Completeness getCompleteness() {
		return completeness;
	}

	/**
	 * Sets whether the value which indicates whether the diagram element is complete.
	 * @param value the new completeness state. Must not be null.
	 * @see DiagramModification#setCompleteness(DiagramElement, Completeness)
	 */
	final void setCompleteness(final Completeness value) {
		this.completeness = Objects.requireNonNull(value, "value must not be null");
	}

	/**
	 * Returns the business object handler
	 * @return the business object handler
	 */
	public final BusinessObjectHandler getBusinessObjectHandler() {
		return boHandler;
	}

	/**
	 * Sets the business object handler.
	 * @param value the new business object handler
	 * @see DiagramModification#setBusinessObjectHandler(DiagramElement, BusinessObjectHandler)
	 */
	public final void setBusinessObjectHandler(final BusinessObjectHandler value) {
		this.boHandler = value;
	}

	/**
	 * Sets the business object
	 * @param value the new business object
	 * @see DiagramModification#updateBusinessObject(DiagramElement, Object, RelativeBusinessObjectReference)
	 * @see DiagramModification#updateBusinessObjectWithSameRelativeReference(DiagramElement, Object)
	 */
	final void setBusinessObject(final Object value) {
		this.bo = Objects.requireNonNull(value, "value must not be null");
	}

	/**
	 * Sets the reference to the business object
	 * @param value the new business object reference
	 */
	final void setRelativeReference(final RelativeBusinessObjectReference value) {
		this.boRelReference = Objects.requireNonNull(value, "value must not be null");
	}

	@Override
	public final RelativeBusinessObjectReference getRelativeReference() {
		return boRelReference;
	}

	/**
	 * Returns the name used for the diagram element's label
	 * @return the name used for the diagram element's label
	 * @see BusinessObjectHandler#getNameForDiagram(org.osate.ge.businessobjecthandling.GetNameForDiagramContext)
	 */
	public final String getLabelName() {
		return labelName;
	}

	/**
	 * Sets the name used for the diagram element's label
	 * @param value the new value
	 * @see DiagramModification#setLabelName(DiagramElement, String)
	 */
	final void setLabelName(final String value) {
		this.labelName = value;
	}

	/**
	 * Returns the name used to identify the diagram element in the user interface
	 * @return the name used to identify the diagram element in the user interface
	 * @see BusinessObjectHandler#getName(org.osate.ge.businessobjecthandling.GetNameContext)
	 */
	public final String getUserInterfaceName() {
		return uiName;
	}

	/**
	 * Sets the name used to identify the diagram element in the user interface
	 * @param value the new value
	 * @see DiagramModification#setUserInterfaceName(DiagramElement, String)
	 */
	final void setUserInterfaceName(final String value) {
		this.uiName = value;
	}

	/**
	 * Returns true if the diagram element has a non-null position
	 * @return true if the diagram element has a non-null position
	 */
	public final boolean hasPosition() {
		return position != null;
	}

	/**
	 * Returns the diagram element's position
	 * @return the element's position or null.
	 */
	public final Point getPosition() {
		return position;
	}

	/**
	 * Returns the X component of the position.
	 * @return 0 if the element does not have a position
	 * @see #hasPosition()
	 * @see #getPosition()
	 */
	public final double getX() {
		return position == null ? 0 : position.x;
	}

	/**
	 * Returns the Y component of the position
	 * @return 0 if the element does not have a position
	 * @see #hasPosition()
	 * @see #getPosition()
	 */
	public final double getY() {
		return position == null ? 0 : position.y;
	}

	/**
	 * Sets the position of the diagram element
	 * @param value the new value
	 * @see DiagramModification#setPosition(DiagramElement, Point)
	 */
	final void setPosition(final Point value) {
		this.position = value;
	}

	/**
	 * Returns true if the diagram element has a non-null size
	 * @return true if the diagram element has a non-null size
	 */
	public boolean hasSize() {
		return size != null;
	}

	/**
	 * Returns the diagram element's size
	 * @return element's size or null.
	 */
	public final Dimension getSize() {
		return size;
	}

	/**
	 * Return the width of the diagram element
	 * @return 0 if the element does not have a size
	 * @see #hasSize()
	 * @see #getSize()
	 */
	public final double getWidth() {
		return size == null ? 0 : size.width;
	}

	/**
	 * Returns the height of the diagram element
	 * @return 0 if the element does not have a size
	 * @see #hasSize()
	 * @see #getSize()
	 */
	public final double getHeight() {
		return size == null ? 0 : size.height;
	}

	/**
	 * Sets the size of the diagram element
	 * @param value the new value
	 * @see DiagramModification#setSize(DiagramElement, Dimension)
	 */
	final void setSize(final Dimension value) {
		this.size = value;
	}

	/**
	 * Returns the diagram element's user-specified style. Fields in this style overrides styles specified in the graphical configuration
	 * and the default style.
	 * @return the diagram element's style
	 */
	public final Style getStyle() {
		return style;
	}

	/**
	 * Sets the user specified style.
	 * @param value the new style. Must not be null.
	 * @see #getStyle()
	 * @see DiagramModification#setStyle(DiagramElement, Style)
	 */
	public final void setStyle(final Style value) {
		this.style = Objects.requireNonNull(value, "value must not be null");
	}

	/**
	 * Returns the diagram element's graphical configuration.
	 * @return the diagram element's graphical configuration
	 */
	public final GraphicalConfiguration getGraphicalConfiguration() {
		return graphicalConfig;
	}

	/**
	 * Sets the diagram element's graphical configuration
	 * @param value the new value
	 */
	final void setGraphicalConfiguration(final GraphicalConfiguration value) {
		this.graphicalConfig = value;
	}

	/**
	 * Returns the graphical configuration's graphic
	 * @return the graphical configuration's graphic. Returns null if the graphical configuration is null.
	 * @see #getGraphicalConfiguration()
	 * @see GraphicalConfiguration#getGraphic()
	 */
	public final Graphic getGraphic() {
		return graphicalConfig == null ? null : graphicalConfig.getGraphic();
	}

	/**
	 * Returns the graphical configuration's connection source
	 * @return the graphical configuration's connection source. Returns null if the graphical configuration is null.
	 * @see #getGraphicalConfiguration()
	 * @see GraphicalConfiguration#getConnectionSource()
	 */
	public final DiagramElement getStartElement() {
		return graphicalConfig.getConnectionSource();
	}

	/**
	 * Returns the graphical configuration's connection destination.
	 * @return the graphical configuration's connection destination. Returns null if the graphical configuration is null.
	 * @see #getGraphicalConfiguration()
	 * @see GraphicalConfiguration#getConnectionDestination()
	 */
	public final DiagramElement getEndElement() {
		return graphicalConfig.getConnectionDestination();
	}

	/**
	 * Returns the diagram element's dock area
	 * @return the diagram element's dock area. Returns null if the diagram element is not docked.
	 */
	public final DockArea getDockArea() {
		return dockArea;
	}

	/**
	 * Sets the diagram element's dock area.
	 * @param value the new value. A null value indicates that the diagram element is not docked.
	 * @see DiagramModification#setDockArea(DiagramElement, DockArea)
	 */
	final void setDockArea(final DockArea value) {
		this.dockArea = value;
	}

	/**
	 * Returns true if the diagram element's bendpoints have been set. Will return true when there are no bendpoints if the bendpoints
	 * have been set to an empty list.
	 * @return true if the diagram element's bendpoints have been set.
	 */
	public final boolean isBendpointsSet() {
		return bendpoints != null;
	}

	/**
	 * Returns an immutable list of the element's bendpoints.
	 * The list is immutable. The returned list will not be updated to reflect changes in the diagram element.
	 * Bendpoints are specified in diagram coordinates rather than relative to the diagram element.
	 * @return never returns null.
	 */
	public final List<Point> getBendpoints() {
		return bendpoints == null ? Collections.emptyList() : bendpoints;
	}

	/**
	 * Sets the diagram element's bendpoints. Only relevant for connections and flow indicators.
	 * Bendpoints may be set to null instead of an empty list to indicate that the bendpoints have not been set.
	 * To indicate that the diagram element does not have any bendpoints, an empty value should be set.
	 * @param value the new bendpoints.
	 * @see DiagramModification#setBendpoints(DiagramElement, List)
	 */
	final void setBendpoints(final List<Point> value) {
		bendpoints = value == null ? null : ImmutableList.copyOf(value);
	}

	/**
	 * Returns the position of the primary label position. Only valid for connections and flow indicators.
	 * The position is relative to the midpoint of the connection.
	 * @return will return null if the position has not been set.
	 */
	public final Point getConnectionPrimaryLabelPosition() {
		return connectionPrimaryLabelPosition;
	}

	/**
	 * Sets the position of the primary label. Only appropriate for connections and flow indicators
	 * @param value the position of the label relative to the connection's midpoint.
	 */
	final void setConnectionPrimaryLabelPosition(final Point value) {
		this.connectionPrimaryLabelPosition = value;
	}

	@Override
	public String toString() {
		final StringBuilder sb = new StringBuilder();
		toString(sb, "");
		return sb.toString();
	}

	/**
	 * Appends a string representation of the object to the specified string builder.
	 * @param sb the string builder to which to append the string representation
	 * @param indentation a string containing the indentation that should be added to each line
	 */
	void toString(final StringBuilder sb, final String indentation) {
		sb.append(indentation);
		sb.append('{');
		sb.append(System.lineSeparator());

		final String innerIndentation = indentation + '\t';
		sb.append(innerIndentation);
		sb.append("relative reference: ");
		sb.append(boRelReference);
		sb.append(System.lineSeparator());

		sb.append(innerIndentation);
		sb.append("id: ");
		sb.append(id);
		sb.append(System.lineSeparator());

		sb.append(innerIndentation);
		sb.append("graphicalConfig: ");
		sb.append(graphicalConfig);
		sb.append(System.lineSeparator());

		if (position != null) {
			sb.append(innerIndentation);
			sb.append("position: ");
			sb.append(position);
			sb.append(System.lineSeparator());
		}

		if (size != null) {
			sb.append(innerIndentation);
			sb.append("size: ");
			sb.append(size);
			sb.append(System.lineSeparator());
		}

		if (dockArea != null) {
			sb.append(innerIndentation);
			sb.append("dock area: ");
			sb.append(dockArea);
			sb.append(System.lineSeparator());
		}

		if (bendpoints != null) {
			sb.append(innerIndentation);
			sb.append("bendpoints: ");
			sb.append(Arrays.toString(bendpoints.toArray(new Point[bendpoints.size()])));
			sb.append(System.lineSeparator());
		}

		if (style != null) {
			sb.append(innerIndentation);
			sb.append("style: ");
			sb.append(style);
			sb.append(System.lineSeparator());
		}

		if (children.size() > 0) {
			children.toString(sb, innerIndentation);
		}

		sb.append(indentation);
		sb.append('}');
		sb.append(System.lineSeparator());
	}
}