package edu.brown.graphs;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.collections15.set.ListOrderedSet;
import org.voltdb.catalog.CatalogType;
import org.voltdb.catalog.Database;
import edu.brown.catalog.CatalogKey;
import edu.brown.utils.StringUtil;
import edu.uci.ics.jung.algorithms.shortestpath.ShortestPathUtils;
import edu.uci.ics.jung.algorithms.shortestpath.UnweightedShortestPath;
import edu.uci.ics.jung.graph.util.EdgeType;
public class InnerGraphInformation<V extends AbstractVertex, E extends AbstractEdge> {
// private static final Logger LOG = Logger.getLogger(InnerGraphInformation.class);
private static final AtomicInteger NEXT_GRAPH_ID = new AtomicInteger(10000);
private enum DirtyIndex {
DESCENDANTS,
ANCESTORS,
ROOTS,
};
private Integer graph_id;
private final Database catalog_db;
private final IGraph<V, E> graph;
private final Map<String, V> catalog_vertex_xref = new HashMap<String, V>();
private final Map<Long, V> element_id_xref = new HashMap<Long, V>();
private String name;
private boolean enable_dirty_checks = false;
private final boolean dirty[];
private ListOrderedSet<V> descendants;
private List<V> ancestors;
private ListOrderedSet<V> roots;
// private transient final Map<V, Map<V, E>> cache_findEdge = new ConcurrentHashMap<V, Map<V, E>>();
/**
* Constructor
* @param graph
* @param catalog_db
*/
protected InnerGraphInformation(IGraph<V, E> graph, Database catalog_db) {
this.graph = graph;
this.catalog_db = catalog_db;
this.dirty = new boolean[DirtyIndex.values().length];
this.markAllDirty(false);
}
public Database getDatabase() {
return catalog_db;
}
public int getGraphId() {
if (this.graph_id == null) {
synchronized (this) {
if (this.graph_id == null) this.graph_id = NEXT_GRAPH_ID.getAndIncrement();
}
}
return (this.graph_id.intValue());
}
public synchronized void setGraphId(int id) {
this.graph_id = id;
NEXT_GRAPH_ID.set(this.graph_id);
}
/**
* Sets verbose output for all elements
* @param verbose
*/
public void setVerbose(boolean verbose) {
this.setEdgeVerbose(verbose);
this.setVertexVerbose(verbose);
}
public void setEdgeVerbose(boolean verbose) {
for (E e : this.graph.getEdges()) {
e.setVerbose(verbose);
} // FOR
}
public void setVertexVerbose(boolean verbose) {
for (V v : this.graph.getVertices()) {
v.setVerbose(verbose);
} // FOR
}
/**
* Enable internal book keeping for when the graph has been modified
*/
public void enableDirtyChecks() {
this.enable_dirty_checks = true;
this.markAllDirty(true);
}
/**
* Returns true if this has been modified
* @return
*/
protected boolean isDirty(DirtyIndex idx) {
return (this.enable_dirty_checks == true && this.dirty[idx.ordinal()]);
}
/**
* Reset the internal dirty bit for this graph to false
*/
protected void resetDirty(DirtyIndex idx) {
this.dirty[idx.ordinal()] = false;
}
/**
* Mark all of the internal dirty flags as the given value
*/
private void markAllDirty(boolean value) {
for (int i = 0, cnt = this.dirty.length; i < cnt; i++) {
this.dirty[i] = value;
} // FOR
}
public synchronized void addVertx(V v) {
this.catalog_vertex_xref.put(CatalogKey.createKey(v.getCatalogItem()), v);
this.element_id_xref.put(v.getElementId(), v);
if (this.enable_dirty_checks) this.markAllDirty(true);
}
public synchronized void addEdge(E e) {
if (this.enable_dirty_checks) this.markAllDirty(true);
}
// public E findEdge(V v1, V v2) {
// this.graph.
// return (this.graph.findEdge(v1, v2));
//// E e = null;
//// synchronized (v1) {
//// Map<V, E> cache = this.cache_findEdge.get(v1);
//// if (cache == null) {
//// cache = new HashMap<V, E>();
//// this.cache_findEdge.put(v1, cache);
//// }
//// e = cache.get(v2);
//// if (v2 == null) {
//// e = this.graph.findEdge(v1, v2);
//// cache.put(v2, e);
//// }
//// System.err.println(v2 + ": " + cache);
//// } // SYNCH
//// return (e);
// }
public V getVertex(String catalog_key) {
return (this.catalog_vertex_xref.get(catalog_key));
}
public V getVertex(CatalogType catalog_item) {
return (this.catalog_vertex_xref.get(CatalogKey.createKey(catalog_item)));
}
public V getVertex(Long element_id) {
return (this.element_id_xref.get(element_id));
}
public List<E> getPath(V source, V target) {
return (ShortestPathUtils.getPath(this.graph, new UnweightedShortestPath<V, E>(this.graph), source, target));
}
/**
* For the given list of vertices, return the list of edges that connect consecutive vertices in the path
* @param path
* @return
*/
public List<E> getPath(List<V> path) {
final List<E> ret = new ArrayList<E>();
V last = null;
for (V v : path) {
if (last != null) {
E e = this.graph.findEdge(last, v);
// if (e == null) return (ret);
assert(e != null) : "No edge exists between " + v + " and " + last;
ret.add(e);
}
last = v;
} // FOR
return (ret);
}
/**
* Return the set of vertices that are descedants of the given vertices
* @param vertex
* @return
*/
public synchronized Set<V> getDescendants(V vertex) {
boolean recompute = false;
// First time
if (this.descendants == null) {
this.descendants = new ListOrderedSet<V>();
recompute = true;
// Check whether it's been marked as dirty. If it hasn't, then
// we know that our cache is still valid
} else if (this.isDirty(DirtyIndex.DESCENDANTS)) {
recompute = true;
}
if (recompute) {
this.descendants.clear();
new VertexTreeWalker<V, E>(this.graph) {
@Override
protected void callback(V element) {
descendants.add(element);
}
}.traverse(vertex);
// Probably a race condition...
this.resetDirty(DirtyIndex.DESCENDANTS);
}
return (Collections.unmodifiableSet(this.descendants));
}
/**
* Get the path of ancestor vertices up to a root
* @param vertex
* @return
*/
public synchronized List<V> getAncestors(final V vertex) {
boolean recompute = false;
// First time
if (this.ancestors == null) {
this.ancestors = new ArrayList<V>();
recompute = true;
// Check whether it's been marked as dirty. If it hasn't, then
// we know that our cache is still valid
} else if (this.isDirty(DirtyIndex.ANCESTORS)) {
recompute = true;
}
if (recompute) {
this.ancestors.clear();
this.buildAncestorsList(vertex);
// Probably a race condition...
this.resetDirty(DirtyIndex.DESCENDANTS);
}
return (Collections.unmodifiableList(this.ancestors));
}
private void buildAncestorsList(final V v) {
for (V parent : this.graph.getPredecessors(v)) {
if (!this.ancestors.contains(parent)) {
this.ancestors.add(parent);
this.buildAncestorsList(parent);
}
} // FOR
return;
}
/**
* Get the lists of roots for this graph.
* This probably won't work if the graph is undirected
* @return
*/
public synchronized Set<V> getRoots() {
boolean recompute = false;
// First time
if (this.roots == null) {
this.roots = new ListOrderedSet<V>();
recompute = true;
// Check whether it's been marked as dirty. If it hasn't, then
// we know that our cache is still valid
} else if (this.isDirty(DirtyIndex.ROOTS)) {
recompute = true;
}
if (recompute) {
this.roots.clear();
for (V v : this.graph.getVertices()) {
if (this.graph.getPredecessorCount(v) == 0) {
this.roots.add(v);
}
} // FOR
// Probably a race condition
this.resetDirty(DirtyIndex.ROOTS);
}
return (Collections.unmodifiableSet(this.roots));
}
public void pruneIsolatedVertices() {
List<V> vertices = new ArrayList<V>(this.graph.getVertices());
for (V V : vertices) {
if (this.graph.getIncidentEdges(V).isEmpty()) {
this.graph.removeVertex(V);
}
} // FOR
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
Map<String, Object> m = new LinkedHashMap<String, Object>();
m.put("Vertices", StringUtil.join("\n", this.graph.getVertices()));
m.put("Edges", StringUtil.join("\n", this.graph.getEdges()));
return StringUtil.formatMaps(m);
}
public String toString(E e, boolean verbose) {
String edge_marker = null;
V source = null;
V dest = null;
// Directed
if (this.graph.getEdgeType(e) == EdgeType.DIRECTED) {
edge_marker = "->";
source = this.graph.getSource(e);
dest = this.graph.getDest(e);
// Undirected
} else {
edge_marker = "--";
for (V v : this.graph.getIncidentVertices(e)) {
if (source == null) source = v;
else if (dest == null) dest = v;
else assert(false);
} // FOR
}
assert(source != null);
assert(dest != null);
String source_lbl = source.toString();
String dest_lbl = dest.toString();
// If the edge doesn't have verbose output enabled, truncate the vertex labels
if (verbose == false) {
source_lbl = source_lbl.substring(0, 2);
dest_lbl = dest_lbl.substring(0, 2);
}
return (source_lbl + edge_marker + dest_lbl);
}
}