Java tutorial
/******************************************************************************* * 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.elements; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; 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.NotationPackage; import org.eclipse.gmf.runtime.notation.View; import org.eclipse.sirius.diagram.DDiagramElement; import org.eclipse.sirius.diagram.DEdge; import org.eclipse.sirius.diagram.sequence.Messages; import org.eclipse.sirius.diagram.sequence.business.internal.RangeHelper; 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.SequenceMessageViewQuery; import org.eclipse.sirius.diagram.sequence.business.internal.util.RangeSetter; 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.EventEndsOrdering; 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.Function; import com.google.common.base.Functions; 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.Ordering; import com.google.common.collect.Sets; /** * Common interface for all the elements of a sequence diagram. * * @author mporhel */ public class Message extends AbstractSequenceElement implements ISequenceEvent { /** * Predicate to filter States, Frames and Operand from possible new source * or target of a message reconnection. */ public static final Predicate<ISequenceEvent> NO_RECONNECTABLE_EVENTS = new Predicate<ISequenceEvent>() { @Override public boolean apply(ISequenceEvent input) { return input instanceof AbstractFrame || input instanceof Operand || input instanceof State; } }; /** * Function to get the Sirius DDiagramElement message kind. */ public static final Function<DEdge, Kind> VIEWPOINT_MESSAGE_KIND = new Function<DEdge, Kind>() { @Override public Kind apply(DEdge from) { Kind result = null; if (AbstractSequenceElement.isSequenceDiagramElement(from, DescriptionPackage.eINSTANCE.getBasicMessageMapping())) { result = Kind.BASIC; } else if (AbstractSequenceElement.isSequenceDiagramElement(from, DescriptionPackage.eINSTANCE.getReturnMessageMapping())) { result = Kind.REPLY; } else if (AbstractSequenceElement.isSequenceDiagramElement(from, DescriptionPackage.eINSTANCE.getCreationMessageMapping())) { result = Kind.CREATION; } else if (AbstractSequenceElement.isSequenceDiagramElement(from, DescriptionPackage.eINSTANCE.getDestructionMessageMapping())) { result = Kind.DESTRUCTION; } assert result != null : Messages.Message_unsupportedMessageKind; return result; } }; /** * The visual ID. * * see org.eclipse.sirius.diagram.internal.edit.parts.DEdgeEditPart. * VISUAL_ID */ public static final int VISUAL_ID = 4001; /** * The different (exclusive) kinds of sequence messages. */ public enum Kind { /** * Normal, basic message. */ BASIC, /** * Reply message, associated to the basic message to which it replies. */ REPLY, /** * Creation message. */ CREATION, /** * Destruction message. */ DESTRUCTION; } /** * Predicate to check whether a Sirius DDiagramElement represents a message. */ private static enum SiriusElementPredicate implements Predicate<DDiagramElement> { INSTANCE; @Override public boolean apply(DDiagramElement input) { return AbstractSequenceElement.isSequenceDiagramElement(input, DescriptionPackage.eINSTANCE.getMessageMapping()); } } /** * . * * @param edge * . */ public Message(Edge edge) { super(edge); Preconditions.checkArgument(Message.notationPredicate().apply(edge), Messages.Message_nonSequenceMessageEdge); } /** * Returns a predicate to check whether a GMF View represents a message. * * @return a predicate to check whether a GMF View represents a message. */ public static Predicate<View> notationPredicate() { return new NotationPredicate(NotationPackage.eINSTANCE.getEdge(), VISUAL_ID, Message.viewpointElementPredicate()); } /** * Returns a predicate to check whether a Sirius DDiagramElement represents * a message. * * @return a predicate to check whether a Sirius DDiagramElement represents * a message. */ public static Predicate<DDiagramElement> viewpointElementPredicate() { return SiriusElementPredicate.INSTANCE; } /** * {@inheritDoc} */ public Edge getNotationEdge() { return (Edge) view; } /** * Returns the precise kind of this message, if this element is valid. * * @return the precise kind of this message, if this element is valid. */ public Kind getKind() { EObject element = view.getElement(); if (element instanceof DEdge) { return VIEWPOINT_MESSAGE_KIND.apply((DEdge) element); } else { // Assume basic message return Kind.BASIC; } } /** * {@inheritDoc} */ public ISequenceNode getSourceElement() { return ISequenceElementAccessor.getISequenceNode(getNotationEdge().getSource()).get(); } /** * {@inheritDoc} */ public ISequenceNode getTargetElement() { return ISequenceElementAccessor.getISequenceNode(getNotationEdge().getTarget()).get(); } @Override public Range getVerticalRange() { return new SequenceMessageViewQuery(getNotationEdge()).getVerticalRange(); } @Override public boolean isLogicallyInstantaneous() { return !isReflective(); } @Override public void setVerticalRange(Range range) throws IllegalStateException { RangeSetter.setVerticalRange(this, range); } @Override public Option<Lifeline> getLifeline() { if (isReflective()) { return getSourceLifeline(); } return Options.newNone(); } /** * Tests whether this a reflective message, i.e. both its source and target * are in the context of the same lifeline. * * @return <code>true</code> if this message is reflective. */ public boolean isReflective() { Option<Lifeline> sourceLifeline = getSourceLifeline(); Option<Lifeline> targetLifeline = getTargetLifeline(); return sourceLifeline.some() && targetLifeline.some() && sourceLifeline.get() == targetLifeline.get(); } /** * Returns the lifeline in the context of which this message is sent. * * @return the lifeline in the context of which this message is sent. */ public Option<Lifeline> getSourceLifeline() { ISequenceNode sourceElement = getSourceElement(); if (sourceElement != null) { return sourceElement.getLifeline(); } return Options.newNone(); } /** * Returns the lifeline in the context of which this message is received. * * @return the lifeline in the context of which this message is received. */ public Option<Lifeline> getTargetLifeline() { ISequenceNode targetElement = getTargetElement(); if (targetElement != null) { return targetElement.getLifeline(); } return Options.newNone(); } /** * Returns the lifeline on "the other side" of the message, with respect to * the specified lifeline. For reflective messages, this is the same as the * local lifeline. The specified local lifeline <em>must</em> be either the * source of target lifeline of this message. Otherwise the result is * unspecified. * * @param local * the lifeline to consider as "local", either the source or * target lifeline of the message. * @return the lifeline on "the other side" of the message, i.e. the the * target lifeline is <code>local</code> it the source lifeline, and * the source lifeline otherwise. */ public Option<Lifeline> getRemoteLifeline(Lifeline local) { Option<Lifeline> sourceLifeline = getSourceLifeline(); if (local == sourceLifeline.get()) { return getTargetLifeline(); } else { return sourceLifeline; } } public boolean isCompoundMessage() { return !Iterables.isEmpty(Iterables.filter(getDiagram().findEnds(this), CompoundEventEnd.class)); } @Override public Rectangle getProperLogicalBounds() { Range range = getVerticalRange(); ISequenceNode srcElement = getSourceElement(); ISequenceNode tgtElement = getTargetElement(); Rectangle srcLogicalBounds = srcElement.getProperLogicalBounds().getCopy(); Rectangle tgtLogicalBounds = tgtElement.getProperLogicalBounds().getCopy(); int srcCenterX = srcLogicalBounds.getCenter().x; int tgtCenterX = tgtLogicalBounds.getCenter().x; int srcX = 0; int tgtX = 0; if (isReflective()) { srcX = srcLogicalBounds.getRight().x; tgtX = tgtLogicalBounds.getRight().x; } else if (srcCenterX <= tgtCenterX) { srcX = srcLogicalBounds.getRight().x; tgtX = tgtLogicalBounds.getLeft().x; } else { srcX = srcLogicalBounds.getLeft().x; tgtX = tgtLogicalBounds.getRight().x; } if (srcElement instanceof Lifeline) { srcX = srcLogicalBounds.getCenter().x; } if (tgtElement instanceof Lifeline) { tgtX = tgtLogicalBounds.getCenter().x; } return new Rectangle(srcX, range.getLowerBound(), tgtX - srcX, range.width()); } /** * Messages have no sub-events. * <p> * {@inheritDoc} */ @Override public List<ISequenceEvent> getSubEvents() { return Collections.emptyList(); } @Override public Collection<ISequenceEvent> getEventsToMoveWith() { return getSubEvents(); } @Override public ISequenceEvent getParentEvent() { ISequenceNode sourceElement = getSourceElement(); if (sourceElement instanceof ISequenceEvent) { return (ISequenceEvent) sourceElement; } return null; } @Override public ISequenceEvent getHierarchicalParentEvent() { return null; } @Override public Range getOccupiedRange() { return new ISequenceEventQuery(this).getOccupiedRange(); } /** * Messages have no sub-events. * <p> * {@inheritDoc} */ @Override public Range getValidSubEventsRange() { return Range.emptyRange(); } /** * Messages have no sub-events. * <p> * {@inheritDoc} */ @Override public boolean canChildOccupy(ISequenceEvent child, Range range) { return false; } /** * Messages have no sub-events. * <p> * {@inheritDoc} */ @Override public boolean canChildOccupy(ISequenceEvent child, Range range, List<ISequenceEvent> eventsToIgnore, Collection<Lifeline> lifelines) { return false; } @Override public Option<Operand> getParentOperand() { Option<Lifeline> sourceLifeline = getSourceLifeline(); Option<Operand> sourceParentOperand = Options.newNone(); Range verticalRange = getVerticalRange(); if (sourceLifeline.some()) { sourceParentOperand = sourceLifeline.get().getParentOperand(verticalRange); } Option<Lifeline> targetLifeline = getTargetLifeline(); Option<Operand> targetParentOperand = Options.newNone(); if (targetLifeline.some()) { targetParentOperand = targetLifeline.get().getParentOperand(verticalRange); } boolean noOperand = !sourceParentOperand.some() && !targetParentOperand.some(); boolean lostEnd = sourceLifeline.some() && !targetLifeline.some() || !sourceLifeline.some() && targetLifeline.some(); boolean sameOperand = lostEnd || noOperand || sourceParentOperand.get().equals(targetParentOperand.get()); Preconditions.checkArgument(noOperand || sameOperand, Messages.Message_invalidOperand); Option<Operand> parentOperand = sourceParentOperand; if (!parentOperand.some()) { parentOperand = targetParentOperand; } return parentOperand; } /** * Check if the current message is reflexive and surrounds other events on * the same lifeline. * * @return true if the current message is reflexive and surrounds other * events on the same lifeline. */ public boolean surroundsEventOnSameLifeline() { return !getSurroundedSameLifelineEvents().isEmpty(); } /** * Get the surrounded reflexives message depth. * * @return the surrounded reflexives message depth. */ public int getReflexiveMessageWidth() { Collection<ISequenceEvent> events = getSurroundedSameLifelineEvents(); final Range range = getVerticalRange(); Predicate<ISequenceEvent> toConsider = new Predicate<ISequenceEvent>() { @Override public boolean apply(ISequenceEvent input) { boolean toConsider = range.includes(input.getVerticalRange()); if (input instanceof Message) { toConsider = toConsider && ((Message) input).isReflective(); } return toConsider; } }; List<ISequenceEvent> impactingEvents = Lists.newArrayList(Iterables.filter(events, toConsider)); Collections.sort(impactingEvents, Ordering.natural() .onResultOf(Functions.compose(RangeHelper.lowerBoundFunction(), ISequenceEvent.VERTICAL_RANGE))); int subMessagesMaxRight = 0; for (Message msg : Iterables.filter(impactingEvents, Message.class)) { int reflexiveMessageWidth = msg.getReflexiveMessageWidth(); int origin = msg.getSourceElement().getProperLogicalBounds().right(); origin = Math.max(origin, msg.getTargetElement().getProperLogicalBounds().right()); subMessagesMaxRight = Math.max(subMessagesMaxRight, origin + reflexiveMessageWidth); } int maxRight = 0; for (AbstractNodeEvent node : Iterables.filter(impactingEvents, AbstractNodeEvent.class)) { maxRight = Math.max(maxRight, node.getProperLogicalBounds().right()); } int origin = getSourceElement().getProperLogicalBounds().right(); origin = Math.max(origin, getTargetElement().getProperLogicalBounds().right()); int width = LayoutConstants.MESSAGE_TO_SELF_BENDPOINT_HORIZONTAL_GAP; width = Math.max(width, maxRight - origin + LayoutConstants.MESSAGE_TO_SELF_HORIZONTAL_GAP); width = Math.max(width, subMessagesMaxRight - origin + LayoutConstants.MESSAGE_TO_SELF_HORIZONTAL_GAP); return width; } /** * Get the surrounded events on the same lifeline. * * @return the surrounded events on the same lifeline. */ public Collection<ISequenceEvent> getSurroundedSameLifelineEvents() { Set<ISequenceEvent> englobedEvents = new LinkedHashSet<ISequenceEvent>(); if (isReflective()) { Lifeline lifeline = getLifeline().get(); SequenceDiagram diagram = getDiagram(); EventEndsOrdering semanticOrdering = diagram.getSequenceDDiagram().getSemanticOrdering(); List<EventEnd> msgEnds = EventEndHelper.findEndsFromSemanticOrdering(this); if (msgEnds.size() == 2) { int start = semanticOrdering.getEventEnds().indexOf(msgEnds.get(0)); int end = semanticOrdering.getEventEnds().indexOf(msgEnds.get(1)); if (Math.abs(start - end) > 1) { Collection<SingleEventEnd> sees = Sets.newHashSet(); Collection<ISequenceEvent> interEvents = Sets.newHashSet(); for (int i = start + 1; i < end; i++) { EventEnd eventEnd = semanticOrdering.getEventEnds().get(i); if (eventEnd instanceof SingleEventEnd) { sees.add((SingleEventEnd) eventEnd); } else if (eventEnd instanceof CompoundEventEnd) { sees.addAll(((CompoundEventEnd) eventEnd).getEventEnds()); } } for (SingleEventEnd see : sees) { ISequenceEvent perturbing = EventEndHelper.findISequenceEvent(see, diagram); if (perturbing != null) { interEvents.add(perturbing); } } englobedEvents.addAll(getValidInterEvents(interEvents, lifeline)); } } } return englobedEvents; } private Collection<? extends ISequenceEvent> getValidInterEvents(Collection<ISequenceEvent> interEvents, Lifeline lifeline) { Set<ISequenceEvent> englobedEvents = new LinkedHashSet<ISequenceEvent>(); for (ISequenceEvent pertub : interEvents) { if (pertub instanceof Message) { Message msg = (Message) pertub; Option<Lifeline> sourceLifeline = msg.getSourceLifeline(); Option<Lifeline> targetLifeline = msg.getTargetLifeline(); if (targetLifeline.some() && targetLifeline.get().equals(lifeline)) { englobedEvents.add(pertub); } else if (sourceLifeline.some() && sourceLifeline.get().equals(lifeline)) { englobedEvents.add(pertub); } } else if (pertub instanceof CombinedFragment) { CombinedFragment cf = (CombinedFragment) pertub; Collection<Lifeline> coverage = cf.computeCoveredLifelines(); if (coverage.contains(lifeline)) { englobedEvents.add(pertub); } } else { Option<Lifeline> pLif = pertub.getLifeline(); if (pLif.some() && pLif.get().equals(lifeline)) { englobedEvents.add(pertub); } } } return englobedEvents; } }