org.eclipse.sirius.diagram.sequence.business.internal.layout.vertical.SequenceVerticalLayout.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.sirius.diagram.sequence.business.internal.layout.vertical.SequenceVerticalLayout.java

Source

/*******************************************************************************
 * 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.layout.vertical;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.gmf.runtime.notation.Edge;
import org.eclipse.gmf.runtime.notation.LayoutConstraint;
import org.eclipse.gmf.runtime.notation.Location;
import org.eclipse.gmf.runtime.notation.Size;
import org.eclipse.sirius.diagram.DDiagramElement;
import org.eclipse.sirius.diagram.DNode;
import org.eclipse.sirius.diagram.sequence.SequenceDDiagram;
import org.eclipse.sirius.diagram.sequence.business.internal.RangeHelper;
import org.eclipse.sirius.diagram.sequence.business.internal.elements.AbstractNodeEvent;
import org.eclipse.sirius.diagram.sequence.business.internal.elements.CombinedFragment;
import org.eclipse.sirius.diagram.sequence.business.internal.elements.EndOfLife;
import org.eclipse.sirius.diagram.sequence.business.internal.elements.ISequenceElement;
import org.eclipse.sirius.diagram.sequence.business.internal.elements.ISequenceElementAccessor;
import org.eclipse.sirius.diagram.sequence.business.internal.elements.ISequenceEvent;
import org.eclipse.sirius.diagram.sequence.business.internal.elements.ISequenceNode;
import org.eclipse.sirius.diagram.sequence.business.internal.elements.InstanceRole;
import org.eclipse.sirius.diagram.sequence.business.internal.elements.InteractionUse;
import org.eclipse.sirius.diagram.sequence.business.internal.elements.Lifeline;
import org.eclipse.sirius.diagram.sequence.business.internal.elements.LostMessageEnd;
import org.eclipse.sirius.diagram.sequence.business.internal.elements.Message;
import org.eclipse.sirius.diagram.sequence.business.internal.elements.Operand;
import org.eclipse.sirius.diagram.sequence.business.internal.elements.SequenceDiagram;
import org.eclipse.sirius.diagram.sequence.business.internal.elements.State;
import org.eclipse.sirius.diagram.sequence.business.internal.layout.AbstractSequenceLayout;
import org.eclipse.sirius.diagram.sequence.business.internal.layout.AbstractSequenceOrderingLayout;
import org.eclipse.sirius.diagram.sequence.business.internal.layout.EventEndToPositionFunction;
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.ISequenceElementQuery;
import org.eclipse.sirius.diagram.sequence.business.internal.query.SequenceMessageViewQuery;
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.diagram.ui.business.internal.query.DNodeQuery;
import org.eclipse.sirius.ext.base.Option;
import org.eclipse.sirius.ext.base.Options;

import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;

/**
 * Computes the appropriate graphical locations of sequence events and lifelines
 * on a sequence diagram to reflect the semantic order.
 * 
 * @author pcdavid, mporhel
 */
public class SequenceVerticalLayout extends AbstractSequenceOrderingLayout<ISequenceElement, Range, EventEnd> {

    /**
     * A map to link an {@link EventEnd} to the attached {@link ISequenceEvent}.
     */
    protected final Map<EventEnd, Message> creators;

    /**
     * A map to link an {@link EventEnd} to the attached {@link ISequenceEvent}.
     */
    protected final Map<EventEnd, Message> destructors;

    /**
     * A map to link an {@link EventEnd} to an attached {@link LostMessageEnd}.
     */
    protected final Map<EventEnd, LostMessageEnd> losts;

    /**
     * Unconnected lostMessageEnds.
     */
    protected final List<LostMessageEnd> unconnectedLostEnds;

    /**
     * Semantic flagged event ends at creation.
     */
    protected final List<EventEnd> toolCreatedEnds = Lists.newArrayList();

    /**
     * A map to link an {@link EventEnd} to the attached {@link ISequenceEvent}.
     */
    protected final Multimap<EventEnd, ISequenceEvent> endToISequencEvents;

    /**
     * A map to link an {@link ISequenceEvent} to its starting and ending
     * {@link EventEnd}.
     */
    protected final Multimap<ISequenceEvent, EventEnd> iSequenceEventsToEventEnds;

    /**
     * A function to compute the sequence events corresponding to an event end.
     */
    protected final Function<EventEnd, Collection<ISequenceEvent>> eventEndToSequenceEvents = new Function<EventEnd, Collection<ISequenceEvent>>() {
        public Collection<ISequenceEvent> apply(EventEnd from) {
            return endToISequencEvents.get(from);
        }
    };

    /**
     * The global time range of the diagram. Can be udpated during layout
     * computation.
     */
    protected Range timeRange;

    /**
     * A function to get the instance role height of a lifeline.
     */
    private final Function<Lifeline, Integer> instanceRoleHeight = new Function<Lifeline, Integer>() {
        public Integer apply(Lifeline from) {
            InstanceRole irep = from.getInstanceRole();
            if (irep != null) {
                return ((Size) irep.getNotationNode().getLayoutConstraint()).getHeight();
            }
            return 0;
        }
    };

    /**
     * An ordering to sort {@link Lifeline} regarding the height of their
     * {@link InstanceRole}.
     */
    private final Ordering<Lifeline> heightOrdering = Ordering.natural().onResultOf(instanceRoleHeight);

    private final Function<ISequenceEvent, Option<Range>> oldRangeFunction = new Function<ISequenceEvent, Option<Range>>() {
        public Option<Range> apply(ISequenceEvent from) {
            Range range = oldLayoutData.get(from);
            if (range == null) {
                range = Range.emptyRange();
            }
            return Options.newSome(range);
        }
    };

    private final Function<ISequenceEvent, Option<Range>> oldFlaggedRange = new Function<ISequenceEvent, Option<Range>>() {
        public Option<Range> apply(ISequenceEvent from) {
            Rectangle rect = oldFlaggedLayoutData.get(from);
            Range result = null;
            if (rect != null) {
                result = RangeHelper.verticalRange(rect);
            }
            return Options.newSome(result);
        }
    };

    private final Function<EventEnd, Integer> eventEndOldPosition = new EventEndToPositionFunction(
            eventEndToSequenceEvents, oldRangeFunction) {

        @Override
        protected Integer getOldPositionFromRange(SingleEventEnd see, ISequenceEvent ise) {
            Integer oldPos = super.getOldPositionFromRange(see, ise);

            if (ise instanceof Message && !ise.isLogicallyInstantaneous() && see != null) {
                // Real position (diagram initialization, message creation)
                Message smep = (Message) ise;
                Edge notationView = smep.getNotationEdge();
                SequenceMessageViewQuery query = new SequenceMessageViewQuery(notationView);
                oldPos = see.isStart() ? query.getFirstPointVerticalPosition(true)
                        : query.getLastPointVerticalPosition(true);
            }

            return oldPos;
        }

    };

    private final Function<EventEnd, Integer> eventEndOldFlaggedPosition = new EventEndToPositionFunction(
            eventEndToSequenceEvents, oldFlaggedRange);

    /**
     * Constructor.
     * 
     * @param sequenceDiagram
     *            the sequence diagram for which to compute the messages
     *            locations.
     */
    public SequenceVerticalLayout(SequenceDiagram sequenceDiagram) {
        super(sequenceDiagram);

        this.iSequenceEventsToEventEnds = LinkedHashMultimap.create();
        this.endToISequencEvents = HashMultimap.create();

        this.creators = Maps.newHashMap();
        this.destructors = Maps.newHashMap();
        this.losts = Maps.newHashMap();
        this.unconnectedLostEnds = Lists.newArrayList();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void init(boolean pack) {
        initSortedEventEnds(pack);
        initLifelinesOldLayoutData();
        initTimeBounds(pack);
        registerEventEnds();

        lookForUnconnectedLostEnd();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected Range getOldLayoutData(ISequenceElement ise) {
        Range verticalRange = Range.emptyRange();

        if (ise instanceof ISequenceEvent) {
            verticalRange = ((ISequenceEvent) ise).getVerticalRange();

            if (ise instanceof Message) {
                Message msg = (Message) ise;
                ISequenceElementQuery query = null;
                ISequenceNode sourceElement = msg.getSourceElement();
                ISequenceNode targetElement = msg.getTargetElement();
                if (sourceElement instanceof LostMessageEnd
                        && AbstractSequenceLayout.createdFromTool((LostMessageEnd) sourceElement)) {
                    query = new ISequenceElementQuery(sourceElement);
                } else if (targetElement instanceof LostMessageEnd
                        && AbstractSequenceLayout.createdFromTool((LostMessageEnd) targetElement)) {
                    query = new ISequenceElementQuery(targetElement);
                }

                if (query != null && query.hasAbsoluteBoundsFlag()) {
                    Rectangle flag = query.getFlaggedAbsoluteBounds();
                    verticalRange = new Range(flag.y, flag.y);
                }
            }
        }

        return verticalRange;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected boolean applyComputedLayout(Map<? extends ISequenceElement, Range> finalRanges, boolean pack) {
        boolean applied = false;
        Iterable<ISequenceEvent> keySet = Iterables.filter(finalRanges.keySet(), ISequenceEvent.class);

        // Begin with lifelines and executions (anchor positions move)
        for (ISequenceEvent ise : Iterables.filter(keySet, Predicates.not(Predicates.instanceOf(Message.class)))) {
            final Range newRange = finalRanges.get(ise);
            ise.setVerticalRange(newRange);
            applied = true;
        }

        // Then apply computed vertical range on messages
        for (Message smep : Iterables.filter(keySet, Message.class)) {
            final Range newRange = finalRanges.get(smep);
            smep.setVerticalRange(newRange);
            applied = true;
        }

        applied = layoutUnconnectedLostMessageEnd() || applied;

        return applied;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected Map<? extends ISequenceElement, Range> computeLayout(boolean pack) {
        LinkedHashMap<ISequenceEvent, Range> sequenceEventRanges = new LinkedHashMap<ISequenceEvent, Range>();

        // Compute the position of each event end.
        Map<EventEnd, Integer> endLocations = computeEndBounds(pack);

        // Compute ISequenceEvent vertical ranges from event end locations.
        Map<ISequenceEvent, Range> basicRanges = computeBasicRanges(endLocations);

        // Compute punctual States vertical range
        Map<ISequenceEvent, Range> punctualEventRanges = computePunctualEventsGraphicalRanges(endLocations, pack);

        // Update lifeline size.
        Map<ISequenceEvent, Range> lifelinesRanges = computeLifelineRanges(endLocations);

        sequenceEventRanges.putAll(lifelinesRanges);
        sequenceEventRanges.putAll(basicRanges);
        sequenceEventRanges.putAll(punctualEventRanges);

        return sequenceEventRanges;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void dispose() {
        creators.clear();
        destructors.clear();
        losts.clear();
        unconnectedLostEnds.clear();
        toolCreatedEnds.clear();

        endToISequencEvents.clear();
        iSequenceEventsToEventEnds.clear();

        super.dispose();
    }

    private Map<ISequenceEvent, Range> computePunctualEventsGraphicalRanges(Map<EventEnd, Integer> endLocations,
            boolean pack) {
        final Map<ISequenceEvent, Range> sequenceEventsToRange = new LinkedHashMap<ISequenceEvent, Range>();
        if (pack) {
            for (EventEnd cee : Iterables.filter(semanticOrdering, EventEndHelper.PUNCTUAL_COMPOUND_EVENT_END)) {
                if (endLocations.containsKey(cee) && endToISequencEvents.containsKey(cee)) {
                    int loc = endLocations.get(cee);
                    Collection<ISequenceEvent> ises = endToISequencEvents.get(cee);

                    if (Iterables.any(ises, Predicates.instanceOf(State.class)) && ises.size() == 1) {
                        State ise = (State) ises.iterator().next();
                        int midSize = getAbstractNodeEventVerticalSize(cee, ise, ises, pack) / 2;
                        sequenceEventsToRange.put(ise, new Range(loc - midSize, loc + midSize));
                    }
                }
            }
        }
        return sequenceEventsToRange;
    }

    /**
     * Computes the absolute vertical (Y) location for all the messages in the
     * sequence diagram.
     * 
     * @return a map associating each message edit part to the new absolute
     *         vertical location it should have.
     */
    private Map<EventEnd, Integer> computeEndBounds(boolean pack) {
        final Map<EventEnd, Integer> result = Maps.newLinkedHashMap();

        if (semanticOrdering == null || semanticOrdering.isEmpty()) {
            return result;
        }

        // current y location
        int currentY = this.timeRange.getLowerBound();

        EventEnd endBefore = null;
        for (EventEnd end : semanticOrdering) {
            currentY = computeLocation(currentY, end, endBefore, pack, result);
            result.put(end, currentY);
            endBefore = end;
        }
        return result;
    }

    private int getGapFromCommonSequenceEvent(EventEnd end, Collection<ISequenceEvent> commonIses, boolean pack,
            int genericGap) {
        int beforeGap = genericGap;

        if (commonIses.isEmpty()) {
            return beforeGap;
        }

        ISequenceEvent commonIse = commonIses.iterator().next();
        if (commonIse instanceof Message && ((Message) commonIse).isReflective()) {
            beforeGap = LayoutConstants.MESSAGE_TO_SELF_BENDPOINT_VERTICAL_GAP;
        } else if (commonIse instanceof AbstractNodeEvent) {
            beforeGap = Math.max(genericGap,
                    getAbstractNodeEventVerticalSize(end, (AbstractNodeEvent) commonIse, commonIses, pack));
        } else if (commonIse instanceof InteractionUse) {
            beforeGap = LayoutConstants.DEFAULT_INTERACTION_USE_HEIGHT;
        } else if (commonIse instanceof Operand) {
            beforeGap = LayoutConstants.DEFAULT_OPERAND_HEIGHT;
        }
        return beforeGap;
    }

    private int getAbstractNodeEventVerticalSize(EventEnd end, AbstractNodeEvent ise,
            Collection<ISequenceEvent> commonIses, boolean pack) {
        int vSize = 0;
        if (pack) {
            DNode execution = (DNode) ise.getNotationView().getElement();
            int specifiedVSize = getSpecifiedVSize(execution);
            if (specifiedVSize != 0) {
                vSize = specifiedVSize;
            }
        } else if (isFlagguedByRefreshExtension(end, commonIses)) {
            Rectangle rect = oldFlaggedLayoutData.get(ise);
            vSize = rect != null ? rect.height : LayoutConstants.DEFAULT_EXECUTION_HEIGHT;
        } else {
            Range range = oldLayoutData.get(ise);
            vSize = range != null ? range.width() : LayoutConstants.DEFAULT_EXECUTION_HEIGHT;
        }

        return vSize;
    }

    private Map<ISequenceEvent, Range> computeBasicRanges(Map<EventEnd, Integer> endLocations) {
        final Map<ISequenceEvent, Range> sequenceEventsToRange = new LinkedHashMap<ISequenceEvent, Range>();
        Predicate<ISequenceEvent> notMoved = Predicates.not(Predicates.in(sequenceEventsToRange.keySet()));

        // CombinedFragments
        for (EventEnd sortedEnd : semanticOrdering) {
            Predicate<ISequenceEvent> frames = Predicates.and(notMoved, Predicates.or(
                    Predicates.instanceOf(CombinedFragment.class), Predicates.instanceOf(InteractionUse.class)));
            for (ISequenceEvent ise : Iterables.filter(endToISequencEvents.get(sortedEnd), frames)) {
                computeFinalRange(endLocations, sequenceEventsToRange, ise);
            }
        }

        // Operands
        for (EventEnd sortedEnd : semanticOrdering) {
            Predicate<ISequenceEvent> operands = Predicates.and(notMoved, Predicates.instanceOf(Operand.class));
            for (ISequenceEvent ise : Iterables.filter(endToISequencEvents.get(sortedEnd), operands)) {
                computeFinalRange(endLocations, sequenceEventsToRange, ise);
            }
        }

        // Other sequence events
        for (EventEnd sortedEnd : semanticOrdering) {
            for (ISequenceEvent ise : Iterables.filter(endToISequencEvents.get(sortedEnd), notMoved)) {
                computeFinalRange(endLocations, sequenceEventsToRange, ise);
            }
        }
        return sequenceEventsToRange;
    }

    private void computeFinalRange(Map<EventEnd, Integer> endLocations,
            final Map<ISequenceEvent, Range> sequenceEventsToRange, ISequenceEvent ise) {
        Collection<EventEnd> ends = iSequenceEventsToEventEnds.get(ise);
        if (ends.size() == 2) {
            Iterator<EventEnd> it = ends.iterator();
            EventEnd start = it.next();
            EventEnd finish = it.next();

            Range newRange = getNewRange(ise, start, finish, endLocations);
            sequenceEventsToRange.put(ise, newRange);
        } else if (ends.size() == 1 && ise.isLogicallyInstantaneous() && (ise instanceof Message
                || EventEndHelper.PUNCTUAL_COMPOUND_EVENT_END.apply(ends.iterator().next()))) {
            Iterator<EventEnd> it = ends.iterator();
            EventEnd middle = it.next();

            Range newRange = getNewRange(ise, middle, middle, endLocations);
            sequenceEventsToRange.put(ise, newRange);
        }
    }

    private Range getNewRange(final ISequenceEvent event, final EventEnd start, final EventEnd end,
            final Map<EventEnd, Integer> endLocations) {
        Range oldRange = oldLayoutData.containsKey(event) ? oldLayoutData.get(event) : Range.emptyRange();
        int lowerBound = endLocations.containsKey(start) ? endLocations.get(start) : oldRange.getLowerBound();
        int upperBound = endLocations.containsKey(end) ? endLocations.get(end) : oldRange.getUpperBound();

        if (event.isLogicallyInstantaneous() && start == end) {
            lowerBound = lowerBound - oldRange.width() / 2;
            upperBound = lowerBound + oldRange.width();
        }

        updateTimerange(upperBound);
        return new Range(lowerBound, upperBound);
    }

    private Map<ISequenceEvent, Range> computeLifelineRanges(Map<EventEnd, Integer> endLocations) {
        final Map<ISequenceEvent, Range> sequenceEventsToRange = new LinkedHashMap<ISequenceEvent, Range>();
        int endOfLife = timeRange.getUpperBound() + LayoutConstants.TIME_STOP_OFFSET;

        layoutLifelinesWithoutCreation(sequenceEventsToRange);
        layoutCreatedLifelines(endLocations, sequenceEventsToRange);
        layoutDestructedLifelines(endLocations, sequenceEventsToRange);
        layoutNonDestructedLifelines(sequenceEventsToRange, Math.max(endOfLife, LayoutConstants.LIFELINES_MIN_Y));

        return sequenceEventsToRange;
    }

    private void layoutLifelinesWithoutCreation(final Map<ISequenceEvent, Range> sequenceEventsToRange) {
        for (ISequenceEvent event : getLifeLinesWithoutCreation()) {
            Range oldRange = oldLayoutData.get(event);
            Option<Lifeline> parentLifeline = event.getLifeline();
            if (parentLifeline.some()) {
                InstanceRole instanceRole = parentLifeline.get().getInstanceRole();
                if (instanceRole != null) {
                    int newLBound = getLifelineMinLowerBound(instanceRole);
                    if (newLBound != oldRange.getLowerBound()) {
                        sequenceEventsToRange.put(event, new Range(newLBound, oldRange.getUpperBound()));
                    }
                }
            }
        }
    }

    private void layoutCreatedLifelines(Map<EventEnd, Integer> endLocations,
            final Map<ISequenceEvent, Range> sequenceEventsToRange) {
        for (Message smep : creators.values()) {
            Collection<EventEnd> ends = iSequenceEventsToEventEnds.get(smep);
            if (!ends.isEmpty()) {
                Iterator<EventEnd> it = ends.iterator();
                EventEnd first = it.next();
                if (endLocations.containsKey(first)) {
                    int endMove = endLocations.get(first);
                    int vGap = getTargetFigureMidHeight(smep);
                    Lifeline lep = smep.getTargetElement().getLifeline().get();
                    Range oldRange = sequenceEventsToRange.containsKey(lep) ? sequenceEventsToRange.get(lep)
                            : oldLayoutData.get(lep);
                    sequenceEventsToRange.put(lep, new Range(endMove + vGap, endMove + vGap + oldRange.width()));
                }
            }
        }
    }

    private void layoutDestructedLifelines(Map<EventEnd, Integer> endLocations,
            final Map<ISequenceEvent, Range> sequenceEventsToRange) {
        for (Message smep : destructors.values()) {
            Collection<EventEnd> ends = iSequenceEventsToEventEnds.get(smep);
            if (!ends.isEmpty()) {
                Iterator<EventEnd> it = ends.iterator();
                EventEnd first = it.next();
                int endMove = endLocations.get(first);
                int vGap = getTargetFigureMidHeight(smep);
                int newY = endMove - vGap;
                Lifeline lep = ((EndOfLife) smep.getTargetElement()).getLifeline().get();
                Range oldRange = sequenceEventsToRange.containsKey(lep) ? sequenceEventsToRange.get(lep)
                        : oldLayoutData.get(lep);
                sequenceEventsToRange.put(lep, new Range(oldRange.getLowerBound(), newY));
            }
        }
    }

    private void layoutNonDestructedLifelines(final Map<ISequenceEvent, Range> sequenceEventsToRange,
            int endOfLife) {
        // update lifeline ranges
        for (ISequenceEvent event : getLifeLinesWithoutDestruction()) {
            Range currentRange = sequenceEventsToRange.containsKey(event) ? sequenceEventsToRange.get(event)
                    : oldLayoutData.get(event);
            if (currentRange.getUpperBound() != endOfLife) {
                sequenceEventsToRange.put(event, new Range(currentRange.getLowerBound(), endOfLife));
            }
        }
    }

    private int computeLocation(final int currentY, final EventEnd end, final EventEnd endBefore,
            final boolean pack, Map<EventEnd, Integer> alreadyComputedLocations) {
        int location = currentY;
        Collection<ISequenceEvent> commonIses = getCommonISequenceEvent(endBefore, end);

        if (shouldMove(commonIses)) {
            int newMinY = getMinY(endBefore, end, commonIses, pack, location, alreadyComputedLocations);
            if (pack) {
                location = newMinY;
            } else {
                // try to save position
                int oldPosition = getOldStablePosition(currentY, end);

                // don't minimize previous range
                int rangeStableY = getRangeStablePosition(currentY, end, alreadyComputedLocations);

                // don't reduce previous delta with known/flagged predecessor
                int deltaStableY = getDeltaStablePosition(currentY, end, alreadyComputedLocations);

                location = Math.max(newMinY, Math.max(oldPosition, Math.max(deltaStableY, rangeStableY)));
            }
        }
        return location;
    }

    private int getOldStablePosition(final int currentY, final EventEnd end) {
        int oldPosition = currentY;

        // Should we trust GMF positions ?
        if (flaggedEnds.contains(end) || toolCreatedEnds.contains(end)) {
            oldPosition = eventEndOldPosition.apply(end);
        }

        if (isFlagguedByRefreshExtension(end, endToISequencEvents.get(end))) {
            oldPosition = eventEndOldFlaggedPosition.apply(end);
        }

        return oldPosition;
    }

    private int getRangeStablePosition(final int currentY, final EventEnd end,
            Map<EventEnd, Integer> alreadyComputedLocations) {
        int rangeStabilityPos = currentY;
        Collection<ISequenceEvent> ises = endToISequencEvents.get(end);
        for (ISequenceEvent ise : ises) {
            if (!ise.isLogicallyInstantaneous()) {
                SingleEventEnd see = EventEndHelper.getSingleEventEnd(end, ise.getSemanticTargetElement().get());
                if (!see.isStart() && !(ise instanceof Message && !Iterables
                        .isEmpty(Iterables.filter(iSequenceEventsToEventEnds.get(ise), CompoundEventEnd.class)))) {
                    int startLocation = getStartLocation(ise, alreadyComputedLocations);
                    Option<Range> oldRange = oldRangeFunction.apply(ise);

                    if (isFlagguedByRefreshExtension(end, Collections.singleton(ise))) {
                        oldRange = oldFlaggedRange.apply(ise);
                    }

                    int width = oldRange.some() ? oldRange.get().width() : 0;
                    rangeStabilityPos = Math.max(rangeStabilityPos, startLocation + width);
                }
            }
        }
        return rangeStabilityPos;
    }

    private boolean isFlagguedByRefreshExtension(EventEnd end, Collection<ISequenceEvent> ises) {
        if (flaggedEnds.contains(end)) {
            for (ISequenceEvent ise : ises) {
                Rectangle flaggedAbsoluteBounds = new ISequenceElementQuery(ise).getFlaggedAbsoluteBounds();
                if (flaggedAbsoluteBounds.x == LayoutConstants.EXTERNAL_CHANGE_FLAG.x) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean shouldMove(Collection<ISequenceEvent> commonIses) {
        boolean shouldMove = true;
        if (!commonIses.isEmpty()) {
            ISequenceEvent commonIse = commonIses.iterator().next();
            shouldMove = !commonIse.isLogicallyInstantaneous();
        }

        return shouldMove;
    }

    private int getMinY(EventEnd endBefore, EventEnd end, Collection<ISequenceEvent> commonIses, boolean pack,
            int currentLocation, Map<EventEnd, Integer> alreadyComputedLocations) {
        int genericGap = getGenericGap(endBefore, end, pack);
        int minGap = genericGap;

        if (!commonIses.isEmpty()) {
            int commonIseGap = getGapFromCommonSequenceEvent(end, commonIses, pack, genericGap);
            minGap = commonIseGap;
        } else {
            boolean operands = Iterables.any(eventEndToSequenceEvents.apply(end),
                    Predicates.instanceOf(Operand.class));
            if (operands) {
                minGap = getGapBeforeOperandEnd(endBefore, end, currentLocation, genericGap,
                        alreadyComputedLocations);
            }
        }

        return currentLocation + minGap;
    }

    private int getGenericGap(EventEnd endBefore, EventEnd end, boolean pack) {
        int beforeGap = 0;

        if (endBefore != null) {
            Collection<ISequenceEvent> endBeforeEvents = eventEndToSequenceEvents.apply(endBefore);
            beforeGap = pack ? LayoutConstants.MIN_INTER_SEQUENCE_EVENTS_VERTICAL_GAP
                    : LayoutConstants.EXECUTION_CHILDREN_MARGIN;

            // Predecessor : Logically instantaneouse States
            Iterable<State> predStates = Iterables.filter(endBeforeEvents, State.class);
            if (EventEndHelper.PUNCTUAL_COMPOUND_EVENT_END.apply(endBefore) && endBeforeEvents.size() == 1
                    && Iterables.size(predStates) == 1) {
                State predState = Iterables.getOnlyElement(predStates);
                if (predState.isLogicallyInstantaneous()) {
                    beforeGap += getAbstractNodeEventVerticalSize(endBefore, predState, endBeforeEvents, pack) / 2;
                }
            }

            if (Iterables.any(endBeforeEvents, Predicates.instanceOf(InteractionUse.class))
                    && endBefore instanceof SingleEventEnd && ((SingleEventEnd) endBefore).isStart()) {
                beforeGap = LayoutConstants.DEFAULT_INTERACTION_USE_HEIGHT / 2;
            }

            if (Iterables.any(eventEndToSequenceEvents.apply(end), Predicates.instanceOf(InteractionUse.class))
                    && end instanceof SingleEventEnd && !((SingleEventEnd) end).isStart()) {
                beforeGap = LayoutConstants.DEFAULT_INTERACTION_USE_HEIGHT / 2;
            }

            if (creators.keySet().contains(endBefore)) {
                if (pack) {
                    beforeGap += getTargetFigureMidHeight(creators.get(endBefore))
                            + LayoutConstants.TIME_START_OFFSET
                            - LayoutConstants.MIN_INTER_SEQUENCE_EVENTS_VERTICAL_GAP;
                } else {
                    beforeGap += getTargetFigureMidHeight(creators.get(endBefore));
                }
            } else if (losts.containsKey(endBefore)) {
                beforeGap += losts.get(endBefore).getBounds().height / 2;
            }
        } else {
            beforeGap = pack ? LayoutConstants.TIME_START_OFFSET : LayoutConstants.TIME_START_MIN_OFFSET;
        }

        if (destructors.keySet().contains(end)) {
            beforeGap += getTargetFigureMidHeight(destructors.get(end));
        } else if (losts.containsKey(end)) {
            beforeGap += losts.get(end).getBounds().height / 2;
        }

        // current event : Logically instantaneouse States
        Collection<ISequenceEvent> endEvents = eventEndToSequenceEvents.apply(end);
        Iterable<State> states = Iterables.filter(endEvents, State.class);
        if (EventEndHelper.PUNCTUAL_COMPOUND_EVENT_END.apply(end) && endEvents.size() == 1
                && Iterables.size(states) == 1) {
            State state = Iterables.getOnlyElement(states);
            if (state.isLogicallyInstantaneous()) {
                beforeGap += getAbstractNodeEventVerticalSize(endBefore, state, endEvents, pack) / 2;
            }
        }
        return beforeGap;
    }

    private int getGapBeforeOperandEnd(EventEnd endBefore, EventEnd end, int currentLocation, int genericGap,
            Map<EventEnd, Integer> alreadyComputedLocations) {
        int beforeGap = genericGap;
        Iterable<Operand> operands = Iterables.filter(eventEndToSequenceEvents.apply(end), Operand.class);
        if (!Iterables.isEmpty(operands) && endBefore instanceof SingleEventEnd) {
            if (Iterables.any(eventEndToSequenceEvents.apply(endBefore),
                    Predicates.instanceOf(CombinedFragment.class)) && ((SingleEventEnd) endBefore).isStart()) {
                beforeGap = LayoutConstants.COMBINED_FRAGMENT_TITLE_HEIGHT;
            } else {
                Operand op = selectEndedOperand(end, operands);
                if (op != null) {
                    int startLoc = getStartLocation(op, alreadyComputedLocations);
                    int minEndLoc = startLoc + LayoutConstants.DEFAULT_OPERAND_HEIGHT;
                    beforeGap = Math.max(minEndLoc - currentLocation, genericGap);
                }
            }
        }
        return beforeGap;
    }

    private Operand selectEndedOperand(EventEnd end, Iterable<Operand> operands) {
        Operand op = null;
        if (end instanceof CompoundEventEnd) {
            for (SingleEventEnd see : ((CompoundEventEnd) end).getEventEnds()) {
                if (!see.isStart()) {
                    EObject semanticEvent = see.getSemanticEvent();
                    for (Operand opp : operands) {
                        EObject eObject = opp.getSemanticTargetElement().get();
                        if (semanticEvent != null && semanticEvent.equals(eObject)) {
                            op = opp;
                        }
                    }
                }
            }
        }
        return op;
    }

    private int getStartLocation(ISequenceEvent ise, Map<EventEnd, Integer> alreadyComputedLocations) {
        Collection<EventEnd> ends = iSequenceEventsToEventEnds.get(ise);
        for (EventEnd end : ends) {
            SingleEventEnd see = EventEndHelper.getSingleEventEnd(end, ise.getSemanticTargetElement().get());
            if (see.isStart() && alreadyComputedLocations.containsKey(end)) {
                return alreadyComputedLocations.get(end);
            }
        }
        return 0;
    }

    private boolean layoutUnconnectedLostMessageEnd() {
        boolean applied = false;
        for (LostMessageEnd lme : unconnectedLostEnds) {
            if (createdFromTool(lme)) {
                ISequenceElementQuery query = new ISequenceElementQuery(lme);
                int y = query.getFlaggedAbsoluteBounds().y;
                if (y != -1) {
                    LayoutConstraint layoutConstraint = lme.getNotationNode().getLayoutConstraint();
                    if (layoutConstraint instanceof Location) {
                        Rectangle bounds = lme.getProperLogicalBounds();
                        ((Location) layoutConstraint).setY(y - bounds.height / 2);
                        applied = true;
                    }
                }
            }
        }
        return applied;
    }

    private void initSortedEventEnds(boolean pack) {
        SequenceDDiagram sequenceDDiagram = (SequenceDDiagram) sequenceDiagram.getNotationDiagram().getElement();
        graphicalOrdering.addAll(sequenceDDiagram.getGraphicalOrdering().getEventEnds());
        semanticOrdering.addAll(sequenceDDiagram.getSemanticOrdering().getEventEnds());
    }

    private void initLifelinesOldLayoutData() {
        Collection<Lifeline> lifelines = new ArrayList<Lifeline>();
        lifelines.addAll(sequenceDiagram.getAllLifelines());
        for (ISequenceEvent ise : lifelines) {
            oldLayoutData.put(ise, getOldLayoutData(ise));
        }
    }

    private void lookForUnconnectedLostEnd() {
        Collection<LostMessageEnd> allLostMessageEnds = sequenceDiagram.getAllLostMessageEnds();
        Collection<LostMessageEnd> discoveredLostEnds = Lists.newArrayList();
        for (Message knownMsgs : Iterables.filter(iSequenceEventsToEventEnds.keySet(), Message.class)) {
            ISequenceNode sourceElement = knownMsgs.getSourceElement();
            if (sourceElement instanceof LostMessageEnd) {
                discoveredLostEnds.add((LostMessageEnd) sourceElement);
            }

            ISequenceNode targetElement = knownMsgs.getTargetElement();
            if (targetElement instanceof LostMessageEnd) {
                discoveredLostEnds.add((LostMessageEnd) targetElement);
            }
        }

        Iterables.removeAll(allLostMessageEnds, discoveredLostEnds);

        unconnectedLostEnds.addAll(allLostMessageEnds);
    }

    /**
     * Determines the range of absolute Y locations in which the messages can be
     * laid out.
     * 
     * @param pack
     *            packing layout if true.
     * @return
     */
    protected void initTimeBounds(boolean pack) {
        int minTimeBounds = getMinTimeBounds();
        int startTime = minTimeBounds;
        int endTime = getMaxTimeBounds(pack, minTimeBounds) - LayoutConstants.TIME_STOP_OFFSET;
        this.timeRange = new Range(startTime, endTime);
    }

    private int getMaxTimeBounds(boolean pack, int minTimeBounds) {
        int max = getSpecifiedMaxTimeBounds(minTimeBounds);

        if (!pack) {
            Iterable<Lifeline> lifelinesWithoutDestruction = getLifeLinesWithoutDestruction();

            // Avoid to handle lifelines to move up for max computation.
            Predicate<Lifeline> isMaxRangeCandidate = new Predicate<Lifeline>() {
                public boolean apply(Lifeline input) {
                    InstanceRole irep = input.getInstanceRole();
                    if (irep != null) {
                        return irep.getBounds().getLocation().y <= LayoutConstants.LIFELINES_START_Y;
                    }
                    return false;
                }
            };

            Collection<Lifeline> lifelinesToConsider = Lists
                    .newArrayList(Iterables.filter(lifelinesWithoutDestruction, isMaxRangeCandidate));
            Ordering<ISequenceEvent> maxOrdering = Ordering.natural()
                    .onResultOf(Functions.compose(RangeHelper.upperBoundFunction(), ISequenceEvent.VERTICAL_RANGE));
            if (!lifelinesToConsider.isEmpty()) {
                Lifeline lep = maxOrdering.max(lifelinesToConsider);
                max = lep.getVerticalRange().getUpperBound();
            }
        }

        return max;
    }

    private int getSpecifiedMaxTimeBounds(int minTimeBounds) {
        List<Lifeline> allLifelines = sequenceDiagram.getAllLifelines();
        int timeBounds = LayoutConstants.LIFELINES_MIN_Y;

        for (Lifeline lep : allLifelines) {
            DDiagramElement dde = (DDiagramElement) lep.getNotationNode().getElement();
            if (dde instanceof DNode && Lifeline.viewpointElementPredicate().apply(dde)) {
                DNode node = (DNode) dde;
                int specifiedVSize = getSpecifiedVSize(node);
                int endOfLifeVsize = getSpecifiedEndOfLifeVSize(node);
                timeBounds = Math.max(LayoutConstants.LIFELINES_MIN_Y,
                        minTimeBounds + specifiedVSize - endOfLifeVsize / 2);
            }
        }

        return timeBounds;
    }

    private int getSpecifiedEndOfLifeVSize(DNode node) {
        int endOfLifeVsize = 0;

        List<DNode> endOfLifes = Lists
                .newArrayList(Iterables.filter(node.getOwnedBorderedNodes(), new Predicate<DNode>() {
                    public boolean apply(DNode input) {
                        return input.isVisible() && EndOfLife.viewpointElementPredicate().apply(input);
                    }
                }));

        if (!endOfLifes.isEmpty()) {
            endOfLifeVsize = getSpecifiedVSize(endOfLifes.iterator().next());
        }
        return endOfLifeVsize;
    }

    /**
     * Return the specified size of the given node.
     * 
     * @param node
     *            the given node.
     * @return the specified height of a {@link DNode}.
     */
    protected int getSpecifiedVSize(DNode node) {
        return new DNodeQuery(node).getDefaultDimension().height;
    }

    private int getMinTimeBounds() {
        int min = 2 * LayoutConstants.LIFELINES_START_Y;
        Iterable<Lifeline> lifelinesWithoutCreation = getLifeLinesWithoutCreation();

        // Avoid to handle lifelines to move up for min computation.
        Predicate<Lifeline> isMinRangeCandidate = new Predicate<Lifeline>() {
            public boolean apply(Lifeline input) {
                InstanceRole irep = input.getInstanceRole();
                if (irep != null) {
                    return irep.getBounds().getLocation().y <= LayoutConstants.LIFELINES_START_Y;
                }
                return false;
            }
        };

        Collection<Lifeline> lifelinesToConsider = Lists
                .newArrayList(Iterables.filter(lifelinesWithoutCreation, isMinRangeCandidate));
        if (!lifelinesToConsider.isEmpty()) {
            Lifeline lep = heightOrdering.max(lifelinesToConsider);
            min = LayoutConstants.LIFELINES_START_Y + instanceRoleHeight.apply(lep);
        }
        return min;
    }

    /**
     * Get the middle height of the given message's targeted figure.
     * 
     * @param mover
     *            the given message.
     * @return the middle height.
     */
    protected int getTargetFigureMidHeight(Message mover) {
        int midHeight = 0;
        if (mover != null && mover.getTargetElement() != null) {
            midHeight = mover.getTargetElement().getBounds().height / 2;
        }
        return midHeight;
    }

    /**
     * Increase the time range to the given upperBound. If the current upper
     * bounds is bigger than the parameter, it does nothing.
     * 
     * @param upperBound
     *            the new minimum upperBound.
     */
    protected void updateTimerange(int upperBound) {
        if (upperBound > timeRange.getUpperBound()) {
            timeRange = new Range(timeRange.getLowerBound(), upperBound);
        }
    }

    /**
     * Return the minimum valid lower start time on the {@link Lifeline} of the
     * given {@link InstanceRole}.
     * 
     * @param irep
     *            the current {@link InstanceRole}.
     * @return the lifeline minimum valid lower bound.
     */
    protected int getLifelineMinLowerBound(final InstanceRole irep) {
        int vGap = LayoutConstants.LIFELINES_START_Y;
        vGap += irep.getBounds().height;
        return vGap;
    }

    /**
     * Register event old and init context (ends, old layout data, previous
     * bounds flag, creators, destructors, ...).
     */
    protected void registerEventEnds() {
        for (EventEnd end : Lists.newArrayList(semanticOrdering)) {
            registerEventEnd(end);
        }
        Collections.sort(flaggedEnds, Ordering.natural().onResultOf(eventEndOldFlaggedPosition));
    }

    private void registerEventEnd(EventEnd end) {
        Collection<EObject> semanticEvents = EventEndHelper.getSemanticEvents(end);
        Collection<ISequenceEvent> eventParts = Sets.newLinkedHashSet();
        for (EObject semanticEvent : semanticEvents) {
            eventParts.addAll(ISequenceElementAccessor.getEventsForSemanticElement(sequenceDiagram, semanticEvent));
        }

        // ISequenceEvent has not been created
        if (eventParts.isEmpty()) {
            Collection<DDiagramElement> ddes = Sets.newLinkedHashSet();
            for (EObject semanticEvent : semanticEvents) {
                ddes.addAll(ISequenceElementAccessor.getDiagramElementsForSemanticElement(sequenceDiagram,
                        semanticEvent));
            }

            // No ISequenceEvent has been created but DDiagramElement exists,
            // gmf refresh did not occurs, abort
            // current layout.
            if (!ddes.isEmpty()) {
                semanticOrdering.clear();
                graphicalOrdering.clear();
            }
        }

        boolean flagged = false;
        boolean toolCreated = false;
        boolean toolSemanticCreated = false;
        boolean lost = false;
        for (ISequenceEvent ise : eventParts) {
            Range oldData = getOldLayoutData(ise);
            oldLayoutData.put(ise, oldData);
            endToISequencEvents.put(end, ise);
            iSequenceEventsToEventEnds.put(ise, end);

            ISequenceElementQuery query = new ISequenceElementQuery(ise);
            if (query.hasAbsoluteBoundsFlag()) {
                Rectangle flaggedAbsoluteBounds = query.getFlaggedAbsoluteBounds();
                if (LayoutConstants.TOOL_CREATION_FLAG.equals(flaggedAbsoluteBounds)) {
                    toolCreated = true;
                } else if (LayoutConstants.TOOL_CREATION_FLAG_FROM_SEMANTIC.equals(flaggedAbsoluteBounds)) {
                    toolSemanticCreated = true;
                } else {
                    if (flaggedAbsoluteBounds.height == -1) {
                        // Correct auto-size
                        flaggedAbsoluteBounds.height = 0;
                    }
                    oldFlaggedLayoutData.put(ise, flaggedAbsoluteBounds);
                    flagged = true;
                }
            }

            if (ise instanceof Message) {
                Message smep = (Message) ise;
                ISequenceNode targetElement = smep.getTargetElement();
                if (targetElement instanceof InstanceRole) {
                    creators.put(end, smep);
                } else if (targetElement instanceof EndOfLife) {
                    destructors.put(end, smep);
                } else if (targetElement instanceof LostMessageEnd) {
                    lost = true;
                    losts.put(end, (LostMessageEnd) targetElement);
                }

                ISequenceNode sourceElement = smep.getSourceElement();
                if (sourceElement instanceof LostMessageEnd) {
                    lost = true;
                    losts.put(end, (LostMessageEnd) sourceElement);
                }
            }
        }

        if (flagged && !toolCreated) {
            flaggedEnds.add(end);
        } else if (isSafeToolCreation(end)) {
            if (toolCreated) {
                toolCreatedEnds.add(end);
            } else if (toolSemanticCreated
                    && ((end instanceof SingleEventEnd && ((SingleEventEnd) end).isStart()) || lost)) {
                toolCreatedEnds.add(end);
            }
        }
    }

    private boolean isSafeToolCreation(EventEnd end) {
        boolean safe = !(end instanceof CompoundEventEnd);
        safe = safe || EventEndHelper.PUNCTUAL_COMPOUND_EVENT_END.apply(end);
        for (Message msg : Iterables.filter(endToISequencEvents.get(end), Message.class)) {
            safe = safe || msg.getSourceElement() instanceof LostMessageEnd
                    || msg.getTargetElement() instanceof LostMessageEnd;
        }

        return safe;
    }

    /**
     * Get the common {@link ISequenceEvent} between the given ends.
     * 
     * @param end1
     *            a first {@link EventEnd}
     * @param end2
     *            a second {@link EventEnd}
     * @return the common events between given ends
     */
    protected Collection<ISequenceEvent> getCommonISequenceEvent(EventEnd end1, EventEnd end2) {
        if (end1 == null || end2 == null) {
            return Collections.<ISequenceEvent>emptyList();
        }
        Collection<ISequenceEvent> ises1 = endToISequencEvents.get(end1);
        Collection<ISequenceEvent> ises2 = endToISequencEvents.get(end2);
        Collection<ISequenceEvent> commonIses = Lists.newArrayList(ises2);
        Iterables.retainAll(commonIses, ises1);
        return commonIses;
    }

    @Override
    protected Function<EventEnd, Integer> getOldPosition() {
        return eventEndOldPosition;
    }

    @Override
    protected Function<EventEnd, Integer> getOldFlaggedPosition() {
        return eventEndOldFlaggedPosition;
    }
}