/**
* 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.utils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.gmf.runtime.notation.Diagram;
import org.eclipse.gmf.runtime.notation.View;
import org.yakindu.base.expressions.expressions.Expression;
import org.yakindu.sct.model.sgraph.CompositeElement;
import org.yakindu.sct.model.sgraph.Effect;
import org.yakindu.sct.model.sgraph.Reaction;
import org.yakindu.sct.model.sgraph.State;
import org.yakindu.sct.model.sgraph.Transition;
import org.yakindu.sct.model.sgraph.Trigger;
import org.yakindu.sct.model.sgraph.Vertex;
import org.yakindu.sct.model.stext.stext.EntryEvent;
import org.yakindu.sct.model.stext.stext.EventSpec;
import org.yakindu.sct.model.stext.stext.ExitEvent;
import org.yakindu.sct.model.stext.stext.ReactionEffect;
import org.yakindu.sct.model.stext.stext.ReactionTrigger;
/**
* Utility class providing several convenience methods used in refactoring commands.
*
* @author thomas kutz - Initial contribution and API
*
*/
public class RefactoringHelper {
/**
* Collects all actions of the specified transitions and returns them.
*
* @param transitions
* @return list of list of actions for the specified transitions
*/
public List<List<Expression>> getAllActions(List<Transition> transitions) {
List<List<Expression>> allActions = new ArrayList<List<Expression>>();
for (Transition transition : transitions) {
Effect effect = transition.getEffect();
if (effect instanceof ReactionEffect) {
ReactionEffect reactionEffect = (ReactionEffect) effect;
allActions.add(reactionEffect.getActions());
}
else {
allActions.add(Collections.<Expression>emptyList());
}
}
return allActions;
}
/**
* Checks if the specified state has at least one entry action.
* @param state
* @return true if condition is satisfied, false otherwise.
*/
public boolean hasEntryAction(State state) {
EList<Expression> entryActions = getFirstEntryActions(state);
if (entryActions != null && !entryActions.isEmpty()) {
return true;
}
return false;
}
/**
* Checks if the specified state has at least one exit action.
* @param state
* @return true if condition is satisfied, false otherwise.
*/
public boolean hasExitAction(State state) {
EList<Expression> exitActions = getFirstExitActions(state);
if (exitActions != null && !exitActions.isEmpty()) {
return true;
}
return false;
}
/**
* Checks if at least one of the outgoing transitions of the specified state leaves a parent composite
* of this state which has exit actions.
*
* @param state
* @return true if condition is satisfied, false otherwise
*/
public boolean oneOutgoingTransitionLeavesCompositeWithExitActions(State state) {
Set<State> sourceParentStates = new HashSet<State>(getParentStates(state));
for (Transition transition : state.getOutgoingTransitions()) {
// all parent states of target need to be contained in the set of
// the source's parent states
Set<State> targetParentStates = getParentStates(transition.getTarget());
Set<State> crossedStates = new HashSet<State>(sourceParentStates);
crossedStates.removeAll(targetParentStates);
for (State crossedCompositeState : crossedStates) {
if (hasExitAction(crossedCompositeState))
return true;
}
}
return false;
}
/**
* Checks if at least one of the incoming transitions of the specified state enters a parent composite
* of this state which has entry actions.
* @param state
* @return true if condition is satisfied, false otherwise
*/
public boolean oneIncomingTransitionEntersCompositeWithEntryActions(State state) {
Set<State> targetParentStates = new HashSet<State>(getParentStates(state));
for (Transition transition : state.getIncomingTransitions()) {
// all parent states of source need to be contained in the set of
// the target's parent states
Set<State> sourceParentStates = getParentStates(transition.getSource());
Set<State> crossedStates = new HashSet<State>(targetParentStates);
crossedStates.removeAll(sourceParentStates);
for (State crossedCompositeState : crossedStates) {
if (hasEntryAction(crossedCompositeState))
return true;
}
}
return false;
}
/**
* Returns the entry actions of a state.
* Returns null if no entry block is defined.
* If multiple entry blocks are defined, only the actions of the first one are returned.
*
* @param state
* @return list of actions of the first entry block defined in the specified state
*/
public EList<Expression> getFirstEntryActions(State state) {
EList<Reaction> localReactions = state.getLocalReactions();
for (Reaction reaction : localReactions) {
Trigger trigger = reaction.getTrigger();
if (trigger instanceof ReactionTrigger) {
ReactionTrigger reactionTrigger = (ReactionTrigger) trigger;
EList<EventSpec> triggers = reactionTrigger.getTriggers();
for (EventSpec eventSpec : triggers) {
if (eventSpec instanceof EntryEvent && reaction.getEffect() instanceof ReactionEffect) {
return ((ReactionEffect)reaction.getEffect()).getActions();
}
}
}
}
return null;
}
/**
* Returns the exit actions of a state.
* Returns null if no exit block is defined.
* If multiple exit blocks are defined, only the actions of the first one are returned.
*
* @param state
* @return list of actions of the first exit block defined in the specified state
*/
public EList<Expression> getFirstExitActions(State state) {
EList<Reaction> localReactions = state.getLocalReactions();
for (Reaction reaction : localReactions) {
Trigger trigger = reaction.getTrigger();
if (trigger instanceof ReactionTrigger) {
ReactionTrigger reactionTrigger = (ReactionTrigger) trigger;
EList<EventSpec> triggers = reactionTrigger.getTriggers();
for (EventSpec eventSpec : triggers) {
if (eventSpec instanceof ExitEvent && reaction.getEffect() instanceof ReactionEffect) {
return ((ReactionEffect)reaction.getEffect()).getActions();
}
}
}
}
return null;
}
/**
* Checks if the effect definition of a transition contains at least one action.
* @param transition
* @return true if the condition is satisfied, false otherwise
*/
public boolean hasAtLeastOneAction(Transition transition) {
Effect effect = transition.getEffect();
if (effect instanceof ReactionEffect) {
ReactionEffect reactionEffect = (ReactionEffect) effect;
EList<Expression> actions = reactionEffect.getActions();
return !actions.isEmpty();
}
return false;
}
/**
* Collects all actions of the specified state which have the specified type. The collected actions are deleted from their
* current containers and returned.
* @param state
* @param eventType
* @return all actions of the specified state which have the specified type
*/
public List<Expression> extractAllLocalActionsForEventType(State state, Class<? extends EventSpec> eventType) {
// creating new collection required to delete its elements with EcoreUtil
List<Reaction> localReactions = new ArrayList<Reaction>(state.getLocalReactions());
List<Expression> resultActions = new ArrayList<Expression>();
for (Reaction reaction : localReactions) {
if (!(reaction.getEffect() instanceof ReactionEffect) || !(reaction.getTrigger() instanceof ReactionTrigger)) {
continue;
}
ReactionTrigger reactionTrigger = (ReactionTrigger) reaction.getTrigger();
ReactionEffect reactionEffect = (ReactionEffect)reaction.getEffect();
List<EventSpec> triggers = new ArrayList<EventSpec>(reactionTrigger.getTriggers());
if (containsOnlyEventsOfType(triggers, eventType)) {
EList<Expression> entryActions = reactionEffect.getActions();
resultActions.addAll(entryActions);
EcoreUtil.remove(reaction);
}
else if (containsAtLeastOneEventOfType(triggers, eventType)) {
EList<Expression> entryActions = reactionEffect.getActions();
resultActions.addAll(entryActions);
deleteAllEventsOfType(triggers, eventType);
}
}
return resultActions;
}
/**
* Returns all parent states of the specified child state.
*
* @param state child state
* @return all parent states of the specified child state
*/
// TODO are hierarchies of regions possible?
private Set<State> getParentStates(Vertex state) {
Set<State> parentStates = new HashSet<State>();
CompositeElement composite = state.getParentRegion().getComposite();
if (composite instanceof State) {
State parentState = (State) composite;
parentStates.add(parentState);
parentStates.addAll(getParentStates(parentState));
}
return parentStates;
}
private void deleteAllEventsOfType(List<EventSpec> events, Class<? extends EventSpec> eventType) {
for (EventSpec event : events) {
if (event.getClass().getName().equals(eventType.getName())) {
EcoreUtil.remove(event);
}
}
}
private boolean containsAtLeastOneEventOfType(List<EventSpec> events, Class<? extends EventSpec> eventType) {
for (EventSpec event : events) {
if (event.getClass().getName().equals(eventType.getName())) {
return true;
}
}
return false;
}
private boolean containsOnlyEventsOfType(List<EventSpec> events, Class<? extends EventSpec> eventType) {
for (EventSpec event : events) {
if (!event.getClass().getName().equals(eventType.getName())) {
return false;
}
}
return true;
}
/**
* Checks if all given transitions have at least one action.
* Returns false for empty lists.
*
* @param transitions
* @return
*/
public boolean haveAllAtLeastOneAction(
EList<Transition> transitions) {
if (transitions.isEmpty())
return false;
for (Transition transition : transitions) {
if (!hasAtLeastOneAction(transition)) {
return false;
}
}
return true;
}
/**
* Returns for the given semantic element the notation view element in the given diagram
* @param semanticElement
* @param diagram
* @return
*/
public View getViewForSemanticElement(EObject semanticElement, Diagram diagram) {
TreeIterator<EObject> allContents = diagram.eAllContents();
while (allContents.hasNext()) {
EObject next = allContents.next();
if (next instanceof View) {
View view = (View) next;
if (EcoreUtil.equals(view.getElement(), semanticElement)) {
return view;
}
}
}
throw new IllegalArgumentException("No view found for semantic element "+semanticElement);
}
}