/***************************************************************************
* Copyright (C) 2012 by H-Store Project *
* Brown University *
* Massachusetts Institute of Technology *
* Yale University *
* *
* http://hstore.cs.brown.edu/ *
* *
* Permission is hereby granted, free of charge, to any person obtaining *
* a copy of this software and associated documentation files (the *
* "Software"), to deal in the Software without restriction, including *
* without limitation the rights to use, copy, modify, merge, publish, *
* distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to *
* the following conditions: *
* *
* The above copyright notice and this permission notice shall be *
* included in all copies or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, *
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF *
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. *
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR *
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, *
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR *
* OTHER DEALINGS IN THE SOFTWARE. *
***************************************************************************/
package edu.brown.utils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.Stack;
import org.apache.commons.pool.BasePoolableObjectFactory;
import org.apache.log4j.Logger;
import edu.brown.pools.FastObjectPool;
import edu.brown.pools.Poolable;
/**
* Generic abstract class that can be used to build different traversal programs
* for various types of tree/graph structures
* @author pavlo
* @param <E> element type
*/
public abstract class AbstractTreeWalker<E> implements Poolable {
private static final Logger LOG = Logger.getLogger(AbstractTreeWalker.class);
private static final Map<Class<?>, FastObjectPool<?>> CHILDREN_POOLS = new HashMap<Class<?>, FastObjectPool<?>>();
/**
* Children Object Pool Factory
*/
private static class ChildrenFactory<E> extends BasePoolableObjectFactory {
@Override
public Object makeObject() throws Exception {
Children<E> c = new Children<E>();
return (c);
}
public void passivateObject(Object obj) throws Exception {
Children<?> c = (Children<?>) obj;
c.finish();
};
};
/**
* The children data structure keeps track of the elements that we need to
* visit before and after each element in the tree
*/
public static class Children<E> implements Poolable {
private E parent;
private final Queue<E> before_list = new LinkedList<E>();
private final Queue<E> after_list = new LinkedList<E>();
private Children() {
// Nothing...
}
public void init(E parent) {
this.parent = parent;
}
@Override
public boolean isInitialized() {
return (this.parent != null);
}
@Override
public void finish() {
this.parent = null;
this.before_list.clear();
this.after_list.clear();
}
public Queue<E> getBefore() {
return (this.before_list);
}
public void addBefore(E child) {
if (child != null) {
// assert(child.equals(parent) == false) : "Trying to add " +
// parent + " as a child of itself";
this.before_list.add(child);
}
}
public void addBefore(Collection<E> children) {
if (children != null) {
for (E child : children) {
this.addBefore(child);
} // FOR
}
}
public void addBefore(E children[]) {
for (E child : children) {
this.addBefore(child);
} // FOR
}
public Queue<E> getAfter() {
return (this.after_list);
}
public void addAfter(E child) {
if (child != null) {
// assert(child.equals(parent) == false) : "Trying to add " +
// parent + " as a child of itself";
this.after_list.add(child);
}
}
public void addAfter(Collection<E> children) {
for (E child : children) {
this.addAfter(child);
} // FOR
}
public void addAfter(E children[]) {
if (children != null) {
for (E child : children) {
this.addAfter(child);
} // FOR
}
}
@Override
public String toString() {
String ret = this.parent.toString() + ": ";
ret += "BEFORE" + this.before_list + " ";
ret += "AFTER" + this.after_list;
return (ret);
}
}
/**
* Tree Element Stack
*/
private final Stack<E> stack = new Stack<E>();
/**
* List of elements that we visited (in proper order)
*/
private final List<E> visited = new ArrayList<E>();
/**
* The first element that started the traversal
*/
private E first = null;
/**
* How deep we are in the tree
*/
private int depth = -1;
/**
* Flag used to short circuit the traversal
*/
private boolean stop = false;
/**
* If set to true, then we're allowed to revisit the same node
*/
private boolean allow_revisit = false;
/**
* How many nodes we have visited
*/
private int counter = 0;
/**
* Others can attach Children to elements out-of-band so that we can do
* other types of traversal through the tree
*/
private final Map<E, Children<E>> attached_children = new HashMap<E, Children<E>>();
/**
* Cache handle to the object pool we use for the children
*/
private FastObjectPool<E> children_pool;
/**
* If we reach one of these elements, we will halt the traversal
*/
private Set<E> stop_elements;
/**
* Depth limit (for debugging)
*/
private int depth_limit = -1;
private boolean invoked_finished = false;
// ----------------------------------------------------------------------
// POOLABLE METHODS
// ----------------------------------------------------------------------
@Override
public boolean isInitialized() {
return (this.depth == -1);
}
@Override
public void finish() {
this.stack.clear();
this.visited.clear();
this.first = null;
this.depth = -1;
this.stop = false;
this.allow_revisit = false;
this.invoked_finished = false;
this.counter = 0;
this.depth_limit = -1;
if (this.stop_elements != null)
this.stop_elements.clear();
if (this.children_pool != null) {
try {
for (Children<E> c : this.attached_children.values()) {
this.children_pool.returnObject(c);
} // FOR
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
this.attached_children.clear();
this.children_pool = null;
}
// ----------------------------------------------------------------------
// UTILITY METHODS
// ----------------------------------------------------------------------
/**
* Initialize the children object pool needed by this object
*/
@SuppressWarnings("unchecked")
private void initChildrenPool(E element) {
// Grab the handle to the children object pool we'll need
Class<?> elementClass = element.getClass();
synchronized (CHILDREN_POOLS) {
this.children_pool = (FastObjectPool<E>)CHILDREN_POOLS.get(elementClass);
if (this.children_pool == null) {
this.children_pool = new FastObjectPool<E>(new ChildrenFactory<E>());
CHILDREN_POOLS.put(elementClass, this.children_pool);
}
} // SYNCH
}
/**
* Return a Children singleton for a specific element This allows us to add
* children either before our element is invoked
*
* @param element
* @return
*/
@SuppressWarnings("unchecked")
protected final Children<E> getChildren(E element) {
Children<E> c = this.attached_children.get(element);
if (c == null) {
if (this.children_pool == null)
this.initChildrenPool(element);
try {
c = (Children<E>) this.children_pool.borrowObject();
} catch (Exception ex) {
throw new RuntimeException("Failed to borrow object for " + element, ex);
}
c.init(element);
this.attached_children.put(element, c);
}
return (c);
}
/**
* Returns the parent of the current node in callback()
*
* @return
*/
protected final E getPrevious() {
E ret = null;
int size = this.stack.size();
if (size > 1)
ret = this.stack.get(size - 2);
return (ret);
}
/**
* Returns the depth of the current callback() invocation
*
* @return
*/
protected final int getDepth() {
return (this.depth);
}
/**
* Returns the current visitation stack for vertices
*
* @return
*/
protected final Stack<E> getStack() {
return (this.stack);
}
/**
* Returns true if the given element has already been visited
*
* @param element
* @return
*/
protected boolean hasVisited(E element) {
return (this.visited.contains(element));
}
/**
* Mark the given element as having been already visited
*
* @param element
*/
protected void markAsVisited(E element) {
this.visited.add(element);
}
/**
* Returns the ordered list of elements that were visited
*
* @return
*/
public List<E> getVisitPath() {
return (Collections.unmodifiableList(this.visited));
}
/**
* Toggle whether the walker is allowed to revisit a vertex more than one
*
* @param flag
*/
public void setAllowRevisit(boolean flag) {
this.allow_revisit = flag;
}
/**
* Returns the first element that initiated the traversal
*
* @return
*/
protected final E getFirst() {
return (this.first);
}
/**
* Returns the count for the number of nodes that we have visited
*
* @return
*/
public final int getCounter() {
return (this.counter);
}
/**
* Set the depth limit. Once this reached we will dump out the stack
*
* @param limit
*/
protected final void setDepthLimit(int limit) {
this.depth_limit = limit;
}
/**
* The callback() method can call this if it wants the walker to break out
* of the traversal
*/
protected synchronized final void stop() {
if (this.stop == false)
this.callback_stop();
this.stop = true;
}
/**
* Returns true is this walker has been marked as stopped
*
* @return
*/
protected final boolean isStopped() {
return (this.stop);
}
/**
* Mark an element that will cause the traversal to stop when if traverse is
* invoked with it
*
* @param element
*/
protected synchronized final void stopAtElement(E element) {
if (this.stop_elements == null) {
this.stop_elements = new HashSet<E>();
}
this.stop_elements.add(element);
}
// ----------------------------------------------------------------------
// TRAVERSAL METHODS
// ----------------------------------------------------------------------
public final void traverse(Collection<E> elements) {
for (E e : elements) {
this.traverse(e);
} // FOR
}
/**
* Depth first traversal
* @param element
*/
public final void traverse(E element) {
final boolean trace = LOG.isTraceEnabled();
assert (element != null) : "AbstractTreeWalker.innerTraverse() was passed a null element";
if (trace)
LOG.trace("innerTraverse(" + element + ")");
if (this.first == null && this.stop == false) {
assert (this.counter == 0) : "Unexpected counter value on first element [" + this.counter + "]";
this.first = element;
// Grab the handle to the children object pool we'll need
this.initChildrenPool(element);
if (trace)
LOG.trace("callback_first(" + element + ")");
this.callback_first(element);
}
// Check if we should stop here
this.stop = this.stop || (this.stop_elements != null && this.stop_elements.contains(element));
if (this.stop) {
if (trace)
LOG.trace("Stop Called. Halting traversal.");
if (this.invoked_finished == false) {
this.callback_finish();
this.invoked_finished = true;
}
return;
}
this.stack.push(element);
this.depth++;
this.counter++;
if (trace)
LOG.trace("[Stack=" + this.stack.size() + ", " + "Depth=" + this.depth + ", " + "Counter=" + this.counter + ", " + "Visited=" + this.visited.size() + "]");
// Stackoverflow check
if (this.depth_limit >= 0 && this.depth > this.depth_limit) {
LOG.fatal("Reached depth limit [" + this.depth + "]");
System.err.println(StringUtil.join("\n", Thread.currentThread().getStackTrace()));
System.exit(1);
}
if (trace)
LOG.trace("callback_before(" + element + ")");
this.callback_before(element);
if (this.stop) {
if (trace)
LOG.trace("Stop Called. Halting traversal.");
return;
}
// Get the list of children to visit before and after we call ourself
AbstractTreeWalker.Children<E> children = this.getChildren(element);
this.populate_children(children, element);
if (trace)
LOG.trace("Populate Children: " + children);
for (E child : children.before_list) {
if (this.allow_revisit || this.visited.contains(child) == false) {
if (trace)
LOG.trace("Traversing child " + child + "' before " + element);
this.traverse(child);
}
if (this.stop)
break;
} // FOR
if (this.stop) {
if (trace)
LOG.trace("Stop Called. Halting traversal.");
if (this.invoked_finished == false) {
this.callback_finish();
this.invoked_finished = true;
}
return;
}
// Why is this here and not up above when we update the stack?
this.visited.add(element);
if (trace)
LOG.trace("callback(" + element + ")");
this.callback(element);
if (this.stop) {
if (trace)
LOG.trace("Stop Called. Halting traversal.");
if (this.invoked_finished == false) {
this.callback_finish();
this.invoked_finished = true;
}
return;
}
for (E child : children.after_list) {
if (this.stop)
return;
if (this.allow_revisit || this.visited.contains(child) == false) {
if (trace)
LOG.trace("Traversing child " + child + " after " + element);
this.traverse(child);
}
if (this.stop)
break;
} // FOR
if (this.stop) {
if (trace)
LOG.trace("Stop Called. Halting traversal.");
if (this.invoked_finished == false) {
this.callback_finish();
this.invoked_finished = true;
}
return;
}
E check_exp = this.stack.pop();
assert (element.equals(check_exp)) :
String.format("%s != %s", element, check_exp);
this.callback_after(element);
if (this.stop) {
if (trace)
LOG.trace("Stop Called. Halting traversal.");
if (this.invoked_finished == false) {
this.callback_finish();
this.invoked_finished = true;
}
return;
}
this.depth--;
if (this.depth == -1) {
// We need to return all of the children here, because most
// instances are not going to be pooled,
// which means that finish() will not likely be called
try {
for (Children<E> c : this.attached_children.values()) {
this.children_pool.returnObject(c);
} // FOR
} catch (Exception ex) {
throw new RuntimeException(ex);
}
this.attached_children.clear();
if (trace)
LOG.trace("callback_last(" + element + ")");
this.callback_last(element);
if (this.invoked_finished == false) {
this.callback_finish();
this.invoked_finished = true;
}
}
return;
}
/**
* For the given element, populate the Children object with the next items
* to visit either before or after the callback method is called for the
* element
*
* @param element
*/
protected abstract void populate_children(AbstractTreeWalker.Children<E> children, E element);
// ----------------------------------------------------------------------
// CALLBACK METHODS
// ----------------------------------------------------------------------
/**
* This method will be called after the walker has explored a node's children
* @param element
* the object to perform an operation on
*/
protected abstract void callback(E element);
/**
* Optional callback method before we visit any of the node's children
* @param element
*/
protected void callback_before(E element) {
// Do nothing
}
/**
* Optional callback method after we have finished visiting all of a node's
* children and will return back to their parent.
* @param element
*/
protected void callback_after(E element) {
// Do nothing
}
/**
* Optional callback method on the very first element in the tree.
* @param element
*/
protected void callback_first(E element) {
// Do nothing
}
/**
* Optional callback method on the very last element in the tree
* @param element
*/
protected void callback_last(E element) {
// Do nothing
}
/**
* Optional callback method when stop() is called
*/
protected void callback_stop() {
// Do nothing
}
/**
* Optional callback method that is called at the very end, regardless
* of whether the traversal is ending because of stop() or because it
* reached the end of the tree
* This won't be called
*/
protected void callback_finish() {
// Do nothing
}
}