CombinedErrorModelSubclause.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.errormodel.combined;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;

import org.eclipse.emf.common.util.EList;
import org.osate.aadl2.Classifier;
import org.osate.aadl2.ComponentImplementation;
import org.osate.aadl2.ComponentType;
import org.osate.aadl2.NamedElement;
import org.osate.ge.errormodel.model.KeywordPropagationPointType;
import org.osate.ge.errormodel.util.ErrorModelGeUtil;
import org.osate.xtext.aadl2.errormodel.errorModel.ErrorFlow;
import org.osate.xtext.aadl2.errormodel.errorModel.ErrorPath;
import org.osate.xtext.aadl2.errormodel.errorModel.ErrorPropagation;
import org.osate.xtext.aadl2.errormodel.errorModel.ErrorSink;
import org.osate.xtext.aadl2.errormodel.errorModel.ErrorSource;
import org.osate.xtext.aadl2.errormodel.errorModel.PropagationPath;
import org.osate.xtext.aadl2.errormodel.errorModel.PropagationPoint;

import com.google.common.collect.Lists;

/**
 * Class represents the result of combining all error model subclauses for a given classifier. Handles classifier extension and
 * redefinition of model elements.
 *
 */
public final class CombinedErrorModelSubclause {
	/**
	 * Instance that doesn't contain any values. Useful to avoid needing to handle null values.
	 */
	public static final CombinedErrorModelSubclause EMPTY = new CombinedErrorModelSubclause(false,
			Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap(), Collections.emptySet(),
			new PropagationNode());

	private final boolean subclauseFound;
	private final Map<String, PropagationPoint> points;
	private final Map<String, PropagationPath> paths;
	private final Map<String, ErrorFlow> flows;
	private final ReadonlyPropagationNode propagations;
	private final Set<KeywordPropagationPointType> usedKeywordPointTypes;

	private CombinedErrorModelSubclause(final boolean subclauseFound, final Map<String, PropagationPoint> points,
			final Map<String, PropagationPath> paths, final Map<String, ErrorFlow> flows,
			final Set<KeywordPropagationPointType> usedKeywordPointTypes, final ReadonlyPropagationNode propagations) {
		this.subclauseFound = subclauseFound;
		this.points = Collections.unmodifiableMap(points);
		this.paths = Collections.unmodifiableMap(paths);
		this.flows = Collections.unmodifiableMap(flows);
		this.propagations = propagations;
		this.usedKeywordPointTypes = Collections.unmodifiableSet(usedKeywordPointTypes);
	}

	/**
	 * Returns whether at least one error model subclause was found.
	 * @return true if at least one applicable error model subclause was found
	 */
	public final boolean subclauseExists() {
		return subclauseFound;
	}

	/**
	 * Returns all the propagation points
	 * @return the propagation points
	 */
	public final Stream<PropagationPoint> getPoints() {
		return points.values().stream();
	}

	/**
	 * Returns all the propagation paths
	 * @return the propagation paths
	 */
	public final Stream<PropagationPath> getPaths() {
		return paths.values().stream();
	}

	/**
	 * Returns all the error flows
	 * @return the error flows
	 */
	public final Stream<ErrorFlow> getFlows() {
		return flows.values().stream();
	}

	/**
	 * Returns the {@link KeywordPropagationPointType} values which are referenced by other error model elements
	 * @return the {@link KeywordPropagationPointType} values which are referenced by other error model elements
	 */
	public final Set<KeywordPropagationPointType> getUsedKeywordPropagations() {
		return usedKeywordPointTypes;
	}

	/**
	 * Returns the root of propagation tree. Each propagation is represented as a node of this tree. A tree is used to represent propagations
	 * attached to members of feature groups.
	 * @return the root of the propagation tree.
	 */
	public final ReadonlyPropagationNode getPropagations() {
		return propagations;
	}

	/**
	 * Creates an instance for the specified classifier. Processes error model subclauses owned or inherited by the classifier.
	 * @param classifier the classifier for which to create the instance.
	 * @return the new instance
	 */
	public static CombinedErrorModelSubclause create(final Classifier classifier) {
		final Map<String, PropagationPoint> points = new HashMap<>();
		final Map<String, PropagationPath> paths = new HashMap<>();
		final Map<String, ErrorFlow> flows = new HashMap<>();
		final PropagationNode propagations = new PropagationNode();
		final Set<KeywordPropagationPointType> usedKeywordPointTypes = new HashSet<>();

		final EList<Classifier> classifiers = classifier.getSelfPlusAllExtended();
		if (classifier instanceof ComponentImplementation) {
			final ComponentType ct = ((ComponentImplementation) classifier).getType();
			if (ct != null) {
				classifiers.addAll(ct.getSelfPlusAllExtended());
			}
		}

		final boolean[] subclauseFound = { false };
		for (final Classifier tmpClassifier : Lists.reverse(classifiers)) {
			ErrorModelGeUtil.getAllErrorModelSubclauses(tmpClassifier).forEachOrdered(subclause -> {
				subclauseFound[0] = true;
				addAllToMap(subclause.getPoints(), points);
				addAllToMap(subclause.getPaths(), paths);
				addAllToMap(subclause.getFlows(), flows);

				// Create a tree for error propagations
				for (final ErrorPropagation propagation : subclause.getPropagations()) {
					propagations.put(propagation);
				}

				//
				// Find used propagation point keywords
				//
				for (final ErrorPropagation propagation : subclause.getPropagations()) {
					if (propagation.getKind() != null) {
						usedKeywordPointTypes.add(KeywordPropagationPointType.getByKind(propagation.getKind()));
					}
				}

				for (final ErrorFlow errorFlow : subclause.getFlows()) {
					if (errorFlow instanceof ErrorPath) {
						final ErrorPath errorPath = (ErrorPath) errorFlow;
						if (errorPath.isAllIncoming() || errorPath.isAllOutgoing()) {
							usedKeywordPointTypes.add(KeywordPropagationPointType.ALL);
						}

						if (errorPath.getIncoming() != null) {
							usedKeywordPointTypes
									.add(KeywordPropagationPointType.getByKind(errorPath.getIncoming().getKind()));
						}

						if (errorPath.getOutgoing() != null) {
							usedKeywordPointTypes
									.add(KeywordPropagationPointType.getByKind(errorPath.getOutgoing().getKind()));
						}
					} else if (errorFlow instanceof ErrorSink) {
						final ErrorSink errorSink = (ErrorSink) errorFlow;
						if (errorSink.isAllIncoming()) {
							usedKeywordPointTypes.add(KeywordPropagationPointType.ALL);
						} else if (errorSink.getIncoming() != null) {
							usedKeywordPointTypes
									.add(KeywordPropagationPointType.getByKind(errorSink.getIncoming().getKind()));
						}
					} else if (errorFlow instanceof ErrorSource) {
						final ErrorSource errorSrc = (ErrorSource) errorFlow;
						if (errorSrc.isAll()) {
							usedKeywordPointTypes.add(KeywordPropagationPointType.ALL);
						} else if (errorSrc.getSourceModelElement() instanceof ErrorPropagation) {
							usedKeywordPointTypes.add(KeywordPropagationPointType
									.getByKind(((ErrorPropagation) errorSrc.getSourceModelElement()).getKind()));
						}
					}
				}
			});
		}

		return new CombinedErrorModelSubclause(subclauseFound[0], points, paths, flows, usedKeywordPointTypes,
				propagations);
	}

	/**
	 * Adds all the specified named elements to the specified map. Uses the lowercase name as the key.
	 * @param <T> the type of the named elements
	 * @param values the values to add
	 * @param map the map to add the values to
	 */
	private static <T extends NamedElement> void addAllToMap(final Collection<T> values, final Map<String, T> map) {
		for (final T value : values) {
			final String name = value.getName();
			if (name != null) {
				map.put(name.toLowerCase(), value);
			}
		}
	}
}