001/* A graph with optionally-weighted nodes and edges. 002 003 Copyright (c) 1997-2014 The Regents of the University of California. 004 All rights reserved. 005 Permission is hereby granted, without written agreement and without 006 license or royalty fees, to use, copy, modify, and distribute this 007 software and its documentation for any purpose, provided that the above 008 copyright notice and the following two paragraphs appear in all copies 009 of this software. 010 011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 015 SUCH DAMAGE. 016 017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 022 ENHANCEMENTS, OR MODIFICATIONS. 023 024 PT_COPYRIGHT_VERSION_2 025 COPYRIGHTENDKEY 026 027 028 */ 029package ptolemy.graph; 030 031import java.util.ArrayList; 032import java.util.Collection; 033import java.util.Collections; 034import java.util.HashMap; 035import java.util.HashSet; 036import java.util.Iterator; 037 038import ptolemy.graph.analysis.Analysis; 039import ptolemy.graph.analysis.SelfLoopAnalysis; 040import ptolemy.graph.analysis.strategy.CachedStrategy; 041 042/////////////////////////////////////////////////////////////////// 043//// Graph 044 045/** 046 A graph with optionally-weighted nodes and edges. 047 048 <p>Each node or edge may have a weight associated with it 049 (see {@link Edge} and {@link Node}). 050 The nodes (edges) in a graph are always distinct, but their weights 051 need not be. 052 053 <p>Each node (edge) has a unique, integer label associated with it. 054 These labels can be used, for example, to index arrays and matrixes 055 whose rows/columns correspond to nodes (edges). See {@link #nodeLabel(Node)} 056 ({@link #edgeLabel(Edge)}) for details. 057 058 <p>Both directed and undirected graphs can be implemented using this 059 class. In directed graphs, the order of nodes specified to the 060 <code>addEdge</code> method is relevant, whereas in undirected graphs, the 061 order is unimportant. Support for both undirected and directed graphs 062 follows from the combined support for these in the underlying {@link 063 Node} and {@link Edge} classes. For more thorough support for directed 064 graphs, see {@link DirectedGraph}. 065 066 <p>The same node can exist in multiple graphs, but any given graph can 067 contain only one instance of the node. Node labels, however, are local to 068 individual graphs. Thus, the same node may have different labels in different 069 graphs. 070 Furthermore, the label assigned in a given graph to a node may change over 071 time (if the set of nodes in the graph changes). If a node is contained in 072 multiple graphs, it has the same weight in all of the graphs. 073 All of this holds for edges 074 as well. The same weight may be shared among multiple nodes and edges. 075 076 <p> Multiple edges in a graph can connect the same pair of nodes. 077 Thus, multigraphs are supported. 078 079 <p>Once assigned, node and edge weights should not be changed in ways that 080 affect comparison under the <code>equals</code> method. 081 Otherwise, unpredictable behavior may result. 082 083 <p>In discussions of complexity, <em>n</em> and <em>e</em> refers to the 084 number of graph nodes and edges, respectively. 085 086 <p>In derived classes, the following methods need special 087 attention regarding whether or not they should be overridden: 088 <br>{@link #validEdgeWeight(Object)} {@link #validNodeWeight(Object)} 089 090 @author Shuvra S. Bhattacharyya, Ming-Yung Ko, Fuat Keceli, 091 Shahrooz Shahparnia, Yuhong Xiong, Jie Liu. 092 @version $Id$ 093 @since Ptolemy II 0.2 094 @Pt.ProposedRating Red (cxh) 095 @Pt.AcceptedRating Red (cxh) 096 @see ptolemy.graph.Edge 097 @see ptolemy.graph.Node 098 */ 099public class Graph implements Cloneable { 100 /** Construct an empty graph. 101 */ 102 public Graph() { 103 _nodes = new ElementList("node", this); 104 _edges = new ElementList("edge", this); 105 _initializeAnalyses(); 106 _hiddenEdgeSet = new HashSet(); 107 _incidentEdgeMap = new HashMap(); 108 } 109 110 /** Construct an empty graph with enough storage allocated for the 111 * specified number of nodes. Memory management is more 112 * efficient with this constructor if the number of nodes is 113 * known. 114 * @param nodeCount The number of nodes. 115 */ 116 public Graph(int nodeCount) { 117 _nodes = new ElementList("node", this, nodeCount); 118 _edges = new ElementList("edge", this); 119 _initializeAnalyses(); 120 _hiddenEdgeSet = new HashSet(); 121 _incidentEdgeMap = new HashMap(nodeCount); 122 } 123 124 /** Construct an empty graph with enough storage allocated for the 125 * specified number of edges, and number of nodes. Memory 126 * management is more efficient with this constructor if the 127 * number of nodes and edges is known. 128 * @param nodeCount The number of nodes. 129 * @param edgeCount The number of edges. 130 */ 131 public Graph(int nodeCount, int edgeCount) { 132 _nodes = new ElementList("node", this, nodeCount); 133 _edges = new ElementList("edge", this, edgeCount); 134 _initializeAnalyses(); 135 _hiddenEdgeSet = new HashSet(); 136 _incidentEdgeMap = new HashMap(nodeCount); 137 } 138 139 /////////////////////////////////////////////////////////////////// 140 //// public methods //// 141 142 /** Add an analysis to the list of analyses that this graph is associated 143 * with. This method is called by {@link ptolemy.graph.analysis.Analysis} 144 * when an analysis is created, and normally should not be called 145 * elsewhere. 146 * 147 * @param analysis The analysis. 148 * @exception IllegalArgumentException If the graph associated with the 149 * analysis is not equal to this graph, or if the graph already contains 150 * the analysis in its list of analyses. 151 */ 152 public void addAnalysis(Analysis analysis) { 153 if (analysis.graph() != this) { 154 throw new IllegalArgumentException("Invalid associated graph.\n" 155 + "The analysis:\n" + analysis + "\n"); 156 } 157 158 if (_analysisList.contains(analysis)) { 159 throw new IllegalArgumentException("Attempt to add " 160 + "duplicate analysis.\nThe analysis:\n" + analysis); 161 } 162 163 _analysisList.add(analysis); 164 } 165 166 /** Add a weighted edge between two nodes. If the edge is subsequently 167 * operated on as a directed edge, its orientation will be taken 168 * to be directed <i>from</i> the first (<code>node1</code>) node 169 * <i>to</i> the second (<code>node2</code>) node. Multiple edges 170 * between the same nodes are allowed, and are considered 171 * different edges. Self-loops are also allowed. 172 * 173 * @param node1 The first node. 174 * @param node2 The second node. 175 * @param weight The weight. 176 * @return The edge. 177 * @exception GraphElementException If the first node or second 178 * node is not already in the graph. 179 * @exception NullPointerException If the weight is <code>null</code>. 180 */ 181 public Edge addEdge(Node node1, Node node2, Object weight) { 182 return _addEdge(node1, node2, true, weight); 183 } 184 185 /** Add an unweighted edge between two nodes. Operation is the same as in 186 * {@link #addEdge(Node, Node, Object)}, except that no 187 * weight is assigned to the edge. 188 * 189 * @param node1 The first node. 190 * @param node2 The second node. 191 * @return The edge. 192 * @exception GraphElementException If the first node or second 193 * node is not already in the graph. 194 */ 195 public Edge addEdge(Node node1, Node node2) { 196 return _addEdge(node1, node2, false, null); 197 } 198 199 /** Given two node weights <i>w1</i> and <i>w2</i>, add weighted 200 * edges of the form (<i>x1</i>, <i>x2</i>), where 201 * <code>(x1.getWeight() == w1) && (x2.getWeight() == w2)</code>. 202 * 203 * @param weight1 The first node weight. 204 * @param weight2 The second node weight. 205 * @param newEdgeWeight The weight to assign to each new edge. 206 * @return The set of edges that were added; each element 207 * of this set is an instance of {@link Edge}. 208 * @exception GraphElementException If no edge is 209 * added (i.e., if no nodes x1, x2 satisfy the above condition). 210 */ 211 public Collection addEdge(Object weight1, Object weight2, 212 Object newEdgeWeight) { 213 return _addEdges(weight1, weight2, true, newEdgeWeight); 214 } 215 216 /** Given two node weights <i>w1</i> and <i>w2</i>, add all unweighted 217 * edges of the form (<i>x1</i>, <i>x2</i>), where 218 * <code>(x1.getWeight() == w1) && (x2.getWeight() == w2)</code>. 219 * 220 * @param weight1 The first node weight. 221 * @param weight2 The second node weight. 222 * @return The set of edges that were added; each element 223 * of this set is an instance of {@link Edge}. 224 * @exception GraphElementException If no edge is 225 * added (i.e., if no nodes x1, x2 satisfy the above condition). 226 */ 227 public Collection addEdge(Object weight1, Object weight2) { 228 return _addEdges(weight1, weight2, false, null); 229 } 230 231 /** Add a pre-constructed edge (unweighted or weighted). 232 * 233 * @param edge The edge. 234 * @return The edge. 235 * @exception GraphElementException If the source or sink node 236 * of the edge is not already in the graph. 237 * @exception GraphConstructionException If the edge is already in 238 * the graph, or if the edge is hidden in the graph. 239 * @see #hideEdge(Edge) 240 */ 241 public Edge addEdge(Edge edge) { 242 if (!containsNode(edge.source())) { 243 throw new GraphElementException(edge, this, 244 "The source node is not in the graph."); 245 } else if (!containsNode(edge.sink())) { 246 throw new GraphElementException(edge, this, 247 "The sink node is not in the graph."); 248 } else if (containsEdge(edge)) { 249 throw new GraphConstructionException( 250 "Attempt to add an edge that " + "is already in the graph." 251 + GraphException.elementDump(edge, this)); 252 } else if (hidden(edge)) { 253 throw new GraphConstructionException("Attempt to add an edge that " 254 + "is already hidden in the graph." 255 + GraphException.elementDump(edge, this)); 256 } else { 257 _registerEdge(edge); 258 return edge; 259 } 260 } 261 262 /** Add a collection of edges to the graph. Each element in the 263 * argument collection must be a unique {@link Edge}. 264 * @param edgeCollection The collection of edges to add. 265 */ 266 public void addEdges(Collection edgeCollection) { 267 Iterator edges = edgeCollection.iterator(); 268 269 while (edges.hasNext()) { 270 addEdge((Edge) edges.next()); 271 } 272 } 273 274 /** Add a given graph to this graph. This base class method simply 275 * adds all nodes and edges in the given graph to this graph. If a derived 276 * class contains extra fields associated with 277 * edges, nodes and the graph itself, it can override this method to 278 * handle those fields. This method does not add hidden edges of 279 * the argument graph to this graph. 280 * @param graph The graph to add. 281 * @return True if this graph changed as a result of the call. 282 */ 283 public boolean addGraph(Graph graph) { 284 addNodes(graph.nodes()); 285 addEdges(graph.edges()); 286 return graph.nodeCount() + graph.edgeCount() > 0; 287 } 288 289 /** Add an unweighted node to this graph. 290 * @return The node. 291 */ 292 public Node addNode() { 293 Node node = new Node(); 294 _registerNode(node); 295 return node; 296 } 297 298 /** Add a pre-constructed node (unweighted or weighted). 299 * 300 * @param node The node. 301 * @return The node. 302 * @exception GraphConstructionException If the node is already 303 * in the graph. 304 * @exception GraphWeightException If the weight is invalid. 305 */ 306 public Node addNode(Node node) { 307 if (containsNode(node)) { 308 throw new GraphConstructionException("Attempt to add a node " 309 + "that is already contained in the graph." 310 + GraphException.elementDump(node, this)); 311 } else { 312 _registerNode(node); 313 return node; 314 } 315 } 316 317 /** Add a new weighted node to this graph given the node weight. 318 * 319 * @param weight The node weight. 320 * @return The node. 321 * @exception GraphWeightException If the specified weight is null. 322 */ 323 public Node addNodeWeight(Object weight) { 324 if (weight == null) { 325 throw new NullPointerException("weight == null"); 326 } 327 Node node = new Node(weight); 328 _registerNode(node); 329 return node; 330 } 331 332 /** Add a collection of nodes to the graph. 333 * Each element of the collection is interpreted 334 * as a weight of a new node to add in the graph. 335 * @param weightCollection The collection of node weights; each element 336 * is an instance of {@link Object}. 337 * @return The set of nodes that that were added; each element 338 * is an instance of {@link Node}. 339 */ 340 public Collection addNodeWeights(Collection weightCollection) { 341 ArrayList nodes = new ArrayList(); 342 Iterator weights = weightCollection.iterator(); 343 344 while (weights.hasNext()) { 345 nodes.add(addNodeWeight(weights.next())); 346 } 347 348 return nodes; 349 } 350 351 /** Add a collection of nodes to the graph. Each element in the 352 * argument collection must be a unique {@link Node}. 353 * @param nodeCollection The collection of nodes to add. 354 */ 355 public void addNodes(Collection nodeCollection) { 356 Iterator nodes = nodeCollection.iterator(); 357 358 while (nodes.hasNext()) { 359 addNode((Node) nodes.next()); 360 } 361 } 362 363 /** Return the present value of a counter that keeps track 364 * of changes to the graph. 365 * This counter is monitored by {@link Analysis}s to determine 366 * if associated computations are obsolete. Upon overflow, the counter 367 * resets to zero, broadcasts a change to all graph analyses, and 368 * begins counting again. 369 * @return The present value of the counter. 370 */ 371 public long changeCount() { 372 return _changeCount; 373 } 374 375 /** Return a clone of this graph. The clone has the same set of 376 * nodes and edges. Changes to the node or edge weights 377 * affect the clone simultaneously. However, 378 * modifications to the graph topology make the clone different from 379 * this graph (e.g., they are no longer equal (see 380 * {@link #equals(Object)})). 381 * 382 * @return The clone graph. 383 */ 384 @Override 385 public Object clone() { 386 return cloneAs(this); 387 } 388 389 /** Return a clone of this graph in the form of the argument graph type 390 * (i.e., the run-time type of the returned graph is that of the 391 * argument graph). The clone has the 392 * same set of nodes and edges. Changes to the node or edge weights 393 * affect the 394 * clone simultaneously. If the run-time type of the argument graph is 395 * equal to that of this graph, then the clone is equal to this 396 * graph, as determined by {@link #equals(Object)}. However, 397 * modifications to the graph topology 398 * make the clone not equal to this graph. 399 * 400 * @param graph The graph that gives the run-time type of the clone. 401 * @return A clone of this graph. 402 */ 403 public Graph cloneAs(Graph graph) { 404 Graph cloneGraph = graph._emptyGraph(); 405 406 // copy nodes and edges 407 Iterator nodes = nodes().iterator(); 408 409 while (nodes.hasNext()) { 410 cloneGraph.addNode((Node) nodes.next()); 411 } 412 413 Iterator edges = edges().iterator(); 414 415 while (edges.hasNext()) { 416 cloneGraph.addEdge((Edge) edges.next()); 417 } 418 419 return cloneGraph; 420 } 421 422 /** Return the connected components of the graph. The connected 423 * components are returned as a Collection, where each element 424 * of the Collection is a Collection of Nodes. 425 * @return The connected components. 426 */ 427 public Collection connectedComponents() { 428 // We divide the set of nodes into disjoint subsets called 'components'. 429 // These components are repeatedly modified until they coincide with 430 // the connected components. The following HashMap is a map from 431 // nodes into the components that contain them. Each element in the map 432 // is a Node whose weight is an ArrayList of Nodes. We encapsulate 433 // each ArrayList as the weight of a Node (called the 'container' of 434 // the ArrayList) so that we can modify the ArrayList without 435 // interfering with the hashing semantics of the HashMap. 436 HashMap componentMap = new HashMap(nodeCount()); 437 HashSet components = new HashSet(nodeCount()); 438 Iterator nodes = nodes().iterator(); 439 440 while (nodes.hasNext()) { 441 Node node = (Node) nodes.next(); 442 ArrayList component = new ArrayList(); 443 component.add(node); 444 445 Node container = new Node(component); 446 componentMap.put(node, container); 447 components.add(container); 448 } 449 450 Iterator edges = edges().iterator(); 451 452 while (edges.hasNext()) { 453 Edge edge = (Edge) edges.next(); 454 Node sourceContainer = (Node) componentMap.get(edge.source()); 455 Node sinkContainer = (Node) componentMap.get(edge.sink()); 456 ArrayList sourceSet = (ArrayList) sourceContainer.getWeight(); 457 ArrayList sinkSet = (ArrayList) sinkContainer.getWeight(); 458 459 if (sourceSet != sinkSet) { 460 // Construct the union of the two components in the source set. 461 components.remove(sinkContainer); 462 463 Iterator moveNodes = sinkSet.iterator(); 464 465 while (moveNodes.hasNext()) { 466 Node moveNode = (Node) moveNodes.next(); 467 componentMap.put(moveNode, sourceContainer); 468 sourceSet.add(moveNode); 469 } 470 } 471 } 472 473 // Before returning the result, do away with the container that 474 // encapsulates each connected component. 475 ArrayList result = new ArrayList(components.size()); 476 Iterator connectedComponents = components.iterator(); 477 478 while (connectedComponents.hasNext()) { 479 result.add(((Node) connectedComponents.next()).getWeight()); 480 } 481 482 return result; 483 } 484 485 /** Return true if the specified edge exists in the graph, and the 486 * edge is not hidden in the graph. 487 * @param edge The specified edge. 488 * @return True if the specified edge exists in the graph and is not 489 * hidden. 490 * @see #hidden(Edge) 491 * @see #hideEdge(Edge) 492 */ 493 public boolean containsEdge(Edge edge) { 494 return _edges.contains(edge) && !hidden(edge); 495 } 496 497 /** Test if the specified object is an edge weight in this 498 * graph. Equality is 499 * determined by the <code>equals</code> method. If the specified 500 * edge weight is null, return false. 501 * 502 * @param weight The edge weight to be tested. 503 * @return True if the specified object is an edge weight in this graph. 504 */ 505 public boolean containsEdgeWeight(Object weight) { 506 // FIXME: on null, return true if there is an unweighted element. 507 return _edges.containsWeight(weight); 508 } 509 510 /** Return True if the specified node exists in the 511 * graph. 512 * @param node The specified node. 513 * @return True if the specified node exists in the 514 * graph. 515 */ 516 public boolean containsNode(Node node) { 517 return _nodes.contains(node); 518 } 519 520 /** Test if the specified object is a node weight in this 521 * graph. Equality is 522 * determined by the <code>equals</code> method. If the specified 523 * weight is null, return false. 524 * 525 * @param weight The node weight to be tested. 526 * @return True if the specified object is a node weight in this graph. 527 */ 528 public boolean containsNodeWeight(Object weight) { 529 // FIXME: on null, return true if there is an unweighted element. 530 return _nodes.containsWeight(weight); 531 } 532 533 /** Return an edge in this graph that has a specified weight. If multiple 534 * edges have the specified weight, then return one of them 535 * arbitrarily. If the specified weight is null, return an unweighted 536 * edge (again arbitrarily chosen if there are multiple unweighted 537 * edges). 538 * @param weight The specified edge weight. 539 * @return An edge that has this weight. 540 * @exception GraphWeightException If the specified weight 541 * is not an edge weight in this graph or if the specified weight 542 * is null but the graph does not contain any unweighted edges. 543 */ 544 public Edge edge(Object weight) { 545 return (Edge) _edges.element(weight); 546 } 547 548 /** Return an edge in this graph given the edge label; 549 * the returned edge may be hidden see {@link #hideEdge(Edge)}. 550 * @param label The edge label. 551 * @return The edge. 552 * @exception GraphElementException If the label is not associated 553 * with an edge in this graph. 554 * @see #edgeLabel(Edge) 555 */ 556 public Edge edge(int label) { 557 return (Edge) _edges.get(label); 558 } 559 560 /** Return the total number of edges in this graph. Multiple 561 * connections between two nodes are counted multiple times. 562 * Hidden edges are not included in this count. 563 * @return The total number of edges in this graph. 564 */ 565 public int edgeCount() { 566 return _edges.size() - _hiddenEdgeSet.size(); 567 } 568 569 /** Return the edge label of the specified edge. 570 * The edge label is a unique integer from 0 through 571 * <i>E</i>-1, where <i>E</i> is the number of edges 572 * currently in the graph. Edge labels maintain their 573 * consistency (remain constant) during periods when 574 * no edges are removed from the graph. When edges are removed, 575 * the labels assigned to the remaining edges may change. 576 * 577 * @param edge A graph edge. 578 * @return The edge label. 579 * @exception GraphElementException If the specified edge is not 580 * not an edge in this graph. 581 */ 582 public int edgeLabel(Edge edge) { 583 return _edges.label(edge); 584 } 585 586 /** Return the edge label of the specified edge given the edge weight. 587 * If multiple edges have the specified weight, then return one of their 588 * labels arbitrarily. 589 * 590 * @param weight The edge weight. 591 * @return The edge label. 592 * @exception GraphWeightException If the specified weight is not 593 * an edge weight in this graph. 594 * @see #edgeLabel(Edge) 595 */ 596 public int edgeLabel(Object weight) { 597 return _edges.label(edge(weight)); 598 } 599 600 /** Return the weight of a given edge in the graph given the edge label. 601 * 602 * @param label The edge label. 603 * @return The weight of the edge. 604 * @exception IndexOutOfBoundsException If the label is 605 * not valid. 606 * @exception GraphWeightException If the edge corresponding 607 * to the label is unweighted. 608 * @see #edgeLabel(Edge) 609 */ 610 public Object edgeWeight(int label) { 611 return ((Edge) _edges.get(label)).getWeight(); 612 } 613 614 /** Return all the edges in this graph in the form of a collection. 615 * Each element in the returned collection is an instance of {@link Edge}. 616 * Hidden edges are not included in the returned collection. 617 * The returned collection cannot be modified. 618 * This is an <em>O(1)</em> operation if there are no hidden edges; 619 * otherwise, it is an <em>O(e)</em> operation. 620 * @return All the edges in this graph. 621 */ 622 public Collection edges() { 623 if (hiddenEdgeCount() == 0) { 624 return Collections.unmodifiableList(_edges); 625 } 626 627 // There is at least one hidden edge. 628 int visibleEdgeCount = _edges.size() - hiddenEdgeCount(); 629 ArrayList result = new ArrayList(visibleEdgeCount); 630 631 if (visibleEdgeCount == 0) { 632 return Collections.unmodifiableList(result); 633 } 634 635 // There is at least one edge to return. 636 Iterator edges = _edges.iterator(); 637 638 while (edges.hasNext()) { 639 Edge edge = (Edge) edges.next(); 640 641 if (!hidden(edge)) { 642 result.add(edge); 643 } 644 } 645 646 return Collections.unmodifiableList(result); 647 } 648 649 /** Return all the edges in this graph that have a specified weight. 650 * The edges are returned in the form of a collection. 651 * If the specified weight is null, return all the unweighted edges. 652 * If no edges have the specified weight (or if the argument is null and 653 * there are no unweighted edges), return an empty collection. 654 * Each element in the returned collection is an instance of {@link Node}. 655 * @param weight The specified weight. 656 * @return The edges in this graph that have the specified weight. 657 */ 658 public Collection edges(Object weight) { 659 // Hidden edges will not be included since their weights are 660 // disassociated in the element list. 661 return _edges.elements(weight); 662 } 663 664 /** Return all the edges in this graph whose weights are contained 665 * in a specified collection. 666 * The edges are returned in the form of a collection. 667 * Each element in the returned collection is an instance of 668 * {@link Edge}. A null element in the argument collection is interpreted 669 * to mean that all unweighted edges are to be included in the result. 670 * Duplicate weights or null elements in the specified collection result 671 * in duplicate edges in the returned collection. 672 * Non-null elements in the argument collection that are not edge weights 673 * are ignored. 674 * @param collection The specified collection of weights. 675 * @return The edges in this graph whose weights are contained 676 * in the specified collection. 677 * @see #edges(Object) 678 */ 679 public Collection edges(Collection collection) { 680 // Hidden edges will not be included since they are removed from 681 // the weight map. 682 ArrayList edges = new ArrayList(); 683 Iterator weights = collection.iterator(); 684 685 while (weights.hasNext()) { 686 edges.addAll(edges(weights.next())); 687 } 688 689 return Collections.unmodifiableCollection(edges); 690 } 691 692 /** Test if a graph is equal to this one. It is equal 693 * if it is of the same class, and has the same sets of nodes 694 * and edges. 695 * 696 * <p> Derived graph classes may override this method if 697 * there is additional information in the graphs (beyond nodes 698 * and edges) that is relevant to equality. 699 * 700 * @param graph The graph with which to compare this graph. 701 * @return True if the graph is equal to this one. 702 * @see #hashCode() 703 */ 704 @Override 705 public boolean equals(Object graph) { 706 if (graph == null) { 707 return false; 708 } 709 710 if (graph.getClass() != getClass()) { 711 return false; 712 } 713 714 Graph argumentGraph = (Graph) graph; 715 716 if (argumentGraph.nodeCount() != nodeCount() 717 || argumentGraph.edgeCount() != edgeCount()) { 718 return false; 719 } 720 721 Iterator argumentNodes = argumentGraph.nodes().iterator(); 722 723 while (argumentNodes.hasNext()) { 724 if (!containsNode((Node) argumentNodes.next())) { 725 return false; 726 } 727 } 728 729 Iterator argumentEdges = argumentGraph.edges().iterator(); 730 731 while (argumentEdges.hasNext()) { 732 if (!containsEdge((Edge) argumentEdges.next())) { 733 return false; 734 } 735 } 736 737 return true; 738 } 739 740 /** Returns the hash code for this graph. The hash code is the 741 * sum of the hash codes of the nodes and edges. 742 * 743 * <p> Derived graph classes may override this method if 744 * there is additional information in the graphs (beyond nodes 745 * and edges) that is relevant to equality between graphs. 746 * 747 * @return The hash code for this graph. 748 * @see #equals(Object) 749 */ 750 @Override 751 public int hashCode() { 752 int code = getClass().getName().hashCode(); 753 Iterator nodes = nodes().iterator(); 754 755 while (nodes.hasNext()) { 756 code += nodes.next().hashCode(); 757 } 758 759 Iterator edges = edges().iterator(); 760 761 while (edges.hasNext()) { 762 code += edges.next().hashCode(); 763 } 764 765 return code; 766 } 767 768 /** Return true if a given edge is hidden in this graph. 769 * @param edge The given edge. 770 * @return True if the edge is hidden in this graph. 771 */ 772 public boolean hidden(Edge edge) { 773 return _hiddenEdgeSet.contains(edge); 774 } 775 776 /** Return the number of hidden edges in the graph. 777 * @return The number of hidden edges. 778 */ 779 public int hiddenEdgeCount() { 780 return _hiddenEdgeSet.size(); 781 } 782 783 /** Return all the hidden edges in this graph in the form of a collection. 784 * Each element in the returned collection is an instance of {@link Edge}. 785 * This is an <em>O(1)</em> operation. 786 * @return All the hidden edges in this graph. 787 */ 788 public Collection hiddenEdges() { 789 return Collections.unmodifiableCollection(_hiddenEdgeSet); 790 } 791 792 /** Hide an edge if the edge exists in the graph and is not already hidden. 793 * This method removes an edge from the graph, including 794 * removal from the incidence lists of the source and sink nodes, but 795 * preserves the allocation of the edge label to the edge. This 796 * makes the operation more efficient than standard edge removal 797 * {@link #removeEdge(Edge)}, and 798 * allows the same label to be used if the edge is restored later. 799 * This is an <em>O(1)</em> operation. 800 * @param edge The edge to hide. 801 * @return True if the edge was in the graph and not already hidden. 802 * @see #restoreEdge(Edge) 803 */ 804 public boolean hideEdge(Edge edge) { 805 if (!containsEdge(edge)) { 806 return false; 807 } 808 809 if (_hiddenEdgeSet.add(edge)) { 810 _disconnectEdge(edge); 811 return true; 812 } else { 813 // The edge is already hidden. 814 return false; 815 } 816 } 817 818 /** Return the number of edges that are incident to a specified node. 819 * @param node The node. 820 * @return The number of incident edges. 821 * @exception GraphElementException If the specified node is not in 822 * the graph. 823 */ 824 public int incidentEdgeCount(Node node) { 825 GraphElementException.checkNode(node, this); 826 return _incidentEdgeList(node).size(); 827 } 828 829 /** Return the set of incident edges for a specified node. Each element in 830 * the returned set is an {@link Edge}. 831 * 832 * @param node The specified node. 833 * @return The set of incident edges. 834 * @exception GraphElementException If the specified node is not in 835 * the graph. 836 */ 837 public Collection incidentEdges(Node node) { 838 GraphElementException.checkNode(node, this); 839 return Collections.unmodifiableList(_incidentEdgeList(node)); 840 } 841 842 /** Return the collection of edges that make a node node2 a neighbor of a 843 * node node1. In other words, return the set of edges that are incident to 844 * both node1 and node2. Each element of the returned collection is an 845 * instance of {@link Edge}. 846 * @param node1 The node node1. 847 * @param node2 The node node2. 848 * @return The collection of edges that make node2 a neighbor of node1. 849 * @see DirectedGraph#predecessorEdges(Node, Node) 850 * @see DirectedGraph#successorEdges(Node, Node) 851 * @exception GraphElementException If node1 or node2 is not in this 852 * graph. 853 */ 854 public Collection neighborEdges(Node node1, Node node2) { 855 // Method incidentEdges will validate existence of node1 in the graph. 856 Collection edgeCollection = incidentEdges(node1); 857 GraphElementException.checkNode(node2, this); 858 859 Iterator edges = edgeCollection.iterator(); 860 ArrayList commonEdges = new ArrayList(); 861 862 while (edges.hasNext()) { 863 Edge edge = (Edge) edges.next(); 864 865 if (edge.source() == node2) { 866 commonEdges.add(edge); 867 } else if (edge.sink() == node2) { 868 commonEdges.add(edge); 869 } 870 } 871 872 return commonEdges; 873 } 874 875 /** Return all of the neighbors of a given node in the form of a 876 * a collection. Each element of the collection is a Node. 877 * A neighbor of a node X is a node that is the sink 878 * of an edge whose source is X, or the source of a node whose sink 879 * is node X. In other words, a neighbor of X is a node that is adjacent 880 * to X. All elements in the returned collection are unique nodes. 881 * @param node The node whose neighbors are to be returned. 882 * @return The neighbors of the node as a collection. 883 */ 884 public Collection neighbors(Node node) { 885 // Method incidentEdges will validate existence of node in the graph. 886 Collection incidentEdgeCollection = incidentEdges(node); 887 Iterator incidentEdges = incidentEdgeCollection.iterator(); 888 ArrayList result = new ArrayList(incidentEdgeCollection.size()); 889 890 while (incidentEdges.hasNext()) { 891 Edge edge = (Edge) incidentEdges.next(); 892 Node sink = edge.sink(); 893 Node source = edge.source(); 894 895 if (source == node) { 896 if (!result.contains(sink)) { 897 result.add(sink); 898 } 899 } else if (sink == node) { 900 if (!result.contains(source)) { 901 result.add(source); 902 } 903 } 904 } 905 906 return result; 907 } 908 909 /** Return a node in this graph that has a specified weight. If multiple 910 * nodes have the specified weight, then return one of them 911 * arbitrarily. If the specified weight is null, return an unweighted 912 * node (again arbitrarily chosen if there are multiple unweighted 913 * nodes). 914 * @param weight The specified node weight. 915 * @return A node that has this weight. 916 * @exception GraphWeightException If the specified weight 917 * is not a node weight in this graph or if the specified weight 918 * is null but the graph does not contain any unweighted nodes. 919 */ 920 public Node node(Object weight) { 921 return (Node) _nodes.element(weight); 922 } 923 924 /** Return a node in this graph given the node label. 925 * @param label The node label. 926 * @return The node. 927 * @exception IndexOutOfBoundsException If the label is not 928 * associated with a node in this graph. 929 * @see #nodeLabel(Node) 930 */ 931 public Node node(int label) { 932 return (Node) _nodes.get(label); 933 } 934 935 /** Return the total number of nodes in this graph. 936 * @return The total number of nodes in this graph. 937 */ 938 public int nodeCount() { 939 return _nodes.size(); 940 } 941 942 /** Return the node label of the specified node. 943 * The node label is a unique integer from 0 through 944 * <i>N</i>-1, where <i>N</i> is the number of nodes 945 * currently in the graph. Node labels maintain their 946 * consistency (remain constant) during periods when 947 * no nodes are removed from the graph. When nodes are removed, 948 * the labels assigned to the remaining nodes may change. 949 * 950 * @param node A graph node. 951 * @return The node label. 952 * @exception GraphElementException If the specified node is not 953 * a node in this graph. 954 */ 955 public int nodeLabel(Node node) { 956 return _nodes.label(node); 957 } 958 959 /** Return the node label of the specified node given the node weight. 960 * If multiple nodes have the specified weight, then return one of their 961 * labels arbitrarily. 962 * 963 * @param weight The node weight. 964 * @return The node label. 965 * @exception GraphWeightException If the specified weight is not 966 * a node weight in this graph. 967 * @see #nodeLabel(Node) 968 */ 969 public int nodeLabel(Object weight) { 970 return _nodes.label(node(weight)); 971 } 972 973 /** Return the weight of a given node in the graph given the node label. 974 * 975 * @param label The node label. 976 * @return The weight of the node. 977 * @exception IndexOutOfBoundsException If the label is 978 * not valid. 979 * @exception GraphWeightException If the node corresponding 980 * to the label is unweighted. 981 * @see #nodeLabel(Node) 982 */ 983 public Object nodeWeight(int label) { 984 return ((Node) _nodes.get(label)).getWeight(); 985 } 986 987 /** Return all the nodes in this graph in the form of a collection. 988 * Each element in the returned collection is an instance of {@link Node}. 989 * @return All the nodes in this graph. 990 */ 991 public Collection nodes() { 992 return Collections.unmodifiableList(_nodes); 993 } 994 995 /** Return all the nodes in this graph that have a specified weight. 996 * The nodes are returned in the form of a collection. 997 * If the specified weight is null, return all the unweighted nodes. 998 * If no nodes have the specified weight (or if the argument is null and 999 * there are no unweighted nodes), return an empty collection. 1000 * Each element in the returned collection is an instance of {@link Node}. 1001 * @param weight The specified weight. 1002 * @return The nodes in this graph that have the specified weight. 1003 */ 1004 public Collection nodes(Object weight) { 1005 return _nodes.elements(weight); 1006 } 1007 1008 /** Return the collection of nodes in this graph whose weights are 1009 * contained in a specified collection. 1010 * Each element in the returned collection is an instance of 1011 * {@link Node}. 1012 * A null element in the argument collection is interpreted 1013 * to mean that all unweighted nodes are to be included in the result. 1014 * Duplicate weights or null elements in the specified collection result 1015 * in duplicate nodes in the returned collection. 1016 * Non-null elements in the argument collection that are not node weights 1017 * are ignored. 1018 * @param collection The specified collection of weights. 1019 * @return The nodes in this graph whose weights are contained 1020 * in a specified collection. 1021 * @exception GraphWeightException If any specified weight 1022 * is not a node weight in this graph. 1023 */ 1024 public Collection nodes(Collection collection) { 1025 ArrayList nodes = new ArrayList(); 1026 Iterator weights = collection.iterator(); 1027 1028 while (weights.hasNext()) { 1029 nodes.addAll(nodes(weights.next())); 1030 } 1031 1032 return Collections.unmodifiableCollection(nodes); 1033 } 1034 1035 /** Remove an edge from this graph if it exists in the graph. 1036 * The edge may be hidden. 1037 * An edge that is removed from a graph can be re-inserted 1038 * into the graph at a later time (using {@link #addEdge(Edge)}), 1039 * provided that the incident nodes are still in the graph. 1040 * 1041 * <p>This is an <em>O(e)</em> operation. A similar operation can be 1042 * performed in <em>O(1)</em> time using {@link #hideEdge(Edge)}. 1043 * @param edge The edge to be removed. 1044 * @return True if the edge was removed. 1045 * @see #hideEdge(Edge) 1046 */ 1047 public boolean removeEdge(Edge edge) { 1048 if (!_edges.contains(edge)) { 1049 return false; 1050 } 1051 1052 _edges.remove(edge); 1053 1054 if (hidden(edge)) { 1055 _hiddenEdgeSet.remove(edge); 1056 } else { 1057 _disconnectEdge(edge); 1058 } 1059 1060 return true; 1061 } 1062 1063 /** Remove a node from this graph if it exists in the graph. 1064 * All edges incident to the node (excluding hidden edges) are also removed. 1065 * This is an 1066 * <em>O(n + ke)</em> operation, where <em>k</em> is the number of 1067 * incident edges. 1068 * @param node The node to be removed. 1069 * @return True if the node was removed. 1070 */ 1071 public boolean removeNode(Node node) { 1072 if (!_nodes.contains(node)) { 1073 return false; 1074 } 1075 1076 // Avoid concurrent modification of the incident edges list. 1077 Object[] incidentEdgeArray = incidentEdges(node).toArray(); 1078 _nodes.remove(node); 1079 1080 for (Object element : incidentEdgeArray) { 1081 removeEdge((Edge) element); 1082 } 1083 1084 _incidentEdgeMap.remove(node); 1085 _registerChange(); 1086 return true; 1087 } 1088 1089 /** Restore an edge if the edge exists in the graph and is presently 1090 * hidden. This is an <em>O(1)</em> operation. 1091 * @param edge The edge to restore. 1092 * @return True if the edge is in the graph and was hidden. 1093 * @exception GraphElementException If the source node and 1094 * sink node of the given edge are not both in the graph. 1095 * @see #hideEdge(Edge) 1096 */ 1097 public boolean restoreEdge(Edge edge) { 1098 if (_hiddenEdgeSet.remove(edge)) { 1099 // Make sure the source and sink are still in the graph. 1100 if (!containsNode(edge.source())) { 1101 throw new GraphElementException(edge, this, 1102 "Source node is not in the graph."); 1103 } 1104 1105 if (!containsNode(edge.sink())) { 1106 throw new GraphElementException(edge, this, 1107 "Sink node is not in the graph."); 1108 } 1109 1110 // Re-connect the edge. 1111 _connectEdge(edge); 1112 return true; 1113 } else { 1114 // The edge was not hidden. 1115 return false; 1116 } 1117 } 1118 1119 /** Return the number of self loop edges in this graph. 1120 * @return The number of self loop edges. 1121 */ 1122 public int selfLoopEdgeCount() { 1123 return selfLoopEdges().size(); 1124 } 1125 1126 /** Return the number of self loop edges of a specified node. 1127 * @param node The node. 1128 * @return The number of self loop edges. 1129 * @exception GraphElementException If the node is not in the graph. 1130 */ 1131 public int selfLoopEdgeCount(Node node) { 1132 return selfLoopEdges(node).size(); 1133 } 1134 1135 /** Return the collection of all self-loop edges in this graph. 1136 * Each element in the returned collection is an {@link Edge}. 1137 * This operation takes <i>O(E)</i> time, where E is the number 1138 * of edges in the graph. 1139 * @return The self-loop edges in this graph. 1140 */ 1141 public Collection selfLoopEdges() { 1142 return _selfLoopAnalysis.edges(); 1143 } 1144 1145 /** Return the collection of all self-loop edges that are incident to 1146 * a specified node. Each element in the collection is an {@link Edge}. 1147 * 1148 * @param node The node. 1149 * @return The self-loop edges that are incident to the node. 1150 * @exception GraphElementException If the node is not in the graph. 1151 */ 1152 public Collection selfLoopEdges(Node node) { 1153 ArrayList result = new ArrayList(); 1154 1155 // The call to incidentEdges validates existence of the node. 1156 Iterator edges = incidentEdges(node).iterator(); 1157 1158 while (edges.hasNext()) { 1159 Edge edge = (Edge) edges.next(); 1160 1161 if (edge.isSelfLoop()) { 1162 result.add(edge); 1163 } 1164 } 1165 1166 return result; 1167 } 1168 1169 /** Return the subgraph induced by a collection of nodes. 1170 * In other words, return the subgraph formed by the given collection N of 1171 * nodes together with the set of edges of the form (x, y), where 1172 * x and y are both in N. 1173 * Node and edge weights are preserved. In derived classes, this 1174 * method returns the same type of graph as is returned by 1175 * {@link ptolemy.graph.Graph#_emptyGraph()}. 1176 * Derived classes that do not have zero-argument constructors may 1177 * need to override this method to properly initialize the subgraph. 1178 * @param collection The collection of nodes; each element 1179 * is a {@link Node}. 1180 * @return The induced subgraph. 1181 * @exception GraphElementException If the collection contains a node 1182 * that is not in this graph. 1183 */ 1184 public Graph subgraph(Collection collection) { 1185 Graph subgraph = _emptyGraph(); 1186 Iterator nodes = collection.iterator(); 1187 1188 while (nodes.hasNext()) { 1189 subgraph.addNode((Node) nodes.next()); 1190 } 1191 1192 nodes = collection.iterator(); 1193 1194 while (nodes.hasNext()) { 1195 Node node = (Node) nodes.next(); 1196 1197 if (!containsNode(node)) { 1198 throw new GraphElementException(node, this, 1199 "Attempt to form an induced subgraph containing a " 1200 + "node that is not in the 'parent' graph."); 1201 } 1202 1203 Iterator incidentEdges = incidentEdges(node).iterator(); 1204 1205 while (incidentEdges.hasNext()) { 1206 Edge edge = (Edge) incidentEdges.next(); 1207 1208 if (subgraph.containsNode(edge.source()) 1209 && subgraph.containsNode(edge.sink()) 1210 && !subgraph.containsEdge(edge)) { 1211 subgraph.addEdge(edge); 1212 } 1213 } 1214 } 1215 1216 return subgraph; 1217 } 1218 1219 /** Return the subgraph formed by a subset of nodes and a subset of 1220 * edges. Node and edge weights are preserved. 1221 * In derived classes, this 1222 * method returns the same type of graph as is returned by 1223 * {@link ptolemy.graph.Graph#_emptyGraph()}. 1224 * @param nodeCollection The subset of nodes; each element is an instance 1225 * of {@link Node}. 1226 * @param edgeCollection The subset of edges. Each element is an instance 1227 * of {@link Edge}. 1228 * @exception GraphElementException If the argument collections contain 1229 * a node or edge that is not in this graph. 1230 * @return The subgraph. 1231 * @see #addEdges(Collection) 1232 * @see #addNodes(Collection) 1233 */ 1234 public Graph subgraph(Collection nodeCollection, 1235 Collection edgeCollection) { 1236 Graph subgraph = _emptyGraph(); 1237 1238 Iterator nodes = nodeCollection.iterator(); 1239 1240 while (nodes.hasNext()) { 1241 Node node = (Node) nodes.next(); 1242 1243 if (!containsNode(node)) { 1244 throw new GraphElementException(node, this, 1245 "Attempt to form a subgraph containing a node " 1246 + "that is not in the 'parent' graph."); 1247 } 1248 } 1249 1250 Iterator edges = edgeCollection.iterator(); 1251 1252 while (edges.hasNext()) { 1253 Edge edge = (Edge) edges.next(); 1254 1255 if (!containsEdge(edge)) { 1256 throw new GraphElementException(edge, this, 1257 "Attempt to form a subgraph containing an edge " 1258 + "that is not in the 'parent' graph."); 1259 } 1260 } 1261 1262 subgraph.addNodes(nodeCollection); 1263 subgraph.addEdges(edgeCollection); 1264 return subgraph; 1265 } 1266 1267 /** Return a string representation of this graph. The string 1268 * representation lists the nodes, including their labels 1269 * and their weights, followed by the edges, including their 1270 * labels, source nodes, sink nodes, and weights. 1271 * @return A string representation of this graph. 1272 */ 1273 @Override 1274 public String toString() { 1275 StringBuffer result = new StringBuffer( 1276 "{" + this.getClass().getName() + "\n"); 1277 result.append("Node Set:\n" + _nodes.toString("\n", true) + "\n"); 1278 result.append("Edge Set:\n" + _edges.toString("\n", true) + "\n}\n"); 1279 return result.toString(); 1280 } 1281 1282 /** Return true if the given object is a valid edge weight for this graph. 1283 * An object is a valid edge weight if it is meaningful to assign it as 1284 * an edge weight in this type of graph. 1285 * If the given object is null this method returns true if it is valid 1286 * to have an unweighted edge in this type of graph. 1287 * This base class method returns true unconditionally. 1288 * In derived classes, the method should be 1289 * overridden to take into account any restrictions on edge weights. 1290 * @param object The given object. 1291 * @return True if if the given object is a valid edge weight for this 1292 * graph. 1293 */ 1294 public boolean validEdgeWeight(Object object) { 1295 return true; 1296 } 1297 1298 /** Return true if the given object is a valid node weight for this graph. 1299 * An object is a valid node weight if it is meaningful to assign it as 1300 * a node weight in this type of graph. 1301 * If the given object is null this method returns true if it is valid 1302 * to have an unweighted node in this type of graph. 1303 * This base class method returns true unconditionally. 1304 * In derived classes, the method should be 1305 * overridden to take into account any restrictions on node weights. 1306 * @param object The given object. 1307 * @return True if if the given object is a valid node weight for this 1308 * graph. 1309 */ 1310 public boolean validNodeWeight(Object object) { 1311 return true; 1312 } 1313 1314 /** Validate the weight of an edge. Operation parallels that of 1315 * #validateWeight(Node). 1316 * @param edge The edge whose weight is to be validated. 1317 * @return True if the edge weight has changed, as determined by 1318 * the equals method. 1319 * @exception GraphElementException If the specified edge is not in 1320 * the graph. 1321 * @exception GraphWeightException If the weight of the given edge 1322 * is not valid, as determined by {@link #validEdgeWeight(Object)}. 1323 * @see #validateWeight(Edge, Object) 1324 * @see #validateWeight(Node) 1325 */ 1326 public boolean validateWeight(Edge edge) { 1327 if (edge == null) { 1328 throw new NullPointerException("Attempt to validate the weight " 1329 + "of a null graph edge."); 1330 } 1331 1332 if (!containsEdge(edge)) { 1333 throw new GraphElementException(edge, this, 1334 "The specified edge is not in the graph."); 1335 } 1336 1337 Object weightArgument = edge.hasWeight() ? edge.getWeight() : null; 1338 1339 if (!validEdgeWeight(weightArgument)) { 1340 throw new GraphWeightException(weightArgument, edge, this, 1341 "Invalid weight associated with an edge in the graph.\n"); 1342 } 1343 1344 boolean changed = _edges.changeWeight(edge); 1345 1346 if (changed) { 1347 _registerChange(); 1348 } 1349 1350 return changed; 1351 } 1352 1353 /** Validate the weight of an edge given the edge and its previous weight. 1354 * Operation parallels that of #validateWeight(Node, Object). 1355 * 1356 * @param edge The edge whose weight is to be validated. 1357 * @param oldWeight The previous weight of the edge (null if the edge 1358 * was previously unweighted). 1359 * @return True if the edge weight has changed, as determined by 1360 * the equals method. 1361 * @see #validateWeight(Edge) 1362 * @see #validateWeight(Node, Object) 1363 */ 1364 public boolean validateWeight(Edge edge, Object oldWeight) { 1365 if (!containsEdge(edge)) { 1366 throw new GraphElementException(edge, this, 1367 "The specified edge is not in the graph."); 1368 } 1369 1370 Object newWeight = edge.hasWeight() ? edge.getWeight() : null; 1371 1372 if (!validEdgeWeight(newWeight)) { 1373 throw new GraphWeightException(newWeight, edge, this, 1374 "Invalid weight associated with an edge in the graph."); 1375 } 1376 1377 boolean changed = _edges.validateWeight(edge, oldWeight); 1378 1379 if (changed) { 1380 _registerChange(); 1381 } 1382 1383 return changed; 1384 } 1385 1386 /** Validate the weight of a node. This method checks the validity of 1387 * the node weight (using {@link #validNodeWeight(Object)}, and 1388 * updates, if necessary, the internal mapping of weights into 1389 * their associated nodes. 1390 * This updating operation is necessary for correct operation of 1391 * {@link #containsNodeWeight(Object)}, 1392 * {@link #node(Object)}, 1393 * {@link #nodes(Collection)}, and 1394 * {@link #nodes(Object)}, 1395 * if the node weight has changed in a way 1396 * that affects comparison under the equals method. 1397 * This method returns true if the node weight has changed (as determined 1398 * by the equals() method) since the last time the graph was notified 1399 * of the node's weight. Furthermore, if the node weight has changed in 1400 * this way, a graph change is registered. 1401 * This is an <em>O(n)</em> operation. 1402 * @param node The node whose weight is to be validated. 1403 * @return True if the node weight has changed, as determined by 1404 * the equals method. 1405 * @exception GraphElementException If the specified node is not in 1406 * the graph. 1407 * @exception GraphWeightException If the weight of the given node 1408 * is not valid, as determined by {@link #validNodeWeight(Object)}. 1409 * @see #validateWeight(Node, Object) 1410 */ 1411 public boolean validateWeight(Node node) { 1412 if (node == null) { 1413 throw new NullPointerException("Attempt to validate the weight " 1414 + "of a null graph node."); 1415 } 1416 1417 if (!containsNode(node)) { 1418 throw new GraphElementException(node, this, 1419 "The specified node is not in the graph."); 1420 } 1421 1422 Object weightArgument = node.hasWeight() ? node.getWeight() : null; 1423 1424 if (!validNodeWeight(weightArgument)) { 1425 throw new GraphWeightException(weightArgument, node, this, 1426 "Invalid weight associated with a node in the graph."); 1427 } 1428 1429 boolean changed = _nodes.changeWeight(node); 1430 1431 if (changed) { 1432 _registerChange(); 1433 } 1434 1435 return changed; 1436 } 1437 1438 /** Validate the weight of a node given the node and its previous weight. 1439 * The previous weight argument should be set to 1440 * the weight of the node when the node was last added 1441 * to the graph or last had its node validated, whichever was more recent. 1442 * Operation is equivalent to {@link #validateWeight(Node)} 1443 * except that the additional argument is used to improve efficiency. 1444 * The previous node weight should be set to null to indicate that 1445 * the node was previously unweighted. 1446 * 1447 * <p>Consider an example in which a given Node <em>node</em> is 1448 * contained in two graphs <em>graph1</em> and <em> graph2 </em>, 1449 * and suppose that we wish to change the weight of the 1450 * node. Below is a sample code fragment that achieves such a 1451 * weight change with proper notification to the containing 1452 * graphs. 1453 * 1454 * <pre> 1455 * Object oldWeight = node.getWeight(); 1456 * node.setWeight(newWeight); 1457 * graph1.validateWeight(node, oldWeight); 1458 * graph2.validateWeight(node, oldWeight); 1459 * </pre> 1460 * 1461 * <p>In this example, #validateWeight(Node) could be used 1462 * (e.g., if the previous weight <em>oldWeight</em> was not available) 1463 * in place of #validateWeight(Node, Object), but the efficiency would be 1464 * lower. 1465 * 1466 * @param node The node whose weight is to be validated. 1467 * @param oldWeight The previous weight of the node. 1468 * @return True if the node weight has changed, as determined by 1469 * the equals method. 1470 * @see #validateWeight(Node) 1471 */ 1472 public boolean validateWeight(Node node, Object oldWeight) { 1473 if (!containsNode(node)) { 1474 throw new GraphElementException(node, this, 1475 "The specified node is not in the graph."); 1476 } 1477 1478 Object newWeight = node.hasWeight() ? node.getWeight() : null; 1479 1480 if (!validNodeWeight(newWeight)) { 1481 throw new GraphWeightException(newWeight, node, this, 1482 "Invalid weight associated with a node in the graph."); 1483 } 1484 1485 boolean changed = _nodes.validateWeight(node, oldWeight); 1486 1487 if (changed) { 1488 _registerChange(); 1489 } 1490 1491 return changed; 1492 } 1493 1494 /** Given a collection of graph elements (nodes and edges), return an array 1495 * of weights associated with these elements. 1496 * If a weight is common across multiple elements in 1497 * the collection, it will appear multiple times in the array. 1498 * If the element collection is null or empty, an empty (zero-element) 1499 * array is returned. 1500 * @param elementCollection The collection of graph elements; 1501 * each element is a {@link Node} or an {@link Edge}. 1502 * @return The weights of the graph elements, in the order that that 1503 * elements are returned by collection's iterator; each element in the 1504 * returned array is an {@link Object}. 1505 * @exception NullPointerException If the specified collection contains 1506 * a null value. 1507 * @exception GraphElementException If the specified collection 1508 * contains a non-null value that is neither a node nor an edge. 1509 */ 1510 public static Object[] weightArray(Collection elementCollection) { 1511 if (elementCollection == null) { 1512 return new Object[0]; 1513 } else { 1514 Element element = null; 1515 Object[] result = new Object[elementCollection.size()]; 1516 Iterator elements = elementCollection.iterator(); 1517 1518 try { 1519 for (int i = 0; i < elementCollection.size(); i++) { 1520 element = (Element) elements.next(); 1521 1522 if (element == null) { 1523 throw new NullPointerException( 1524 "Null graph element " + "specified.\n"); 1525 } else { 1526 result[i] = element.getWeight(); 1527 } 1528 } 1529 } catch (ClassCastException exception) { 1530 throw new GraphElementException( 1531 "Illegal graph element " 1532 + "(neither a Node nor an Edge) specified.\n" 1533 + "The element's type is: " 1534 + (element == null ? "null" 1535 : element.getClass().getName()) 1536 + ".\n"); 1537 } 1538 1539 return result; 1540 } 1541 } 1542 1543 /////////////////////////////////////////////////////////////////// 1544 //// protected methods //// 1545 1546 /** Create and add an edge with a specified source node, sink node, 1547 * and optional weight. 1548 * The third parameter specifies whether the edge is to be 1549 * weighted, and the fourth parameter is the weight that is 1550 * to be applied if the edge is weighted. 1551 * Returns the edge that is added. 1552 * @param node1 The source node of the edge. 1553 * @param node2 The sink node of the edge. 1554 * @param weighted True if the edge is to be weighted. 1555 * @param weight The weight that is to be applied if the edge is to 1556 * be weighted. 1557 * @return The edge. 1558 * @exception GraphElementException If either of the specified 1559 * nodes is not in the graph. 1560 * @exception NullPointerException If the edge is to be weighted, but 1561 * the specified weight is null. 1562 */ 1563 protected Edge _addEdge(Node node1, Node node2, boolean weighted, 1564 Object weight) { 1565 if (!containsNode(node1)) { 1566 throw new GraphElementException(node1, this, 1567 "The specified first node is not in the graph."); 1568 } else if (!containsNode(node2)) { 1569 throw new GraphElementException(node2, this, 1570 "The specified second node is not in the graph."); 1571 } else if (weighted && weight == null) { 1572 throw new NullPointerException("Attempt to assign a null " 1573 + "weight to an edge. The first node:\n" + node1 1574 + "\nThe second node:\n" + node2 + "\nThe graph: \n" 1575 + this); 1576 } else { 1577 Edge edge = null; 1578 1579 if (weighted) { 1580 edge = new Edge(node1, node2, weight); 1581 } else { 1582 edge = new Edge(node1, node2); 1583 } 1584 1585 _registerEdge(edge); 1586 return edge; 1587 } 1588 } 1589 1590 /** Connect an edge to a node by appropriately modifying 1591 * the adjacency information associated with the node. 1592 * The node is assumed to be in the graph. 1593 * @param edge The edge. 1594 * @param node The node. 1595 * @exception GraphConstructionException If the edge has already 1596 * been connected to the node. 1597 */ 1598 protected void _connect(Edge edge, Node node) { 1599 if (_incidentEdgeList(node).contains(edge)) { 1600 throw new GraphConstructionException( 1601 "Attempt to connect the same edge multiple times." 1602 + GraphException.elementDump(edge, this)); 1603 } else { 1604 _incidentEdgeList(node).add(edge); 1605 } 1606 } 1607 1608 /** Connect a given edge in this graph. The edge and its source and sink 1609 * nodes are assumed to be in 1610 * the graph. This method performs operations that are common to 1611 * the addition of a new edge and the restoration of a hidden edge. 1612 * Specifically, this method connects, using {@link #_connect(Edge, Node)}, 1613 * the given edge to its source and sink nodes; 1614 * updates the mapping of weights into corresponding graph 1615 * edges; and registers a change in the graph. This method should be 1616 * overridden to perform additional operations that are necessary 1617 * to connect edges in derived graph classes. 1618 * @param edge The edge to connect. 1619 * @see #hideEdge(Edge) 1620 * @see #removeEdge(Edge) 1621 * @see #_disconnectEdge(Edge) 1622 * @see #_registerChange() 1623 */ 1624 protected void _connectEdge(Edge edge) { 1625 _connect(edge, edge.source()); 1626 1627 if (!edge.isSelfLoop()) { 1628 _connect(edge, edge.sink()); 1629 } 1630 1631 _edges.registerWeight(edge); 1632 _registerChange(); 1633 } 1634 1635 /** Disconnect an edge from a node that it is incident to. 1636 * Specifically, this method removes the edge from the set of 1637 * edges that are considered incident to the node in this graph. 1638 * This method does nothing if the given edge is not incident to the 1639 * given node. 1640 * This method should be overridden to incorporate additional operations 1641 * that are required to disconnect an edge from a node (see, for 1642 * example, DirectedGraph.#_disconnect(Edge, Node)). 1643 * @param edge The edge. 1644 * @param node The node. 1645 */ 1646 protected void _disconnect(Edge edge, Node node) { 1647 _incidentEdgeList(node).remove(edge); 1648 } 1649 1650 /** Disconnect a given edge in this graph. The edge is assumed to be in 1651 * the graph and 1652 * not already hidden. This method performs operations that are common to 1653 * the removal of and hiding of an edge. Specifically, this method 1654 * disconnects, using {@link #_disconnect(Edge, Node)}, the given edge 1655 * from its source and sink nodes; 1656 * updates the mapping of weights into corresponding graph 1657 * edges; and registers a change in the graph. This method should be 1658 * overridden to perform additional operations that are necessary 1659 * to disconnect edges in derived graph classes. 1660 * @param edge The edge to disconnect. 1661 * @see #hideEdge(Edge) 1662 * @see #removeEdge(Edge) 1663 * @see #_connectEdge(Edge) 1664 * @see #_registerChange() 1665 */ 1666 protected void _disconnectEdge(Edge edge) { 1667 _disconnect(edge, edge.source()); 1668 _disconnect(edge, edge.sink()); 1669 _edges.cancelWeight(edge); 1670 _registerChange(); 1671 } 1672 1673 /** Return an empty graph that has the same run-time type as this graph. 1674 * This class should be overridden in derived classes that do not 1675 * have zero-argument constructors. 1676 * @return An empty graph. 1677 */ 1678 protected Graph _emptyGraph() { 1679 Graph graph = null; 1680 1681 try { 1682 // Kepler (jdk1.4?) requires this cast 1683 graph = getClass().newInstance(); 1684 } catch (Exception exception) { 1685 throw new GraphConstructionException("Could not create an " 1686 + "empty graph from this one.\n" + exception + "\n" 1687 + GraphException.graphDump(this)); 1688 } 1689 1690 return graph; 1691 } 1692 1693 /** Initialize the list of analyses that are associated with this graph, 1694 * and initialize the change counter of the graph. 1695 * @see ptolemy.graph.analysis.Analysis 1696 */ 1697 protected void _initializeAnalyses() { 1698 _analysisList = new ArrayList(); 1699 _selfLoopAnalysis = new SelfLoopAnalysis(this); 1700 _changeCount = 0; 1701 } 1702 1703 /** Register a change to the graph by updating the change counter. 1704 * This method must be called after any change to the graph 1705 * that may affect (invalidate) any of the computations associated with 1706 * analyses that this graph is associated with. 1707 * @see Analysis 1708 */ 1709 protected void _registerChange() { 1710 if (_changeCount == Long.MAX_VALUE) { 1711 // Invalidate all of the associated analyses. 1712 Iterator analyses = _analysisList.iterator(); 1713 1714 while (analyses.hasNext()) { 1715 Analysis analysis = (Analysis) analyses.next(); 1716 1717 if (analysis.analyzer() instanceof CachedStrategy) { 1718 ((CachedStrategy) analysis.analyzer()).reset(); 1719 } 1720 } 1721 1722 _changeCount = 0; 1723 } else { 1724 _changeCount++; 1725 } 1726 } 1727 1728 /** Register a new edge in the graph. The edge is assumed to 1729 * be non-null, unique, and consistent with the node set. 1730 * This method performs updates of internal 1731 * data structures that are required for every edge that is added 1732 * to the graph. 1733 * Derived classes can override this method to perform additional updates 1734 * of internal data structures. 1735 * @param edge The new edge. 1736 * @exception GraphWeightException If the weight of the given edge is 1737 * not valid, as determined by {@link #validEdgeWeight(Object)}. 1738 * @see #_registerNode(Node) 1739 */ 1740 protected void _registerEdge(Edge edge) { 1741 Object weight = edge.hasWeight() ? edge.getWeight() : null; 1742 1743 if (!validEdgeWeight(weight)) { 1744 throw new GraphWeightException(weight, edge, this, 1745 "Invalid edge weight."); 1746 } 1747 1748 _edges.add(edge); 1749 _connectEdge(edge); 1750 } 1751 1752 /** Register a new node in the graph. The node is assumed to 1753 * be non-null and unique. This method performs updates of internal 1754 * data structures that are required for every node that is added 1755 * to the graph. 1756 * Derived classes can override this method to perform additional updates 1757 * of internal data structures. 1758 * @param node The new node. 1759 * @exception GraphWeightException If the weight of the given node is 1760 * not valid, as determined by {@link #validNodeWeight(Object)}. 1761 * @see #_registerEdge(Edge) 1762 */ 1763 protected void _registerNode(Node node) { 1764 Object weight = node.hasWeight() ? node.getWeight() : null; 1765 1766 if (!validNodeWeight(weight)) { 1767 throw new GraphWeightException(weight, node, this, 1768 "Invalid node weight."); 1769 } 1770 1771 _nodes.add(node); 1772 _incidentEdgeMap.put(node, new ArrayList()); 1773 _nodes.registerWeight(node); 1774 _registerChange(); 1775 } 1776 1777 /////////////////////////////////////////////////////////////////// 1778 //// private methods //// 1779 /** Given two node weights w1 and w2, add all edges of the form 1780 * (x1, x2), where 1781 * (x1.getWeight() == w1) && (x2.getWeight() == w2). 1782 * The third parameter specifies whether the edges are to be 1783 * weighted, and the fourth parameter is the weight that is 1784 * to be applied if the edges are weighted. 1785 * The method returns one of the edges that is added. 1786 * The method returns an iterator over the edges that were added; 1787 * each element of this iterator is an instance of Edge. 1788 * The method throws an GraphConstructionException if no edge is 1789 * added (i.e., if no nodes x1, x2 satisfy the above condition. 1790 * The method throws a NullPointerException if w1 or w2 is null. 1791 * @param weight1 The source node weight. 1792 * @param weight2 The sink node weight. 1793 * @param weighted True if weighted. 1794 * @param weight The weight 1795 */ 1796 private Collection _addEdges(Object weight1, Object weight2, 1797 boolean weighted, Object weight) { 1798 if (weight1 == null) { 1799 throw new NullPointerException("Null source node weight"); 1800 } else if (weight2 == null) { 1801 throw new NullPointerException("Null sink node weight"); 1802 } 1803 1804 Iterator nodes1 = nodes(weight1).iterator(); 1805 Edge newEdge = null; 1806 ArrayList newEdges = new ArrayList(); 1807 1808 while (nodes1.hasNext()) { 1809 Node node1 = (Node) nodes1.next(); 1810 Iterator nodes2 = nodes(weight2).iterator(); 1811 1812 while (nodes2.hasNext()) { 1813 newEdge = _addEdge(node1, (Node) nodes2.next(), weighted, 1814 weight); 1815 newEdges.add(newEdge); 1816 } 1817 } 1818 1819 if (newEdges.isEmpty()) { 1820 throw new GraphConstructionException("No edge can be added based " 1821 + "on the specified source and sink node weights.\n" 1822 + "Weight1:\n" + weight1 + "\nWeight2:\n" + weight2 + "\n" 1823 + GraphException.graphDump(this)); 1824 } else { 1825 return newEdges; 1826 } 1827 } 1828 1829 // Return the list of incident edges for a specified node. 1830 // Return null if the specified node is not in the graph. 1831 private ArrayList _incidentEdgeList(Node node) { 1832 return (ArrayList) _incidentEdgeMap.get(node); 1833 } 1834 1835 /////////////////////////////////////////////////////////////////// 1836 //// private variables //// 1837 // A list of analyses that are associated with this graph. Each 1838 // element of the list is an instance of ptolemy.graph.analysis.Analysis. 1839 private ArrayList _analysisList; 1840 1841 // A counter that keeps track of changes to the graph. 1842 private long _changeCount; 1843 1844 // The list of edges in this graph. 1845 // Each element of this list is an Edge. 1846 private ElementList _edges; 1847 1848 // The set of hidden edges. Each element is an Edge. 1849 // Hidden edges remain contained in the _edges list, but are removed from 1850 // the incidence and weight maps. 1851 private HashSet _hiddenEdgeSet; 1852 1853 // A mapping from nodes into their lists of incident edges. 1854 // This redundant information is maintained for improved 1855 // run-time efficiency when handing undirected graphs, or when operating 1856 // on directed graphs in ways for which edge orientation is not relevant. 1857 // Each key in this map is an instance of Node. Each value 1858 // is an instance of ArrayList whose elements are instances of Edge. 1859 private HashMap _incidentEdgeMap; 1860 1861 // The list of nodes in this graph. 1862 // Each element of this list is a Node. 1863 private ElementList _nodes; 1864 1865 // The analysis for computation of self loop edges. 1866 private SelfLoopAnalysis _selfLoopAnalysis; 1867}