PcmRepositoryWriterAnnotator.java

package de.uka.ipd.sdq.beagle.core.pcmconnection;

import de.uka.ipd.sdq.beagle.core.Blackboard;
import de.uka.ipd.sdq.beagle.core.ExternalCallParameter;
import de.uka.ipd.sdq.beagle.core.MeasurableSeffElement;
import de.uka.ipd.sdq.beagle.core.ResourceDemandType;
import de.uka.ipd.sdq.beagle.core.ResourceDemandingInternalAction;
import de.uka.ipd.sdq.beagle.core.SeffBranch;
import de.uka.ipd.sdq.beagle.core.SeffLoop;
import de.uka.ipd.sdq.beagle.core.evaluableexpressions.EvaluableExpression;
import de.uka.ipd.sdq.beagle.core.failurehandling.FailureHandler;
import de.uka.ipd.sdq.beagle.core.failurehandling.FailureReport;

import de.uka.ipd.sdq.identifier.Identifier;

import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EObject;
import org.palladiosimulator.pcm.repository.impl.RepositoryImpl;
import org.palladiosimulator.pcm.seff.BranchAction;
import org.palladiosimulator.pcm.seff.ExternalCallAction;
import org.palladiosimulator.pcm.seff.InternalAction;
import org.palladiosimulator.pcm.seff.LoopAction;
import org.palladiosimulator.pcm.seff.impl.BranchActionImpl;
import org.palladiosimulator.pcm.seff.impl.ExternalCallActionImpl;
import org.palladiosimulator.pcm.seff.impl.InternalActionImpl;
import org.palladiosimulator.pcm.seff.impl.LoopActionImpl;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;

/**
 * This class is offering a method to annotate all final EvaluableExpressions of a given
 * Blackboard to a PCM-repository.
 *
 * @author Ansgar Spiegler
 */
public class PcmRepositoryWriterAnnotator {

	/**
	 * The FailureHandler for this class.
	 */
	private static final FailureHandler FAILURE_HANDLER = FailureHandler.getHandler("PcmStorer");

	/**
	 * Blackboard to get Mapping from.
	 */
	private final Blackboard blackboard;

	/**
	 * The Mappings of Seffs to Ids.
	 */
	private final PcmBeagleMappings pcmMappings;

	/**
	 * Object for converting and annotating EvaEx.
	 */
	private final PcmRepositoryWriterAnnotatorEvaEx annotatorForEvaEx = new PcmRepositoryWriterAnnotatorEvaEx();

	/**
	 * Helper class for {@link PcmRepositoryWriter}. Offering a method to write all final
	 * {@link EvaluableExpression} from a {@link Blackboard} to a given
	 * {@link RepositoryImpl}.
	 *
	 * @param blackboard The blackboard to read from
	 * @param pcmMappings The PcmMapping to get the IDs from
	 */
	public PcmRepositoryWriterAnnotator(final Blackboard blackboard, final PcmBeagleMappings pcmMappings) {
		if (blackboard == null || pcmMappings == null) {
			throw new NullPointerException("Construtor arguments for PcmRepositoryWriterAnnotator must not be null!");
		}
		this.blackboard = blackboard;
		this.pcmMappings = pcmMappings;
	}

	/**
	 * This method looks up each SeffLoop on the {@link #blackboard} and maps its ID to
	 * its finalExpression {@link Blackboard#getFinalExpressionFor(MeasurableSeffElement)}
	 * if such one exists. Otherwise, the SeffElement will not occur as Key-element.
	 *
	 * @return A map of SeffLoops IDs to its final EvaluableExpressions.
	 */
	private Map<String, EvaluableExpression> getMapFromIdToEvaExOfAllSeffLoopsWithFinalExpressionsFromBlackboard() {

		final Map<String, EvaluableExpression> seffLoopIdToEvaEx = new HashMap<String, EvaluableExpression>();

		for (final SeffLoop seffLoop : this.blackboard.getAllSeffLoops()) {
			final EvaluableExpression evaEx = this.blackboard.getFinalExpressionFor(seffLoop);
			if (evaEx != null && this.pcmMappings.hasPcmIdOf(seffLoop)) {

				seffLoopIdToEvaEx.put(this.pcmMappings.getPcmIdOf(seffLoop), evaEx);
			}
		}
		return seffLoopIdToEvaEx;
	}

	/**
	 * This method looks up each SeffBranch on the {@link #blackboard} and maps its ID to
	 * its finalExpression {@link Blackboard#getFinalExpressionFor(MeasurableSeffElement)}
	 * if such one exists. Otherwise, the SeffElement will not occur as Key-element.
	 *
	 * @return A map of SeffBranche IDs to its final EvaluableExpressions.
	 */
	private Map<String, EvaluableExpression> getMapFromIdToEvaExOfAllSeffBranchesWithFinalExpressionsFromBlackboard() {

		final Map<String, EvaluableExpression> seffBranchIdToEvaEx = new HashMap<String, EvaluableExpression>();

		for (final SeffBranch seffBranch : this.blackboard.getAllSeffBranches()) {
			final EvaluableExpression evaEx = this.blackboard.getFinalExpressionFor(seffBranch);
			if (evaEx != null && this.pcmMappings.hasPcmIdOf(seffBranch)) {

				seffBranchIdToEvaEx.put(this.pcmMappings.getPcmIdOf(seffBranch), evaEx);
			}
		}
		return seffBranchIdToEvaEx;
	}

	/**
	 * This method looks up each RDIA on the {@link #blackboard} and maps its ID to its
	 * finalExpression {@link Blackboard#getFinalExpressionFor(MeasurableSeffElement)} if
	 * such one exists. Otherwise, the SeffElement will not occur as Key-element.
	 *
	 * @return A map of RDIA IDs to its final EvaluableExpressions.
	 */
	private Map<String, EvaluableExpression> getMapFromIdToEvaExOfAllRdiasWithFinalExpressionsFromBlackboard() {

		final Map<String, EvaluableExpression> rdiaIdToEvaEx = new HashMap<String, EvaluableExpression>();

		for (final ResourceDemandingInternalAction rdia : this.blackboard.getAllRdias()) {
			final EvaluableExpression evaEx = this.blackboard.getFinalExpressionFor(rdia);
			if (evaEx != null && this.pcmMappings.hasPcmIdOf(rdia)) {

				rdiaIdToEvaEx.put(this.pcmMappings.getPcmIdOf(rdia), evaEx);
			}
		}
		return rdiaIdToEvaEx;
	}

	/**
	 * This method looks up each RDIA on the {@link #blackboard} and maps its ID to its
	 * ResourceDemandType if a final EvaluablExpression is found on the Blackboard.
	 * Otherwise, the RDIA will not occur as Key-element.
	 *
	 * @return A map of RDIA IDs to its ResourceDemandTypes.
	 */
	private Map<String, ResourceDemandType> getMapFromIdToResourceDemandTypeOfAllRdiasWithFinalExpressionsFromBlackboard() {

		final Map<String, ResourceDemandType> rdiaIdToDemandType = new HashMap<String, ResourceDemandType>();

		for (final ResourceDemandingInternalAction rdia : this.blackboard.getAllRdias()) {
			final EvaluableExpression evaEx = this.blackboard.getFinalExpressionFor(rdia);
			if (evaEx != null && this.pcmMappings.hasPcmIdOf(rdia)) {

				rdiaIdToDemandType.put(this.pcmMappings.getPcmIdOf(rdia), rdia.getResourceType());
			}
		}
		return rdiaIdToDemandType;
	}

	/**
	 * This method looks up each ExternalCallParameter on the {@link #blackboard} and maps
	 * its ID to its finalExpression
	 * {@link Blackboard#getFinalExpressionFor(MeasurableSeffElement)} if such one exists.
	 * Otherwise, the SeffElement will no occur as Key-element.
	 *
	 * @return A map of ExternalCallParameter IDs to its final EvaluableExpressions.
	 */
	private Map<String, EvaluableExpression> getMapFromIdToEvaExOfAllExternalCallParameterWithFinalExpressionsFromBlackboard() {

		final Map<String, EvaluableExpression> exParamIdToEvaEx = new HashMap<String, EvaluableExpression>();

		for (final ExternalCallParameter exParam : this.blackboard.getAllExternalCallParameters()) {
			final EvaluableExpression evaEx = this.blackboard.getFinalExpressionFor(exParam);
			if (evaEx != null && this.pcmMappings.hasPcmIdOf(exParam)) {

				exParamIdToEvaEx.put(this.pcmMappings.getPcmIdOf(exParam), evaEx);
			}
		}
		return exParamIdToEvaEx;
	}

	/**
	 * This method annotates given {@link EvaluableExpression} to corresponding PCM
	 * elements. Therefore all IDs of the given SeffElements with EvaluableExpression must
	 * be found in the repository-file.
	 *
	 * @param repository The repository file to store the given EvaluableExpressions (will
	 *            overwrite the old StochasticExpressions)
	 */
	public void annotateAll(final RepositoryImpl repository) {

		if (repository == null) {
			throw new NullPointerException(
				"The repository in the method \"RepositoryWriter.annotateAll\" must not be null!");
		}

		final Map<String, EvaluableExpression> seffLoopIdsToEvaEx =
			this.getMapFromIdToEvaExOfAllSeffLoopsWithFinalExpressionsFromBlackboard();
		final Map<String, EvaluableExpression> seffBranchIdsToEvaEx =
			this.getMapFromIdToEvaExOfAllSeffBranchesWithFinalExpressionsFromBlackboard();
		final Map<String, EvaluableExpression> rdiaIdsToEvaEx =
			this.getMapFromIdToEvaExOfAllRdiasWithFinalExpressionsFromBlackboard();
		final Map<String, ResourceDemandType> rdiaIdsToResourceDemandType =
			this.getMapFromIdToResourceDemandTypeOfAllRdiasWithFinalExpressionsFromBlackboard();
		final Map<String, EvaluableExpression> exParamIdsToEvaEx =
			this.getMapFromIdToEvaExOfAllExternalCallParameterWithFinalExpressionsFromBlackboard();

		//final ListIterator<EObject> contentListIterator = IteratorUtils.toListIterator(repository.eAllContents());
		final TreeIterator<EObject> treeIterator = repository.eAllContents();
		final LinkedList<EObject> copyList = new LinkedList<EObject>();
		while (treeIterator.hasNext()) {
			copyList.add(treeIterator.next());
		}
		final Iterator<EObject> copyListIterator = copyList.iterator();
		
		while (copyListIterator.hasNext()) {
			final EObject content = copyListIterator.next();

			this.annotateForEObject(content, seffLoopIdsToEvaEx, seffBranchIdsToEvaEx, rdiaIdsToEvaEx,
				rdiaIdsToResourceDemandType, exParamIdsToEvaEx);

		}

		this.shouldBeEmpty(seffLoopIdsToEvaEx);
		this.shouldBeEmpty(seffBranchIdsToEvaEx);
		this.shouldBeEmpty(rdiaIdsToEvaEx);
		this.shouldBeEmpty(exParamIdsToEvaEx);

	}

	/**
	 * Find out, if a given EObject has the ID of a given Set. If so, the
	 * EvaluableExpression of the given Set is annotate to the specific PCM element,
	 * represented by this ID.
	 *
	 * @param eObject The EObject to test the ID
	 * @param seffLoopIdsToEvaEx Mapping from seffElement ID to its EvaluableExpression
	 * @param seffBranchIdsToEvaEx Mapping from seffElement ID to its EvaluableExpression
	 * @param rdiaIdsToEvaEx Mapping from seffElement ID to its EvaluableExpression
	 * @param rdiaIdsToResourceDemandType Mapping from RDIA ID to its
	 *            {@link ResourceDemandType}
	 * @param exParamIdsToEvaEx Mapping from seffElement ID to its EvaluableExpression
	 */
	private void annotateForEObject(final EObject eObject, final Map<String, EvaluableExpression> seffLoopIdsToEvaEx,
		final Map<String, EvaluableExpression> seffBranchIdsToEvaEx,
		final Map<String, EvaluableExpression> rdiaIdsToEvaEx,
		final Map<String, ResourceDemandType> rdiaIdsToResourceDemandType,
		final Map<String, EvaluableExpression> exParamIdsToEvaEx) {

		if (eObject instanceof Identifier) {
			final Identifier contentIdentifier = (Identifier) eObject;
			final String contentId = contentIdentifier.getId();

			if (seffLoopIdsToEvaEx.containsKey(contentId) && this.shouldBeLoopAction(contentIdentifier)) {

				final LoopActionImpl loopAction = (LoopActionImpl) contentIdentifier;
				this.annotatorForEvaEx.annotateEvaExFor(loopAction, seffLoopIdsToEvaEx.remove(contentId));

			} else if (seffBranchIdsToEvaEx.containsKey(contentId) && this.shouldBeBranchAction(contentIdentifier)) {

				final BranchActionImpl branchAction = (BranchActionImpl) contentIdentifier;
				this.annotatorForEvaEx.annotateEvaExFor(branchAction, seffBranchIdsToEvaEx.remove(contentId));

			} else if (rdiaIdsToEvaEx.containsKey(contentId) && this.shouldBeInternalAction(contentIdentifier)) {

				final InternalActionImpl internalAction = (InternalActionImpl) contentIdentifier;
				this.annotatorForEvaEx.annotateEvaExFor(internalAction, rdiaIdsToResourceDemandType.get(contentId),
					rdiaIdsToEvaEx.remove(contentId));

			} else if (exParamIdsToEvaEx.containsKey(contentId) && this.shouldBeExternalCallAction(contentIdentifier)) {

				final ExternalCallActionImpl externalAction = (ExternalCallActionImpl) contentIdentifier;
				this.annotatorForEvaEx.annotateEvaExFor(externalAction, exParamIdsToEvaEx.remove(contentId));

			}
		}

	}

	/**
	 * Checking if a given {@link Identifier} is a {@link LoopAction}. Otherwise a Failure
	 * is reported.
	 *
	 * @param content The {{@link Identifier} to check
	 * @return {@code true} if content is {@link LoopAction}
	 */
	private boolean shouldBeLoopAction(final Identifier content) {
		if (content.getClass() == LoopActionImpl.class) {
			return true;
		}

		final FailureReport<Void> failure = new FailureReport<Void>()
			.message("The SeffElement with ID %s is stored by Beagle as a LoopAction"
				+ " but is not a LoopAction in the repository-file!", content.getId())
			.cause(new IllegalArgumentException())
			.recoverable()
			.retryWith(() -> this.shouldBeLoopAction(content));
		FAILURE_HANDLER.handle(failure);

		return false;
	}

	/**
	 * Checking if a given {@link Identifier} is a {@link BranchAction}. Otherwise a
	 * Failure is reported.
	 *
	 * @param content The {{@link Identifier} to check
	 * @return {@code true} if content is {@link BranchAction}
	 */
	private boolean shouldBeBranchAction(final Identifier content) {
		if (content.getClass() == BranchActionImpl.class) {
			return true;
		}
		final FailureReport<Void> failure = new FailureReport<Void>()
			.message("The SeffElement with ID %s is stored by Beagle as a BranchAction"
				+ " but is not a BranchAction in the repository-file!", content.getId())
			.cause(new IllegalArgumentException())
			.recoverable()
			.retryWith(() -> this.shouldBeBranchAction(content));
		FAILURE_HANDLER.handle(failure);
		return false;
	}

	/**
	 * Checking if a given {@link Identifier} is a {@link InternalAction}. Otherwise a
	 * Failure is reported.
	 *
	 * @param content The {{@link Identifier} to check
	 * @return {@code true} if content is {@link InternalAction}
	 */
	private boolean shouldBeInternalAction(final Identifier content) {
		if (content.getClass() == InternalActionImpl.class) {
			return true;
		}
		final FailureReport<Void> failure = new FailureReport<Void>()
			.message("The SeffElement with ID %s is stored by Beagle as an InternalAction but is"
				+ " not an InternalAction in the repository-file!", content.getId())
			.cause(new IllegalArgumentException())
			.recoverable()
			.retryWith(() -> this.shouldBeInternalAction(content));
		FAILURE_HANDLER.handle(failure);
		return false;
	}

	/**
	 * Checking if a given {@link Identifier} is a {@link ExternalCallAction}. Otherwise a
	 * Failure is reported.
	 *
	 * @param content The {{@link Identifier} to check
	 * @return {@code true} if content is {@link ExternalCallAction}
	 */
	private boolean shouldBeExternalCallAction(final Identifier content) {
		if (content.getClass() == ExternalCallActionImpl.class) {
			return true;
		}
		final FailureReport<Void> failure = new FailureReport<Void>()
			.message("The SeffElement with ID %s is stored by Beagle as an ExternalCallAction but is"
				+ " not an ExternalCallAction in the repository-file!", content.getId())
			.cause(new IllegalArgumentException())
			.recoverable()
			.retryWith(() -> this.shouldBeExternalCallAction(content));
		FAILURE_HANDLER.handle(failure);
		return false;
	}

	/**
	 * Checking if a given Map is empty. This should be called right after modifying the
	 * repository file, to check that no ID is missing. In this case, there would be a
	 * SeffElement on the {@link Blackboard} that does not fit to a given Repository. That
	 * causes this method to do a Failure report.
	 *
	 * @param idMap The Map to check
	 * @return {@code true} if the given Map is empty
	 */
	private boolean shouldBeEmpty(final Map<String, EvaluableExpression> idMap) {
		if (idMap.isEmpty()) {
			return true;
		}
		final FailureReport<Void> failure = new FailureReport<Void>()
			.message("The Blackboard contains seffElements that do not fit to the given repository-file!")
			.cause(new IllegalArgumentException());
		FAILURE_HANDLER.handle(failure);
		return false;
	}

}