/** * Copyright 2007-2008 University Of Southern California * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package edu.isi.pegasus.planner.partitioner.graph; import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; import edu.isi.pegasus.common.logging.LogManager; import edu.isi.pegasus.common.logging.LogManagerFactory; import edu.isi.pegasus.planner.classes.NameValue; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; /** * An implementation of the Graph that is backed by a Map. * * @author Karan Vahi vahi@isi.edu * @version $Revision$ */ public class MapGraph implements Graph{ /** * The map indexed by the id of the <code>GraphNode</code>, used for storing * the nodes of the Graph. The value for each key is the corresponding * <code>GraphNode</code> of the class. * */ @Expose @SerializedName( "jobs" ) protected Map mStore; /** * The handle to the logging manager. */ private LogManager mLogger; /** * Handle to the cycle checker */ private CycleChecker mCycleChecker; /** * The default constructor. */ public MapGraph(){ this( false ); } /** * The overloaded constructor * * @param preserveInsertionOrder a boolean indicating whether you want to * preserve insertion order. If set to true, the * nodeIterator() will return nodes in the order * they were added. */ public MapGraph( boolean preserveInsertionOrder ){ mStore = ( preserveInsertionOrder ) ? new LinkedHashMap(): new HashMap(); mLogger = LogManagerFactory.loadSingletonInstance(); mCycleChecker = new CycleChecker(this); } /** * Adds a node to the Graph. It overwrites an already existing node with the * same ID. * * @param node the node to be added to the Graph. */ public void addNode( GraphNode node ){ mStore.put( node.getID(), node ); } /** * Returns the node matching the id passed. * * @param identifier the id of the node. * * @return the node matching the ID else null. */ public GraphNode getNode( String identifier ){ Object obj = get ( identifier ); return ( obj == null ) ? null : (GraphNode)obj; } /** * Adds a single root node to the Graph. All the exisitng roots of the * Graph become children of the root. * * @param root the <code>GraphNode</code> to be added as a root. * * @throws RuntimeException if a node with the same id already exists. */ public void addRoot( GraphNode root ){ //sanity check if( mStore.containsKey( root.getID() ) ){ throw new RuntimeException( "Node with ID already exists:" + root.getID() ); } List existingRoots = getRoots(); root.setChildren( existingRoots ); //for existing root nodes, add a parent as the new Root for( Iterator it = existingRoots.iterator(); it.hasNext(); ){ GraphNode existing = ( GraphNode ) it.next(); existing.addParent( root ); } //add the new root into the graph addNode( root ); } /** * Resets all the dependencies in the Graph, while preserving the nodes. * The resulting Graph is a graph of independent nodes. */ public void resetEdges(){ for(Iterator<GraphNode> it = this.nodeIterator(); it.hasNext(); ){ GraphNode node = it.next(); node.resetEdges(); } } /** * Removes a node from the Graph. * * @param identifier the id of the node to be removed. * * @return boolean indicating whether the node was removed or not. */ public boolean remove( String identifier ){ Object obj = get( identifier ); if ( obj == null ){ //node does not exist only. return false; } GraphNode removalNode = ( GraphNode )obj; // the parents of the node now become parents of the children // the parents of the node now become parents of the children Collection<GraphNode> parents = removalNode.getParents(); Collection<GraphNode> children = removalNode.getChildren(); //for each parent make the parent it's parent instead of removed node for ( GraphNode child : children ){ child.removeParent( removalNode ); } for( GraphNode parent : parents ){ //for the parent the removal node is no longer a parent parent.removeChild( removalNode ); //for each parent make the parent it's parent instead of removed node for ( GraphNode child : children ){ child.addParent( parent ); parent.addChild( child ); } } //we have the correct linkages now //remove the node from the store. mStore.remove( identifier ); return true; } /** * Returns the root nodes of the Graph. * * @return a list containing <code>GraphNode</code> corressponding to the * root nodes. */ public List<GraphNode> getRoots(){ List rootNodes = new LinkedList(); for( Iterator it = mStore.entrySet().iterator(); it.hasNext(); ){ GraphNode gn = (GraphNode)( (Map.Entry)it.next()).getValue(); if(gn.getParents() == null || gn.getParents().isEmpty()){ rootNodes.add(gn); } } return rootNodes; //Not Generating a dummy node //add a dummy node that is a root to all these nodes. // String rootId = this.DUMMY_NODE_ID; // mRoot = new GraphNode(rootId,rootId); // mRoot.setChildren(rootNodes); // put(rootId,mRoot); //System.out.println(dummyNode); } /** * Returns the leaf nodes of the Graph. * * @return a list containing <code>GraphNode</code> corressponding to the * leaf nodes. */ public List<GraphNode> getLeaves(){ List leaves = new LinkedList(); for( Iterator it = mStore.entrySet().iterator(); it.hasNext(); ){ GraphNode gn = (GraphNode)( (Map.Entry)it.next()).getValue(); if( gn.getChildren() == null || gn.getChildren().isEmpty() ){ leaves.add(gn); } } return leaves; } /** * Adds an edge between two already existing nodes in the graph. * * @param parent the parent node ID. * @param parent the parent node ID. */ public void addEdge( String parent, String child ){ //sanity check if( parent.equals( child )){ throw new IllegalArgumentException( "Invalid Edge Specification. An Edge specified from a node to itself for " + parent ); } GraphNode childNode = (GraphNode)getNode( child ); GraphNode parentNode = (GraphNode)getNode( parent ); String notExist = ( childNode == null )? child : ( parentNode == null ) ? parent : null; if ( notExist != null ) { /* should be replaced by Graph Exception */ throw new RuntimeException( "The node with identifier doesnt exist " + notExist ); } this.addEdge(parentNode, childNode); } /** * Adds an edge between two already existing nodes in the graph. * * @param parent the parent node . * @param child the child node . */ public void addEdge( GraphNode parent, GraphNode child ){ child.addParent( parent ); parent.addChild( child); } /** * A convenience method that allows for bulk addition of edges between * already existing nodes in the graph. * * @param parent the parent node ID * @param parents list of parent identifiers as <code>String</code>. */ public void addEdges( String child, List parents ){ //sanity check if( parents.contains( child )){ throw new IllegalArgumentException( "Invalid Edge Specification. Parents " + parents + " include the child " + child ); } GraphNode childNode = (GraphNode)getNode( child ); if( childNode == null ) { /* should be replaced by Graph Exception */ throw new RuntimeException( "The node with identifier doesnt exist " + child ); } String parentId; List parentList = new LinkedList(); //construct the references to the parent nodes for( Iterator it = parents.iterator(); it.hasNext(); ){ parentId = ( String )it.next(); GraphNode parentNode = (GraphNode)get( parentId ); if( parentNode == null ) { /* should be replaced by Graph Exception */ throw new RuntimeException( "The node with identifier doesnt exist " + parentId ); } parentList.add( parentNode ); //add the parent to the parent's parent list parentNode.addChild( childNode ); } childNode.setParents( parentList ); } /** * Returns the number of nodes in the graph. * * @return the number of nodes */ public int size(){ return mStore.values().size(); } /** * Returns an iterator for the nodes in the Graph. * * @return Iterator */ public Iterator nodeIterator(){ return mStore.values().iterator(); } /** * Returns an iterator that traverses through the graph using a graph * traversal algorithm. At any one time, only one iterator can * iterate through the graph. * * @return Iterator through the nodes of the graph. */ public Iterator iterator(){ return new MapGraphIterator(); } /** * Returns an iterator that traverses the graph bottom up from the leaves. * At any one time, only one iterator can * iterate through the graph. * * @return Iterator through the nodes of the graph. */ public Iterator bottomUpIterator(){ return new BottomUpIterator(); } /** * Returns an iterator for the graph that traverses in topological sort * order. * * @return Iterator through the nodes of the graph. */ public Iterator<GraphNode> topologicalSortIterator(){ return new TopologicalSortIterator( this ); } /** * Returns a boolean indicating whether a graph has cyclic edges or not. * * @return boolean */ public boolean hasCycles(){ return this.mCycleChecker.hasCycles(); } /** * Returns the detected cyclic edge if , hasCycles returns true * * @return */ public NameValue getCyclicEdge(){ return this.mCycleChecker.getCyclicEdge(); } /** * The textual representation of the graph node. * * @return textual description. */ public String toString() { String newLine = System.getProperty( "line.separator", "\r\n" ); String indent = "\t"; StringBuffer sb = new StringBuffer( 32 ); GraphNode node; for( Iterator it = nodeIterator(); it.hasNext(); ){ node = ( GraphNode )it.next(); sb.append( newLine ).append( indent ).append( "Job ->" ).append( node.getID() ); //write out the node children sb.append(" Children's {"); for( Iterator it1 = node.getChildren().iterator(); it1.hasNext(); ){ sb.append( ((GraphNode)it1.next()).getID() ).append(','); } sb.append("}"); //write out the node's parents sb.append(" Parents {"); for( Iterator it1 = node.getParents().iterator(); it1.hasNext(); ){ sb.append( ((GraphNode)it1.next()).getID() ).append(','); } sb.append("}"); } return sb.toString(); } /** * Returns a boolean if there are no nodes in the graph. * * @return boolean */ public boolean isEmpty(){ return this.mStore.isEmpty(); } /** * Returns a copy of the object. * * @return clone of the object. */ public Object clone(){ return new java.lang.CloneNotSupportedException( "Clone() not implemented in GraphNode"); } /** * It returns the value associated with the key in the map. * * @param key the key to the entry in the map. */ public Object get( Object key ){ return mStore.get(key); } public void setGraphNodeReference(GraphNode node) { //noop } /** * An inner iterator class that traverses through the Graph. * The traversal of the graph is a modified BFS. A node is added to * the queue only when all it's parents have been added to the queue. */ protected class MapGraphIterator implements Iterator{ /** * The first in first out queue, that manages the set of gray vertices in a * breadth first search. */ private LinkedList mQueue; /** * The current depth of the nodes that are being traversed in the BFS. */ private int mCurrentDepth; /** * A temporary list that stores all the nodes on a particular level. */ private List mLevelList; /** * The default constructor. */ public MapGraphIterator(){ mQueue = new LinkedList(); mLevelList = new LinkedList(); mCurrentDepth = -1; //sanity intialization of all nodes depth for( Iterator it = nodeIterator(); it.hasNext(); ){ GraphNode node = ( GraphNode )it.next(); node.setDepth( mCurrentDepth ); node.setColor( GraphNode.WHITE_COLOR ); } //intialize all the root nodes depth to 0 //and put them in the queue mCurrentDepth = 0; for( Iterator it = getRoots().iterator(); it.hasNext(); ){ GraphNode node = ( GraphNode )it.next(); node.setDepth( mCurrentDepth ); mQueue.add( node ); } } /** * Always returns false, as an empty iterator. * * @return true if there are still nodes in the queue. */ public boolean hasNext(){ return !mQueue.isEmpty(); } /** * Returns the next object in the traversal order. * * @return null */ public Object next(){ GraphNode node = (GraphNode)mQueue.getFirst(); int depth = node.getDepth(); if( mCurrentDepth < depth ){ if( mCurrentDepth > 0 ){ //we are done with one level! //that is when the callback should happen } //a new level starts mCurrentDepth++; mLevelList.clear(); } //mLogger.log( "Adding to level " + mCurrentDepth + " " + node.getID(), // LogManager.DEBUG_MESSAGE_LEVEL); mLevelList.add( node ); node.setColor( GraphNode.BLACK_COLOR ); //add the children to the list only if all the parents //of the parent nodes have been traversed. for( Iterator it = node.getChildren().iterator(); it.hasNext(); ){ GraphNode child = (GraphNode)it.next(); if(!child.isColor( GraphNode.GRAY_COLOR ) && child.parentsColored( GraphNode.BLACK_COLOR )){ //mLogger.log( "Adding to queue " + parent.getID(), // LogManager.DEBUG_MESSAGE_LEVEL ); child.setDepth( depth + 1 ); child.setColor( GraphNode.GRAY_COLOR ); mQueue.addLast( child ); } } node = (GraphNode)mQueue.removeFirst(); //mLogger.log( "Removed " + node.getID(), // LogManager.DEBUG_MESSAGE_LEVEL); return node; } /** * Method is not supported. */ public void remove(){ throw new java.lang.UnsupportedOperationException( "Method remove() not supported" ); } }//end of internal iterator class /** * An inner iterator class that traverses through the Graph bottom up. * The traversal of the graph is a modified BFS. A node is added to * the queue only when all it's parents children been added to the queue. */ protected class BottomUpIterator implements Iterator{ /** * The first in first out queue, that manages the set of gray vertices in a * breadth first search. */ private LinkedList mQueue; /** * The current height of the nodes that are being traversed in the BFS starting from * the leaf. */ private int mCurrentDepth; /** * A temporary list that stores all the nodes on a particular level. */ private List mLevelList; /** * The default constructor. */ public BottomUpIterator(){ mQueue = new LinkedList(); mLevelList = new LinkedList(); mCurrentDepth = -1; //sanity intialization of all nodes depth for( Iterator it = nodeIterator(); it.hasNext(); ){ GraphNode node = ( GraphNode )it.next(); node.setDepth( mCurrentDepth ); node.setColor( GraphNode.WHITE_COLOR ); } //intialize all the root nodes depth to 0 //and put them in the queue mCurrentDepth = 0; for( Iterator it = getLeaves().iterator(); it.hasNext(); ){ GraphNode node = ( GraphNode )it.next(); node.setDepth( mCurrentDepth ); mQueue.add( node ); } } /** * Always returns false, as an empty iterator. * * @return true if there are still nodes in the queue. */ public boolean hasNext(){ return !mQueue.isEmpty(); } /** * Returns the next object in the traversal order. * * @return null */ public Object next(){ GraphNode node = (GraphNode)mQueue.getFirst(); int depth = node.getDepth(); if( mCurrentDepth < depth ){ if( mCurrentDepth > 0 ){ //we are done with one level! //that is when the callback should happen } //a new level starts mCurrentDepth++; mLevelList.clear(); } //mLogger.log( "Adding to level " + mCurrentDepth + " " + node.getID(), // LogManager.DEBUG_MESSAGE_LEVEL); mLevelList.add( node ); node.setColor( GraphNode.BLACK_COLOR ); //add the parents to the list only if all the children //of the parent nodes have been traversed. for( GraphNode parent : node.getParents()){ if(!parent.isColor( GraphNode.GRAY_COLOR ) && parent.childrenColored( GraphNode.BLACK_COLOR )){ //mLogger.log( "Adding to queue " + parent.getID(), // LogManager.DEBUG_MESSAGE_LEVEL ); parent.setDepth( depth + 1 ); parent.setColor( GraphNode.GRAY_COLOR ); mQueue.addLast( parent ); } } node = (GraphNode)mQueue.removeFirst(); //mLogger.log( "Removed " + node.getID(), // LogManager.DEBUG_MESSAGE_LEVEL); return node; } /** * Method is not supported. */ public void remove(){ throw new java.lang.UnsupportedOperationException( "Method remove() not supported" ); } }//end of internal iterator class }