package com.baselet.element.sequence_aio.facet; import java.util.HashMap; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.baselet.control.basics.geom.Line; import com.baselet.control.basics.geom.PointDouble; import com.baselet.control.enums.AlignHorizontal; import com.baselet.control.enums.AlignVertical; import com.baselet.control.enums.LineType; import com.baselet.diagram.draw.DrawHandler; import com.baselet.diagram.draw.TextSplitter; import com.baselet.element.relation.helper.RelationDrawer; import com.baselet.element.relation.helper.RelationDrawer.ArrowEndType; public class Message implements LifelineSpanningTickSpanningOccurrence { private static final Logger log = LoggerFactory.getLogger(Message.class); protected static final double LIFELINE_TEXT_PADDING = RelationDrawer.ARROW_LENGTH + 3; protected static final double SELF_MESSAGE_LIFELINE_GAP = RelationDrawer.ARROW_LENGTH + 7; protected static final double SELF_MESSAGE_TEXT_PADDING = 5; protected final Lifeline from; protected final Lifeline to; /** how many ticks it takes to transmit the message */ protected final int duration; protected final int sendTick; protected final String[] textLines; protected final ArrowType arrowType; protected final LineType lineType; /** * * @param from * @param to * @param duration >= 0; if from==to (a self message) then duration must be > 0 * @param sendTick * @param text can be more than one line, but if so the lines must be separated by a \n * @param arrowType * @param lineType */ public Message(Lifeline from, Lifeline to, int duration, int sendTick, String text, ArrowType arrowType, LineType lineType) { super(); this.from = from; this.to = to; this.duration = duration; this.sendTick = sendTick; textLines = text.split("\n"); this.arrowType = arrowType; this.lineType = lineType; } @Override public Lifeline getFirstLifeline() { return from.getIndex() < to.getIndex() ? from : to; } @Override public Lifeline getLastLifeline() { return from.getIndex() > to.getIndex() ? from : to; } protected double getSendCenterXOffset() { if (from == to) { return from.getLifelineRightPartWidth(sendTick); } else if (getFirstLifeline() == from) { return from.getLifelineRightPartWidth(sendTick); } else { return -from.getLifelineLeftPartWidth(sendTick); } } protected double getReceiveCenterXOffset() { if (from == to) { return to.getLifelineRightPartWidth(sendTick + duration); } else if (getFirstLifeline() == from) { return -to.getLifelineLeftPartWidth(sendTick + duration); } else { return to.getLifelineRightPartWidth(sendTick + duration); } } protected double getSendX(HorizontalDrawingInfo hDrawingInfo) { return hDrawingInfo.getHDrawingInfo(from).getHorizontalCenter() + getSendCenterXOffset(); } protected double getReceiveX(HorizontalDrawingInfo hDrawingInfo) { LifelineHorizontalDrawingInfo llHDrawingInfo = hDrawingInfo.getHDrawingInfo(to); double receiveX = llHDrawingInfo.getHorizontalCenter(); receiveX += getReceiveCenterXOffset(); if (!to.isCreatedOnStart() && to.getCreated() != null && sendTick + duration == to.getCreated()) { // create message must end at the head if (getFirstLifeline() == to) { receiveX = llHDrawingInfo.getSymmetricHorizontalEnd(sendTick + duration); } else { receiveX = llHDrawingInfo.getSymmetricHorizontalStart(sendTick + duration); } } return receiveX; } @Override public void draw(DrawHandler drawHandler, DrawingInfo drawingInfo) { PointDouble send = new PointDouble( getSendX(drawingInfo), drawingInfo.getVerticalCenter(sendTick)); PointDouble receive = new PointDouble(getReceiveX(drawingInfo), drawingInfo.getVerticalCenter(sendTick + duration)); RelationDrawer.ArrowEndType arrowEndType = ArrowEndType.NORMAL; boolean fillArrow = false; switch (arrowType) { case OPEN: arrowEndType = ArrowEndType.NORMAL; fillArrow = false; break; case FILLED: arrowEndType = ArrowEndType.CLOSED; fillArrow = true; break; default: log.error("Encountered unhandled enumeration value '" + arrowType + "'."); break; } LineType oldLt = drawHandler.getLineType(); drawHandler.setLineType(lineType); if (from == to) { drawSelfMessage(drawHandler, send, receive, arrowEndType, fillArrow, drawingInfo); } else { drawNormalMessage(drawHandler, send, receive, arrowEndType, fillArrow, drawingInfo); } drawHandler.setLineType(oldLt); } /** * draws a message which is sent between two different lifelines */ protected void drawNormalMessage(DrawHandler drawHandler, PointDouble send, PointDouble receive, RelationDrawer.ArrowEndType arrowEndType, boolean fillArrow, DrawingInfo drawingInfo) { Line line = new Line(send, receive); drawHandler.drawLine(line); drawHandler.setLineType(LineType.SOLID); RelationDrawer.drawArrowToLine(receive, drawHandler, line, false, arrowEndType, fillArrow, false); double height = send.y - drawingInfo.getVerticalStart(sendTick); double topLeftX; AlignHorizontal hAlignment; if (from == getFirstLifeline()) { topLeftX = send.x; hAlignment = AlignHorizontal.LEFT; } else { topLeftX = receive.x; hAlignment = AlignHorizontal.RIGHT; } if (duration == 0) { hAlignment = AlignHorizontal.CENTER; } topLeftX += LIFELINE_TEXT_PADDING; TextSplitter.drawText(drawHandler, textLines, topLeftX, send.y - height, Math.abs(send.x - receive.x) - LIFELINE_TEXT_PADDING * 2, height, hAlignment, AlignVertical.BOTTOM); } /** * draws a message which is sent to the same lifeline */ protected void drawSelfMessage(DrawHandler drawHandler, PointDouble send, PointDouble receive, RelationDrawer.ArrowEndType arrowEndType, boolean fillArrow, DrawingInfo hInfo) { double rightBorderX = Math.max(send.x, receive.x) + SELF_MESSAGE_LIFELINE_GAP; PointDouble[] msgLine = new PointDouble[] { send, new PointDouble(rightBorderX, send.y), new PointDouble(rightBorderX, receive.y), receive }; drawHandler.drawLines(msgLine); drawHandler.setLineType(LineType.SOLID); RelationDrawer.drawArrowToLine(receive, drawHandler, new Line(msgLine[2], msgLine[3]), false, arrowEndType, fillArrow, false); rightBorderX += SELF_MESSAGE_TEXT_PADDING; double lifelineXEnd = Math.min(hInfo.getHDrawingInfo(to).getSymmetricHorizontalEnd(sendTick), hInfo.getHDrawingInfo(to).getSymmetricHorizontalEnd(sendTick + duration)); TextSplitter.drawText(drawHandler, textLines, rightBorderX, send.y, lifelineXEnd - rightBorderX, receive.y - send.y, AlignHorizontal.LEFT, AlignVertical.CENTER); } @Override public double getOverallMinWidth(DrawHandler drawHandler, double lifelineHorizontalPadding) { if (from == to) { return getOverallMinWidthSelfMessage(drawHandler, lifelineHorizontalPadding); } else { return getOverallMinWidthNormalMessage(drawHandler, lifelineHorizontalPadding); } } protected double getOverallMinWidthSelfMessage(DrawHandler drawHandler, double lifelineHorizontalPadding) { double executionSpecWidth = Math.max(from.getLifelineRightPartWidth(sendTick), to.getLifelineRightPartWidth(sendTick + duration)); return (executionSpecWidth + SELF_MESSAGE_LIFELINE_GAP + SELF_MESSAGE_TEXT_PADDING + TextSplitter.getTextMinWidth(textLines, drawHandler)) * 2.0; // multiplied by 2, because this space is needed on the right half of the lifeline } protected double getOverallMinWidthNormalMessage(DrawHandler drawHandler, double lifelineHorizontalPadding) { double executionSpecWidth = Math.abs(getSendCenterXOffset()) + Math.abs(getReceiveCenterXOffset()); double neededWidth = executionSpecWidth + TextSplitter.getTextMinWidth(textLines, drawHandler) + LIFELINE_TEXT_PADDING * 2; int affectedLifelineCount = getLastLifeline().getIndex() - getFirstLifeline().getIndex() + 1; // increase the needed width because we only calculated the width for the arrow, but we need the overall width if (!to.isCreatedOnStart() && to.getCreated() != null && to.getCreated() == sendTick + duration) { // here we must compensate for the 1 1/2 lifelines (half one at the send and a full lifeline at the receive) return (2 * affectedLifelineCount * neededWidth - 3 * (affectedLifelineCount - 1) * lifelineHorizontalPadding) / (2 * affectedLifelineCount - 3); } else { // here we must compensate for the 2 half lifelines on each end return neededWidth / (affectedLifelineCount - 1) * affectedLifelineCount - lifelineHorizontalPadding; } } @Override public Map<Integer, Double> getEveryAdditionalYHeight(DrawHandler drawHandler, HorizontalDrawingInfo hInfo, double defaultTickHeight) { Map<Integer, Double> ret = new HashMap<Integer, Double>(); if (from == to) { getEveryAdditionalYHeightSelfMessage(drawHandler, hInfo, defaultTickHeight, ret); } else { getEveryAdditionalYHeightNormalMessage(drawHandler, hInfo, defaultTickHeight, ret); } return ret; } protected void getEveryAdditionalYHeightNormalMessage(DrawHandler drawHandler, HorizontalDrawingInfo hInfo, double defaultTickHeight, Map<Integer, Double> ret) { double maxTextWidth; double additionalHeight; maxTextWidth = Math.abs(getSendX(hInfo) - getReceiveX(hInfo)); maxTextWidth -= LIFELINE_TEXT_PADDING * 2; additionalHeight = TextSplitter.getSplitStringHeight(textLines, maxTextWidth, drawHandler); // message text is always drawn at the send position only increase send tick height // since the message is always drawn in the V center we only can use one half of the tick height additionalHeight -= defaultTickHeight / 2.0; additionalHeight *= 2; if (additionalHeight > 0) { ret.put(sendTick, additionalHeight); } } protected void getEveryAdditionalYHeightSelfMessage(DrawHandler drawHandler, HorizontalDrawingInfo hInfo, double defaultTickHeight, Map<Integer, Double> ret) { double maxTextWidth; double additionalHeight; // if more y space is needed the all covered ticks will be increased double executionSpecWidth = Math.max(from.getLifelineRightPartWidth(sendTick), to.getLifelineRightPartWidth(sendTick + duration)); maxTextWidth = Math.min(hInfo.getHDrawingInfo(to).getSymmetricWidth(sendTick), hInfo.getHDrawingInfo(to).getSymmetricWidth(sendTick + duration)) / 2.0; maxTextWidth = maxTextWidth - (executionSpecWidth + SELF_MESSAGE_LIFELINE_GAP + SELF_MESSAGE_TEXT_PADDING); additionalHeight = TextSplitter.getSplitStringHeight(textLines, maxTextWidth, drawHandler) - duration * defaultTickHeight; if (additionalHeight > 0) { for (int i = sendTick + 1; i < sendTick + duration; i++) { ret.put(i, additionalHeight / duration); } ret.put(sendTick, additionalHeight / duration); ret.put(sendTick + duration, additionalHeight / duration); } } @Override public ContainerPadding getPaddingInformation() { return null; } public enum ArrowType { OPEN, FILLED } public OccurrenceSpecification sendOccurrenceSpecification() { return new MessageSendEndpoint(); } public OccurrenceSpecification receiveOccurrenceSpecification() { return new MessageReceiveEndpoint(); } private class MessageSendEndpoint implements OccurrenceSpecification { @Override public boolean hasFixedPosition() { return true; } @Override public PointDouble getPosition(DrawingInfo drawingInfo, boolean left) { return new PointDouble(0, 0); } @Override public PointDouble getPosition(DrawingInfo drawingInfo) { return new PointDouble(getHorizonatlPosition(drawingInfo), drawingInfo.getVerticalCenter(sendTick)); } @Override public Lifeline getLifeline() { return from; } @Override public double getHorizontalPosition(HorizontalDrawingInfo hDrawingInfo, boolean left) { return 0; } @Override public double getHorizonatlPosition(HorizontalDrawingInfo hDrawingInfo) { return getSendX(hDrawingInfo); } @Override public AlignHorizontal getFixedPositionAlignment() { if (from == to) { return AlignHorizontal.RIGHT; } else if (from.getIndex() < to.getIndex()) { return AlignHorizontal.RIGHT; } else { return AlignHorizontal.LEFT; } } } private class MessageReceiveEndpoint implements OccurrenceSpecification { @Override public boolean hasFixedPosition() { return true; } @Override public PointDouble getPosition(DrawingInfo drawingInfo, boolean left) { return new PointDouble(0, 0); } @Override public PointDouble getPosition(DrawingInfo drawingInfo) { return new PointDouble(getHorizonatlPosition(drawingInfo), drawingInfo.getVerticalCenter(sendTick + duration)); } @Override public Lifeline getLifeline() { return to; } @Override public double getHorizontalPosition(HorizontalDrawingInfo hDrawingInfo, boolean left) { return 0; } @Override public double getHorizonatlPosition(HorizontalDrawingInfo hDrawingInfo) { return getReceiveX(hDrawingInfo); } @Override public AlignHorizontal getFixedPositionAlignment() { if (from == to) { return AlignHorizontal.RIGHT; } else if (from.getIndex() < to.getIndex()) { return AlignHorizontal.RIGHT; } else { return AlignHorizontal.LEFT; } } } }