PropertyValueFormatter.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.internal.aadlproperties;

import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtext.nodemodel.INode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.osate.aadl2.AbstractNamedValue;
import org.osate.aadl2.ArrayRange;
import org.osate.aadl2.BasicPropertyAssociation;
import org.osate.aadl2.BooleanLiteral;
import org.osate.aadl2.Classifier;
import org.osate.aadl2.ClassifierValue;
import org.osate.aadl2.ComputedValue;
import org.osate.aadl2.ContainmentPathElement;
import org.osate.aadl2.IntegerLiteral;
import org.osate.aadl2.NamedElement;
import org.osate.aadl2.NamedValue;
import org.osate.aadl2.RangeValue;
import org.osate.aadl2.RealLiteral;
import org.osate.aadl2.RecordValue;
import org.osate.aadl2.StringLiteral;
import org.osate.aadl2.instance.InstanceReferenceValue;
import org.osate.aadl2.modelsupport.util.AadlUtil;
import org.osate.ge.BusinessObjectContext;
import org.osate.ge.aadl2.internal.model.AgePropertyValue;
import org.osate.ge.aadl2.internal.model.PropertyValueGroup;

import com.google.common.collect.Ordering;

public class PropertyValueFormatter {
	private final static Comparator<AgePropertyValue> arrayIndicesComparator = new Comparator<AgePropertyValue>() {
		@Override
		public int compare(final AgePropertyValue pv1, final AgePropertyValue pv2) {
			return compare(pv1.getArrayIndices(), pv2.getArrayIndices(), 0);
		}

		private int compare(final List<Integer> list1, final List<Integer> list2, final int index) {
			if(list1.size() < index) {
				return -1;
			}

			if(list2.size() < index) {
				return 1;
			}

			int result = Integer.compare(list1.get(index), list2.get(index));
			return result == 0 ? compare(list1, list2, index+1) : result;
		}
	};

	private static final Comparator<String> caseInsensitiveNullFirstComparator = Ordering.from(String.CASE_INSENSITIVE_ORDER).nullsFirst();
	private static final Comparator<AgePropertyValue> appliesToComparator = Comparator.comparing((AgePropertyValue pv) -> pv.getAppliesToRef(), caseInsensitiveNullFirstComparator);
	private static final Comparator<AgePropertyValue> propertyValueComparator = Ordering.from(appliesToComparator).compound(arrayIndicesComparator);


	/**
	 *
	 * @param propertyValueQueryable
	 * @param prv
	 * @param expandComplexValues if true values inside of lists and groups will be contained in the result.
	 * @return
	 */
	public static String getUserString(
			final BusinessObjectContext pvgQueryable,
			final boolean singleLine,
			final boolean includeOnlyValuesBasedOnCompletelyProcessedAssociations,
			final boolean includeValues,
			final boolean includeAppliesTo,
			final boolean expandComplexValues) {
		if(pvgQueryable == null || pvgQueryable.getParent() == null) {
			return "";
		}

		if(!(pvgQueryable.getBusinessObject() instanceof PropertyValueGroup)) {
			throw new RuntimeException("Queryable business object must be a PropertyValueGroup");
		}
		final PropertyValueGroup pvg = (PropertyValueGroup)pvgQueryable.getBusinessObject();

		final StringBuilder sb = new StringBuilder();

		Stream<AgePropertyValue> propertyValuesStream = pvg.getPropertyValues().stream();
		if(includeOnlyValuesBasedOnCompletelyProcessedAssociations) {
			propertyValuesStream = propertyValuesStream.filter(pv -> pv.isBasedOnCompletelyProcessedAssociation());
		}

		final List<AgePropertyValue> sortedPropertyValues = propertyValuesStream.
				sorted(propertyValueComparator).
				collect(Collectors.toList());

		if(singleLine && sortedPropertyValues.size() > 1) {
			sb.append(AadlUtil.getPropertySetElementName(pvg.getProperty()));

			if(includeValues) {
				sb.append(": <multiple>");
			}
		} else {
			final BusinessObjectContext pvgParentQueryable = pvgQueryable.getParent();
			for(final AgePropertyValue pv : sortedPropertyValues) {
				if(sb.length() != 0) {
					sb.append('\n');
				}

				sb.append(AadlUtil.getPropertySetElementName(pvg.getProperty()));
				appendArrayIndices(sb, pv);

				if(includeValues) {
					sb.append(": ");
					appendPropertyResultValue(sb, pv.getPropertyResult(), pv.getValue(), pvgParentQueryable, expandComplexValues);
				}

				// Add applies to
				if(includeAppliesTo) {
					if(pv.getAppliesToRef() != null) {
						sb.append(" applies to ");
						sb.append(pv.getAppliesToRef());
					}
				}
			}
		}

		return sb.toString();
	}

	private static void appendArrayIndices(final StringBuilder sb,
			final AgePropertyValue pv) {
		// Append Indices
		for(final Integer idx : pv.getArrayIndices()) {
			sb.append('[');
			sb.append(idx.intValue()+1);
			sb.append(']');
		}
	}

	private static void appendPropertyResultValue(final StringBuilder sb,
			final PropertyResult pr,
			final Object valueToDisplay,
			final BusinessObjectContext pvgParentQueryable,
			final boolean expandComplexValues) {
		if(valueToDisplay == null) {
			// Append explanation for null value
			if(pr.nullReason != null) {
				final String nullReasonStr;
				switch(pr.nullReason) {
				case ARRAY_ELEMENT_SPECIFIC:
					nullReasonStr = "<array element specific>";
					break;

				case BINDING_SPECIFIC:
					nullReasonStr = "<binding specific>";
					break;

				case MODAL:
					nullReasonStr = "<modal>";
					break;

				default:
					nullReasonStr = "";
					break;
				}

				sb.append(nullReasonStr);
			}
		} else {
			appendPropertyValue(pvgParentQueryable, valueToDisplay, expandComplexValues, sb);
		}
	}

	@SuppressWarnings("unchecked")
	public static void appendPropertyValue(final BusinessObjectContext q,
			final Object value,
			final boolean expandComplexValues,
			final StringBuilder sb) {
		if(value instanceof List) {
			if(expandComplexValues) {
				sb.append('(');
				boolean isFirst = true;
				for(final Object element : (List<Object>)value) {
					if(!isFirst) {
						sb.append(", ");
					}
					isFirst = false;

					appendPropertyValue(q, element, expandComplexValues, sb);
				}
				sb.append(')');
			} else {
				sb.append("<list>");
			}
		} else {
			if(value instanceof BooleanLiteral) {
				final BooleanLiteral bl = (BooleanLiteral)value;
				sb.append(bl.getValue() ? "true" : "false");
			} else if(value instanceof ClassifierValue) {
				final ClassifierValue cv = (ClassifierValue)value;
				sb.append(cv.getClassifier() == null ? "<error>" : cv.getClassifier().getQualifiedName());
			} else if(value instanceof ComputedValue) {
				final ComputedValue cv = (ComputedValue)value;
				sb.append(cv.getFunction());
			} else if(value instanceof NamedValue) {
				final NamedValue nv = (NamedValue)value;
				if(nv.getNamedValue() == null) {
					sb.append("<null>");
				} else {
					appendPropertyValue(q, nv.getNamedValue(), expandComplexValues, sb);
				}
			} else if(value instanceof AbstractNamedValue) {
				final AbstractNamedValue anv = (AbstractNamedValue)value;
				sb.append(anv instanceof NamedElement ? ((NamedElement)anv).getName() : "<error>");
			} else if(value instanceof IntegerLiteral) {
				final IntegerLiteral il = (IntegerLiteral)value;
				sb.append(il.getValue());
				if(il.getUnit() != null) {
					sb.append(' ');
					sb.append(il.getUnit().getName());
				}
			} else if(value instanceof RealLiteral) {
				final RealLiteral rl = (RealLiteral)value;
				sb.append(rl.getValue());
				if(rl.getUnit() != null) {
					sb.append(' ');
					sb.append(rl.getUnit().getName());
				}
			} else if(value instanceof RangeValue) {
				final RangeValue rv = (RangeValue)value;
				appendPropertyValue(q, rv.getMinimum(), expandComplexValues, sb);
				sb.append(" .. ");
				appendPropertyValue(q, rv.getMaximum(), expandComplexValues, sb);
				if(rv.getDeltaValue() != null) {
					sb.append("delta ");
					appendPropertyValue(q, rv.getDelta(), expandComplexValues, sb);
				}
			} else if(value instanceof RecordValue) {
				final RecordValue rv = (RecordValue)value;
				if(expandComplexValues) {
					sb.append('[');
					for(final BasicPropertyAssociation bpa : rv.getOwnedFieldValues()) {
						sb.append(bpa.getProperty() == null ? "<UnknownField>" : bpa.getProperty().getName());
						sb.append(" => ");
						appendPropertyValue(q, bpa.getValue(), expandComplexValues, sb);
						sb.append("; ");
					}
					sb.append(']');
				} else {
					sb.append("<record>");
				}
			} else if(value instanceof StringLiteral) {
				final StringLiteral sl = (StringLiteral)value;
				sb.append('"');
				sb.append(sl.getValue());
				sb.append('"');
			} else if(value instanceof ReferenceValueWithContext) {
				final ReferenceValueWithContext rv = (ReferenceValueWithContext)value;

				BusinessObjectContext tmp = q;
				for(int i = 0; i < rv.propertyAssociationOwnerAncestorLevel; i++) {
					tmp = tmp.getParent();
					if(tmp == null) {
						return;
					}
				}

				// The reference is relative to the current value of tmp
				// Append Each Level Until Reaching the Containing Classifier
				String prefix = null;
				while(tmp != null && tmp.getBusinessObject() instanceof NamedElement && !(tmp.getBusinessObject() instanceof Classifier)) {
					final String containerName = ((NamedElement)tmp.getBusinessObject()).getName();
					if(containerName == null) {
						// Ignore
						return;
					}

					if(prefix == null) {
						prefix = containerName;
					} else {
						prefix = containerName + "." + prefix;
					}

					tmp = tmp.getParent();
				}

				// Handle relative portion.. need to add appropriate ancestors.
				if(prefix != null) {
					sb.append(prefix);
				}
				boolean isFirst = prefix == null;
				for(ContainmentPathElement pathElement = rv.referenceValue.getPath(); pathElement != null; pathElement = pathElement.getPath()) {
					if(!isFirst) {
						sb.append(".");
					}
					isFirst = false;

					final NamedElement ne = pathElement.getNamedElement();
					if(ne == null) {
						sb.append("<null>");
					} else {
						sb.append(ne.getName());
					}

					for(final ArrayRange ar : pathElement.getArrayRanges()) {
						sb.append('[');
						sb.append(ar.getLowerBound());
						if(ar.getUpperBound() > 0) {
							sb.append(" .. ");
							sb.append(ar.getUpperBound());
						}
						sb.append(']');
					}
				}

			} else if(value instanceof InstanceReferenceValue) {
				final InstanceReferenceValue irv = (InstanceReferenceValue)value;
				if(irv.getReferencedInstanceObject() != null) {
					sb.append(irv.getReferencedInstanceObject().getComponentInstancePath());
				} else {
					sb.append("?");
				}
			} else if(value instanceof EObject) {
				final INode n = NodeModelUtils.getNode((EObject)value);
				if(n != null) {
					final String txt = NodeModelUtils.getTokenText(n);
					if(txt != null) {
						sb.append(txt);
						return;
					}
				}
				sb.append("<Unable to Retrieve>");
			} else {
				sb.append("<Unsupported Value Type>");
			}
		}
	}
}