blob: 41cedd51f3efb598733f9d0b6e81c43aa8bcfb95 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2012 THALES GLOBAL SERVICES.
* 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:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.sirius.diagram.sequence.business.internal.elements;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.gmf.runtime.notation.Edge;
import org.eclipse.gmf.runtime.notation.Node;
import org.eclipse.gmf.runtime.notation.NotationPackage;
import org.eclipse.gmf.runtime.notation.View;
import org.eclipse.sirius.diagram.DDiagramElement;
import org.eclipse.sirius.diagram.sequence.business.internal.layout.LayoutConstants;
import org.eclipse.sirius.diagram.sequence.business.internal.ordering.EventEndHelper;
import org.eclipse.sirius.diagram.sequence.business.internal.query.ISequenceEventQuery;
import org.eclipse.sirius.diagram.sequence.business.internal.query.SequenceNodeQuery;
import org.eclipse.sirius.diagram.sequence.business.internal.util.ParentOperandFinder;
import org.eclipse.sirius.diagram.sequence.business.internal.util.RangeSetter;
import org.eclipse.sirius.diagram.sequence.business.internal.util.SubEventsHelper;
import org.eclipse.sirius.diagram.sequence.description.DescriptionPackage;
import org.eclipse.sirius.diagram.sequence.ordering.CompoundEventEnd;
import org.eclipse.sirius.diagram.sequence.ordering.EventEnd;
import org.eclipse.sirius.diagram.sequence.ordering.SingleEventEnd;
import org.eclipse.sirius.diagram.sequence.util.Range;
import org.eclipse.sirius.ext.base.Option;
import org.eclipse.sirius.ext.base.Options;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
/**
* Represents an execution on a lifeline or another parent execution.
*
* @author mporhel, pcdavid, smonnier
*/
public class Execution extends AbstractNodeEvent {
/**
* Predicate to filter Frames and Operand from possible new parents of an
* execution reparent.
*/
public static final Predicate<ISequenceEvent> NO_REPARENTABLE_EVENTS = new Predicate<ISequenceEvent>() {
public boolean apply(ISequenceEvent input) {
return input instanceof AbstractFrame || input instanceof Operand || input instanceof Message;
}
};
/**
* The visual ID. Same as a normal bordered node.
*/
public static final int VISUAL_ID = 3001;
/**
* Predicate to check whether a Sirius DDiagramElement represents an
* execution.
*/
private static enum SiriusElementPredicate implements Predicate<DDiagramElement> {
INSTANCE;
public boolean apply(DDiagramElement input) {
return AbstractSequenceElement.isSequenceDiagramElement(input, DescriptionPackage.eINSTANCE.getExecutionMapping())
&& !InstanceRole.viewpointElementPredicate().apply((DDiagramElement) input.eContainer());
}
}
/**
* Constructor.
*
* @param node
* the GMF Node representing this execution.
*/
Execution(Node node) {
super(node);
Preconditions.checkArgument(Execution.notationPredicate().apply(node), "The node does not represent an execution.");
}
/**
* Returns a predicate to check whether a GMF View represents an execution.
*
* @return a predicate to check whether a GMF View represents an execution.
*/
public static Predicate<View> notationPredicate() {
return new NotationPredicate(NotationPackage.eINSTANCE.getNode(), VISUAL_ID, Execution.viewpointElementPredicate());
}
/**
* Returns a predicate to check whether a Sirius DDiagramElement represents
* an execution.
*
* @return a predicate to check whether a Sirius DDiagramElement represents
* an execution.
*/
public static Predicate<DDiagramElement> viewpointElementPredicate() {
return SiriusElementPredicate.INSTANCE;
}
/**
* {@inheritDoc}
*/
@Override
public List<Message> getLinkedMessages() {
List<Message> linkedMessages = Lists.newArrayList();
Option<Message> startMessage = getStartMessage();
if (startMessage.some()) {
linkedMessages.add(startMessage.get());
}
Option<Message> targetMessage = getEndMessage();
if (targetMessage.some()) {
linkedMessages.add(targetMessage.get());
}
return linkedMessages;
}
/**
* Returns the message linked to the start (i.e. top side) of this
* execution, if any.
*
* @return the message linked to the start of this execution, if any.
*/
public Option<Message> getStartMessage() {
return getCompoundMessage(true);
}
private Option<Message> getCompoundMessage(boolean start) {
Node node = getNotationNode();
Set<Edge> edges = Sets.newHashSet();
Iterables.addAll(edges, Iterables.filter(node.getSourceEdges(), Edge.class));
Iterables.addAll(edges, Iterables.filter(node.getTargetEdges(), Edge.class));
List<EventEnd> ends = EventEndHelper.findEndsFromSemanticOrdering(this);
for (Edge edge : edges) {
Option<Message> message = ISequenceElementAccessor.getMessage(edge);
if (message.some()) {
List<EventEnd> messageEnds = EventEndHelper.findEndsFromSemanticOrdering(message.get());
Iterables.retainAll(messageEnds, ends);
if (!messageEnds.isEmpty()) {
SingleEventEnd see = EventEndHelper.getSingleEventEnd(messageEnds.get(0), getSemanticTargetElement().get());
if (start == see.isStart()) {
return message;
}
}
}
}
return Options.newNone();
}
/**
* Returns the message linked to the end (i.e. bottom side) of this
* execution, if any.
*
* @return the message linked to the end of this execution, if any.
*/
public Option<Message> getEndMessage() {
return getCompoundMessage(false);
}
/**
* Tests whether this execution starts with a reflective message.
*
* @return <code>true</code> if this execution has a reflective message
* linked to its start.
*/
public boolean startsWithReflectiveMessage() {
Option<Message> startMessage = getStartMessage();
if (startMessage.some()) {
return startMessage.get().isReflective();
} else {
return false;
}
}
/**
* Tests whether this execution ends with a reflective message.
*
* @return <code>true</code> if this execution has a reflective message
* linked to its end.
*/
public boolean endsWithReflectiveMessage() {
Option<Message> finishMessage = getEndMessage();
if (finishMessage.some()) {
return finishMessage.get().isReflective();
} else {
return false;
}
}
/**
* Validate that the execution is reflective. Therefore, its start message
* must be reflective and its return message must be null (Asynchronous
* message) or reflexive.
*
* @return if the execution is reflective
*/
public boolean isReflective() {
Option<Message> startMessage = getStartMessage();
Option<Message> endMessage = getEndMessage();
return startMessage.some() && startMessage.get().isReflective() && (!endMessage.some() || endMessage.get().isReflective());
}
/**
* {@inheritDoc}
*/
@Override
public ISequenceEvent getParentEvent() {
ISequenceEvent parent = getHierarchicalParentEvent();
List<ISequenceEvent> potentialSiblings = parent.getSubEvents();
if (!potentialSiblings.contains(this)) {
// look for parentOperand
parent = getParentOperand().get();
}
return parent;
}
/**
* {@inheritDoc}
*/
@Override
public ISequenceEvent getHierarchicalParentEvent() {
EObject viewContainer = this.view.eContainer();
if (viewContainer instanceof View) {
View parentView = (View) viewContainer;
Option<ISequenceEvent> parentElement = ISequenceElementAccessor.getISequenceEvent(parentView);
if (parentElement.some()) {
return parentElement.get();
}
}
throw new RuntimeException("Invalid context for execution " + this);
}
/**
* Finds the deepest Operand container including the position if existing.
*
* @param verticalPosition
* the position where to look for the deepest operand
* @return the deepest Operand convering this lifeline at this range
* @see ISequenceEvent#getParentOperand()
*/
@Override
public Option<Operand> getParentOperand(final int verticalPosition) {
return new ParentOperandFinder(this).getParentOperand(new Range(verticalPosition, verticalPosition));
}
/**
* Finds the deepest Operand container including the position if existing.
*
* @param range
* the range where to look for the deepest operand
* @return the deepest Operand convering this lifeline at this range
* @see ISequenceEvent#getParentOperand()
*/
@Override
public Option<Operand> getParentOperand(final Range range) {
return new ParentOperandFinder(this).getParentOperand(range);
}
/**
* Finds the deepest Operand container if existing.
*
* @return the deepest Operand container if existing
*/
@Override
public Option<Operand> getParentOperand() {
return new ParentOperandFinder(this).getParentOperand();
}
/**
* {@inheritDoc}
*/
public List<ISequenceEvent> getSubEvents() {
return new SubEventsHelper(this).getSubEvents();
}
/**
* {@inheritDoc}
*/
public Collection<ISequenceEvent> getEventsToMoveWith() {
Set<ISequenceEvent> toMove = Sets.newLinkedHashSet();
List<ISequenceEvent> subEvents = getSubEvents();
toMove.addAll(findLinkedExecutions(subEvents));
toMove.addAll(getLinkedMessages());
toMove.addAll(findCoveredExecutions(subEvents));
toMove.addAll(subEvents);
return toMove;
}
private Collection<? extends ISequenceEvent> findLinkedExecutions(List<ISequenceEvent> subEvents) {
Set<Execution> linkedExecutions = Sets.newLinkedHashSet();
for (Message message : Iterables.filter(subEvents, Message.class)) {
if (this.equals(message.getSourceElement()) && message.getTargetElement() instanceof Execution) {
Execution targetExecution = (Execution) message.getTargetElement();
for (CompoundEventEnd messageCompoundEventEnd : Iterables.filter(EventEndHelper.findEndsFromSemanticOrdering(message), CompoundEventEnd.class)) {
for (CompoundEventEnd executionCompoundEventEnd : Iterables.filter(EventEndHelper.findEndsFromSemanticOrdering(targetExecution), CompoundEventEnd.class)) {
if (messageCompoundEventEnd.equals(executionCompoundEventEnd)) {
if (!this.equals(targetExecution)) {
linkedExecutions.add(targetExecution);
}
}
}
}
}
}
return linkedExecutions;
}
private Collection<? extends ISequenceEvent> findCoveredExecutions(List<ISequenceEvent> subEvents) {
Collection<Execution> coveredExecutions = Lists.newArrayList();
for (AbstractFrame frame : Iterables.filter(subEvents, AbstractFrame.class)) {
Collection<ISequenceEvent> parentEvents = frame.computeParentEvents();
parentEvents.remove(this);
Iterables.addAll(coveredExecutions, Iterables.filter(parentEvents, Execution.class));
}
return coveredExecutions;
}
/**
* {@inheritDoc}
*/
@Override
public Range getVerticalRange() {
return new SequenceNodeQuery(getNotationNode()).getVerticalRange();
}
/**
* {@inheritDoc}
*/
@Override
public boolean isLogicallyInstantaneous() {
return false;
}
/**
* {@inheritDoc}
*/
@Override
public void setVerticalRange(Range range) throws IllegalStateException {
RangeSetter.setVerticalRange(this, range);
}
/**
* {@inheritDoc}
*/
@Override
public Option<Lifeline> getLifeline() {
return getParentLifeline();
}
/**
* {@inheritDoc}
*/
public boolean canChildOccupy(ISequenceEvent child, Range range) {
return new SubEventsHelper(this).canChildOccupy(child, range);
}
/**
* {@inheritDoc}
*/
public boolean canChildOccupy(ISequenceEvent child, Range range, List<ISequenceEvent> eventsToIgnore, Collection<Lifeline> lifelines) {
return new SubEventsHelper(this).canChildOccupy(child, range, eventsToIgnore, lifelines);
}
/**
* {@inheritDoc}
*/
public Range getOccupiedRange() {
return new ISequenceEventQuery(this).getOccupiedRange();
}
/**
* Sub-events can occur anywhere on a normal execution as long as it is
* strictly inside.
* <p>
* {@inheritDoc}
*/
public Range getValidSubEventsRange() {
Range range = getVerticalRange();
if (range.width() > 2 * LayoutConstants.EXECUTION_CHILDREN_MARGIN) {
return range.shrinked(LayoutConstants.EXECUTION_CHILDREN_MARGIN);
} else {
return range;
}
}
/**
* Finds all linked executions (by messages with CompoundEventEnd) from
* executionEditPart. Depending on the value of investigateRecursively, it
* will recursively investigate the linked execution.
*
* @param recurse
* investigate recursively if true
* @return the list of linked {@link Execution} from executionEditPart
*/
public List<Execution> findLinkedExecutions(boolean recurse) {
List<Execution> impactedExecutions = Lists.newArrayList();
findLinkedExecutions(impactedExecutions, this, recurse);
return impactedExecutions;
}
/**
* Recursive function of the previous one that add the result in the
* parameter list impactedExecutionEditPart.
*
* @param impactedExecutioExecutions
* the list of linked {@link Execution} from executionEditPart
* @param execution
* the current {@link Execution}
* @param recurse
* investigate recursively if true
*/
private void findLinkedExecutions(List<Execution> impactedExecutions, Execution execution, boolean recurse) {
List<Message> messagesFrom = new ISequenceEventQuery(execution).getAllMessagesFrom();
for (Message message : messagesFrom) {
boolean targetsUnseenExecution = message.getTargetElement() instanceof Execution && !impactedExecutions.contains(message.getTargetElement());
if (targetsUnseenExecution) {
Execution targetExecution = (Execution) message.getTargetElement();
for (CompoundEventEnd messageCompoundEventEnd : Iterables.filter(EventEndHelper.findEndsFromSemanticOrdering(message), CompoundEventEnd.class)) {
for (CompoundEventEnd executionCompoundEventEnd : Iterables.filter(EventEndHelper.findEndsFromSemanticOrdering(targetExecution), CompoundEventEnd.class)) {
if (messageCompoundEventEnd.equals(executionCompoundEventEnd)) {
if (!impactedExecutions.contains(targetExecution)) {
impactedExecutions.add(targetExecution);
if (recurse) {
findLinkedExecutions(impactedExecutions, targetExecution, recurse);
}
}
}
}
}
}
}
}
/**
* Returns the extended vertical range of this execution, i.e. the vertical
* range of the execution including any extensions like branches for linked
* start/end reflective messages. This corresponds to the range of all the
* elements which are tied to the execution and will move along with it when
* the execution is moved.
*
* @return the extended vertical range of this execution.
*/
public Range getExtendedVerticalRange() {
Range result = getVerticalRange();
for (Message linkedMessage : getLinkedMessages()) {
// For non-reflective and non-deferred messages, this is a no-op.
result = result.union(linkedMessage.getVerticalRange());
}
return result;
}
}