OperationExecutor.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.operations;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;
import org.eclipse.emf.ecore.EObject;
import org.osate.ge.RelativeBusinessObjectReference;
import org.osate.ge.internal.services.AadlModificationService;
import org.osate.ge.operations.Operation;
import org.osate.ge.operations.StepResult;
import org.osate.ge.operations.StepResultBuilder;
import org.osate.ge.services.ReferenceBuilderService;
/**
* Executes an operation. Only supports operation instances of type {@link Step}
*
*/
public class OperationExecutor {
private final AadlModificationService modificationService;
private final ReferenceBuilderService referenceBuilder;
/**
* Creates a new instance
* @param modificationService the service to use to modify the model
* @param referenceBuilder the reference builder
*/
public OperationExecutor(final AadlModificationService modificationService,
final ReferenceBuilderService referenceBuilder) {
this.modificationService = Objects.requireNonNull(modificationService, "modificationService must not be null");
this.referenceBuilder = Objects.requireNonNull(referenceBuilder, "referenceBuidler must nto be null");
}
private class ExecutionState {
final LinkedHashSet<Supplier<? extends StepResult<?>>> pendingStepConsumers = new LinkedHashSet<>();
final List<AadlModificationService.Modification<?, ?>> modifications = new ArrayList<>();
final OperationResults results = new OperationResults();
boolean aborted = false;
private void addStepResult(final StepResult<?> stepResult) {
stepResult.getContainerToBoToShowMap().entries().stream().forEachOrdered(e -> {
final Object bo = e.getValue();
final RelativeBusinessObjectReference ref = referenceBuilder.getRelativeReference(bo);
results.getContainerToBoToShowDetailsMap()
.put(e.getKey(), new OperationResults.BusinessObjectToShowDetails(bo, ref));
});
}
}
/**
* Executes an operation
* @param op the operation to execute. Must be of type {@link Step}.
* @return the operation result. Will not return null.
* @throws IllegalArgumentException if the operation is non-null and is not of type {@link Step}
*/
public OperationResults execute(final Operation op) {
final ExecutionState executionState = new ExecutionState();
if (op == null) {
return null;
}
if (!(op instanceof Step)) {
throw new IllegalArgumentException("Operation is not of type Step");
}
prepareToExecute((Step) op, () -> StepResultBuilder.create().build(), executionState);
if (executionState.modifications.isEmpty()) {
finishExecution(executionState.pendingStepConsumers);
} else {
modificationService.modify(executionState.modifications, allSuccessful -> {
if (allSuccessful) {
finishExecution(executionState.pendingStepConsumers);
}
});
}
return executionState.results;
}
/**
* Finishes executing an operation. Ensures that pending step suppliers are called and the results processor is called
* @param pendingStepConsumers
*/
private void finishExecution(final LinkedHashSet<Supplier<? extends StepResult<?>>> pendingStepConsumers) {
// Evaluate steps which have not been evaluated.
while (!pendingStepConsumers.isEmpty()) {
pendingStepConsumers.iterator().next().get();
}
}
/**
*
* @param step is the step to process. The steps and all subsequent steps will be processed.
* @param prevResultSupplier the supplier for the value of the previous step. Must not be null.
* @param allResults a list to which the results of each step should be added. This list will be modified when the actual execution is performed. Will only contain non-null results.
* @param modifications a list of modifications that need to be executed by the the AADL modification service.
* @param uncalledStepResultSuppliers a list which will contain consumers generated by steps which have not been evaluated. This list will be modified as the consumers are executed.
*/
private <PrevResultUserType, ResultUserType> void prepareToExecute(final Step step,
final Supplier<StepResult<PrevResultUserType>> prevResultSupplier, final ExecutionState executionState) {
Objects.requireNonNull(step, "step must not be null");
final Supplier<StepResult<ResultUserType>> stepResultSupplier;
if (step instanceof SplitStep) {
for (final Step nextStep : ((SplitStep) step).getSteps()) {
prepareToExecute(nextStep, prevResultSupplier, executionState);
}
stepResultSupplier = () -> null; // Split steps don't produce a result and shouldn't have next steps either.
} else if (step instanceof ModelModificationStep) {
@SuppressWarnings("unchecked")
final ModelModificationStep<?, ?, PrevResultUserType, ResultUserType> ms = (ModelModificationStep<?, ?, PrevResultUserType, ResultUserType>) step;
stepResultSupplier = prepareToExecuteModification(ms, prevResultSupplier, executionState);
} else if (step instanceof SuboperationStep) {
stepResultSupplier = new Supplier<StepResult<ResultUserType>>() {
@Override
public StepResult<ResultUserType> get() {
@SuppressWarnings("unchecked")
final SuboperationStep<PrevResultUserType> ss = (SuboperationStep<PrevResultUserType>) step;
final StepResult<PrevResultUserType> prevResult = prevResultSupplier.get();
executionState.pendingStepConsumers.remove(this);
if (executionState.aborted) {
return StepResult.abort();
} else {
Operation suboperation = ss.getOperationProvider().apply(prevResult.getUserValue());
final OperationResults suboperationResults = execute(suboperation);
executionState.results.getContainerToBoToShowDetailsMap()
.putAll(suboperationResults.getContainerToBoToShowDetailsMap());
}
return StepResult.forValue(null);
}
};
// Suboperation steps should never have a next step so they must be added ot the pending step consumers.
executionState.pendingStepConsumers.add(stepResultSupplier);
} else if (step instanceof MapStep) {
stepResultSupplier = new Supplier<StepResult<ResultUserType>>() {
private boolean resultIsValid = false;
private StepResult<ResultUserType> result;
@Override
public StepResult<ResultUserType> get() {
if (!resultIsValid) {
@SuppressWarnings("unchecked")
final MapStep<PrevResultUserType, ResultUserType> ts = (MapStep<PrevResultUserType, ResultUserType>) step;
final StepResult<PrevResultUserType> prevResult = prevResultSupplier.get();
if (executionState.aborted) {
result = StepResult.abort();
} else {
result = ts.getMapper().apply(prevResult.getUserValue());
if (result.aborted()) {
executionState.aborted = true;
}
}
resultIsValid = true;
executionState.pendingStepConsumers.remove(this);
if (result != null) {
executionState.addStepResult(result);
}
}
return result;
}
};
// Only add the step result supplier to the uncalled step suppliers if there isn't a next step.
// If there is a next step, the supplier will be called when that step is evaluated.
// Modification step result suppliers aren't added to the list because the result is produced when the modification is executed.
if (step.getNextStep() == null) {
executionState.pendingStepConsumers.add(stepResultSupplier);
}
} else {
throw new IllegalArgumentException("Unexpected step: " + step);
}
if (step.getNextStep() != null) {
prepareToExecute(step.getNextStep(), stepResultSupplier, executionState);
}
}
private static <TagType, BusinessObjectType extends EObject, PrevResultUserType, ResultUserType> Supplier<StepResult<ResultUserType>> prepareToExecuteModification(
ModelModificationStep<TagType, BusinessObjectType, PrevResultUserType, ResultUserType> modificationStep,
final Supplier<StepResult<PrevResultUserType>> prevResultSupplier, final ExecutionState executionState) {
class ModificationStepModifier implements AadlModificationService.Modifier<TagType, BusinessObjectType> {
StepResult<ResultUserType> result;
@Override
public void modify(final TagType tag, final BusinessObjectType boToModify) {
result = modificationStep.getModifier()
.modify(tag, boToModify, prevResultSupplier.get().getUserValue());
if (result != null) {
executionState.addStepResult(result);
if (result.aborted()) {
executionState.aborted = true;
}
}
}
}
final ModificationStepModifier modifier = new ModificationStepModifier();
final AadlModificationService.Modification<TagType, BusinessObjectType> modification = AadlModificationService.Modification
.create(modificationStep.getTag(), (tag) -> {
final StepResult<PrevResultUserType> prevResult = prevResultSupplier.get();
return executionState.aborted ? null
: modificationStep.getBusinessObjectProvider()
.getBusinessObject(tag, prevResult.getUserValue());
}, modifier);
executionState.modifications.add(modification);
return () -> modifier.result;
}
}