001 /*
002 * $Id: SequenceGrapher.java,v 1.30 2014/03/22 21:32:48 oboehm Exp $
003 *
004 * Copyright (c) 2013 by Oliver Boehm
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express orimplied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 *
018 * (c)reated 06.09.2013 by oliver (ob@oasd.de)
019 */
020
021 package patterntesting.runtime.log;
022
023 import java.io.*;
024 import java.nio.charset.Charset;
025 import java.util.*;
026 import java.util.Map.Entry;
027
028 import org.apache.commons.io.IOUtils;
029 import org.apache.commons.lang.*;
030 import org.aspectj.lang.JoinPoint;
031 import org.aspectj.lang.reflect.ConstructorSignature;
032 import org.slf4j.*;
033
034 import patterntesting.annotation.check.runtime.NullArgsAllowed;
035 import patterntesting.runtime.exception.NotFoundException;
036 import patterntesting.runtime.util.*;
037 import patterntesting.runtime.util.regex.TypePattern;
038
039
040 /**
041 * This class supports the creation of simple sequence diagrams as desribed
042 * in the user manual of <a href="http://umlgraph.org/">UML Graph</a>.
043 *
044 * @author oliver
045 * @since 1.3.1 (06.09.2013)
046 */
047 public class SequenceGrapher extends AbstractLogger {
048
049 private static final Logger log = LoggerFactory.getLogger(SequenceGrapher.class);
050 private final Writer writer;
051 private final List<DrawStatement> statements = new ArrayList<SequenceGrapher.DrawStatement>();
052 private final Map<Object, String> objnames = new HashMap<Object, String>();
053 private final Map<Object, String> placeHolderNames = new HashMap<Object, String>();
054 private final Map<Object, String> varnames = new HashMap<Object, String>();
055 private final Stack<String> callerNames = new Stack<String>();
056 private TypePattern[] excludeFilter = new TypePattern[0];
057 private String active = "";
058 private int objectNumber = 0;
059
060 /**
061 * Instantiates a new SequenceGrapher. The generated sequence diagram is
062 * stored in a temporary file.
063 */
064 public SequenceGrapher() {
065 this(createTempLogFile("seq-diagram", ".pic"));
066 }
067
068 /**
069 * Instantiates a new SequenceGrapher. The generated sequence diagram is
070 * stored in the given log file.
071 *
072 * @param logFile the log file
073 */
074 public SequenceGrapher(final File logFile) {
075 this(logFile, "seq-head.template");
076 }
077
078 /**
079 * Instantiates a new sequence grapher. The header for the generated diagram
080 * is read from the given resource:
081 * <ul>
082 * <li><code>"seq-head.template"</code> (default): default header with with
083 * needed <code>"sequence.pic"</code> and comments inside</li>
084 * <li><code>"seq-head-small.template"</code>: a sort header with copy
085 * instruction for the needed <code>"sequence.pic"</code> and without any
086 * comments.</li>
087 * </ul>
088 *
089 * @param logFile the log file
090 * @param resource the resource ("seq-head.template" or "seq-head-small")
091 */
092 public SequenceGrapher(final File logFile, final String resource) {
093 this(getStreamFor(logFile), resource);
094 log.info("Sequence diagram will be written to \"{}\" with header from \"{}\".", logFile,
095 resource);
096 }
097
098 /**
099 * Instantiates a new object logger to the given stream.
100 *
101 * @param ostream the ostream
102 */
103 public SequenceGrapher(final OutputStream ostream) {
104 this(ostream, "seq-head.template");
105 }
106
107 /**
108 * /**
109 * Instantiates a new sequence grapher. The header for the generated diagram
110 * is read from the given resource:
111 * <ul>
112 * <li><code>"seq-head.template"</code> (default): default header with with
113 * needed <code>"sequence.pic"</code> and comments inside</li>
114 * <li><code>"seq-head-small.template"</code>: a sort header with copy
115 * instruction for the needed <code>"sequence.pic"</code> and without any
116 * comments.</li>
117 * </ul>
118 *
119 * @param ostream the ostream
120 * @param resource the resource ("seq-head.template" or "seq-head-small")
121 */
122 public SequenceGrapher(final OutputStream ostream, final String resource) {
123 super(ostream);
124 this.writer = new BufferedWriter(new OutputStreamWriter(ostream,
125 Charset.forName("ISO-8859-1")));
126 this.writeTemplate(resource);
127 }
128
129 private void writeTemplate(final String resource) {
130 InputStream istream = this.getClass().getResourceAsStream(resource);
131 if (istream == null) {
132 log.warn("Resource \"{}\" not found - content will be missing in generated diagram.",
133 resource);
134 } else {
135 try {
136 String head = IOUtils.toString(istream, "ISO-8859-1");
137 this.writer.write(head);
138 } catch (IOException ioe) {
139 log.warn("Content of \"" + resource + "\" will be missing in generated diagram.",
140 ioe);
141 } finally {
142 IOUtils.closeQuietly(istream);
143 }
144 }
145 }
146
147 /**
148 * Sets the exclude filter. Classes which matches the filter will not appear
149 * in the generated sequence diagram.
150 *
151 * @param pattern the new exclude filter
152 * @since 1.4.1
153 */
154 public void setExcludeFilter(final String[] pattern) {
155 this.excludeFilter = new TypePattern[pattern.length];
156 for (int i = 0; i < pattern.length; i++) {
157 this.excludeFilter[i] = new TypePattern(pattern[i]);
158 }
159 }
160
161 /**
162 * This method is called at shutdown to close the open writer.
163 *
164 * @see java.lang.Thread#run()
165 */
166 @Override
167 public void run() {
168 this.closeQuietly();
169 super.run();
170 }
171
172 /**
173 * Closes the stream with the logged objects.
174 */
175 @Override
176 public void close() {
177 this.closeQuietly();
178 super.close();
179 }
180
181 private void closeQuietly() {
182 this.sortOutEmptyCreateMessages();
183 this.writeDefines();
184 this.writeMessages();
185 this.completeObjects();
186 this.writeTemplate("seq-tail.template");
187 IOUtils.closeQuietly(this.writer);
188 }
189
190 /**
191 * Here we prepare the cached statements to the file. If a create message is
192 * found with no other activities this creation will be sorted out to keep the
193 * generated sequence diagram simple.
194 */
195 private void sortOutEmptyCreateMessages() {
196 List<DrawStatement> emptyCreateMessages = new ArrayList<SequenceGrapher.DrawStatement>();
197 for (DrawStatement stmt : this.statements) {
198 if ((stmt.getType() == DrawType.CREATE_MESSAGE) && !hasActivities(stmt)) {
199 log.debug("{} will be ignored because it is a single statement.", stmt);
200 emptyCreateMessages.add(stmt);
201 removeValue(this.placeHolderNames, stmt.getTarget());
202 removeValue(this.varnames, stmt.getTarget());
203 }
204 }
205 this.statements.removeAll(emptyCreateMessages);
206 }
207
208 private static void removeValue(final Map<Object, String> map, final String name) {
209 for (Map.Entry<Object, String> entry : map.entrySet()) {
210 if (name.equals(entry.getValue())) {
211 map.remove(entry.getKey());
212 break;
213 }
214 }
215 }
216
217 private void writeDefines() {
218 SortedMap<String, Object> sortedObjnames = new TreeMap<String, Object>(
219 new VarnameComparator());
220 for (Entry<Object, String> entry : objnames.entrySet()) {
221 sortedObjnames.put(entry.getValue(), entry.getKey());
222 }
223 for (Entry<String, Object> entry : sortedObjnames.entrySet()) {
224 Class<?> clazz = entry.getValue().getClass();
225 if (entry.getValue() instanceof Class<?>) {
226 clazz = (Class<?>) entry.getValue();
227 }
228 this.writeLine(getBoxwidStatementFor(clazz.getSimpleName()));
229 this.writeLine("object(" + entry.getKey() + ",\":" + clazz.getSimpleName() + "\");");
230 }
231 Collection<String> objectNames = new TreeSet<String>(new VarnameComparator());
232 objectNames.addAll(this.placeHolderNames.values());
233 for (String name : objectNames) {
234 this.writeLine("placeholder_object(" + name + ");");
235 }
236 }
237
238 private static String getBoxwidStatementFor(final String name) {
239 return "boxwid = " + getBoxwidFor(name) + ";";
240 }
241
242 private static String getBoxwidFor(final String name) {
243 int length = name.length() + 1;
244 if (length > 16) {
245 return "1.5";
246 } else if (length > 10) {
247 return "1.0";
248 } else {
249 return "0.75";
250 }
251 }
252
253 private void writeMessages() {
254 this.writeLine("step();");
255 this.writeLine("");
256 this.writeComment("Message sequences");
257 if (this.statements.isEmpty()) {
258 log.debug("No draw statemtents are logged.");
259 return;
260 }
261 DrawStatement previous = this.statements.get(0);
262 previous.writeStatement(this.writer);
263 for (int i = 1; i < this.statements.size(); i++) {
264 DrawStatement stmt = this.statements.get(i);
265 if (previous.hasMessageToLeft() && stmt.hasMessageToRight()) {
266 this.writeLine("step();");
267 }
268 if (stmt.hasMessage()) {
269 previous = stmt;
270 }
271 stmt.writeStatement(this.writer);
272 }
273 }
274
275 /**
276 * If the target of the given statement is part of any other
277 * statement 'true' will be returned.
278 *
279 * @param drawStatement the draw statement
280 * @return true, if successful
281 */
282 private boolean hasActivities(final DrawStatement drawStatement) {
283 String target = drawStatement.getTarget();
284 for (DrawStatement stmt : this.statements) {
285 if (drawStatement.equals(stmt)) {
286 continue;
287 }
288 if (stmt.hasActor(target)) {
289 return true;
290 }
291 }
292 return false;
293 }
294
295 private void completeObjects() {
296 this.writeLine("");
297 this.writeComment("Complete the lifelines");
298 this.writeLine("step();");
299 Collection<String> names = new TreeSet<String>(this.varnames.values());
300 for (String name : names) {
301 this.writeLine("complete(" + name + ");");
302 }
303 }
304
305 /**
306 * Logs the creation of an object in the created sequence diagram.
307 *
308 * @param call the call
309 * @param result the created object
310 */
311 public void createMessage(final JoinPoint call, final Object result) {
312 this.addComment(JoinPointHelper.getAsLongString(call) + " = " + result);
313 Object creator = call.getThis();
314 if (creator == null) {
315 String classname = JoinPointHelper.getCallerOf(call).getClassName();
316 try {
317 creator = Class.forName(classname);
318 } catch (ClassNotFoundException ex) {
319 throw new NotFoundException(classname, ex);
320 }
321 }
322 this.createMessage(creator, result);
323 }
324
325 /**
326 * Logs the creation of an object in the created sequence diagram.
327 *
328 * @param creator the creator
329 * @param createdObject the created object
330 */
331 public void createMessage(final Object creator, final Object createdObject) {
332 if (this.matches(creator) || this.matches(createdObject)) {
333 log.debug("{} --creates--> {} is not logged because of exclude filter.", creator, createdObject);
334 return;
335 }
336 String name = this.varnames.get(createdObject);
337 String typeName = this.addPlaceHolder(createdObject);
338 if (name != null) {
339 log.trace("Creation of {} is already logged.", createdObject);
340 this.objnames.remove(createdObject);
341 return;
342 }
343 name = this.getVarnameFor(creator);
344 Class<?> type = createdObject.getClass();
345 this.addCreateMessage(name, type, typeName);
346 }
347
348 private boolean matches(final Object creator) {
349 for (int i = 0; i < this.excludeFilter.length; i++) {
350 if (this.excludeFilter[i].matches(creator)) {
351 return true;
352 }
353 }
354 return false;
355 }
356
357 private String addPlaceHolder(final Object obj) {
358 String name = this.placeHolderNames.get(obj);
359 if (name == null) {
360 name = this.objnames.get(obj);
361 if (name == null) {
362 name = this.addVarnameFor(obj);
363 this.placeHolderNames.put(obj, name);
364 } else {
365 //this.objnames.remove(obj);
366 this.placeHolderNames.put(obj, name);
367 }
368 }
369 return name;
370 }
371
372 private String addObject(final Object obj) {
373 String name = this.addVarnameFor(obj);
374 objnames.put(obj, name);
375 return name;
376 }
377
378 @NullArgsAllowed
379 private String getVarnameFor(final Object obj) {
380 if (obj == null) {
381 return getActorName();
382 }
383 String name = this.varnames.get(obj);
384 if (name == null) {
385 if (obj instanceof Class<?>) {
386 name = this.getVarnameFor((Class<?>) obj);
387 } else {
388 name = this.varnames.get(obj.getClass());
389 }
390 }
391 if (name == null) {
392 name = addObject(obj);
393 }
394 return name;
395 }
396
397 private String getVarnameFor(final Class<?> clazz) {
398 String name = this.varnames.get(clazz);
399 if (name == null) {
400 for (Entry<Object, String> entry : this.varnames.entrySet()) {
401 if (clazz.equals(entry.getKey().getClass())) {
402 return entry.getValue();
403 }
404 }
405 name = addObject(clazz);
406 }
407 return name;
408 }
409
410 private String getActorName() {
411 String name = this.varnames.get("Actor");
412 if (name == null) {
413 name = addVarnameFor("Actor");
414 this.writeLine("actor(" + name + ",\"\");");
415 }
416 return name;
417 }
418
419 private String addVarnameFor(final Object obj) {
420 if (obj instanceof Class<?>) {
421 return addVarnameFor((Class<?>) obj);
422 }
423 String name = toName(obj.getClass());
424 return addVarname(name, obj);
425 }
426
427 private String addVarnameFor(final Class<?> clazz) {
428 String name = toName(clazz);
429 return addVarname(name, clazz);
430 }
431
432 private String addVarname(final String name, final Object obj) {
433 if (this.varnames.containsKey(obj)) {
434 log.trace("{} already in map of var names.", obj);
435 } else {
436 this.varnames.put(obj, name);
437 this.objectNumber++;
438 }
439 return this.varnames.get(obj);
440 }
441
442 private String toName(final Class<?> clazz) {
443 return clazz.getSimpleName().substring(0, 1).toUpperCase()
444 + Integer.toString(this.objectNumber, Character.MAX_RADIX);
445 }
446
447 /**
448 * This is the opposite to the toName() method and returns the number part
449 * of a variable name.
450 *
451 * @param name the name
452 * @return the string
453 */
454 private static String toNumber(final String name) {
455 return name.substring(1);
456 }
457
458 // /**
459 // * A message points to left, if the given senderName was created after
460 // * the given targetName. This information can be derived from the name.
461 // *
462 // * @param senderName the sender name
463 // * @param targetName the target name
464 // * @return true, if successful
465 // */
466 // private static boolean messagePointsToLeft(final String senderName, final String targetName) {
467 // return toNumber(senderName).compareTo(toNumber(targetName)) > 0;
468 // }
469
470 private void setActive(final String name) {
471 this.statements.add(new DrawStatement(DrawType.ACTIVE, name));
472 this.active = name;
473 }
474
475 private boolean isActive(final String name) {
476 return this.active.equals(name);
477 }
478
479 /**
480 * Trys to log the call of the given excecution joinpoint. For this reason
481 * we must find the caller which is a little bit tricky. We use the
482 * classname of the mapped variable names to guess which could be the
483 * caller.
484 *
485 * @param execution the execution joinpoint
486 */
487 public void execute(final JoinPoint execution) {
488 this.addComment(JoinPointHelper.getAsLongString(execution));
489 String senderName = getCallerNameOf(execution);
490 String targetName = getTargetName(execution);
491 if (execution.getSignature() instanceof ConstructorSignature) {
492 this.addCreateMessage(senderName, execution.getThis().getClass(), targetName);
493 } else {
494 this.message(senderName, targetName, execution.getSignature().getName(),
495 execution.getArgs());
496 }
497 }
498
499 private String getTargetName(final JoinPoint execution) {
500 Object thisObject = execution.getThis();
501 if (thisObject == null) {
502 StackTraceElement element = StackTraceScanner.find(execution.getSignature());
503 try {
504 return this.getVarnameFor(Class.forName(element.getClassName()));
505 } catch (ClassNotFoundException ex) {
506 log.debug("Classname of " + element + " not found.", ex);
507 return this.getVarnameFor(null);
508 }
509 } else {
510 return this.getVarnameFor(thisObject);
511 }
512 }
513
514 private String getCallerNameOf(final JoinPoint execution) {
515 StackTraceElement caller = JoinPointHelper.getCallerOf(execution);
516 String classname = caller.getClassName();
517 for (Entry<Object, String> entry : varnames.entrySet()) {
518 if (classname.equals(entry.getKey().getClass().getName())) {
519 log.trace("Caller of {} is {}.", execution, entry);
520 return entry.getValue();
521 }
522 }
523 log.trace("Caller of {} not found in {}.", execution, varnames);
524 try {
525 return this.addObject(Class.forName(caller.getClassName()));
526 } catch (ClassNotFoundException ex) {
527 log.info("cannot get class for {} because of {}.", caller, ex.getMessage());
528 return getActorName();
529 }
530 }
531
532 /**
533 * Logs the call of a method to the generated sequence diagram.
534 *
535 * @param call the call
536 */
537 public void message(final JoinPoint call) {
538 this.message(call.getThis(), call);
539 }
540
541 /**
542 * Logs the call of a method to the generated sequence diagram.
543 *
544 * @param caller the caller
545 * @param call the call
546 */
547 public void message(final Object caller, final JoinPoint call) {
548 this.addComment(JoinPointHelper.getAsLongString(call));
549 this.message(caller, call.getTarget(), call.getSignature().getName(), call.getArgs());
550 }
551
552 /**
553 * Logs the call of a method to the generated sequence diagram.
554 *
555 * @param sender the sender
556 * @param target the target
557 * @param methodName the method name
558 * @param args the args
559 */
560 public void message(final Object sender, final Object target, final String methodName,
561 final Object[] args) {
562 if (this.matches(sender) || this.matches(target)) {
563 log.debug("{} -----------> {} is not logged because of exclude filter.", sender, target);
564 return;
565 }
566 String senderName = this.getVarnameFor(sender);
567 String targetName = this.getVarnameFor(target);
568 this.message(senderName, targetName, methodName, args);
569 }
570
571 private void message(final String senderName, final String targetName, final String methodName,
572 final Object[] args) {
573 this.addMessage(senderName, targetName, methodName, args);
574 }
575
576 private static String getArgsAsString(final Object... args) {
577 return StringEscapeUtils.escapeJava(Converter.toShortString(JoinPointHelper
578 .getArgsAsString(args)));
579 }
580
581 /**
582 * Logs the return arrow from the last call to the generated sequence
583 * diagram.
584 *
585 * @param call the call
586 */
587 public void returnMessage(final JoinPoint call) {
588 this.returnMessage(call, "");
589 }
590
591 /**
592 * Logs the return arrow from the last call to the generated sequence
593 * diagram.
594 *
595 * @param call the call
596 * @param returnValue the return value
597 */
598 public void returnMessage(final JoinPoint call, final Object returnValue) {
599 this.addComment(call.toLongString() + " = " + returnValue);
600 //writeComment(call.toLongString() + " = " + returnValue, cache);
601 this.returnMessage(call.getTarget(), returnValue);
602 }
603
604 /**
605 * Return message.
606 *
607 * @param returnee the returnee
608 * @param returnValue the return value
609 */
610 public void returnMessage(final Object returnee, final Object returnValue) {
611 if (this.matches(returnee)) {
612 log.debug("{} <--{}-- is not logged because of exclude filter.", returnee, returnValue);
613 return;
614 }
615 this.addReturnMessage(returnee, returnValue);
616 }
617
618 private static String toEscapedString(final Object returnValue) {
619 if (returnValue == null) {
620 return "null";
621 }
622 return StringEscapeUtils.escapeJava(returnValue.toString());
623 }
624
625 private void writeComment(final String comment) {
626 this.writeLine("# " + comment);
627 }
628
629 private void writeLine(final String line) {
630 writeLine(line, this.writer);
631 }
632
633 private static void writeLine(final String line, final Writer lineWriter) {
634 try {
635 lineWriter.write(line);
636 lineWriter.write("\n");
637 } catch (IOException ioe) {
638 log.debug("Writing to {} failed because of {}.", lineWriter, ioe.getMessage());
639 log.info(line);
640 }
641 }
642
643 private void addComment(final String comment) {
644 DrawStatement stmt = new DrawStatement(DrawType.COMMENT, comment);
645 this.statements.add(stmt);
646 }
647
648 private void addCreateMessage(final String senderName, final Class<?> type, final String typeName) {
649 if (!isActive(senderName)) {
650 this.setActive(senderName);
651 }
652 DrawStatement stmt = new DrawStatement(senderName, type, typeName);
653 this.statements.add(stmt);
654 }
655
656 private void addMessage(final String senderName, final String targetName, final String methodName, final Object[] args) {
657 this.callerNames.push(senderName);
658 DrawStatement stmt = new DrawStatement(senderName, targetName, methodName, args);
659 this.statements.add(stmt);
660 }
661
662 private void addReturnMessage(final Object returnee, final Object returnValue) {
663 String receiverName = this.callerNames.pop();
664 String returneeName = this.getVarnameFor(returnee);
665 DrawStatement stmt = new DrawStatement(receiverName, returneeName, returnValue);
666 this.statements.add(stmt);
667 }
668
669
670
671 /**
672 * The Class VarnameComparator compares two variable names. The name is of
673 * the form "A999...", e.g. first character is a (uppercase) letter and the
674 * next characters are a number (to base {@link Character#MAX_RADIX}). Only
675 * the number are used for comparison to get the creation order.
676 */
677 private static final class VarnameComparator implements Comparator<String>, Serializable {
678
679 private static final long serialVersionUID = 20140104L;
680
681 /**
682 * Compares two variable names.
683 *
684 * @param x1 the x1
685 * @param x2 the x2
686 * @return a negativ valule if x1 < x2, "0" for x1 == x2, otherwise
687 * positive value
688 * @see java.util.Comparator#compare(Object, Object)
689 */
690 public int compare(final String x1, final String x2) {
691 return toNumber(x1) - toNumber(x2);
692 }
693
694 private static int toNumber(final String varname) {
695 String numberPart = varname.substring(1);
696 return Integer.parseInt(numberPart, Character.MAX_RADIX);
697 }
698
699 }
700
701
702
703 //-------------------------------------------------------------------------
704
705 /**
706 * The different types of drawings.
707 */
708 private enum DrawType {
709 UNKNOWN,
710 COMMENT,
711 ACTIVE,
712 CREATE_MESSAGE,
713 MESSAGE,
714 RETURN_MESSAGE;
715 }
716
717 /**
718 * Internal class for caching the different draw statements.
719 *
720 * @author oliver
721 * @since 1.4.1 (17.01.2014)
722 */
723 private static class DrawStatement {
724
725 private final DrawType type;
726 private final String sender;
727 private final String target;
728 private final String methodName;
729 private final Object[] args;
730 private final String comment;
731
732 /**
733 * Instantiates a new draw statement.
734 *
735 * @param type the type
736 * @param comment the comment or active argument
737 */
738 public DrawStatement(final DrawType type, final String comment) {
739 this.type = type;
740 this.sender = comment;
741 this.target = null;
742 this.methodName = null;
743 this.args = null;
744 this.comment = comment;
745 }
746
747 /**
748 * Instantiates a new draw statement.
749 *
750 * @param senderName the sender name
751 * @param createdClass the created class
752 * @param typeName the type name
753 */
754 public DrawStatement(final String senderName, final Class<?> createdClass, final String typeName) {
755 this.type = DrawType.CREATE_MESSAGE;
756 this.sender = senderName;
757 this.target = typeName;
758 this.methodName = null;
759 this.args = createObjectArray(createdClass);
760 this.comment = null;
761 }
762
763 /**
764 * Instantiates a new draw statement.
765 *
766 * @param sender the sender
767 * @param target the target
768 * @param name the name
769 * @param args the args
770 */
771 public DrawStatement(final String sender, final String target, final String name,
772 final Object[] args) {
773 this.type = DrawType.MESSAGE;
774 this.sender = sender;
775 this.target = target;
776 this.methodName = name;
777 this.args = args;
778 this.comment = null;
779 }
780
781 /**
782 * Instantiates a new draw statement.
783 *
784 * @param receiverName the receiver name
785 * @param returnee the returnee
786 * @param returnValue the return value
787 */
788 public DrawStatement(final String receiverName, final String returnee, final Object returnValue) {
789 this.type = DrawType.RETURN_MESSAGE;
790 this.sender = receiverName;
791 this.target = returnee;
792 this.methodName = null;
793 this.args = createObjectArray(returnValue);
794 this.comment = null;
795 }
796
797 private Object[] createObjectArray(final Object...objects) {
798 return objects;
799 }
800
801 /**
802 * Gets the type.
803 *
804 * @return the type
805 */
806 public DrawType getType() {
807 return this.type;
808 }
809
810 /**
811 * Gets the target.
812 *
813 * @return the target
814 */
815 public String getTarget() {
816 return this.target;
817 }
818
819 /**
820 * If the given name is part of any stored actor in this statement
821 * 'true' will be returned.
822 *
823 * @param name the name
824 * @return true, if is involved
825 */
826 public boolean hasActor(final String name) {
827 switch (this.type) {
828 case CREATE_MESSAGE:
829 case MESSAGE:
830 return name.equals(this.target);
831 case RETURN_MESSAGE:
832 return name.equals(this.sender) || name.equals(this.target);
833 default:
834 return false;
835 }
836 }
837
838 /**
839 * Checks for message.
840 *
841 * @return true, if successful
842 */
843 public boolean hasMessage() {
844 switch (this.type) {
845 case CREATE_MESSAGE:
846 case MESSAGE:
847 case RETURN_MESSAGE:
848 return true;
849 default:
850 return false;
851 }
852 }
853
854 /**
855 * A message points to left, if the given senderName was created after
856 * the given targetName. This information can be derived from the name.
857 *
858 * @return true, if successful
859 */
860 public boolean hasMessageToLeft() {
861 switch (this.type) {
862 case CREATE_MESSAGE:
863 case MESSAGE:
864 return toNumber(this.sender).compareTo(toNumber(this.target)) > 0;
865 case RETURN_MESSAGE:
866 return toNumber(this.target).compareTo(toNumber(this.sender)) > 0;
867 default:
868 return false;
869 }
870 }
871
872 /**
873 * Checks for message to right.
874 *
875 * @return true, if successful
876 */
877 public boolean hasMessageToRight() {
878 switch (this.type) {
879 case CREATE_MESSAGE:
880 case MESSAGE:
881 case RETURN_MESSAGE:
882 return !this.hasMessageToLeft();
883 default:
884 return false;
885 }
886 }
887
888 /**
889 * Write statement.
890 *
891 * @param writer the writer
892 */
893 public void writeStatement(final Writer writer) {
894 switch (type) {
895 case COMMENT:
896 this.writeComment(writer);
897 break;
898 case ACTIVE:
899 this.writeActive(writer);
900 break;
901 case CREATE_MESSAGE:
902 this.writeCreateMessage(writer);
903 break;
904 case MESSAGE:
905 this.writeMessage(writer);
906 break;
907 case RETURN_MESSAGE:
908 this.writeReturnMessage(writer);
909 break;
910 default:
911 log.warn("{} statement is ignored.", type);
912 break;
913 }
914 }
915
916 /**
917 * Write comment.
918 *
919 * @param writer the writer
920 */
921 public void writeComment(final Writer writer) {
922 writeLine(this.toString(), writer);
923 }
924
925 /**
926 * Write active.
927 *
928 * @param writer the writer
929 */
930 public void writeActive(final Writer writer) {
931 writeLine(this.toString(), writer);
932 }
933
934 /**
935 * Write create message.
936 *
937 * @param writer the writer
938 */
939 public void writeCreateMessage(final Writer writer) {
940 String classname = getTargetType();
941 writeLine(getBoxwidStatementFor(classname), writer);
942 writeLine(this.toString(), writer);
943 }
944
945 private String getTargetType() {
946 Class<?> targetType = (Class<?>) this.args[0];
947 return targetType.getSimpleName();
948 }
949
950 /**
951 * Write message.
952 *
953 * @param writer the writer
954 */
955 public void writeMessage(final Writer writer) {
956 if (this.hasMessageToLeft()) {
957 writeLine("step();", writer);
958 }
959 writeLine(this.toString(), writer);
960 writeLine("active(" + this.target + ");", writer);
961 }
962
963 /**
964 * Write return message.
965 *
966 * @param writer the writer
967 */
968 public void writeReturnMessage(final Writer writer) {
969 if (this.hasMessageToLeft()) {
970 writeLine("step();", writer);
971 }
972 writeLine(this.toString(), writer);
973 writeLine("inactive(" + this.target + ");", writer);
974 }
975
976 /**
977 * Hash code.
978 *
979 * @return the int
980 * @see java.lang.Object#hashCode()
981 */
982 @Override
983 public int hashCode() {
984 return this.type.hashCode() + ((this.sender == null) ? 0 : this.sender.hashCode());
985 }
986
987 /**
988 * Equals.
989 *
990 * @param obj the obj
991 * @return true, if successful
992 * @see java.lang.Object#equals(java.lang.Object)
993 */
994 @Override
995 public boolean equals(final Object obj) {
996 if (!(obj instanceof DrawStatement)) {
997 return false;
998 }
999 DrawStatement other = (DrawStatement) obj;
1000 return (this.type == other.type)
1001 && StringUtils.equals(this.sender, other.sender)
1002 && StringUtils.equals(this.target, other.target)
1003 && StringUtils.equals(this.methodName, other.methodName)
1004 && StringUtils.equals(this.comment, other.comment)
1005 && Arrays.equals(this.args, other.args);
1006 }
1007
1008 /**
1009 * To string.
1010 *
1011 * @return the string
1012 * @see java.lang.Object#toString()
1013 */
1014 @Override
1015 public String toString() {
1016 switch (type) {
1017 case COMMENT:
1018 return "# " + this.comment;
1019 case ACTIVE:
1020 return "active(" + this.sender + ");";
1021 case CREATE_MESSAGE:
1022 String classname = this.getTargetType();
1023 return "create_message(" + this.sender + "," + this.target + ",\":" + classname + "\");";
1024 case MESSAGE:
1025 return "message(" + this.sender + "," + this.target + ",\"" + this.methodName
1026 + getArgsAsString(this.args) + "\");";
1027 case RETURN_MESSAGE:
1028 return "return_message(" + this.target + "," + this.sender + ",\""
1029 + toEscapedString(Converter.toShortString(this.args[0])) + "\");";
1030 default:
1031 return "# " + this.type + " statement";
1032 }
1033 }
1034
1035 }
1036
1037 }