org.eclipse.sirius.diagram.sequence.business.internal.operation.VerticalSpaceExpansion.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.sirius.diagram.sequence.business.internal.operation.VerticalSpaceExpansion.java

Source

/*******************************************************************************
 * Copyright (c) 2010, 2015 THALES GLOBAL SERVICES 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:
 *    Obeo - initial API and implementation
 *******************************************************************************/
package org.eclipse.sirius.diagram.sequence.business.internal.operation;

import java.text.MessageFormat;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.eclipse.gmf.runtime.notation.LayoutConstraint;
import org.eclipse.gmf.runtime.notation.Location;
import org.eclipse.gmf.runtime.notation.Node;
import org.eclipse.gmf.runtime.notation.Size;
import org.eclipse.sirius.diagram.sequence.Messages;
import org.eclipse.sirius.diagram.sequence.business.internal.elements.AbstractNodeEvent;
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.Lifeline;
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.query.ISequenceEventQuery;
import org.eclipse.sirius.diagram.sequence.util.Range;
import org.eclipse.sirius.diagram.ui.business.internal.operation.AbstractModelChangeOperation;
import org.eclipse.sirius.ext.base.Option;

import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

/**
 * An operation to shift all the atomic events on a sequence diagram below a
 * certain point to make room.
 * 
 * @author pcdavid, smonnier
 */
public class VerticalSpaceExpansion extends AbstractModelChangeOperation<Void> {

    private final SequenceDiagram sequenceDiagram;

    private int insertionPoint;

    private int expansionSize;

    private Set<ISequenceEvent> eventsToIgnore;

    private Set<ISequenceNode> eventsToResize;

    private Set<ISequenceNode> eventsToShift;

    private Set<Message> messagesToResize;

    private Set<Message> messagesToShift;

    private Map<Message, Range> finalMessagesRanges;

    private Integer move;

    /**
     * Constructor.
     * 
     * @param diagram
     *            the sequence diagram in which to make the change.
     * @param shift
     *            the zone to expand.
     * @param move
     *            how much the main execution which triggered this change is
     *            vertically moved.
     * @param eventsToIgnore
     *            the events which should be ignored, as they will be moved into
     *            the new space.
     */
    public VerticalSpaceExpansion(SequenceDiagram diagram, Range shift, Integer move,
            Collection<ISequenceEvent> eventsToIgnore) {
        super(MessageFormat.format(Messages.VerticalSpaceExpansion_operationName, shift));
        this.sequenceDiagram = diagram;
        this.move = move;
        this.insertionPoint = shift.getLowerBound();
        this.expansionSize = shift.width();
        // Complete the specified eventsToIgnore with all their descendants.
        this.eventsToIgnore = Sets.newHashSet();
        for (ISequenceEvent evt : eventsToIgnore) {
            this.eventsToIgnore.add(evt);
            this.eventsToIgnore.addAll(new ISequenceEventQuery(evt).getAllDescendants());
            this.eventsToIgnore.addAll(new ISequenceEventQuery(evt).getAllMessages());
        }
    }

    @Override
    public Void execute() {
        categorizeSequenceNodes(findAllSequenceNodesToConsider());
        categorizeMessages(findAllMessagesToConsider());
        computeFinalMessageRanges();

        expandLifelines();
        shiftSequenceNodes();
        resizeSequenceNodes();
        setFinalMessagesRanges();

        return null;
    }

    private void setFinalMessagesRanges() {
        for (Entry<Message, Range> smep : finalMessagesRanges.entrySet()) {
            smep.getKey().setVerticalRange(smep.getValue());
        }
    }

    private void computeFinalMessageRanges() {
        finalMessagesRanges = Maps.newHashMap();
        Set<Message> messages = sequenceDiagram.getAllMessages();
        for (Message msg : messages) {
            if (messagesToShift.contains(msg)) {
                if (!isConnectedToAMovedExecutionByASingleEnd(msg) && !isContainedReflexiveMessage(msg)) {
                    finalMessagesRanges.put(msg, msg.getVerticalRange().shifted(expansionSize));
                } else {
                    finalMessagesRanges.put(msg, msg.getVerticalRange().shifted(move));
                }
            } else if (messagesToResize.contains(msg)) {
                final Range rangeBefore = msg.getVerticalRange();
                finalMessagesRanges.put(msg,
                        new Range(rangeBefore.getLowerBound(), rangeBefore.getUpperBound() + expansionSize));
            } else {
                finalMessagesRanges.put(msg, msg.getVerticalRange());
            }
        }
    }

    private boolean isConnectedToAMovedExecutionByASingleEnd(Message msg) {
        boolean onlySourceIsInMovedExecutions = eventsToIgnore.contains(msg.getSourceElement())
                && !eventsToIgnore.contains(msg.getTargetElement());
        boolean onlyTargetIsInMovedExecutions = !eventsToIgnore.contains(msg.getSourceElement())
                && eventsToIgnore.contains(msg.getTargetElement());
        return onlySourceIsInMovedExecutions || onlyTargetIsInMovedExecutions;
    }

    /**
     * Expand all the lifelines which do not have a destroy message, but keep
     * the messages they contain stable.
     */
    private void expandLifelines() {
        List<Lifeline> lifelines = sequenceDiagram.getAllLifelines();
        lifelines.removeAll(eventsToIgnore);
        for (Lifeline lifeline : lifelines) {
            Option<Message> cm = lifeline.getCreationMessage();
            if (cm.some() && isStrictlyBelowInsertionPoint(cm.get())) {
                /*
                 * The whole lifeline is below the insertion point.
                 */
                InstanceRole irep = lifeline.getInstanceRole();
                Node node = (Node) irep.getNotationView();
                LayoutConstraint layoutConstraint = node.getLayoutConstraint();
                if (layoutConstraint instanceof Location) {
                    Location location = (Location) layoutConstraint;
                    location.setY(location.getY() + expansionSize);
                }
            } else {
                /*
                 * Only the end of the lifeline is below the insertion point.
                 */
                Option<Message> dm = lifeline.getDestructionMessage();
                if (!dm.some() || isStrictlyBelowInsertionPoint(dm.get())) {
                    expandDown(lifeline, expansionSize);
                }
            }
        }
    }

    /**
     * Find all the executions in the diagram which may be affected by the
     * operation. This is <em>all</em> the executions except the ones we are
     * explicitly told to ignore (and their descendants).
     */
    private Set<ISequenceNode> findAllSequenceNodesToConsider() {
        Set<ISequenceNode> sequenceNodes = Sets.newLinkedHashSet();
        sequenceNodes.addAll(sequenceDiagram.getAllAbstractNodeEvents());
        sequenceNodes.addAll(sequenceDiagram.getAllInteractionUses());
        sequenceNodes.addAll(sequenceDiagram.getAllCombinedFragments());
        sequenceNodes.addAll(sequenceDiagram.getAllOperands());
        sequenceNodes.removeAll(eventsToIgnore);
        return sequenceNodes;
    }

    private Set<Message> findAllMessagesToConsider() {
        Set<Message> messages = Sets.newHashSet();
        for (Message msg : sequenceDiagram.getAllMessages()) {
            if (!isBetweenTwoMovedEvents(msg) || isContainedReflexiveMessage(msg)) {
                messages.add(msg);
            }
        }
        return messages;
    }

    private boolean isBetweenTwoMovedEvents(Message msg) {
        return eventsToIgnore.contains(msg.getSourceElement()) && eventsToIgnore.contains(msg.getTargetElement());
    }

    /**
     * Validate if the message is a reflexive message between two ignored
     * executions
     * 
     * @param msg
     *            a Message
     * @return if the message "msg" is a reflexive message between two ignored
     *         executions
     */
    private boolean isContainedReflexiveMessage(Message msg) {
        return eventsToIgnore.contains(msg.getSourceElement()) && eventsToIgnore.contains(msg.getTargetElement())
                && msg.isReflective();
    }

    private void categorizeMessages(Set<Message> messages) {
        messagesToResize = Sets.newHashSet();
        messagesToShift = Sets.newHashSet();
        for (Message ise : Iterables.filter(messages, Predicates.not(Predicates.in(eventsToIgnore)))) {
            if (containsInsertionPoint(ise)) {
                messagesToResize.add(ise);
            } else if (isStrictlyBelowInsertionPoint(ise) || isConnectedToAMovedExecutionByASingleEnd(ise)) {
                messagesToShift.add(ise);
            }
        }
    }

    /**
     * Decide what needs to be done for each of the specified ISequenceNode:
     * <ul>
     * <li>nothing if it is above the expansion zone.</li>
     * <li>a resize if it intersects the insertion point, i.e. its top is above
     * the point but its bottom is below.</li>
     * <li>a complete shift if it is completely below the insertion point.</li>
     * </ul>
     * <p>
     * After completion of this method, <code>toResize</code> contains all the
     * executions which need to be resized and <code>toShift</code> all the
     * executions which need to be shifted.
     */
    private void categorizeSequenceNodes(Set<? extends ISequenceNode> sequenceNodes) {
        eventsToResize = Sets.newHashSet();
        eventsToShift = Sets.newHashSet();
        for (ISequenceNode isn : sequenceNodes) {
            if (isn instanceof ISequenceEvent) {
                ISequenceEvent ise = (ISequenceEvent) isn;
                if (containsInsertionPoint(ise)) {
                    eventsToResize.add(isn);
                } else if (isStrictlyBelowInsertionPoint(ise) && !(isn instanceof Operand
                        && eventsToShift.contains(((Operand) isn).getCombinedFragment()))) {
                    eventsToShift.add(isn);
                }
            }
        }
    }

    private void shiftSequenceNodes() {

        for (ISequenceNode nodes : Iterables.filter(eventsToShift,
                Predicates.not(Predicates.instanceOf(AbstractNodeEvent.class)))) {
            shift(nodes, expansionSize);
        }

        for (AbstractNodeEvent execution : Iterables.filter(eventsToShift, AbstractNodeEvent.class)) {
            Lifeline lep = execution.getLifeline().get();
            Option<Message> cm = lep.getCreationMessage();
            if (cm.some() && isStrictlyBelowInsertionPoint(cm.get())) {
                continue;
            }
            /*
             * Only actually shift the "top-level" executions. The rest will be
             * moved along with their shifted ancestor, as execution position is
             * relative to its parent.
             */

            if (!containsAncestors(eventsToShift, execution)) {
                shift(execution, expansionSize);
            }
        }

        for (AbstractNodeEvent execution : Iterables.filter(eventsToIgnore, AbstractNodeEvent.class)) {
            /*
             * Unshift events to ignore...
             */
            if (eventsToShift.contains(execution.getHierarchicalParentEvent())) {
                shift(execution, -expansionSize);
            }
        }
    }

    private void resizeSequenceNodes() {
        for (ISequenceNode ise : eventsToResize) {
            expandDown(ise, expansionSize);
        }
    }

    private boolean containsAncestors(Set<ISequenceNode> events, AbstractNodeEvent ise) {
        ISequenceEvent parent = ise.getHierarchicalParentEvent();
        if (parent == null || !(parent instanceof AbstractNodeEvent)) {
            return false;
        } else {
            return Iterables.contains(events, parent) || containsAncestors(events, (AbstractNodeEvent) parent);
        }
    }

    private boolean containsInsertionPoint(ISequenceEvent event) {
        return event != null && event.getVerticalRange().includes(insertionPoint);
    }

    private boolean isStrictlyBelowInsertionPoint(ISequenceEvent event) {
        return event != null && event.getVerticalRange().getLowerBound() > insertionPoint;
    }

    private void expandDown(Lifeline lifeline, int height) {
        Range range = lifeline.getVerticalRange();
        lifeline.setVerticalRange(new Range(range.getLowerBound(), range.getUpperBound() + height));
    }

    private void expandDown(ISequenceNode isn, int height) {
        Node node = isn.getNotationNode();
        LayoutConstraint layoutConstraint = node.getLayoutConstraint();
        if (layoutConstraint instanceof Size) {
            Size s = (Size) layoutConstraint;
            s.setHeight(s.getHeight() + height);
        }
    }

    private void shift(ISequenceNode isn, int height) {
        Node node = (Node) isn.getNotationView();
        LayoutConstraint layoutConstraint = node.getLayoutConstraint();
        if (layoutConstraint instanceof Location && height != 0) {
            Location location = (Location) layoutConstraint;
            location.setY(location.getY() + height);
        }
    }
}