org.eclipse.sirius.diagram.sequence.business.internal.util.SubEventsHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.sirius.diagram.sequence.business.internal.util.SubEventsHelper.java

Source

/*******************************************************************************
 * Copyright (c) 2010, 2011 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.util;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.eclipse.gmf.runtime.notation.View;
import org.eclipse.sirius.diagram.sequence.business.internal.RangeHelper;
import org.eclipse.sirius.diagram.sequence.business.internal.elements.AbstractFrame;
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.Execution;
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.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.elements.State;
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.util.Range;
import org.eclipse.sirius.ext.base.Option;

import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;

/**
 * .
 * 
 * @author mporhel
 * 
 */
public final class SubEventsHelper {

    private static Collection<Class<?>> types = Lists.newArrayList();
    {
        types.add(Execution.class);
        types.add(Lifeline.class);
        types.add(Operand.class);
    }

    private ISequenceEvent parentEvent;

    private Range parentRange;

    private final Multimap<ISequenceEvent, Lifeline> coverage = LinkedHashMultimap.create();

    /**
     * Default constructor.
     * 
     * @param event
     *            a supported {@link ISequenceEvent} : {@linkLifeline},
     *            {@link AbstractNodeEvent}, {@link Operand}.
     */
    public SubEventsHelper(ISequenceEvent event) {
        Preconditions.checkArgument(types.contains(event.getClass()));
        Preconditions.checkNotNull(event);
        this.parentEvent = event;
        this.parentRange = event.getVerticalRange();
    }

    /**
     * Common implementation of {@link ISequenceEvent#getSubEvents()}.
     * 
     * @return the sub-events of the (root) execution, ordered by their starting
     *         position (graphically) from top to bottom.
     */
    public List<ISequenceEvent> getSubEvents() {
        List<ISequenceEvent> result = getValidSubEvents();
        Collections.sort(result, RangeHelper.lowerBoundOrdering().onResultOf(ISequenceEvent.VERTICAL_RANGE));
        return result;
    }

    /**
     * Compute childs of Execution/Operand/Lifeline.
     * 
     * @return the sub events.
     */
    private List<ISequenceEvent> getValidSubEvents() {
        List<ISequenceEvent> childrenEvents = Lists.newArrayList();

        Set<ISequenceEvent> localParents = Sets.newLinkedHashSet();
        Set<Lifeline> coveredLifelines = Sets.newLinkedHashSet();
        if (parentEvent instanceof AbstractNodeEvent || parentEvent instanceof Lifeline) {
            localParents.add(parentEvent);
            coveredLifelines.add(parentEvent.getLifeline().get());
        } else if (parentEvent instanceof Operand) {
            Operand op = (Operand) parentEvent;
            CombinedFragment parentCombinedFragment = op.getCombinedFragment();
            coveredLifelines.addAll(getCoverage(parentCombinedFragment));
            localParents.addAll(getCarryingParents(parentCombinedFragment, coveredLifelines));
        }

        Set<ISequenceEvent> hierarchicalChildren = getNotationDirectChildrenInParentRange(localParents);
        Set<ISequenceEvent> frameChildren = getFrameChildrenInParentRange(coveredLifelines);

        childrenEvents.addAll(getTopLevelEvents(hierarchicalChildren, frameChildren, coveredLifelines));
        childrenEvents.addAll(getTopLevelEvents(frameChildren, childrenEvents, coveredLifelines));

        return childrenEvents;
    }

    private Collection<Lifeline> getCoverage(ISequenceEvent ise) {
        if (coverage.containsKey(ise)) {
            return coverage.get(ise);
        }

        Option<Lifeline> lifeline = ise.getLifeline();
        Collection<Lifeline> coveredLifelines = Sets.newLinkedHashSet();
        if (lifeline.some()) {
            coveredLifelines.add(lifeline.get());
        } else if (ise instanceof Operand) {
            coveredLifelines.addAll(getCoverage(((Operand) ise).getCombinedFragment()));
        } else if (ise instanceof AbstractFrame) {
            coveredLifelines.addAll(((AbstractFrame) ise).computeCoveredLifelines());
        } else if (ise instanceof Message) {
            Message msg = (Message) ise;
            Option<Lifeline> sourceLifeline = msg.getSourceLifeline();
            if (sourceLifeline.some()) {
                coveredLifelines.add(sourceLifeline.get());
            }
            Option<Lifeline> targetLifeline = msg.getTargetLifeline();
            if (targetLifeline.some()) {
                coveredLifelines.add(targetLifeline.get());
            }
        }
        coverage.putAll(ise, coveredLifelines);
        return coveredLifelines;
    }

    private Collection<ISequenceEvent> getCarryingParents(AbstractFrame frame, Set<Lifeline> coveredLifelines) {
        Set<ISequenceEvent> coveredEvents = Sets.newLinkedHashSet();
        for (Lifeline lifeline : coveredLifelines) {
            EventFinder localParentFinder = new EventFinder(lifeline);
            localParentFinder.setReparent(true);
            localParentFinder.setEventsToIgnore(Predicates.equalTo((ISequenceEvent) frame));
            ISequenceEvent localParent = localParentFinder.findMostSpecificEvent(frame.getVerticalRange());
            if (localParent != null) {
                coveredEvents.add(localParent);
            }
        }
        return coveredEvents;
    }

    /**
     * Look for execution and lifeline
     * 
     * @param localParents
     * @return
     */
    private Set<ISequenceEvent> getNotationDirectChildrenInParentRange(Collection<ISequenceEvent> localParents) {
        Collection<View> childViews = Sets.newLinkedHashSet();
        Collection<View> parentConnections = Sets.newHashSet();
        Set<ISequenceEvent> childrenEvents = Sets.newLinkedHashSet();

        for (ISequenceEvent ise : localParents) {
            View notationView = ise.getNotationView();
            childViews.addAll(Lists.newArrayList(Iterables.filter(notationView.getChildren(), View.class)));

            Collection<View> sourceEdges = Lists
                    .newArrayList(Iterables.filter(notationView.getSourceEdges(), View.class));
            Collection<View> targetEdges = Lists
                    .newArrayList(Iterables.filter(notationView.getTargetEdges(), View.class));

            childViews.addAll(sourceEdges);
            childViews.addAll(targetEdges);

            // In some resize cases, edges could appears to be out of their
            // source/target ranges
            if (ise == parentEvent) {
                parentConnections.addAll(sourceEdges);
                parentConnections.addAll(targetEdges);
            }
        }

        for (View view : childViews) {
            Option<ISequenceEvent> iSequenceEvent = ISequenceElementAccessor.getISequenceEvent(view);
            if (iSequenceEvent.some()) {
                ISequenceEvent ise = iSequenceEvent.get();
                if (parentConnections.contains(view) || parentRange.includes(ise.getVerticalRange())) {
                    childrenEvents.add(iSequenceEvent.get());
                }
            }
        }

        return Sets.newLinkedHashSet(EventEndHelper.getIndependantEvents(parentEvent, childrenEvents));
    }

    private Set<ISequenceEvent> getFrameChildrenInParentRange(Set<Lifeline> coveredLifelines) {
        Set<ISequenceEvent> childrenEvents = Sets.newHashSet();
        SequenceDiagram diagram = parentEvent.getDiagram();
        Set<AbstractFrame> frames = Sets.newHashSet();
        frames.addAll(diagram.getAllFrames());

        for (AbstractFrame frame : frames) {
            Range frameRange = frame.getVerticalRange();
            if (parentRange.includes(frameRange) && validCoverage(frame, coveredLifelines)) {
                childrenEvents.add(frame);
            }
        }
        return getTopLevelEvents(childrenEvents, childrenEvents, coveredLifelines);
    }

    private Set<ISequenceEvent> getTopLevelEvents(Set<ISequenceEvent> events,
            Collection<ISequenceEvent> potentialParents, Set<Lifeline> coveredLifelines) {
        HashSet<ISequenceEvent> topLevel = Sets.newLinkedHashSet();
        boolean parentFrames = Iterables.size(Iterables.filter(potentialParents, AbstractFrame.class)) != 0;

        for (ISequenceEvent event : events) {
            final Range verticalRange = event.getVerticalRange();
            final ISequenceEvent potentialChild = event;

            Predicate<ISequenceEvent> isParentOfCurrent = new Predicate<ISequenceEvent>() {
                public boolean apply(ISequenceEvent input) {
                    Range inputRange = input.getVerticalRange();
                    boolean isParent = inputRange.includes(verticalRange);
                    return isParent && input != potentialChild;
                }
            };

            List<ISequenceEvent> parents = Lists
                    .newArrayList(Iterables.filter(potentialParents, isParentOfCurrent));
            if (parents.isEmpty()) {
                topLevel.add(potentialChild);
            } else if (potentialChild instanceof AbstractFrame && !parentFrames) {
                Collection<ISequenceEvent> carriers = Lists
                        .newArrayList(getCarryingParents((AbstractFrame) potentialChild, coveredLifelines));
                Iterables.removeAll(carriers, parents);
                if (!carriers.isEmpty()) {
                    topLevel.add(potentialChild);
                }
            }
        }
        return topLevel;
    }

    private boolean validCoverage(AbstractFrame frame, Set<Lifeline> parentCoveredLifelines) {
        boolean result = false;
        Collection<Lifeline> coveredLifelines = getCoverage(frame);
        if (parentEvent instanceof Operand) {
            result = parentCoveredLifelines.containsAll(coveredLifelines);
        } else {
            result = coveredLifelines.contains(parentEvent.getLifeline().get());
        }

        return result;
    }

    /**
     * Implementation of
     * {@link ISequenceEvent#canChildOccupy(ISequenceEvent, Range)} .
     * 
     * @param child
     *            the child.
     * @param range
     *            the vertical range to test.
     * @return <code>true</code> if the child can be placed anywhere inside the
     *         specified vertical range (including occupying the whole range).
     */
    public boolean canChildOccupy(ISequenceEvent child, Range range) {
        return canChildOccupy(child, range, null, child == null ? getCoverage(parentEvent) : getCoverage(child));
    }

    /**
     * Implementation of
     * {@link ISequenceEvent#canChildOccupy(ISequenceEvent, Range)} .
     * 
     * @param child
     *            the child, if child is null it means that it is a insertion
     *            point request from a CreationTool.
     * @param range
     *            the vertical range to test.
     * @param eventsToIgnore
     *            the list of events to ignore while compute canChildOccupy.
     * @param lifelines
     *            lifelines to inspect
     * @return <code>true</code> if the child can be placed anywhere inside the
     *         specified vertical range (including occupying the whole range).
     */
    public boolean canChildOccupy(ISequenceEvent child, final Range range, List<ISequenceEvent> eventsToIgnore,
            Collection<Lifeline> lifelines) {
        boolean result = true;
        if (!parentEvent.getValidSubEventsRange().includes(range)) {
            result = false;
        } else {
            for (ISequenceEvent event : getSequenceEventsToFilter(parentEvent, child, range, lifelines)) {
                Range eventRange = event.getVerticalRange();
                if (eventsToIgnore != null && eventsToIgnore.contains(event)) {
                    // do nothing
                } else if (event instanceof Message) {
                    Message msg = (Message) event;
                    if (!checkOverlapWithSiblingMessage(child, range, msg, eventRange)) {
                        result = false;
                        break;
                    }
                } else if (eventRange.intersects(range) || !range.validatesBoundsAreDifferent(eventRange)) {
                    result = false;
                    break;
                }
            }
        }
        return result;
    }

    private boolean checkOverlapWithSiblingMessage(ISequenceEvent child, Range childRange, Message msg,
            Range msgRange) {
        boolean result = true;
        // if event is a SequenceMessageEditPart reconnect it to the
        // moved child
        // only if event is a simple message or if full range of its
        // associated Execution
        // with the associated messages (call and return) is included in
        // the future range of the child being dropped
        if (child instanceof State) {
            // A state can not have an overlapping message
            result = !childRange.includesAtLeastOneBound(msgRange);
        } else if (msg.isReflective()) {
            if (msgRange.intersects(childRange) && (childRange.getLowerBound() <= msgRange.getLowerBound()
                    || childRange.getUpperBound() >= msgRange.getUpperBound())) {
                result = false;
            }
        } else {
            ISequenceNode sourceEvent = msg.getSourceElement();
            ISequenceNode targetEvent = msg.getTargetElement();
            Execution parentExecEvent = null;
            if (sourceEvent instanceof Execution) {
                Execution sourceExec = (Execution) sourceEvent;
                if (msg.equals(sourceExec.getEndMessage().get())) {
                    parentExecEvent = sourceExec;
                }
            }

            if (targetEvent instanceof Execution) {
                Execution targetExec = (Execution) targetEvent;
                if (msg.equals(targetExec.getStartMessage().get())) {
                    parentExecEvent = targetExec;
                }
            }

            if (parentExecEvent != null) {
                Range verticalRange = parentExecEvent.getVerticalRange();
                if (child != null && (!childRange.validatesBoundsAreDifferent(verticalRange)
                        || !childRange.includes(verticalRange) && !verticalRange.includes(childRange))) {
                    result = false;
                }
            }
        }
        return result;
    }

    private Iterable<ISequenceEvent> getSequenceEventsToFilter(ISequenceEvent self, ISequenceEvent child,
            final Range range, final Collection<Lifeline> lifelines) {
        Set<ISequenceEvent> result = Sets.newHashSet(self.getSubEvents());
        Predicate<ISequenceEvent> inRangePredicate = new Predicate<ISequenceEvent>() {

            public boolean apply(ISequenceEvent input) {
                Range inputRange = input.getVerticalRange();
                return range.includesAtLeastOneBound(inputRange)
                        || new ISequenceEventQuery(input).isReflectiveMessage()
                                && inputRange.includesAtLeastOneBound(range);
            }

        };
        Predicate<ISequenceEvent> inCoverage = new Predicate<ISequenceEvent>() {

            public boolean apply(ISequenceEvent input) {
                Collection<Lifeline> inputCoverage = Lists.newArrayList(getCoverage(input));
                return Iterables.removeAll(inputCoverage, lifelines);
            }

        };

        @SuppressWarnings("unchecked")
        Predicate<ISequenceEvent> predicateFilter = Predicates.and(Predicates.not(Predicates.equalTo(child)),
                inRangePredicate, inCoverage);
        return Iterables.filter(result, predicateFilter);
    }
}