/**
* Copyright (c) 2013 committers of YAKINDU and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
* Contributors:
* committers of YAKINDU - initial API and implementation
*
*/
package org.yakindu.sct.refactoring.refactor.impl;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.yakindu.base.expressions.expressions.Expression;
import org.yakindu.sct.model.sgraph.Effect;
import org.yakindu.sct.model.sgraph.Scope;
import org.yakindu.sct.model.sgraph.State;
import org.yakindu.sct.model.sgraph.Transition;
import org.yakindu.sct.model.stext.stext.ExitEvent;
import org.yakindu.sct.model.stext.stext.LocalReaction;
import org.yakindu.sct.model.stext.stext.ReactionEffect;
import org.yakindu.sct.model.stext.stext.ReactionTrigger;
import org.yakindu.sct.model.stext.stext.StextFactory;
import org.yakindu.sct.refactoring.refactor.AbstractRefactoring;
/**
* This refactoring moves actions of outgoing transitions to the exit block of a state. Actions can only be moved if
* they are used at all outgoing transitions in the same order (checked from front to back).
* <br><br>
* Context:
* <ul>
* <li>Exactly one state.</li>
* </ul>
* Preconditions:
* <ul>
* <li>At least one action is foldable.</li>
* <li>No outgoing transition leaves a composite state which has exit actions.</li>
* </ul>
* @author thomas kutz - Initial contribution and API
*
*/
public class FoldOutgoingActionsRefactoring extends AbstractRefactoring<State> {
@Override
protected void internalExecute() {
EList<Transition> outgoingTransitions = getContextObject()
.getOutgoingTransitions();
List<Expression> actionsToFold = getFoldableActions(outgoingTransitions);
addActionsToExitReaction(actionsToFold);
}
/**
* {@inheritDoc}
*/
@Override
public boolean isExecutable() {
return super.isExecutable()
&& atLeastOneFoldableAction()
&& noOutgoingTransitionLeavesCompositeWithExitActions();
}
private boolean noOutgoingTransitionLeavesCompositeWithExitActions() {
return !helper.oneOutgoingTransitionLeavesCompositeWithExitActions(getContextObject());
}
private boolean atLeastOneFoldableAction() {
EList<Transition> transitions = getContextObject().getOutgoingTransitions();
return getFirstFoldableAction(helper.getAllActions(transitions), 0) != null;
}
private List<Expression> getFoldableActions(EList<Transition> transitions) {
List<Expression> foldableActions = new ArrayList<Expression>();
Expression firstFoldableAction;
int index = 0;
while ((firstFoldableAction = getFirstFoldableAction(
helper.getAllActions(transitions), index)) != null) {
foldableActions.add(firstFoldableAction);
index++;
}
removeFirstActions(transitions, index);
return foldableActions;
}
private void addActionsToExitReaction(final List<Expression> actionsToAdd) {
if (actionsToAdd.isEmpty()) {
return;
}
EList<Expression> actionsOriginal = helper
.getFirstExitActions(getContextObject());
if (actionsOriginal == null) {
actionsOriginal = createExitBlock();
}
actionsOriginal.addAll(actionsToAdd);
}
private EList<Expression> createExitBlock() {
EList<Expression> actionsOriginal;
LocalReaction newLocalReaction = StextFactory.eINSTANCE
.createLocalReaction();
ReactionTrigger newReactionTrigger = StextFactory.eINSTANCE
.createReactionTrigger();
ExitEvent exitEvent = StextFactory.eINSTANCE.createExitEvent();
ReactionEffect newReactionEffect = StextFactory.eINSTANCE
.createReactionEffect();
newLocalReaction.setTrigger(newReactionTrigger);
newReactionTrigger.getTriggers().add(exitEvent);
newLocalReaction.setEffect(newReactionEffect);
Scope scope = getContextObject().getScopes().get(0);
scope.getReactions().add(newLocalReaction);
actionsOriginal = newReactionEffect.getActions();
return actionsOriginal;
}
private void removeFirstActions(EList<Transition> transitions, int number) {
for (Transition transition : transitions) {
List<Expression> actionsToRemove = getFirstActions(transition,
number);
for (Expression action : actionsToRemove) {
EcoreUtil.delete(action);
}
// delete transition's effect when no more actions left
Effect effect = transition.getEffect();
if (!helper.hasAtLeastOneAction(transition) && effect != null) {
EcoreUtil.delete(effect);
}
}
}
private List<Expression> getFirstActions(Transition transition, int number) {
List<Expression> firstActions = new ArrayList<Expression>();
Effect effect = transition.getEffect();
if (effect instanceof ReactionEffect) {
ReactionEffect reactionEffect = (ReactionEffect) effect;
List<Expression> actions = reactionEffect.getActions();
for (int i = 0; i < number; i++) {
firstActions.add(actions.get(i));
}
}
return firstActions;
}
private Expression getFirstFoldableAction(
List<List<Expression>> allActions, int index) {
Expression actionToCheck = null;
for (List<Expression> actionList : allActions) {
if (index >= actionList.size()) {
return null;
}
Expression firstAction = actionList.get(index);
if (actionToCheck == null) {
actionToCheck = firstAction;
} else if (!EcoreUtil.equals(actionToCheck, firstAction)) {
return null;
}
}
return actionToCheck;
}
@Override
protected String getCommandLabel() {
return "Fold Outgoing Actions";
}
}