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.ui.graphicGraph; 033 034import java.io.IOException; 035import java.util.AbstractCollection; 036import java.util.ArrayList; 037import java.util.Arrays; 038import java.util.Collection; 039import java.util.HashMap; 040import java.util.Iterator; 041 042import org.graphstream.graph.Edge; 043import org.graphstream.graph.EdgeFactory; 044import org.graphstream.graph.Element; 045import org.graphstream.graph.Graph; 046import org.graphstream.graph.Node; 047import org.graphstream.graph.NodeFactory; 048import org.graphstream.graph.ElementNotFoundException; 049import org.graphstream.graph.IdAlreadyInUseException; 050import org.graphstream.graph.implementations.AbstractElement; 051import org.graphstream.stream.AttributeSink; 052import org.graphstream.stream.ElementSink; 053import org.graphstream.stream.Sink; 054import org.graphstream.stream.SourceBase.ElementType; 055import org.graphstream.stream.file.FileSink; 056import org.graphstream.stream.file.FileSource; 057import org.graphstream.ui.geom.Point3; 058import org.graphstream.ui.graphicGraph.stylesheet.Style; 059import org.graphstream.ui.graphicGraph.stylesheet.StyleConstants; 060import org.graphstream.ui.graphicGraph.stylesheet.StyleSheet; 061import org.graphstream.ui.graphicGraph.stylesheet.Value; 062import org.graphstream.ui.graphicGraph.stylesheet.Values; 063import org.graphstream.ui.graphicGraph.stylesheet.StyleConstants.Units; 064import org.graphstream.util.GraphListeners; 065 066/** 067 * Graph representation used in display classes. 068 * 069 * <p> 070 * Warning: This class is NOT a general graph class, and it should NOT be used 071 * as it. This class is particularly dedicated to fast drawing of the graph and 072 * is internally arranged to be fast for this task only. It implements graph 073 * solely to be easily susceptible to be used as a sink and source for graph 074 * events. Some of the common methods of the Graph interface are not functional 075 * and will throw an exception if used (as documented in their respective 076 * JavaDoc). 077 * </p> 078 * 079 * <p> 080 * The purpose of the graphic graph is to represent a graph with some often used 081 * graphic attributes (like position, label, etc.) stored as fields in the nodes 082 * and edges and most of the style stored in styles pertaining to a style sheet 083 * that tries to imitate the way CSS works. For example, the GraphicNode class 084 * defines a label, a position (x,y,z) and a style that is taken from the style 085 * sheet. 086 * </p> 087 * 088 * <p> 089 * The style sheet is uploaded on the graph using an attribute correspondingly 090 * named "ui.stylesheet" or "ui.stylesheet" (the second one is better). It can 091 * be a string that contains the whole style sheet, or an URL of the form : 092 * </p> 093 * 094 * <pre> 095 * url(name) 096 * </pre> 097 * 098 * <p> 099 * The graphic graph does not completely duplicate a graph, it only store things 100 * that are useful for drawing it. Although it implements "Graph", some methods 101 * are not implemented and will throw a runtime exception. These methods are 102 * mostly utility methods like write(), read(), and naturally display(). 103 * </p> 104 * 105 * <p> 106 * The graphic graph has the ability to store attributes like any other graph 107 * element, however the attributes stored by the graphic graph are restricted. 108 * There is a filter on the attribute adding methods that let pass only: 109 * <ul> 110 * <li>All attributes starting with "ui.".</li> 111 * <li>The "x", "y", "z", "xy" and "xyz" attributes.</li> 112 * <li>The "stylesheet" attribute (although "ui.stylesheet" is preferred).</li> 113 * <li>The "label" attribute.</li> 114 * </ul> 115 * All other attributes are filtered and not stored. The result is that if the 116 * graphic graph is used as an input (a source of graph events) some attributes 117 * will not pass through the filter. 118 * </p> 119 * 120 * <p> 121 * The implementation of this graph relies on the StyleGroupSet class and this 122 * is indeed its way to store its elements (grouped by style and Z level). 123 * </p> 124 * 125 * <p> 126 * In addition to this, it provides, as all graphs do, the relational 127 * information for edges. 128 * </p> 129 * 130 * TODO : this graph cannot handle modification inside event listener methods !! 131 */ 132public class GraphicGraph extends AbstractElement implements Graph, 133 StyleGroupListener { 134 /** 135 * Set of styles. 136 */ 137 protected StyleSheet styleSheet; 138 139 /** 140 * Associate graphic elements with styles. 141 */ 142 protected StyleGroupSet styleGroups; 143 144 /** 145 * Connectivity. The way nodes are connected one with another via edges. The 146 * map is sorted by node. For each node an array of edges lists the 147 * connectivity. 148 */ 149 protected HashMap<GraphicNode, ArrayList<GraphicEdge>> connectivity; 150 151 /** 152 * The style of this graph. This is a shortcut to avoid searching it in the 153 * style sheet. 154 */ 155 public StyleGroup style; 156 157 /** 158 * Memorize the step events. 159 */ 160 public double step = 0; 161 162 /** 163 * Set to true each time the graph was modified internally and a redraw is 164 * needed. 165 */ 166 public boolean graphChanged; 167 168 /** 169 * Set to true each time a sprite or node moved. 170 */ 171 protected boolean boundsChanged = true; 172 173 /** 174 * Maximum position of a node or sprite in the graphic graph. Computed by 175 * {@link #computeBounds()}. 176 */ 177 protected Point3 hi = new Point3(); 178 179 /** 180 * Minimum position of a node or sprite in the graphic graph. Computed by 181 * {@link #computeBounds()}. 182 */ 183 protected Point3 lo = new Point3(); 184 185 /** 186 * Set of listeners of this graph. 187 */ 188 protected GraphListeners listeners; 189 190 /** 191 * Time of other known sources. 192 */ 193 // protected SinkTime sinkTime = new SinkTime(); 194 195 /** 196 * Are null attributes access an error ? 197 */ 198 protected boolean nullAttrError = false; 199 200 /** 201 * Report back the XYZ events on nodes and sprites? If enabled, each change 202 * in the position of nodes and sprites will be sent to potential listeners 203 * of the graph. By default this is disabled as long there are no listeners. 204 */ 205 protected boolean feedbackXYZ = true; 206 207 /** 208 * New empty graphic graph. 209 * 210 * A default style sheet is created, it then can be "cascaded" with other 211 * style sheets. 212 */ 213 public GraphicGraph(String id) { 214 super(id); 215 216 listeners = new GraphListeners(this); 217 styleSheet = new StyleSheet(); 218 styleGroups = new StyleGroupSet(styleSheet); 219 connectivity = new HashMap<GraphicNode, ArrayList<GraphicEdge>>(); 220 221 styleGroups.addListener(this); 222 styleGroups.addElement(this); // Add style to this graph. 223 224 style = styleGroups.getStyleFor(this); 225 } 226 227 // Access 228 229 /** 230 * True if the graph was edited or changed in any way since the last reset 231 * of the "changed" flag. 232 * 233 * @return true if the graph was changed. 234 */ 235 public boolean graphChangedFlag() { 236 return graphChanged; 237 } 238 239 /** 240 * Reset the "changed" flag. 241 * 242 * @see #graphChangedFlag() 243 */ 244 public void resetGraphChangedFlag() { 245 graphChanged = false; 246 } 247 248 /** 249 * The style sheet. This style sheet is the result of the "cascade" or 250 * accumulation of styles added via attributes of the graph. 251 * 252 * @return A style sheet. 253 */ 254 public StyleSheet getStyleSheet() { 255 return styleSheet; 256 } 257 258 /** 259 * The graph style group. 260 * 261 * @return A style group. 262 */ 263 public StyleGroup getStyle() { 264 return style; 265 } 266 267 /** 268 * The complete set of style groups. 269 * 270 * @return The style groups. 271 */ 272 public StyleGroupSet getStyleGroups() { 273 return styleGroups; 274 } 275 276 @Override 277 public String toString() { 278 return String.format("[%s %d nodes %d edges]", getId(), getNodeCount(), 279 getEdgeCount()); 280 } 281 282 public double getStep() { 283 return step; 284 } 285 286 /** 287 * The maximum position of a node or sprite. Notice that this is updated 288 * only each time the {@link #computeBounds()} method is called. 289 * 290 * @return The maximum node or sprite position. 291 */ 292 public Point3 getMaxPos() { 293 return hi; 294 } 295 296 /** 297 * The minimum position of a node or sprite. Notice that this is updated 298 * only each time the {@link #computeBounds()} method is called. 299 * 300 * @return The minimum node or sprite position. 301 */ 302 public Point3 getMinPos() { 303 return lo; 304 } 305 306 /** 307 * Does the graphic graph publish via attribute changes the XYZ changes on 308 * nodes and sprites when changed ?. This is disabled by default, and 309 * enabled as soon as there is at least one listener. 310 */ 311 public boolean feedbackXYZ() { 312 return feedbackXYZ; 313 } 314 315 // Command 316 317 /** 318 * Should the graphic graph publish via attribute changes the XYZ changes on 319 * nodes and sprites when changed ?. 320 */ 321 public void feedbackXYZ(boolean on) { 322 feedbackXYZ = on; 323 } 324 325 /** 326 * Compute the overall bounds of the graphic graph according to the nodes 327 * and sprites positions. We can only compute the graph bounds from the 328 * nodes and sprites centres since the node and graph bounds may in certain 329 * circumstances be computed according to the graph bounds. The bounds are 330 * stored in the graph metrics. 331 * 332 * This operation will process each node and sprite and is therefore costly. 333 * However it does this computation again only when a node or sprite moved. 334 * Therefore it can be called several times, if nothing moved in the graph, 335 * the computation will not be redone. 336 * 337 * @see #getMaxPos() 338 * @see #getMinPos() 339 */ 340 public void computeBounds() { 341 if (boundsChanged) { 342 lo.x = lo.y = lo.z = Double.POSITIVE_INFINITY;// 1000000000; // A 343 // bug with 344 // Double.MAX_VALUE 345 // during 346 // comparisons ? 347 hi.x = hi.y = hi.z = Double.NEGATIVE_INFINITY;// -1000000000; // A 348 // bug with 349 // Double.MIN_VALUE 350 // during 351 // comparisons ? 352 353 for (Node n : getEachNode()) { 354 GraphicNode node = (GraphicNode) n; 355 356 if (!node.hidden && node.positionned) { 357 if (node.x < lo.x) 358 lo.x = node.x; 359 if (node.x > hi.x) 360 hi.x = node.x; 361 if (node.y < lo.y) 362 lo.y = node.y; 363 if (node.y > hi.y) 364 hi.y = node.y; 365 if (node.z < lo.z) 366 lo.z = node.z; 367 if (node.z > hi.z) 368 hi.z = node.z; 369 } 370 } 371 372 for (GraphicSprite sprite : spriteSet()) { 373 if (!sprite.isAttached() 374 && sprite.getUnits() == StyleConstants.Units.GU) { 375 double x = sprite.getX(); 376 double y = sprite.getY(); 377 double z = sprite.getZ(); 378 379 if (!sprite.hidden) { 380 if (x < lo.x) 381 lo.x = x; 382 if (x > hi.x) 383 hi.x = x; 384 if (y < lo.y) 385 lo.y = y; 386 if (y > hi.y) 387 hi.y = y; 388 if (z < lo.z) 389 lo.z = z; 390 if (z > hi.z) 391 hi.z = z; 392 } 393 } 394 } 395 396 if ((hi.x - lo.x < 0.000001)) { 397 hi.x = 1; 398 lo.x = -1; 399 } 400 if ((hi.y - lo.y < 0.000001)) { 401 hi.y = 1; 402 lo.y = -1; 403 } 404 if ((hi.z - lo.z < 0.000001)) { 405 hi.z = 1; 406 lo.z = -1; 407 } 408 409 boundsChanged = false; 410 } 411 } 412 413 protected void moveNode(String id, double x, double y, double z) { 414 GraphicNode node = (GraphicNode) styleGroups.getNode(id); 415 416 if (node != null) { 417 node.x = x; 418 node.y = y; 419 node.z = z; 420 node.addAttribute("x", x); 421 node.addAttribute("y", y); 422 node.addAttribute("z", z); 423 424 graphChanged = true; 425 } 426 } 427 428 @SuppressWarnings("unchecked") 429 public <T extends Node> T getNode(String id) { 430 return (T) styleGroups.getNode(id); 431 } 432 433 @SuppressWarnings("unchecked") 434 public <T extends Edge> T getEdge(String id) { 435 return (T) styleGroups.getEdge(id); 436 } 437 438 public GraphicSprite getSprite(String id) { 439 return styleGroups.getSprite(id); 440 } 441 442 @Override 443 protected void attributeChanged(AttributeChangeEvent event, 444 String attribute, Object oldValue, Object newValue) { 445 446 // One of the most important method. Most of the communication comes 447 // from attributes. 448 449 if (attribute.equals("ui.repaint")) { 450 graphChanged = true; 451 } else if (attribute.equals("ui.stylesheet") 452 || attribute.equals("stylesheet")) { 453 if (event == AttributeChangeEvent.ADD 454 || event == AttributeChangeEvent.CHANGE) { 455 if (newValue instanceof String) { 456 try { 457 styleSheet.load((String) newValue); 458 graphChanged = true; 459 } catch (IOException e) { 460 System.err 461 .printf("Error while parsing style sheet for graph '%s' : %n", 462 getId()); 463 if (((String) newValue).startsWith("url")) 464 System.err.printf(" %s%n", ((String) newValue)); 465 System.err.printf(" %s%n", e.getMessage()); 466 } 467 } else { 468 System.err 469 .printf("Error with stylesheet specification what to do with '%s' ?%n", 470 newValue); 471 } 472 } else // Remove the style. 473 { 474 styleSheet.clear(); 475 graphChanged = true; 476 } 477 } else if (attribute.startsWith("ui.sprite.")) { 478 // Defers the sprite handling to the sprite API. 479 spriteAttribute(event, null, attribute, newValue); 480 graphChanged = true; 481 } 482 483 listeners.sendAttributeChangedEvent(getId(), ElementType.GRAPH, 484 attribute, event, oldValue, newValue); 485 } 486 487 /** 488 * Display the node/edge relations. 489 */ 490 public void printConnectivity() { 491 Iterator<GraphicNode> keys = connectivity.keySet().iterator(); 492 493 System.err.printf("Graphic graph connectivity:%n"); 494 495 while (keys.hasNext()) { 496 GraphicNode node = keys.next(); 497 System.err.printf(" [%s] -> ", node.getId()); 498 ArrayList<GraphicEdge> edges = connectivity.get(node); 499 for (GraphicEdge edge : edges) 500 System.err.printf(" (%s %d)", edge.getId(), 501 edge.getMultiIndex()); 502 System.err.printf("%n"); 503 } 504 } 505 506 // Style group listener interface 507 508 public void elementStyleChanged(Element element, StyleGroup oldStyle, 509 StyleGroup style) { 510 if (element instanceof GraphicElement) { 511 GraphicElement ge = (GraphicElement) element; 512 ge.style = style; 513 graphChanged = true; 514 } else if (element instanceof GraphicGraph) { 515 GraphicGraph gg = (GraphicGraph) element; 516 gg.style = style; 517 graphChanged = true; 518 } else { 519 throw new RuntimeException("WTF ?"); 520 } 521 } 522 523 public void styleChanged(StyleGroup style) { 524 525 } 526 527 // Graph interface 528 529 public Iterable<? extends Edge> getEachEdge() { 530 return styleGroups.edges(); 531 } 532 533 public Iterable<? extends Node> getEachNode() { 534 return styleGroups.nodes(); 535 } 536 537 @SuppressWarnings("all") 538 public <T extends Node> Collection<T> getNodeSet() { 539 return new AbstractCollection<T>() { 540 public Iterator<T> iterator() { 541 return getNodeIterator(); 542 } 543 544 public int size() { 545 return getNodeCount(); 546 } 547 }; 548 } 549 550 @SuppressWarnings("all") 551 public <T extends Edge> Collection<T> getEdgeSet() { 552 return new AbstractCollection<T>() { 553 public Iterator<T> iterator() { 554 return getEdgeIterator(); 555 } 556 557 public int size() { 558 return getEdgeCount(); 559 } 560 }; 561 } 562 563 @SuppressWarnings("unchecked") 564 public Iterator<Node> iterator() { 565 return (Iterator<Node>) styleGroups.getNodeIterator(); 566 } 567 568 /* 569 * (non-Javadoc) 570 * 571 * @see org.graphstream.stream.Source#addSink(org.graphstream.stream.Sink) 572 */ 573 public void addSink(Sink listener) { 574 listeners.addSink(listener); 575 } 576 577 /* 578 * (non-Javadoc) 579 * 580 * @see 581 * org.graphstream.stream.Source#removeSink(org.graphstream.stream.Sink) 582 */ 583 public void removeSink(Sink listener) { 584 listeners.removeSink(listener); 585 } 586 587 /* 588 * *(non-Javadoc) 589 * 590 * @see 591 * org.graphstream.stream.Source#addAttributeSink(org.graphstream.stream 592 * .AttributeSink) 593 */ 594 public void addAttributeSink(AttributeSink listener) { 595 listeners.addAttributeSink(listener); 596 } 597 598 /* 599 * *(non-Javadoc) 600 * 601 * @see 602 * org.graphstream.stream.Source#removeAttributeSink(org.graphstream.stream 603 * .AttributeSink) 604 */ 605 public void removeAttributeSink(AttributeSink listener) { 606 listeners.removeAttributeSink(listener); 607 } 608 609 /* 610 * *(non-Javadoc) 611 * 612 * @see org.graphstream.stream.Source#addElementSink(org.graphstream.stream. 613 * ElementSink) 614 */ 615 public void addElementSink(ElementSink listener) { 616 listeners.addElementSink(listener); 617 } 618 619 /* 620 * *(non-Javadoc) 621 * 622 * @see 623 * org.graphstream.stream.Source#removeElementSink(org.graphstream.stream 624 * .ElementSink) 625 */ 626 public void removeElementSink(ElementSink listener) { 627 listeners.removeElementSink(listener); 628 } 629 630 /* 631 * *(non-Javadoc) 632 * 633 * @see org.graphstream.graph.Graph#attributeSinks() 634 */ 635 public Iterable<AttributeSink> attributeSinks() { 636 return listeners.attributeSinks(); 637 } 638 639 /* 640 * *(non-Javadoc) 641 * 642 * @see org.graphstream.graph.Graph#elementSinks() 643 */ 644 public Iterable<ElementSink> elementSinks() { 645 return listeners.elementSinks(); 646 } 647 648 /* 649 * (non-Javadoc) 650 * 651 * @see org.graphstream.graph.Graph#addEdge(java.lang.String, 652 * java.lang.String, java.lang.String) 653 */ 654 @SuppressWarnings("unchecked") 655 public <T extends Edge> T addEdge(String id, String from, String to) 656 throws IdAlreadyInUseException, ElementNotFoundException { 657 return (T) addEdge(id, from, to, false); 658 } 659 660 /* 661 * (non-Javadoc) 662 * 663 * @see org.graphstream.graph.Graph#addEdge(java.lang.String, 664 * java.lang.String, java.lang.String, boolean) 665 */ 666 @SuppressWarnings("unchecked") 667 public <T extends Edge> T addEdge(String id, String from, String to, 668 boolean directed) throws IdAlreadyInUseException, 669 ElementNotFoundException { 670 GraphicEdge edge = (GraphicEdge) styleGroups.getEdge(id); 671 672 if (edge == null) { 673 GraphicNode n1 = (GraphicNode) styleGroups.getNode(from); 674 GraphicNode n2 = (GraphicNode) styleGroups.getNode(to); 675 676 if (n1 == null) 677 throw new ElementNotFoundException("node \"%s\"", from); 678 679 if (n2 == null) 680 throw new ElementNotFoundException("node \"%s\"", to); 681 682 edge = new GraphicEdge(id, n1, n2, directed, null);//, attributes); 683 684 styleGroups.addElement(edge); 685 686 ArrayList<GraphicEdge> l1 = connectivity.get(n1); 687 ArrayList<GraphicEdge> l2 = connectivity.get(n2); 688 689 if (l1 == null) { 690 l1 = new ArrayList<GraphicEdge>(); 691 connectivity.put(n1, l1); 692 } 693 694 if (l2 == null) { 695 l2 = new ArrayList<GraphicEdge>(); 696 connectivity.put(n2, l2); 697 } 698 699 l1.add(edge); 700 l2.add(edge); 701 edge.countSameEdges(l1); 702 703 graphChanged = true; 704 705 listeners.sendEdgeAdded(id, from, to, directed); 706 } 707 708 return (T) edge; 709 } 710 711 /* 712 * (non-Javadoc) 713 * 714 * @see org.graphstream.graph.Graph#addNode(java.lang.String) 715 */ 716 @SuppressWarnings("unchecked") 717 public <T extends Node> T addNode(String id) throws IdAlreadyInUseException { 718 GraphicNode node = (GraphicNode) styleGroups.getNode(id); 719 720 if (node == null) { 721 node = new GraphicNode(this, id, null);//, attributes); 722 723 styleGroups.addElement(node); 724 725 graphChanged = true; 726 727 listeners.sendNodeAdded(id); 728 } 729 730 return (T) node; 731 } 732 733 /* 734 * (non-Javadoc) 735 * 736 * @see org.graphstream.graph.Graph#clear() 737 */ 738 public void clear() { 739 listeners.sendGraphCleared(); 740 741 clearAttributesWithNoEvent(); 742 743 connectivity.clear(); 744 styleGroups.clear(); 745 styleSheet.clear(); 746 747 step = 0; 748 graphChanged = true; 749 750 styleGroups.addElement(this); 751 style = styleGroups.getStyleFor(this); 752 } 753 754 /* 755 * (non-Javadoc) 756 * 757 * @see org.graphstream.graph.Graph#removeEdge(java.lang.String) 758 */ 759 @SuppressWarnings("unchecked") 760 public <T extends Edge> T removeEdge(String id) 761 throws ElementNotFoundException { 762 GraphicEdge edge = (GraphicEdge) styleGroups.getEdge(id); 763 764 if (edge != null) { 765 listeners.sendEdgeRemoved(id); 766 767 if (connectivity.get(edge.from) != null) 768 connectivity.get(edge.from).remove(edge); 769 if (connectivity.get(edge.to) != null) 770 connectivity.get(edge.to).remove(edge); 771 772 styleGroups.removeElement(edge); 773 edge.removed(); 774 775 graphChanged = true; 776 } 777 778 return (T) edge; 779 } 780 781 /* 782 * (non-Javadoc) 783 * 784 * @see org.graphstream.graph.Graph#removeEdge(java.lang.String, 785 * java.lang.String) 786 */ 787 @SuppressWarnings("unchecked") 788 public <T extends Edge> T removeEdge(String from, String to) 789 throws ElementNotFoundException { 790 GraphicNode node0 = (GraphicNode) styleGroups.getNode(from); 791 GraphicNode node1 = (GraphicNode) styleGroups.getNode(to); 792 793 if (node0 != null && node1 != null) { 794 ArrayList<GraphicEdge> edges0 = connectivity.get(node0); 795 ArrayList<GraphicEdge> edges1 = connectivity.get(node1); 796 797 for (GraphicEdge edge0 : edges0) { 798 for (GraphicEdge edge1 : edges1) { 799 if (edge0 == edge1) { 800 removeEdge(edge0.getId()); 801 return (T) edge0; 802 } 803 } 804 } 805 } 806 807 return null; 808 } 809 810 /* 811 * *(non-Javadoc) 812 * 813 * @see org.graphstream.graph.Graph#removeNode(java.lang.String) 814 */ 815 @SuppressWarnings("unchecked") 816 public <T extends Node> T removeNode(String id) 817 throws ElementNotFoundException { 818 GraphicNode node = (GraphicNode) styleGroups.getNode(id); 819 820 if (node != null) { 821 listeners.sendNodeRemoved(id); 822 823 if (connectivity.get(node) != null) { 824 // We must do a copy of the connectivity set for the node 825 // since we will be modifying the connectivity as we process 826 // edges. 827 ArrayList<GraphicEdge> l = new ArrayList<GraphicEdge>( 828 connectivity.get(node)); 829 830 for (GraphicEdge edge : l) 831 removeEdge(edge.getId()); 832 833 connectivity.remove(node); 834 } 835 836 styleGroups.removeElement(node); 837 node.removed(); 838 839 graphChanged = true; 840 } 841 842 return (T) node; 843 } 844 845 public org.graphstream.ui.swingViewer.Viewer display() { 846 throw new RuntimeException( 847 "GraphicGraph is used by display() and cannot recursively define display()"); 848 } 849 850 public org.graphstream.ui.swingViewer.Viewer display(boolean autoLayout) { 851 throw new RuntimeException( 852 "GraphicGraph is used by display() and cannot recursively define display()"); 853 } 854 855 /* 856 * (non-Javadoc) 857 * 858 * @see org.graphstream.graph.Graph#stepBegins(double) 859 */ 860 public void stepBegins(double step) { 861 listeners.sendStepBegins(step); 862 this.step = step; 863 } 864 865 public EdgeFactory<? extends Edge> edgeFactory() { 866 throw new RuntimeException("GraphicGraph does not support EdgeFactory"); 867 } 868 869 public int getEdgeCount() { 870 return styleGroups.getEdgeCount(); 871 } 872 873 @SuppressWarnings("unchecked") 874 public <T extends Edge> Iterator<T> getEdgeIterator() { 875 return (Iterator<T>) styleGroups.getEdgeIterator(); 876 } 877 878 public int getNodeCount() { 879 return styleGroups.getNodeCount(); 880 } 881 882 public int getSpriteCount() { 883 return styleGroups.getSpriteCount(); 884 } 885 886 @SuppressWarnings("unchecked") 887 public <T extends Node> Iterator<T> getNodeIterator() { 888 return (Iterator<T>) styleGroups.getNodeIterator(); 889 } 890 891 public Iterator<? extends GraphicSprite> getSpriteIterator() { 892 return styleGroups.getSpriteIterator(); 893 } 894 895 public Iterable<? extends GraphicSprite> spriteSet() { 896 return styleGroups.sprites(); 897 } 898 899 public boolean isAutoCreationEnabled() { 900 return false; 901 } 902 903 public NodeFactory<? extends Node> nodeFactory() { 904 throw new RuntimeException("GraphicGraph does not support NodeFactory"); 905 } 906 907 public void setAutoCreate(boolean on) { 908 throw new RuntimeException( 909 "GraphicGraph does not support auto-creation"); 910 } 911 912 public boolean isStrict() { 913 return false; 914 } 915 916 public void setStrict(boolean on) { 917 throw new RuntimeException( 918 "GraphicGraph does not support strict checking"); 919 } 920 921 @Override 922 public boolean nullAttributesAreErrors() { 923 return nullAttrError; 924 } 925 926 public void setNullAttributesAreErrors(boolean on) { 927 nullAttrError = on; 928 } 929 930 public void setEdgeFactory(EdgeFactory<? extends Edge> ef) { 931 throw new RuntimeException( 932 "you cannot change the edge factory for graphic graphs !"); 933 } 934 935 public void setNodeFactory(NodeFactory<? extends Node> nf) { 936 throw new RuntimeException( 937 "you cannot change the node factory for graphic graphs !"); 938 } 939 940 public void read(String filename) throws IOException { 941 throw new RuntimeException("GraphicGraph does not support I/O"); 942 } 943 944 public void read(FileSource input, String filename) throws IOException { 945 throw new RuntimeException("GraphicGraph does not support I/O"); 946 } 947 948 public void write(FileSink output, String filename) throws IOException { 949 throw new RuntimeException("GraphicGraph does not support I/O"); 950 } 951 952 public void write(String filename) throws IOException { 953 throw new RuntimeException("GraphicGraph does not support I/O"); 954 } 955 956 // Output interface 957 958 /* 959 * (non-Javadoc) 960 * 961 * @see 962 * org.graphstream.stream.AttributeSink#edgeAttributeAdded(java.lang.String, 963 * long, java.lang.String, java.lang.String, java.lang.Object) 964 */ 965 public void edgeAttributeAdded(String sourceId, long timeId, String edgeId, 966 String attribute, Object value) { 967 listeners 968 .edgeAttributeAdded(sourceId, timeId, edgeId, attribute, value); 969 } 970 971 /* 972 * (non-Javadoc) 973 * 974 * @see 975 * org.graphstream.stream.AttributeSink#edgeAttributeChanged(java.lang.String 976 * , long, java.lang.String, java.lang.String, java.lang.Object, 977 * java.lang.Object) 978 */ 979 public void edgeAttributeChanged(String sourceId, long timeId, 980 String edgeId, String attribute, Object oldValue, Object newValue) { 981 listeners.edgeAttributeChanged(sourceId, timeId, edgeId, attribute, 982 oldValue, newValue); 983 } 984 985 /* 986 * (non-Javadoc) 987 * 988 * @see 989 * org.graphstream.stream.AttributeSink#edgeAttributeRemoved(java.lang.String 990 * , long, java.lang.String, java.lang.String) 991 */ 992 public void edgeAttributeRemoved(String sourceId, long timeId, 993 String edgeId, String attribute) { 994 listeners.edgeAttributeRemoved(sourceId, timeId, edgeId, attribute); 995 } 996 997 /* 998 * (non-Javadoc) 999 * 1000 * @see 1001 * org.graphstream.stream.AttributeSink#graphAttributeAdded(java.lang.String 1002 * , long, java.lang.String, java.lang.Object) 1003 */ 1004 public void graphAttributeAdded(String sourceId, long timeId, 1005 String attribute, Object value) { 1006 listeners.graphAttributeAdded(sourceId, timeId, attribute, value); 1007 } 1008 1009 /* 1010 * (non-Javadoc) 1011 * 1012 * @see 1013 * org.graphstream.stream.AttributeSink#graphAttributeChanged(java.lang. 1014 * String, long, java.lang.String, java.lang.Object, java.lang.Object) 1015 */ 1016 public void graphAttributeChanged(String sourceId, long timeId, 1017 String attribute, Object oldValue, Object newValue) { 1018 listeners.graphAttributeChanged(sourceId, timeId, attribute, oldValue, 1019 newValue); 1020 } 1021 1022 /* 1023 * (non-Javadoc) 1024 * 1025 * @see 1026 * org.graphstream.stream.AttributeSink#graphAttributeRemoved(java.lang. 1027 * String, long, java.lang.String) 1028 */ 1029 public void graphAttributeRemoved(String sourceId, long timeId, 1030 String attribute) { 1031 listeners.graphAttributeRemoved(sourceId, timeId, attribute); 1032 } 1033 1034 /* 1035 * (non-Javadoc) 1036 * 1037 * @see 1038 * org.graphstream.stream.AttributeSink#nodeAttributeAdded(java.lang.String, 1039 * long, java.lang.String, java.lang.String, java.lang.Object) 1040 */ 1041 public void nodeAttributeAdded(String sourceId, long timeId, String nodeId, 1042 String attribute, Object value) { 1043 listeners 1044 .nodeAttributeAdded(sourceId, timeId, nodeId, attribute, value); 1045 } 1046 1047 /* 1048 * (non-Javadoc) 1049 * 1050 * @see 1051 * org.graphstream.stream.AttributeSink#nodeAttributeChanged(java.lang.String 1052 * , long, java.lang.String, java.lang.String, java.lang.Object, 1053 * java.lang.Object) 1054 */ 1055 public void nodeAttributeChanged(String sourceId, long timeId, 1056 String nodeId, String attribute, Object oldValue, Object newValue) { 1057 listeners.nodeAttributeChanged(sourceId, timeId, nodeId, attribute, 1058 oldValue, newValue); 1059 } 1060 1061 /* 1062 * (non-Javadoc) 1063 * 1064 * @see 1065 * org.graphstream.stream.AttributeSink#nodeAttributeRemoved(java.lang.String 1066 * , long, java.lang.String, java.lang.String) 1067 */ 1068 public void nodeAttributeRemoved(String sourceId, long timeId, 1069 String nodeId, String attribute) { 1070 listeners.nodeAttributeRemoved(sourceId, timeId, nodeId, attribute); 1071 } 1072 1073 /* 1074 * (non-Javadoc) 1075 * 1076 * @see org.graphstream.stream.ElementSink#edgeAdded(java.lang.String, long, 1077 * java.lang.String, java.lang.String, java.lang.String, boolean) 1078 */ 1079 public void edgeAdded(String sourceId, long timeId, String edgeId, 1080 String fromNodeId, String toNodeId, boolean directed) { 1081 listeners.edgeAdded(sourceId, timeId, edgeId, fromNodeId, toNodeId, 1082 directed); 1083 } 1084 1085 /* 1086 * *(non-Javadoc) 1087 * 1088 * @see org.graphstream.stream.ElementSink#edgeRemoved(java.lang.String, 1089 * long, java.lang.String) 1090 */ 1091 public void edgeRemoved(String sourceId, long timeId, String edgeId) { 1092 listeners.edgeRemoved(sourceId, timeId, edgeId); 1093 } 1094 1095 /* 1096 * *(non-Javadoc) 1097 * 1098 * @see org.graphstream.stream.ElementSink#graphCleared(java.lang.String, 1099 * long) 1100 */ 1101 public void graphCleared(String sourceId, long timeId) { 1102 listeners.graphCleared(sourceId, timeId); 1103 } 1104 1105 /* 1106 * (non-Javadoc) 1107 * 1108 * @see org.graphstream.stream.ElementSink#nodeAdded(java.lang.String, long, 1109 * java.lang.String) 1110 */ 1111 public void nodeAdded(String sourceId, long timeId, String nodeId) { 1112 listeners.nodeAdded(sourceId, timeId, nodeId); 1113 } 1114 1115 /* 1116 * (non-Javadoc) 1117 * 1118 * @see org.graphstream.stream.ElementSink#nodeRemoved(java.lang.String, 1119 * long, java.lang.String) 1120 */ 1121 public void nodeRemoved(String sourceId, long timeId, String nodeId) { 1122 listeners.nodeRemoved(sourceId, timeId, nodeId); 1123 } 1124 1125 /* 1126 * (non-Javadoc) 1127 * 1128 * @see org.graphstream.stream.ElementSink#stepBegins(java.lang.String, 1129 * long, double) 1130 */ 1131 public void stepBegins(String sourceId, long timeId, double time) { 1132 listeners.sendStepBegins(sourceId, timeId, time); 1133 stepBegins(time); 1134 } 1135 1136 // Sprite interface 1137 1138 protected void spriteAttribute(AttributeChangeEvent event, Element element, 1139 String attribute, Object value) { 1140 String spriteId = attribute.substring(10); // Remove the "ui.sprite." 1141 // prefix. 1142 int pos = spriteId.indexOf('.'); // Look if there is something after the 1143 // sprite id. 1144 String attr = null; 1145 1146 if (pos > 0) { 1147 attr = spriteId.substring(pos + 1); // Cut the sprite id. 1148 spriteId = spriteId.substring(0, pos); // Cut the sprite attribute 1149 // name. 1150 } 1151 1152 if (attr == null) { 1153 addOrChangeSprite(event, element, spriteId, value); 1154 } else { 1155 if (event == AttributeChangeEvent.ADD) { 1156 GraphicSprite sprite = styleGroups.getSprite(spriteId); 1157 1158 // We add the sprite, in case of a replay, some attributes of 1159 // the sprite can be 1160 // changed before the sprite is declared. 1161 if (sprite == null) { 1162 addOrChangeSprite(AttributeChangeEvent.ADD, element, 1163 spriteId, null); 1164 sprite = styleGroups.getSprite(spriteId); 1165 } 1166 1167 sprite.addAttribute(attr, value); 1168 } else if (event == AttributeChangeEvent.CHANGE) { 1169 GraphicSprite sprite = styleGroups.getSprite(spriteId); 1170 1171 if (sprite == null) { 1172 addOrChangeSprite(AttributeChangeEvent.ADD, element, 1173 spriteId, null); 1174 sprite = styleGroups.getSprite(spriteId); 1175 } 1176 1177 sprite.changeAttribute(attr, value); 1178 } else if (event == AttributeChangeEvent.REMOVE) { 1179 GraphicSprite sprite = styleGroups.getSprite(spriteId); 1180 1181 if (sprite != null) 1182 sprite.removeAttribute(attr); 1183 } 1184 } 1185 } 1186 1187 protected void addOrChangeSprite(AttributeChangeEvent event, 1188 Element element, String spriteId, Object value) { 1189 1190 if (event == AttributeChangeEvent.ADD 1191 || event == AttributeChangeEvent.CHANGE) { 1192 GraphicSprite sprite = styleGroups.getSprite(spriteId); 1193 1194 if (sprite == null) 1195 sprite = addSprite_(spriteId); 1196 1197 if (element != null) { 1198 if (element instanceof GraphicNode) 1199 sprite.attachToNode((GraphicNode) element); 1200 else if (element instanceof GraphicEdge) 1201 sprite.attachToEdge((GraphicEdge) element); 1202 } 1203 1204 if (value != null && (!(value instanceof Boolean))) 1205 positionSprite(sprite, value); 1206 } else if (event == AttributeChangeEvent.REMOVE) { 1207 if (element == null) { 1208 if (styleGroups.getSprite(spriteId) != null) { 1209 removeSprite_(spriteId); 1210 } 1211 } else { 1212 GraphicSprite sprite = styleGroups.getSprite(spriteId); 1213 1214 if (sprite != null) 1215 sprite.detach(); 1216 } 1217 } 1218 } 1219 1220 public GraphicSprite addSprite(String id) { 1221 String prefix = String.format("ui.sprite.%s", id); 1222 System.out.printf("add sprite %s\n", id); 1223 addAttribute(prefix, 0, 0, 0); 1224 GraphicSprite s = styleGroups.getSprite(id); 1225 assert (s != null); 1226 return s; 1227 } 1228 1229 protected GraphicSprite addSprite_(String id) { 1230 GraphicSprite s = new GraphicSprite(id, this); 1231 styleGroups.addElement(s); 1232 graphChanged = true; 1233 1234 return s; 1235 } 1236 1237 public void removeSprite(String id) { 1238 String prefix = String.format("ui.sprite.%s", id); 1239 removeAttribute(prefix); 1240 } 1241 1242 protected GraphicSprite removeSprite_(String id) { 1243 GraphicSprite sprite = (GraphicSprite) styleGroups.getSprite(id); 1244 1245 if (sprite != null) { 1246 sprite.detach(); 1247 styleGroups.removeElement(sprite); 1248 sprite.removed(); 1249 1250 graphChanged = true; 1251 } 1252 1253 return sprite; 1254 } 1255 1256 protected void positionSprite(GraphicSprite sprite, Object value) { 1257 if (value instanceof Object[]) { 1258 Object[] values = (Object[]) value; 1259 1260 if (values.length == 4) { 1261 if (values[0] instanceof Number && values[1] instanceof Number 1262 && values[2] instanceof Number 1263 && values[3] instanceof Style.Units) { 1264 sprite.setPosition(((Number) values[0]).doubleValue(), 1265 ((Number) values[1]).doubleValue(), 1266 ((Number) values[2]).doubleValue(), 1267 (Style.Units) values[3]); 1268 } else { 1269 System.err 1270 .printf("GraphicGraph : cannot parse values[4] for sprite position.%n"); 1271 } 1272 } else if (values.length == 3) { 1273 if (values[0] instanceof Number && values[1] instanceof Number 1274 && values[2] instanceof Number) { 1275 sprite.setPosition(((Number) values[0]).doubleValue(), 1276 ((Number) values[1]).doubleValue(), 1277 ((Number) values[2]).doubleValue(), Units.GU); 1278 } else { 1279 System.err 1280 .printf("GraphicGraph : cannot parse values[3] for sprite position.%n"); 1281 } 1282 } else if (values.length == 1) { 1283 if (values[0] instanceof Number) { 1284 sprite.setPosition(((Number) values[0]).doubleValue()); 1285 } else { 1286 System.err 1287 .printf("GraphicGraph : sprite position percent is not a number.%n"); 1288 } 1289 } else { 1290 System.err 1291 .printf("GraphicGraph : cannot transform value '%s' (length=%d) into a position%n", 1292 Arrays.toString(values), values.length); 1293 } 1294 } else if (value instanceof Number) { 1295 sprite.setPosition(((Number) value).doubleValue()); 1296 } else if (value instanceof Value) { 1297 sprite.setPosition(((Value) value).value); 1298 } else if (value instanceof Values) { 1299 sprite.setPosition((Values) value); 1300 } else if (value == null) { 1301 throw new RuntimeException("What do you expect with a null value ?"); 1302 } else { 1303 System.err 1304 .printf("GraphicGraph : cannot place sprite with posiiton '%s' (instance of %s)%n", 1305 value, value.getClass().getName()); 1306 } 1307 } 1308 1309 /* 1310 * (non-Javadoc) 1311 * 1312 * @see org.graphstream.stream.Source#clearAttributeSinks() 1313 */ 1314 public void clearAttributeSinks() { 1315 listeners.clearAttributeSinks(); 1316 } 1317 1318 /* 1319 * *(non-Javadoc) 1320 * 1321 * @see org.graphstream.stream.Source#clearElementSinks() 1322 */ 1323 public void clearElementSinks() { 1324 listeners.clearElementSinks(); 1325 } 1326 1327 /* 1328 * (non-Javadoc) 1329 * 1330 * @see org.graphstream.stream.Source#clearSinks() 1331 */ 1332 public void clearSinks() { 1333 listeners.clearSinks(); 1334 } 1335 1336 // stubs for the new methods 1337 1338 public <T extends Edge> T addEdge(String id, int index1, int index2) { 1339 throw new RuntimeException("not implemented !"); 1340 } 1341 1342 public <T extends Edge> T addEdge(String id, int fromIndex, int toIndex, 1343 boolean directed) { 1344 throw new RuntimeException("not implemented !"); 1345 } 1346 1347 public <T extends Edge> T addEdge(String id, Node node1, Node node2) { 1348 throw new RuntimeException("not implemented !"); 1349 } 1350 1351 public <T extends Edge> T addEdge(String id, Node from, Node to, 1352 boolean directed) { 1353 throw new RuntimeException("not implemented !"); 1354 } 1355 1356 public <T extends Edge> T getEdge(int index) 1357 throws IndexOutOfBoundsException { 1358 throw new RuntimeException("not implemented !"); 1359 } 1360 1361 public <T extends Node> T getNode(int index) 1362 throws IndexOutOfBoundsException { 1363 throw new RuntimeException("not implemented !"); 1364 } 1365 1366 public <T extends Edge> T removeEdge(int index) { 1367 throw new RuntimeException("not implemented !"); 1368 } 1369 1370 public <T extends Edge> T removeEdge(int fromIndex, int toIndex) { 1371 throw new RuntimeException("not implemented !"); 1372 } 1373 1374 public <T extends Edge> T removeEdge(Node node1, Node node2) { 1375 throw new RuntimeException("not implemented !"); 1376 } 1377 1378 public <T extends Edge> T removeEdge(Edge edge) { 1379 throw new RuntimeException("not implemented !"); 1380 } 1381 1382 public <T extends Node> T removeNode(int index) { 1383 throw new RuntimeException("not implemented !"); 1384 } 1385 1386 public <T extends Node> T removeNode(Node node) { 1387 throw new RuntimeException("not implemented !"); 1388 } 1389 1390 /** 1391 * Replay all the elements of the graph and all attributes as new events to 1392 * all connected sinks. 1393 * 1394 * Be very careful with this method, it introduces new events in the event 1395 * stream and some sinks may therefore receive them twice !! Graph replay is 1396 * always dangerous ! 1397 */ 1398 public void replay() { 1399 // Replay all graph attributes. 1400 1401 if (getAttributeKeySet() != null) 1402 for (String key : getAttributeKeySet()) { 1403 listeners.sendGraphAttributeAdded(id, key, getAttribute(key)); 1404 } 1405 1406 // Replay all nodes and their attributes. 1407 1408 for (Node node : this) { 1409 listeners.sendNodeAdded(id, node.getId()); 1410 1411 if (node.getAttributeKeySet() != null) { 1412 for (String key : node.getAttributeKeySet()) { 1413 listeners.sendNodeAttributeAdded(id, node.getId(), key, 1414 node.getAttribute(key)); 1415 } 1416 } 1417 } 1418 1419 // Replay all edges and their attributes. 1420 1421 for (Edge edge : getEachEdge()) { 1422 listeners.sendEdgeAdded(id, edge.getId(), edge.getSourceNode() 1423 .getId(), edge.getTargetNode().getId(), edge.isDirected()); 1424 1425 if (edge.getAttributeKeySet() != null) { 1426 for (String key : edge.getAttributeKeySet()) { 1427 listeners.sendEdgeAttributeAdded(id, edge.getId(), key, 1428 edge.getAttribute(key)); 1429 } 1430 } 1431 } 1432 } 1433}