001/* 002 * Copyright 2006 - 2013 003 * Stefan Balev <stefan.balev@graphstream-project.org> 004 * Julien Baudry <julien.baudry@graphstream-project.org> 005 * Antoine Dutot <antoine.dutot@graphstream-project.org> 006 * Yoann Pigné <yoann.pigne@graphstream-project.org> 007 * Guilhelm Savin <guilhelm.savin@graphstream-project.org> 008 * 009 * This file is part of GraphStream <http://graphstream-project.org>. 010 * 011 * GraphStream is a library whose purpose is to handle static or dynamic 012 * graph, create them from scratch, file or any source and display them. 013 * 014 * This program is free software distributed under the terms of two licenses, the 015 * CeCILL-C license that fits European law, and the GNU Lesser General Public 016 * License. You can use, modify and/ or redistribute the software under the terms 017 * of the CeCILL-C license as circulated by CEA, CNRS and INRIA at the following 018 * URL <http://www.cecill.info> or under the terms of the GNU LGPL as published by 019 * the Free Software Foundation, either version 3 of the License, or (at your 020 * option) any later version. 021 * 022 * This program is distributed in the hope that it will be useful, but WITHOUT ANY 023 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 024 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. 025 * 026 * You should have received a copy of the GNU Lesser General Public License 027 * along with this program. If not, see <http://www.gnu.org/licenses/>. 028 * 029 * The fact that you are presently reading this means that you have had 030 * knowledge of the CeCILL-C and LGPL licenses and that you accept their terms. 031 */ 032package org.graphstream.util; 033 034import java.io.PrintStream; 035import java.lang.reflect.Array; 036import java.util.EnumMap; 037import java.util.HashMap; 038import java.util.Stack; 039 040import org.graphstream.stream.Sink; 041 042/** 043 * A sink that can be used to display event in a PrintStream like System.out. 044 * Format of messages can be customized, inserting keywords quoted with '%' in 045 * the format. 046 * 047 * '%sourceId%' and '%timeId%' keywords are defined for each event. Following 048 * defines keywords available for each event types: 049 * <dl> 050 * <dt>ADD_NODE</dt> 051 * <dd> 052 * <ul> 053 * <li>%nodeId%</li> 054 * </ul> 055 * </dd> 056 * <dt>ADD_NODE_ATTRIBUTE</dt> 057 * <dd> 058 * <ul> 059 * <li>%nodeId%</li> 060 * <li>%attributeId%</li> 061 * <li>%value%</li> 062 * </ul> 063 * </dd> 064 * <dt>SET_NODE_ATTRIBUTE</dt> 065 * <dd> 066 * <ul> 067 * <li>%nodeId%</li> 068 * <li>%attributeId%</li> 069 * <li>%value%</li> 070 * </ul> 071 * </dd> 072 * <dt>DEL_NODE_ATTRIBUTE</dt> 073 * <dd> 074 * <ul> 075 * <li>%nodeId%</li> 076 * <li>%attributeId%</li> 077 * </ul> 078 * </dd> 079 * <dt>DEL_NODE</dt> 080 * <dd> 081 * <ul> 082 * <li>%nodeId%</li> 083 * </ul> 084 * </dd> 085 * <dt>ADD_EDGE</dt> 086 * <dd> 087 * <ul> 088 * <li>%edgeId%</li> 089 * <li>%source%</li> 090 * <li>%target%</li> 091 * <li>%directed%</li> 092 * </ul> 093 * </dd> 094 * <dt>ADD_EDGE_ATTRIBUTE</dt> 095 * <dd> 096 * <ul> 097 * <li>%edgeId%</li> 098 * <li>%attributeId%</li> 099 * <li>%value%</li> 100 * </ul> 101 * </dd> 102 * <dt>SET_EDGE_ATTRIBUTE</dt> 103 * <dd> 104 * <ul> 105 * <li>%edgeId%</li> 106 * <li>%attributeId%</li> 107 * <li>%value%</li> 108 * </ul> 109 * </dd> 110 * <dt>DEL_EDGE_ATTRIBUTE</dt> 111 * <dd> 112 * <ul> 113 * <li>%edgeId%</li> 114 * <li>%attributeId%</li> 115 * </ul> 116 * </dd> 117 * <dt>DEL_EDGE</dt> 118 * <dd> 119 * <ul> 120 * <li>%edgeId%</li> 121 * </ul> 122 * </dd> 123 * <dt>ADD_GRAPH_ATTRIBUTE</dt> 124 * <dd> 125 * <ul> 126 * <li>%attributeId%</li> 127 * <li>%value%</li> 128 * </ul> 129 * </dd> 130 * <dt>SET_GRAPH_ATTRIBUTE</dt> 131 * <dd> 132 * <ul> 133 * <li>%attributeId%</li> 134 * <li>%value%</li> 135 * </ul> 136 * </dd> 137 * <dt>DEL_GRAPH_ATTRIBUTE</dt> 138 * <dd> 139 * <ul> 140 * <li>%attributeId%</li> 141 * </ul> 142 * </dd> 143 * <dt>CLEAR</dt> 144 * <dd></dd> 145 * <dt>STEP_BEGINS</dt> 146 * <dd> 147 * <ul> 148 * <li>%step%</li> 149 * </ul> 150 * </dd> 151 * </dl> 152 */ 153public class VerboseSink implements Sink { 154 public static final String DEFAULT_AN_FORMAT = "%prefix%[%sourceId%:%timeId%] add node \"%nodeId%\"%suffix%"; 155 public static final String DEFAULT_CNA_FORMAT = "%prefix%[%sourceId%:%timeId%] set node \"%nodeId%\" +\"%attributeId%\"=%value%%suffix%"; 156 public static final String DEFAULT_CNC_FORMAT = "%prefix%[%sourceId%:%timeId%] set node \"%nodeId%\" \"%attributeId%\"=%value%%suffix%"; 157 public static final String DEFAULT_CNR_FORMAT = "%prefix%[%sourceId%:%timeId%] set node \"%nodeId%\" -\"%attributeId%\"%suffix%"; 158 public static final String DEFAULT_DN_FORMAT = "%prefix%[%sourceId%:%timeId%] remove node \"%nodeId%\"%suffix%"; 159 160 public static final String DEFAULT_AE_FORMAT = "%prefix%[%sourceId%:%timeId%] add edge \"%edgeId%\" : \"%source%\" -- \"%target%\"%suffix%"; 161 public static final String DEFAULT_CEA_FORMAT = "%prefix%[%sourceId%:%timeId%] set edge \"%edgeId%\" +\"%attributeId%\"=%value%%suffix%"; 162 public static final String DEFAULT_CEC_FORMAT = "%prefix%[%sourceId%:%timeId%] set edge \"%edgeId%\" \"%attributeId%\"=%value%%suffix%"; 163 public static final String DEFAULT_CER_FORMAT = "%prefix%[%sourceId%:%timeId%] set edge \"%edgeId%\" -\"%attributeId%\"%suffix%"; 164 public static final String DEFAULT_DE_FORMAT = "%prefix%[%sourceId%:%timeId%] remove edge \"%edgeId%\"%suffix%"; 165 166 public static final String DEFAULT_CGA_FORMAT = "%prefix%[%sourceId%:%timeId%] set +\"%attributeId%\"=%value%%suffix%"; 167 public static final String DEFAULT_CGC_FORMAT = "%prefix%[%sourceId%:%timeId%] set \"%attributeId%\"=%value%%suffix%"; 168 public static final String DEFAULT_CGR_FORMAT = "%prefix%[%sourceId%:%timeId%] set -\"%attributeId%\"%suffix%"; 169 170 public static final String DEFAULT_CL_FORMAT = "%prefix%[%sourceId%:%timeId%] clear%suffix%"; 171 public static final String DEFAULT_ST_FORMAT = "%prefix%[%sourceId%:%timeId%] step %step% begins%suffix%"; 172 173 /* 174 * Shortcut to use HashMap<String, Object>. 175 */ 176 private static class Args extends HashMap<String, Object> { 177 private static final long serialVersionUID = 3064164898156692557L; 178 } 179 180 /** 181 * Enumeration defining type of events. 182 * 183 */ 184 public static enum EventType { 185 ADD_NODE, ADD_NODE_ATTRIBUTE, SET_NODE_ATTRIBUTE, DEL_NODE_ATTRIBUTE, DEL_NODE, ADD_EDGE, ADD_EDGE_ATTRIBUTE, SET_EDGE_ATTRIBUTE, DEL_EDGE_ATTRIBUTE, DEL_EDGE, ADD_GRAPH_ATTRIBUTE, SET_GRAPH_ATTRIBUTE, DEL_GRAPH_ATTRIBUTE, CLEAR, STEP_BEGINS 186 } 187 188 /** 189 * Flag used to indicate if the sink has to flush the output when writting a 190 * message. 191 */ 192 protected boolean autoflush; 193 /** 194 * Stream used to write message. 195 */ 196 protected final PrintStream out; 197 /** 198 * Format of messages associated with each event. 199 */ 200 protected final EnumMap<EventType, String> formats; 201 /** 202 * Flag used to indicate if an event has to be written or note. 203 */ 204 protected final EnumMap<EventType, Boolean> enable; 205 /* 206 * Used to avoid to create a lot of hashmap when passing event arguments. 207 */ 208 private final Stack<Args> argsStack; 209 210 protected String prefix; 211 212 protected String suffix; 213 214 /** 215 * Create a new verbose sink using System.out. 216 */ 217 public VerboseSink() { 218 this(System.out); 219 } 220 221 /** 222 * Create a new verbose sink. 223 * 224 * @param out 225 * stream used to output message 226 */ 227 public VerboseSink(PrintStream out) { 228 this.out = out; 229 argsStack = new Stack<Args>(); 230 enable = new EnumMap<EventType, Boolean>(EventType.class); 231 formats = new EnumMap<EventType, String>(EventType.class); 232 233 formats.put(EventType.ADD_NODE, DEFAULT_AN_FORMAT); 234 formats.put(EventType.ADD_NODE_ATTRIBUTE, DEFAULT_CNA_FORMAT); 235 formats.put(EventType.SET_NODE_ATTRIBUTE, DEFAULT_CNC_FORMAT); 236 formats.put(EventType.DEL_NODE_ATTRIBUTE, DEFAULT_CNR_FORMAT); 237 formats.put(EventType.DEL_NODE, DEFAULT_DN_FORMAT); 238 239 formats.put(EventType.ADD_EDGE, DEFAULT_AE_FORMAT); 240 formats.put(EventType.ADD_EDGE_ATTRIBUTE, DEFAULT_CEA_FORMAT); 241 formats.put(EventType.SET_EDGE_ATTRIBUTE, DEFAULT_CEC_FORMAT); 242 formats.put(EventType.DEL_EDGE_ATTRIBUTE, DEFAULT_CER_FORMAT); 243 formats.put(EventType.DEL_EDGE, DEFAULT_DE_FORMAT); 244 245 formats.put(EventType.ADD_GRAPH_ATTRIBUTE, DEFAULT_CGA_FORMAT); 246 formats.put(EventType.SET_GRAPH_ATTRIBUTE, DEFAULT_CGC_FORMAT); 247 formats.put(EventType.DEL_GRAPH_ATTRIBUTE, DEFAULT_CGR_FORMAT); 248 249 formats.put(EventType.CLEAR, DEFAULT_CL_FORMAT); 250 formats.put(EventType.STEP_BEGINS, DEFAULT_ST_FORMAT); 251 252 for (EventType t : EventType.values()) 253 enable.put(t, Boolean.TRUE); 254 255 suffix = ""; 256 prefix = ""; 257 } 258 259 /** 260 * Enable or disable autoflush. 261 * 262 * @param on 263 * true to enable autoflush 264 */ 265 public void setAutoFlush(boolean on) { 266 this.autoflush = on; 267 } 268 269 /** 270 * Redefines message format of an event. 271 * 272 * @param type 273 * type of the event 274 * @param format 275 * new format of the message attached with the event 276 */ 277 public void setEventFormat(EventType type, String format) { 278 formats.put(type, format); 279 } 280 281 /** 282 * Enable or disable an event. 283 * 284 * @param type 285 * type of the event 286 * @param on 287 * true to enable message for this event 288 */ 289 public void setEventEnabled(EventType type, boolean on) { 290 enable.put(type, on); 291 } 292 293 /** 294 * Enable or disable all messages associated with attribute events. 295 * 296 * @param on 297 * true to enable events 298 */ 299 public void setElementEventEnabled(boolean on) { 300 enable.put(EventType.ADD_EDGE_ATTRIBUTE, on); 301 enable.put(EventType.SET_EDGE_ATTRIBUTE, on); 302 enable.put(EventType.DEL_EDGE_ATTRIBUTE, on); 303 enable.put(EventType.ADD_NODE_ATTRIBUTE, on); 304 enable.put(EventType.SET_NODE_ATTRIBUTE, on); 305 enable.put(EventType.DEL_NODE_ATTRIBUTE, on); 306 enable.put(EventType.ADD_GRAPH_ATTRIBUTE, on); 307 enable.put(EventType.SET_GRAPH_ATTRIBUTE, on); 308 enable.put(EventType.DEL_GRAPH_ATTRIBUTE, on); 309 } 310 311 /** 312 * Enable or disable all messages associated with element events. 313 * 314 * @param on 315 * true to enable events 316 */ 317 public void setAttributeEventEnabled(boolean on) { 318 enable.put(EventType.ADD_EDGE, on); 319 enable.put(EventType.DEL_EDGE, on); 320 enable.put(EventType.ADD_NODE, on); 321 enable.put(EventType.DEL_NODE, on); 322 enable.put(EventType.CLEAR, on); 323 } 324 325 /** 326 * Set prefix used in messages. 327 * 328 * @param prefix 329 * new prefix 330 */ 331 public void setPrefix(String prefix) { 332 this.prefix = prefix; 333 } 334 335 /** 336 * Set suffix used in messages. 337 * 338 * @param suffix 339 * new suffix 340 */ 341 public void setSuffix(String suffix) { 342 this.suffix = suffix; 343 } 344 345 private void print(EventType type, Args args) { 346 if (!enable.get(type)) 347 return; 348 349 String out = formats.get(type); 350 351 for (String k : args.keySet()) { 352 Object o = args.get(k); 353 out = out.replace(String.format("%%%s%%", k), o == null ? "null" 354 : o.toString()); 355 } 356 357 this.out.print(out); 358 this.out.printf("\n"); 359 360 if (autoflush) 361 this.out.flush(); 362 363 argsPnP(args); 364 } 365 366 private Args argsPnP(Args args) { 367 if (args == null) { 368 if (argsStack.size() > 0) 369 args = argsStack.pop(); 370 else 371 args = new Args(); 372 373 args.put("prefix", prefix); 374 args.put("suffix", suffix); 375 376 return args; 377 } else { 378 args.clear(); 379 argsStack.push(args); 380 381 return null; 382 } 383 } 384 385 private String toStringValue(Object o) { 386 if (o == null) 387 return "<null>"; 388 389 if (o instanceof String) 390 return "\"" + ((String) o).replace("\"", "\\\""); 391 else if (o.getClass().isArray()) { 392 StringBuilder buffer = new StringBuilder(); 393 buffer.append("{"); 394 395 for (int i = 0; i < Array.getLength(o); i++) { 396 if (i > 0) 397 buffer.append(", "); 398 buffer.append(toStringValue(Array.get(o, i))); 399 } 400 401 buffer.append("}"); 402 return buffer.toString(); 403 } 404 405 return o.toString(); 406 } 407 408 /* 409 * (non-Javadoc) 410 * 411 * @see 412 * org.graphstream.stream.AttributeSink#edgeAttributeAdded(java.lang.String, 413 * long, java.lang.String, java.lang.String, java.lang.Object) 414 */ 415 public void edgeAttributeAdded(String sourceId, long timeId, String edgeId, 416 String attribute, Object value) { 417 Args args = argsPnP(null); 418 419 args.put("sourceId", sourceId); 420 args.put("timeId", timeId); 421 args.put("edgeId", edgeId); 422 args.put("attributeId", attribute); 423 args.put("value", toStringValue(value)); 424 425 print(EventType.ADD_EDGE_ATTRIBUTE, args); 426 } 427 428 /* 429 * (non-Javadoc) 430 * 431 * @see 432 * org.graphstream.stream.AttributeSink#edgeAttributeChanged(java.lang.String 433 * , long, java.lang.String, java.lang.String, java.lang.Object, 434 * java.lang.Object) 435 */ 436 public void edgeAttributeChanged(String sourceId, long timeId, 437 String edgeId, String attribute, Object oldValue, Object newValue) { 438 Args args = argsPnP(null); 439 440 args.put("sourceId", sourceId); 441 args.put("timeId", timeId); 442 args.put("edgeId", edgeId); 443 args.put("attributeId", attribute); 444 args.put("value", toStringValue(newValue)); 445 446 print(EventType.SET_EDGE_ATTRIBUTE, args); 447 } 448 449 /* 450 * (non-Javadoc) 451 * 452 * @see 453 * org.graphstream.stream.AttributeSink#edgeAttributeRemoved(java.lang.String 454 * , long, java.lang.String, java.lang.String) 455 */ 456 public void edgeAttributeRemoved(String sourceId, long timeId, 457 String edgeId, String attribute) { 458 Args args = argsPnP(null); 459 460 args.put("sourceId", sourceId); 461 args.put("timeId", timeId); 462 args.put("edgeId", edgeId); 463 args.put("attributeId", attribute); 464 465 print(EventType.DEL_EDGE_ATTRIBUTE, args); 466 } 467 468 /* 469 * (non-Javadoc) 470 * 471 * @see 472 * org.graphstream.stream.AttributeSink#graphAttributeAdded(java.lang.String 473 * , long, java.lang.String, java.lang.Object) 474 */ 475 public void graphAttributeAdded(String sourceId, long timeId, 476 String attribute, Object value) { 477 Args args = argsPnP(null); 478 479 args.put("sourceId", sourceId); 480 args.put("timeId", timeId); 481 args.put("attributeId", attribute); 482 args.put("value", toStringValue(value)); 483 484 print(EventType.ADD_GRAPH_ATTRIBUTE, args); 485 } 486 487 /* 488 * (non-Javadoc) 489 * 490 * @see 491 * org.graphstream.stream.AttributeSink#graphAttributeChanged(java.lang. 492 * String, long, java.lang.String, java.lang.Object, java.lang.Object) 493 */ 494 public void graphAttributeChanged(String sourceId, long timeId, 495 String attribute, Object oldValue, Object newValue) { 496 Args args = argsPnP(null); 497 498 args.put("sourceId", sourceId); 499 args.put("timeId", timeId); 500 args.put("attributeId", attribute); 501 args.put("value", toStringValue(newValue)); 502 503 print(EventType.SET_GRAPH_ATTRIBUTE, args); 504 } 505 506 /* 507 * (non-Javadoc) 508 * 509 * @see 510 * org.graphstream.stream.AttributeSink#graphAttributeRemoved(java.lang. 511 * String, long, java.lang.String) 512 */ 513 public void graphAttributeRemoved(String sourceId, long timeId, 514 String attribute) { 515 Args args = argsPnP(null); 516 517 args.put("sourceId", sourceId); 518 args.put("timeId", timeId); 519 args.put("attributeId", attribute); 520 521 print(EventType.DEL_GRAPH_ATTRIBUTE, args); 522 } 523 524 /* 525 * (non-Javadoc) 526 * 527 * @see 528 * org.graphstream.stream.AttributeSink#nodeAttributeAdded(java.lang.String, 529 * long, java.lang.String, java.lang.String, java.lang.Object) 530 */ 531 public void nodeAttributeAdded(String sourceId, long timeId, String nodeId, 532 String attribute, Object value) { 533 Args args = argsPnP(null); 534 535 args.put("sourceId", sourceId); 536 args.put("timeId", timeId); 537 args.put("nodeId", nodeId); 538 args.put("attributeId", attribute); 539 args.put("value", toStringValue(value)); 540 541 print(EventType.ADD_NODE_ATTRIBUTE, args); 542 } 543 544 /* 545 * (non-Javadoc) 546 * 547 * @see 548 * org.graphstream.stream.AttributeSink#nodeAttributeChanged(java.lang.String 549 * , long, java.lang.String, java.lang.String, java.lang.Object, 550 * java.lang.Object) 551 */ 552 public void nodeAttributeChanged(String sourceId, long timeId, 553 String nodeId, String attribute, Object oldValue, Object newValue) { 554 Args args = argsPnP(null); 555 556 args.put("sourceId", sourceId); 557 args.put("timeId", timeId); 558 args.put("nodeId", nodeId); 559 args.put("attributeId", attribute); 560 args.put("value", toStringValue(newValue)); 561 562 print(EventType.SET_NODE_ATTRIBUTE, args); 563 } 564 565 /* 566 * (non-Javadoc) 567 * 568 * @see 569 * org.graphstream.stream.AttributeSink#nodeAttributeRemoved(java.lang.String 570 * , long, java.lang.String, java.lang.String) 571 */ 572 public void nodeAttributeRemoved(String sourceId, long timeId, 573 String nodeId, String attribute) { 574 Args args = argsPnP(null); 575 576 args.put("sourceId", sourceId); 577 args.put("timeId", timeId); 578 args.put("nodeId", nodeId); 579 args.put("attributeId", attribute); 580 581 print(EventType.DEL_NODE_ATTRIBUTE, args); 582 } 583 584 /* 585 * (non-Javadoc) 586 * 587 * @see org.graphstream.stream.ElementSink#edgeAdded(java.lang.String, long, 588 * java.lang.String, java.lang.String, java.lang.String, boolean) 589 */ 590 public void edgeAdded(String sourceId, long timeId, String edgeId, 591 String fromNodeId, String toNodeId, boolean directed) { 592 Args args = argsPnP(null); 593 594 args.put("sourceId", sourceId); 595 args.put("timeId", timeId); 596 args.put("edgeId", edgeId); 597 args.put("source", fromNodeId); 598 args.put("target", toNodeId); 599 600 print(EventType.ADD_EDGE, args); 601 } 602 603 /* 604 * (non-Javadoc) 605 * 606 * @see org.graphstream.stream.ElementSink#edgeRemoved(java.lang.String, 607 * long, java.lang.String) 608 */ 609 public void edgeRemoved(String sourceId, long timeId, String edgeId) { 610 Args args = argsPnP(null); 611 612 args.put("sourceId", sourceId); 613 args.put("timeId", timeId); 614 args.put("edgeId", edgeId); 615 616 print(EventType.DEL_EDGE, args); 617 } 618 619 /* 620 * (non-Javadoc) 621 * 622 * @see org.graphstream.stream.ElementSink#graphCleared(java.lang.String, 623 * long) 624 */ 625 public void graphCleared(String sourceId, long timeId) { 626 Args args = argsPnP(null); 627 628 args.put("sourceId", sourceId); 629 args.put("timeId", timeId); 630 631 print(EventType.CLEAR, args); 632 } 633 634 /* 635 * (non-Javadoc) 636 * 637 * @see org.graphstream.stream.ElementSink#nodeAdded(java.lang.String, long, 638 * java.lang.String) 639 */ 640 public void nodeAdded(String sourceId, long timeId, String nodeId) { 641 Args args = argsPnP(null); 642 643 args.put("sourceId", sourceId); 644 args.put("timeId", timeId); 645 args.put("nodeId", nodeId); 646 647 print(EventType.ADD_NODE, args); 648 } 649 650 /* 651 * (non-Javadoc) 652 * 653 * @see org.graphstream.stream.ElementSink#nodeRemoved(java.lang.String, 654 * long, java.lang.String) 655 */ 656 public void nodeRemoved(String sourceId, long timeId, String nodeId) { 657 Args args = argsPnP(null); 658 659 args.put("sourceId", sourceId); 660 args.put("timeId", timeId); 661 args.put("nodeId", nodeId); 662 663 print(EventType.DEL_NODE, args); 664 } 665 666 /* 667 * (non-Javadoc) 668 * 669 * @see org.graphstream.stream.ElementSink#stepBegins(java.lang.String, 670 * long, double) 671 */ 672 public void stepBegins(String sourceId, long timeId, double step) { 673 Args args = argsPnP(null); 674 675 args.put("sourceId", sourceId); 676 args.put("timeId", timeId); 677 args.put("step", step); 678 679 print(EventType.STEP_BEGINS, args); 680 } 681}