/*********************************************************************************************************************** * Copyright (C) 2010-2013 by the Stratosphere project (http://stratosphere.eu) * * 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 eu.stratosphere.nephele.executiongraph; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import java.util.Stack; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * This class provides an implementation of the {@link Iterator} interface which allows to * traverse an execution graph and visit every reachable vertex exactly once. The order * in which the vertices are visited corresponds to the order of their discovery in a depth first * search. * <p> * This class is not thread-safe. * */ public class ExecutionGraphIterator implements Iterator<ExecutionVertex> { /** * The log object used for debugging. */ private static final Log LOG = LogFactory.getLog(ExecutionGraphIterator.class); /** * The execution this iterator traverses. */ private final ExecutionGraph executionGraph; /** * Stores whether the graph is traversed starting from the input or the output vertices. */ private final boolean forward; /** * The stage that should be traversed by this iterator. */ private final int startStage; /** * Stores whether the iterator is confined to the start stage or not. */ private final boolean confinedToStage; /** * The number of visited vertices from the entry set (either input or output vertices). */ private int numVisitedEntryVertices = 0; /** * Stack used for the depth first search. */ private final Stack<TraversalEntry> traversalStack = new Stack<TraversalEntry>(); /** * Set of already visited vertices during traversal. */ private final Set<ExecutionVertex> alreadyVisited = new HashSet<ExecutionVertex>(); /** * Auxiliary class which stores which vertices have already been visited. * */ private static class TraversalEntry { /** * Execution vertex this entry has been created for. */ private final ExecutionVertex executionVertex; /** * Next gate to traverse. */ private int currentGate; /** * Next channel to traverse. */ private int currentChannel; /** * Constructs a new traversal entry. * * @param executionVertex * the execution vertex this entry belongs to * @param currentGate * the gate index to use to visit the next vertex * @param currentChannel * the channel index to use to visit the next vertex */ public TraversalEntry(final ExecutionVertex executionVertex, final int currentGate, final int currentChannel) { this.executionVertex = executionVertex; this.currentGate = currentGate; this.currentChannel = currentChannel; } /** * Returns the execution vertex this entry belongs to. * * @return the execution vertex this entry belongs to */ public ExecutionVertex getExecutionVertex() { return this.executionVertex; } /** * Returns the gate index to use to visit the next vertex. * * @return the gate index to use to visit the next vertex */ public int getCurrentGate() { return this.currentGate; } /** * Returns the channel index to use to visit the next vertex. * * @return the channel index to use to visit the next vertex */ public int getCurrentChannel() { return this.currentChannel; } /** * Increases the channel index by one. */ public void increaseCurrentChannel() { this.currentChannel++; } /** * Increases the gate index by one. */ public void increaseCurrentGate() { this.currentGate++; } /** * Resets the channel index. */ public void resetCurrentChannel() { this.currentChannel = 0; } } /** * Creates a new execution graph iterator. * * @param executionGraph * the execution graph that should be traversed * @param forward * <code>true</code> if the graph should be traversed in correct order, <code>false</code> to traverse it in * reverse order */ public ExecutionGraphIterator(final ExecutionGraph executionGraph, final boolean forward) { this(executionGraph, forward ? 0 : (executionGraph.getNumberOfStages() - 1), false, forward); } /** * Creates a new execution graph iterator. * * @param executionGraph * the execution graph that should be traversed * @param startStage * the index of the stage of the graph where the traversal is supposed to begin * @param confinedToStage * <code>false</code> if the graph iterator is allowed to traverse to upper (in case of reverse order * traversal lower) stages, <code>true</code> otherwise. * @param forward * <code>true</code> if the graph should be traversed in correct order, <code>false</code> to traverse it in * reverse order */ public ExecutionGraphIterator(final ExecutionGraph executionGraph, final int startStage, final boolean confinedToStage, final boolean forward) { this.executionGraph = executionGraph; this.forward = forward; this.startStage = startStage; this.confinedToStage = confinedToStage; if (startStage >= this.executionGraph.getNumberOfStages()) { return; } if (forward) { if (executionGraph.getNumberOfInputVertices(startStage) > 0) { final TraversalEntry te = new TraversalEntry(executionGraph.getInputVertex(startStage, 0), 0, 0); this.traversalStack.push(te); this.alreadyVisited.add(te.getExecutionVertex()); } } else { if (executionGraph.getNumberOfOutputVertices(startStage) > 0) { final TraversalEntry te = new TraversalEntry(executionGraph.getOutputVertex(startStage, 0), 0, 0); this.traversalStack.push(te); this.alreadyVisited.add(te.getExecutionVertex()); } } } /** * Creates a new execution graph iterator. This constructor can be used to * traverse only specific parts of the graph starting at <code>startVertex</code>. * The iterator will not switch to the next input/output vertex of an output/input vertex * has been reached. * * @param executionGraph * the execution graph that should be traversed * @param startVertex * the vertex to start the traversal from * @param forward * <code>true</code> if the graph should be traversed in correct order, <code>false</code> to reverse it in * reverse order */ public ExecutionGraphIterator(final ExecutionGraph executionGraph, final ExecutionVertex startVertex, final boolean forward) { this.executionGraph = executionGraph; this.forward = forward; this.numVisitedEntryVertices = -1; this.startStage = 0; this.confinedToStage = false; final TraversalEntry te = new TraversalEntry(startVertex, 0, 0); this.traversalStack.push(te); this.alreadyVisited.add(startVertex); } @Override public boolean hasNext() { if (this.traversalStack.isEmpty()) { if (this.numVisitedEntryVertices < 0) { // User chose a specific starting vertex return false; } ++this.numVisitedEntryVertices; if (this.forward) { if (this.executionGraph.getNumberOfInputVertices(this.startStage) <= this.numVisitedEntryVertices) { return false; } } else { if (this.executionGraph.getNumberOfOutputVertices(this.startStage) <= this.numVisitedEntryVertices) { return false; } } } return true; } @Override public ExecutionVertex next() { if (this.traversalStack.isEmpty()) { if (this.numVisitedEntryVertices < 0) { // User chose a specific entry vertex return null; } TraversalEntry newentry; if (this.forward) { newentry = new TraversalEntry(this.executionGraph.getInputVertex(this.startStage, this.numVisitedEntryVertices), 0, 0); } else { newentry = new TraversalEntry(this.executionGraph.getOutputVertex(this.startStage, this.numVisitedEntryVertices), 0, 0); } this.traversalStack.push(newentry); this.alreadyVisited.add(newentry.getExecutionVertex()); } final ExecutionVertex returnVertex = this.traversalStack.peek().getExecutionVertex(); // Propose vertex to be visited next do { final TraversalEntry te = this.traversalStack.peek(); // Check if we can traverse deeper into the graph final ExecutionVertex candidateVertex = getCandidateVertex(te, forward); if (candidateVertex == null) { // Pop it from the stack this.traversalStack.pop(); } else { // Create new entry and put it on the stack final TraversalEntry newte = new TraversalEntry(candidateVertex, 0, 0); this.traversalStack.push(newte); this.alreadyVisited.add(candidateVertex); break; } } while (!this.traversalStack.isEmpty()); return returnVertex; } /** * Returns a candidate vertex which could potentially be visited next because it is reachable from the * currently considered vertex. * * @param te * the traversal entry for the current source vertex * @param forward * <code>true</code> if the graph should be traversed in correct order, <code>false</code> to traverse it in * reverse order * @return a candidate vertex which could potentially be visited next */ private ExecutionVertex getCandidateVertex(final TraversalEntry te, final boolean forward) { if (forward) { while (true) { if (this.confinedToStage && te.getCurrentChannel() == 0) { while (currentGateLeadsToOtherStage(te, this.forward)) { te.increaseCurrentGate(); } } // No more outgoing edges to consider if (te.getCurrentGate() >= te.getExecutionVertex().getNumberOfOutputGates()) { break; } if (te.getCurrentChannel() >= te.getExecutionVertex().getOutputGate(te.getCurrentGate()) .getNumberOfEdges()) { te.increaseCurrentGate(); te.resetCurrentChannel(); } else { final ExecutionEdge outputChannel = te.getExecutionVertex().getOutputGate(te.getCurrentGate()) .getEdge(te.getCurrentChannel()); final ExecutionVertex tmp = outputChannel.getInputGate().getVertex(); if (tmp == null) { LOG.error("Inconsistency in vertex map found (forward)!"); } te.increaseCurrentChannel(); if (!this.alreadyVisited.contains(tmp)) { return tmp; } } } } else { while (true) { // No more outgoing edges to consider if (te.getCurrentGate() >= te.getExecutionVertex().getNumberOfInputGates()) { break; } if (te.getCurrentChannel() >= te.getExecutionVertex().getInputGate(te.getCurrentGate()) .getNumberOfEdges()) { te.increaseCurrentGate(); te.resetCurrentChannel(); } else { final ExecutionEdge inputChannel = te.getExecutionVertex().getInputGate(te.getCurrentGate()).getEdge(te.getCurrentChannel()); final ExecutionVertex tmp = inputChannel.getOutputGate().getVertex(); if (tmp == null) { LOG.error("Inconsistency in vertex map found (backward)!"); } te.increaseCurrentChannel(); if (!this.alreadyVisited.contains(tmp)) { return tmp; } } } } return null; } private boolean currentGateLeadsToOtherStage(final TraversalEntry te, final boolean forward) { final ExecutionGroupVertex groupVertex = te.getExecutionVertex().getGroupVertex(); if (forward) { if (te.getCurrentGate() >= groupVertex.getNumberOfForwardLinks()) { return false; } final ExecutionGroupEdge edge = groupVertex.getForwardEdge(te.getCurrentGate()); if (edge.getTargetVertex().getStageNumber() == groupVertex.getStageNumber()) { return false; } } else { if (te.getCurrentGate() >= groupVertex.getNumberOfBackwardLinks()) { return false; } final ExecutionGroupEdge edge = groupVertex.getBackwardEdge(te.getCurrentGate()); if (edge.getSourceVertex().getStageNumber() == groupVertex.getStageNumber()) { return false; } } return true; } @Override public void remove() { throw new UnsupportedOperationException("The method remove is not implemented for this type of iterator"); } }