// TimeSequenceDiagram.java package simulator; // simulator package import java.awt.*; // import Java AWT classes import java.util.*; // import Java utility classes import support.*; // import Jasper support classes /** This is the class for a time sequence diagram. @author Iain A. Robin, Kenneth J. Turner @version 1.0 (1st September 1999, IAR): initial version

1.4 (9th March 2006, KJT): updated for JDK 1.5, use of Swing graphics, minor tidying
1.5 (27th July 2010, KJT): minor tidying; vertical space before DELIVER; vertical space before COMMENT not just for first event; addition of TRAVERSE for events that span columns */ public class TimeSequenceDiagram extends Canvas { /** Vertical extent of primitive */ private final static int primitiveHeight = 10; /** Width of primitive arrow */ private final static int arrowWidth = 30; /** Top/left insert of diagram */ private final static int inset = 15; /** Page left-hand margin */ private final static int margin = 15; // /** Maximum printing height */ private final static int maxHeight = 800; /** Max symbols to print */ private final static int maxSymbols = 64; /** Diagram/page header font */ public final static Font headerFont = new Font("Sans serif", Font.BOLD, 14); /** Page footer font */ public final static Font footerFont = new Font("Sans serif", Font.BOLD, 14); /** Diagram label font */ public final static Font labelFont = new Font("Sans serif", Font.PLAIN, 13); /** Mid green comments */ public final static Color commentColour = new Color(0, 128, 0); /** Red last symbol */ public final static Color lastColour = Color.red; /** Tan timeouts */ public final static Color timeoutColour = new Color(128, 64, 48); /** Tan timeouts */ private String[] columnHeaders; /** Header text height */ private int headerTextHeight; /** Column width in pixels */ private int columnWidth; /** Number of columns */ private int columns; /** Y-coordinate for symbol area top */ private int topOfBlock; /** Default symbol height */ private int defaultSymbolHeight; /** Vertical scroll position */ private int verticalPosition; /** List of symbols */ private Vector symbols; /** List of protocol entities */ private Vector entities; /** Printing starting */ private boolean starting; /** Printing finished */ private boolean stopped; /** Y-coordinate for initial symbol */ private int initialHeight; /** Medium column number */ private int mediumColumn; /** Last column number */ private int lastColumn; /** Y-coordinate for start of SEND arrow */ private int start = 0; /** Current printing top */ private int currentHeight; /** Current page number */ private int page; /** Constructor for the TimeSequenceDiagram object @param protocol Parameter */ public TimeSequenceDiagram(Protocol protocol) { setBackground(Color.white); entities = protocol.getEntities(); mediumColumn = protocol.getMediumColumn(); columns = entities.size(); columnHeaders = new String[columns]; for (int i = 0; i < columns; i++) columnHeaders[i] = ((ProtocolEntity) entities.elementAt(i)).getName(); symbols = new Vector(); verticalPosition = 0; lastColumn = -1; starting = true; } /** Adds a feature to the CommentSymbol attribute of the TimeSequenceDiagram object. @param comment comment text @param lToR true = left-to-right @param sourceCol source column */ private void addCommentSymbol(String comment, boolean lToR, int sourceCol) { CommentSymbol st = new CommentSymbol(null, comment, lToR, sourceCol, topOfBlock); addSymbol(st); } // determine types of protocol events // and add appropriate symbols to diagram /** Add events to time sequence diagram. @param events event list */ public void addEvents(Vector events) { int eventNo = 0; int source = 0; int destination = 0; for (Enumeration e = events.elements(); e.hasMoreElements(); ) { ProtocolEvent event = (ProtocolEvent) e.nextElement(); PDU pdu = null; if (event != null) { int eventType = event.getType(); pdu = event.getPDU(); if (pdu != null) { source = entities.indexOf(pdu.getSource()); destination = entities.indexOf(pdu.getDestination()); boolean lToR = destination > source; int col = lToR ? source + 1 : source - 1; String label = pdu.getLabel(); int transmissionHeight = defaultSymbolHeight; switch (eventType) { case ProtocolEvent.SEND: addSpace(source); addServicePrimitive( event, lToR, col, topOfBlock, label, ServicePrimitive.TAIL); break; case ProtocolEvent.TRANSMIT: addSpace(source); addServicePrimitive( event, lToR, col, topOfBlock, label, ServicePrimitive.TAIL); break; case ProtocolEvent.RECEIVE: start = getTransmissionStart(pdu); if (start != topOfBlock) transmissionHeight = Math.max( defaultSymbolHeight, topOfBlock - start + primitiveHeight); if (destination == lastColumn) transmissionHeight += 7; addSuccessfulTransmission( event, lToR, mediumColumn, start, transmissionHeight); addServicePrimitive( event, lToR, destination, start + transmissionHeight, label, ServicePrimitive.HEAD); lastColumn = destination; break; case ProtocolEvent.DELIVER: addSpace(source); addServicePrimitive( event, lToR, destination, topOfBlock, label, ServicePrimitive.HEAD); break; case ProtocolEvent.FRAGMENT: if (eventNo == 0) start = getTransmissionStart(pdu); if (start != topOfBlock) transmissionHeight = Math.max( defaultSymbolHeight, topOfBlock - start + primitiveHeight); if (destination == lastColumn) transmissionHeight += 7; addSuccessfulTransmission( event, lToR, mediumColumn, start, transmissionHeight); addServicePrimitive( event, lToR, destination, start + transmissionHeight, label, ServicePrimitive.HEAD); lastColumn = destination; break; case ProtocolEvent.LOSE: if (eventNo == 0) start = getTransmissionStart(pdu); if (start != topOfBlock) transmissionHeight = Math.max( defaultSymbolHeight, topOfBlock - start + primitiveHeight); if (destination == lastColumn) transmissionHeight += 7; addFailedTransmission( event, lToR, mediumColumn, start, transmissionHeight); lastColumn = destination; break; case ProtocolEvent.TIMEOUT: if (eventNo == 0) addSpace(source); addSenderTimeout(event, lToR, source); addServicePrimitive( event, lToR, col, topOfBlock, label, ServicePrimitive.TAIL); break; case ProtocolEvent.TRAVERSE: source++; // incr. source column int columns = destination - source; // set number of columns start = getTransmissionStart(pdu); // get y for start if (start != topOfBlock) // not top of block? transmissionHeight = Math.max( // set trans. height defaultSymbolHeight, topOfBlock - start + primitiveHeight); if (columns > 1) transmissionHeight = columns*(transmissionHeight + primitiveHeight); if (destination == lastColumn) // last column? transmissionHeight += 7; // allow extra space addTraverseTransmission( // add traverse trans. event, lToR, source, columns, start, transmissionHeight); addServicePrimitive( // add service prim. event, lToR, destination, start + transmissionHeight, label, ServicePrimitive.HEAD); lastColumn = destination; // set last column break; } } if (eventType == ProtocolEvent.COMMENT) { // if (eventNo == 0) addSpace(source); int col = entities.indexOf(event.getTarget()); addCommentSymbol(event.getComment(), col < mediumColumn, col); } } eventNo++; } } /** Adds a failed transmission to a time sequence diagram. @param event protocol event @param lToR true = left-to-right @param mediumColumn medium column @param top top of service primitive @param height height of successful transmission */ private void addFailedTransmission( ProtocolEvent event, boolean lToR, int mediumColumn, int top, int height) { FailedTransmission t = new FailedTransmission(event, lToR, mediumColumn, top, height); addSymbol(t); } /** Adds a feature to the SenderTimeout attribute of the TimeSequenceDiagram object. @param event protocol event @param lToR true = left-to-right @param sourceCol source column */ private void addSenderTimeout( ProtocolEvent event, boolean lToR, int sourceCol) { CommentSymbol st = new CommentSymbol(event, "timeout", lToR, sourceCol, topOfBlock); addSymbol(st); } /** Adds a feature to the ServicePrimitive attribute of the TimeSequenceDiagram object. @param event protocol event @param lToR true = left-to-right @param destination destination column @param top top of service primitive @param label label for service primitive @param pos position for label */ private void addServicePrimitive( ProtocolEvent event, boolean lToR, int destination, int top, String label, int pos) { ServicePrimitive sp = new ServicePrimitive(event, lToR, destination, top, primitiveHeight); sp.setLabel(label, pos); addSymbol(sp); } /** Adds a feature to the Space attribute of the TimeSequenceDiagram object @param col column */ private void addSpace(int col) { addSymbol(new SpaceFiller(col, topOfBlock)); } /** Adds a successful transmission to a time sequence diagram. @param event protocol event @param lToR true = left-to-right @param mediumColumn medium column @param top top of service primitive @param height height of successful transmission */ private void addSuccessfulTransmission( ProtocolEvent event, boolean lToR, int mediumColumn, int top, int height) { SuccessfulTransmission t = new SuccessfulTransmission(event, lToR, mediumColumn, top, height); addSymbol(t); } /** Adds a feature to the Symbol attribute of the TimeSequenceDiagram object @param symbol The feature to be added to the Symbol attribute */ private void addSymbol(TSDSymbol symbol) { int height = getSize().height; int symbolHeight = symbol.getHeight(); int top = symbol.getTop(); topOfBlock = top + symbolHeight; symbols.addElement(symbol); if (topOfBlock + symbolHeight + 2 * inset >= height) { int width = getSize().width; setSize(width, topOfBlock + symbolHeight + 2 * inset); } verticalPosition = getSize().height - initialHeight; repaint(); } /** Adds a traverse transmission to a time sequence diagram. @param event protocol event @param lToR true = left-to-right @param source source column @param columns columns @param top top of service primitive @param height height of traverse transmission */ private void addTraverseTransmission( ProtocolEvent event, boolean lToR, int source, int columns, int top, int height) { TraverseTransmission t = new TraverseTransmission(event, lToR, source, columns, top, height); addSymbol(t); } /** Remove everything from the time sequence diagram. */ public void clear() { symbols.removeAllElements(); verticalPosition = 0; setSize(getSize().width, initialHeight); topOfBlock = 3 * inset + headerTextHeight; repaint(); } /** Draw the columns of the time sequence diagram. @param g graphics object @param margin column margin @param width column width @param height column height */ private void drawColumns(Graphics g, int margin, int width, int height) { int headerWidth; int columnCentre; g.setFont(headerFont); // column headings FontMetrics fm = getFontMetrics(headerFont); headerTextHeight = fm.getHeight(); for (int c = 0; c < columns; c++) { columnCentre = margin + inset + (2 * c + 1) * columnWidth / 2; headerWidth = fm.stringWidth(columnHeaders[c]); g.drawString( columnHeaders[c], columnCentre - headerWidth / 2, inset + fm.getAscent()); } g.drawLine( margin + inset, inset + headerTextHeight, margin + width - inset, inset + headerTextHeight); for (int c = 1; c < columns; c++) // column separators g.drawLine( margin + inset + c * columnWidth, inset, margin + inset + c * columnWidth, height - inset); } /** Gets the transmissionStart attribute of the TimeSequenceDiagram object. @param pdu PDU associated with the TRANSMIT event @return Y-coordinate of the end of the SEND event arrow from which the corresponding TRANSMIT event symbol will start */ private int getTransmissionStart(PDU pdu) { int y = topOfBlock; for (Enumeration e = symbols.elements(); e.hasMoreElements(); ) { TSDSymbol symbol = (TSDSymbol) e.nextElement(); ProtocolEvent event = symbol.getEvent(); if (event != null) { PDU pduSent = event.getPDU(); if (pdu != null && pduSent.matches(pdu)) y = symbol.getTop() + symbol.getHeight(); } } return(y); } /** Get the verticalPosition attribute of the TimeSequenceDiagram object. @return vertical position */ public int getVerticalPosition() { return(verticalPosition); } /** Paint the time sequene diagram. @param g graphics object */ public void paint(Graphics g) { TSDSymbol symbol; int width = getSize().width; int height = getSize().height; columnWidth = (width - 2 * inset) / columns; TSDSymbol.setParameters(inset, columnWidth, arrowWidth); defaultSymbolHeight = (columnWidth - arrowWidth) * primitiveHeight / arrowWidth; drawColumns(g, 0, width, height); if (starting) { initialHeight = height; topOfBlock = 3 * inset + headerTextHeight; starting = false; } for (Enumeration e = symbols.elements(); e.hasMoreElements(); ) { symbol = (TSDSymbol) e.nextElement(); if (!e.hasMoreElements()) // colour last symbol g.setColor(lastColour); symbol.draw(g); } } /** Print the time sequence diagram. @param g graphcs object */ public void print(Graphics g) { TSDSymbol symbol = null; // current symbol TSDSymbol symbolBuff[] = // symbols to print new TSDSymbol[maxSymbols]; int nextSymbol = 0; // next symbol position int sym; // symbol count int width = getSize().width; // diagram width int height = getSize().height; // diagram height int offset; // offset from page top int nextHeight; // next printing top int nullHeight = 0; // height at last null if (starting) { // first page? starting = false; // note not first page stopped = false; // printing not finished offset = 0; // set first page offset currentHeight = 0; // set symbol height page = 0; // set page number System.out.print("Printing " + // start page listing ProtocolSimulator.protocolType + " page"); } else // not first page offset = // set later page offset 3 * inset + headerTextHeight - currentHeight; nextHeight = currentHeight + maxHeight; // next printing top columnWidth = (width - 2 * inset) / columns; // set column width TSDSymbol.setParameters( // set symbol details margin + inset, columnWidth, arrowWidth); defaultSymbolHeight = // set transm. height (columnWidth - arrowWidth) * primitiveHeight / arrowWidth; drawColumns(g, margin, width, height); // draw column headers System.err.print(" " + ++page); // print page number for (Enumeration e = symbols.elements(); // go through symbols e.hasMoreElements(); ) { symbol = (TSDSymbol) e.nextElement(); // get next symbol if (symbol.getClass() == SpaceFiller.class) { // symbol gap? if (symbol.top + symbol.height >= nextHeight) { // past end of page? printFooter(g); // print page footer currentHeight = nullHeight; // set next page top return; // stop rendering } else { // still within page nullHeight = symbol.top; // save last null height for (sym = 0; sym < nextSymbol; sym++) { // go through symbols symbol = symbolBuff[sym]; // get stored symbol symbol.top += offset; // move symbol up page symbol.draw(g); // draw symbol symbol.top -= offset; // move symbol back } nextSymbol = 0; // back to buffer start } } else if (symbol.top >= currentHeight) { // inside current page? if (nextSymbol < maxSymbols) // room for symbol? symbolBuff[nextSymbol++] = symbol; // store symbol else { // no room for symbol System.err.println( // report error "Cannot print more than " + maxSymbols + " symbols"); System.exit(1); // leave programme } } } if (symbol != null && // non-null symbol? symbol.top >= nextHeight) // past end of page? currentHeight = nullHeight; // set next page top else { // still within page for (sym = 0; sym < nextSymbol; sym++) { // go through symbols symbol = symbolBuff[sym]; // get stored symbol symbol.top += offset; // move symbol up page symbol.draw(g); // draw symbol symbol.top -= offset; // move symbol back } System.err.println(); // end page listing stopped = true; // printing finished } printFooter(g); // print page footer } /** Print footer of time sequence diagram. @param g graphics object */ public void printFooter(Graphics g) { // print page footer String footer = // set page footer ProtocolSimulator.protocolType + " Page " + page; FontMetrics fm = g.getFontMetrics(footerFont); // get font metrics int diagWidth = getSize().width; // diagram width int footerWidth = fm.stringWidth(footer); // get footer width g.drawString(ProtocolSimulator.protocolType + // print page footer " Page " + page, margin + (diagWidth - footerWidth) / 2, inset + maxHeight); } /** Note printing as started. */ public void printStart() { // start printing starting = true; // set not started } /** Check if printing has stopped. @return true/false if printing has/has not stopped */ public boolean printStopped() { // return print status return((stopped)); // get printing stopped } }