View Javadoc
1   /*
2    * The baseCode project
3    * 
4    * Copyright (c) 2006 University of British Columbia
5    * 
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *       http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   *
18   */
19  package ubic.basecode.dataStructure.graph;
20  
21  import java.lang.reflect.Constructor;
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.Collections;
25  import java.util.HashMap;
26  import java.util.HashSet;
27  import java.util.Iterator;
28  import java.util.LinkedHashMap;
29  import java.util.LinkedList;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Queue;
33  import java.util.Set;
34  
35  import javax.swing.JTree;
36  import javax.swing.tree.DefaultMutableTreeNode;
37  import javax.swing.tree.DefaultTreeModel;
38  
39  import org.slf4j.Logger;
40  import org.slf4j.LoggerFactory;
41  
42  /**
43   * A graph that contains DirectedGraphNodes. It can be cyclic. Small unconnected parts of the graph will be ignored for
44   * many operation. Tree traversals start from the root node. There can be only one root.
45   * 
46   * @todo do something about cyclicity; make this a dag or a subclass...
47   * @author Paul Pavlidis
48   * 
49   */
50  public class DirectedGraph<K, V> extends AbstractGraph<DirectedGraphNode<K, V>, K, V> {
51  
52      private static Logger log = LoggerFactory.getLogger( DirectedGraph.class );
53      protected DefaultTreeModel dtm;
54      protected Map<K, DirectedGraphNode<K, V>> items;
55  
56      private JTree treeView;
57  
58      public DirectedGraph() {
59          items = new LinkedHashMap<K, DirectedGraphNode<K, V>>();
60      }
61  
62      /**
63       * Add a child to a particular node identified by key; if the node is not in the graph, an exception is thrown.
64       * 
65       * @param key Object
66       * @param newChildKey Object
67       * @throws IllegalStateException if the graph doesn't contain the child node.
68       */
69      public void addChildTo( K key, K newChildKey ) throws IllegalStateException {
70  
71          assert key != null;
72          assert newChildKey != null;
73  
74          if ( !items.containsKey( newChildKey ) ) {
75              throw new IllegalArgumentException( "Attempt to add link to node that is not in the graph" );
76          }
77  
78          if ( items.containsKey( key ) ) {
79              items.get( key ).addChild( newChildKey );
80              items.get( newChildKey ).addParent( key );
81          } else {
82              throw new IllegalArgumentException( "Attempt to add child to a node that is not in the graph" );
83          }
84  
85      }
86  
87      /**
88       * Add a child to a particualar node identified by key. If it is a new node, it will be added to the graph first.
89       * 
90       * @param key Object
91       * @param newChildKey Object
92       * @param newChild Object
93       */
94      public void addChildTo( K key, K newChildKey, V newChild ) {
95  
96          assert key != null;
97          assert newChildKey != null;
98          assert newChild != null;
99  
100         if ( !items.containsKey( key ) ) {
101             throw new IllegalArgumentException( "Attempt to add a child to a non-existent node: " + key );
102         }
103 
104         if ( !items.containsKey( newChildKey ) ) {
105             this.addNode( newChildKey, newChild );
106         }
107 
108         this.addChildTo( key, newChildKey );
109         this.addParentTo( newChildKey, key );
110     }
111 
112     /*
113      * (non-Javadoc)
114      * 
115      * @see ubic.basecode.dataStructure.graph.AbstractGraph#addNode(ubic.basecode.dataStructure.graph.GraphNode)
116      */
117     @Override
118     public void addNode( DirectedGraphNode<K, V> node ) {
119         assert node != null;
120         node.setGraph( this );
121         items.put( node.getKey(), node );
122     }
123 
124     /**
125      * Will not be attached to any other node.
126      * 
127      * @param key Object
128      * @param item Object
129      */
130     @Override
131     public void addNode( K key, V item ) {
132         assert key != null;
133         assert item != null;
134 
135         if ( !items.containsKey( key ) ) {
136             items.put( key, new DirectedGraphNode<K, V>( key, item, this ) );
137         }
138     }
139 
140     /**
141      * @param key Object
142      * @param newParentKey Object
143      * @throws IllegalArgumentException if the new parent isn't already in the graph.
144      */
145     public void addParentTo( K key, K newParentKey ) throws IllegalStateException {
146         assert key != null;
147         assert newParentKey != null;
148 
149         if ( !items.containsKey( newParentKey ) ) {
150             throw new IllegalArgumentException(
151                     "Attempt to add as parent a node that is not in the graph: " + newParentKey );
152         }
153 
154         if ( items.containsKey( key ) ) {
155             items.get( key ).addParent( newParentKey );
156             items.get( newParentKey ).addChild( key );
157         }
158 
159     }
160 
161     /**
162      * @param key Object
163      * @param newParentKey Object
164      * @param newParent Object
165      */
166     public void addParentTo( K key, K newParentKey, V newParent ) {
167 
168         assert newParentKey != null;
169         assert newParent != null;
170 
171         if ( !items.containsKey( newParent ) ) {
172             this.addNode( newParentKey, newParent );
173         }
174 
175         assert key != null;
176 
177         this.addChildTo( key, newParentKey );
178         this.addParentTo( newParentKey, key );
179     }
180 
181     /*
182      * (non-Javadoc)
183      * 
184      * @see ubic.basecode.dataStructure.graph.AbstractGraph#containsKey(java.lang.Object)
185      */
186     @Override
187     public boolean containsKey( K key ) {
188         if ( key == null ) return false;
189         return getItems().containsKey( key );
190     }
191 
192     /**
193      * @param leaf the key for the node. If it is not leaf, nothing will happen.
194      */
195     public void deleteLeaf( K leaf ) {
196         assert leaf != null;
197         DirectedGraphNode<K, V> leafNode = this.get( leaf );
198         if ( leafNode == null ) {
199             throw new IllegalArgumentException( "No such node: " + leaf );
200         }
201 
202         if ( !leafNode.isLeaf() ) {
203             return;
204         }
205 
206         Set<DirectedGraphNode<K, V>> parents = leafNode.getAllParentNodes();
207 
208         items.remove( leaf );
209 
210         for ( DirectedGraphNode<K, V> node : parents ) {
211             node.prune();
212         }
213 
214     }
215 
216     /*
217      * (non-Javadoc)
218      * 
219      * @see ubic.basecode.dataStructure.graph.AbstractGraph#getItems()
220      */
221     @Override
222     public Map<K, DirectedGraphNode<K, V>> getItems() {
223         return this.items;
224     }
225 
226     /**
227      * @return root of the tree.
228      */
229     public DirectedGraphNode<K, V> getRoot() {
230         this.topoSort();
231         List<DirectedGraphNode<K, V>> nodes = new ArrayList<DirectedGraphNode<K, V>>( items.values() );
232         Collections.sort( nodes );
233         return nodes.get( 0 );
234     }
235 
236     public DefaultTreeModel getTreeModel() {
237         if ( treeView == null ) this.treeView();
238         return dtm;
239     }
240 
241     /**
242      * Get all the values in thee graph.
243      * 
244      * @return
245      */
246     public Collection<V> getValues() {
247         Collection<V> result = new HashSet<V>();
248         for ( K element : this.items.keySet() ) {
249             DirectedGraphNode<K, V> v = items.get( element );
250             result.add( v.getItem() );
251         }
252         return result;
253     }
254 
255     /**
256      * Remove edges to nodes that aren't in the graph.
257      */
258     public void prune() {
259         for ( K element : this.items.keySet() ) {
260             assert element != null;
261             DirectedGraphNode<K, V> v = items.get( element );
262             v.prune();
263         }
264     }
265 
266     /**
267      * Fills in the topoSortOrder for each node.
268      */
269     public void topoSort() {
270         Queue<DirectedGraphNode<K, V>> q = new LinkedList<DirectedGraphNode<K, V>>();
271         int counter = 0;
272 
273         Map<DirectedGraphNode<K, V>, Integer> degrees = new HashMap<DirectedGraphNode<K, V>, Integer>();
274 
275         /* Get the degrees of all items, and enqueue zero-indegree nodes */
276         for ( K element : this.items.keySet() ) {
277             DirectedGraphNode<K, V> v = items.get( element );
278             degrees.put( v, new Integer( v.inDegree() ) );
279             if ( degrees.get( v ).intValue() == 0 ) {
280                 q.offer( v );
281             }
282         }
283 
284         while ( !q.isEmpty() ) {
285             DirectedGraphNode<K, V> v = q.remove();
286             v.setTopoSortOrder( ++counter );
287             for ( DirectedGraphNode<K, V> w : v.getChildNodes() ) {
288                 /* decrement the degree of this node */
289                 assert w != null;
290                 assert degrees.containsKey( w );
291 
292                 Integer inDegree = degrees.get( w );
293                 inDegree--;
294                 degrees.put( w, inDegree );
295 
296                 /* see if this now is one of the zero-indegree nodes */
297                 if ( inDegree == 0 ) {
298                     q.offer( w );
299                 }
300             }
301         }
302 
303         if ( counter != items.size() ) {
304             throw new IllegalStateException(
305                     "Graph contains a cycle? " + counter + " items found, " + items.size() + " expected" );
306         }
307 
308     }
309 
310     /**
311      * Shows the tree as a tabbed list. Items that have no parents are shown at the 'highest' level.
312      * 
313      * @return String
314      */
315     @Override
316     public String toString() {
317         prune();
318         this.topoSort();
319         List<DirectedGraphNode<K, V>> nodes = new ArrayList<DirectedGraphNode<K, V>>( items.values() );
320         Collections.sort( nodes );
321         DirectedGraphNode<K, V> root = nodes.get( 0 );
322         StringBuffer buf = new StringBuffer();
323         int tablevel = 0;
324         makeString( root, buf, tablevel );
325         return buf.toString();
326     }
327 
328     /**
329      * Generate a JTree corresponding to this graph.
330      * 
331      * @return javax.swing.JTree
332      */
333     public JTree treeView() {
334         if ( treeView == null ) {
335             throw new IllegalStateException(
336                     "You must call treeview(Class<? extends DefaultMutableTreeNode> nodeClass ) first" );
337         }
338         return treeView;
339     }
340 
341     /**
342      * Generate a JTree corresponding to this graph.
343      * 
344      * @param nodeClass The class to be used for TreeNodes. Must provide a constructor that takes a DirectedGraphNode
345      *        (the root)
346      * @return javax.swing.JTree
347      */
348     @SuppressWarnings("unchecked")
349     public JTree treeView( Class<? extends DefaultMutableTreeNode> nodeClass ) {
350         log.debug( "Constructing tree view of graph" );
351         DirectedGraphNode<K, V> root = getRoot();
352         Constructor<DefaultMutableTreeNode> constructor;
353         DefaultMutableTreeNode top = null;
354         treeView = null;
355         try {
356             constructor = ( Constructor<DefaultMutableTreeNode> ) nodeClass
357                     .getConstructor( new Class[] { root.getClass() } );
358             top = constructor.newInstance( new Object[] { root } );
359             log.debug( "Starting tree with: " + top.getClass().getName() );
360             root.mark();
361             addJTreeNode( top, root, constructor );
362             dtm = new DefaultTreeModel( top );
363             treeView = new JTree( dtm );
364 
365         } catch ( Exception e ) {
366             throw new RuntimeException( e );
367         }
368         return treeView;
369     }
370 
371     /**
372      * @param startJTreeNode
373      * @param startNode
374      * @param constructor
375      */
376     private void addJTreeNode( DefaultMutableTreeNode startJTreeNode, DirectedGraphNode<K, V> startNode,
377             Constructor<DefaultMutableTreeNode> constructor ) {
378         if ( startJTreeNode == null ) return;
379         Set<DirectedGraphNode<K, V>> children = startNode.getChildNodes();
380 
381         for ( Iterator<DirectedGraphNode<K, V>> it = children.iterator(); it.hasNext(); ) {
382             DirectedGraphNode<K, V> nextNode = it.next();
383             if ( !nextNode.isVisited() ) {
384                 DefaultMutableTreeNode newJTreeNode = null;
385                 try {
386                     newJTreeNode = constructor.newInstance( new Object[] { nextNode } );
387                 } catch ( Exception e ) {
388                     log.error( e.getMessage(), e );
389                 }
390                 startJTreeNode.add( newJTreeNode );
391                 nextNode.mark();
392                 this.addJTreeNode( newJTreeNode, nextNode, constructor );
393             }
394         }
395     }
396 
397     /*
398      * Helper for toString. Together with toString, demonstrates how to iterate over the tree
399      */
400     private String makeString( DirectedGraphNode<K, V> startNode, StringBuffer buf, int tabLevel ) {
401 
402         if ( buf == null ) {
403             buf = new StringBuffer();
404         }
405 
406         Set<DirectedGraphNode<K, V>> children = startNode.getChildNodes();
407 
408         if ( !startNode.isVisited() ) {
409             buf.append( startNode + "\n" );
410             startNode.mark();
411         }
412         tabLevel++;
413         for ( Iterator<DirectedGraphNode<K, V>> it = children.iterator(); it.hasNext(); ) {
414             DirectedGraphNode<K, V> f = it.next();
415             if ( !f.isVisited() ) {
416                 for ( int i = 0; i < tabLevel; i++ ) {
417                     buf.append( "\t" );
418                 }
419                 buf.append( f + "\n" );
420                 f.mark();
421                 this.makeString( f, buf, tabLevel );
422             }
423         }
424 
425         return buf.toString();
426     }
427 
428 }