package edu.brown.graphs;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.log4j.Logger;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONStringer;
import org.voltdb.catalog.Database;
import edu.brown.gui.common.GraphVisualizationPanel;
import edu.brown.logging.LoggerUtil;
import edu.brown.logging.LoggerUtil.LoggerBoolean;
import edu.brown.utils.ClassUtil;
import edu.brown.utils.CollectionUtil;
import edu.brown.utils.FileUtil;
import edu.brown.utils.JSONUtil;
import edu.brown.utils.StringUtil;
import edu.brown.utils.ThreadUtil;
import edu.uci.ics.jung.graph.util.Pair;
public abstract class GraphUtil {
private static final Logger LOG = Logger.getLogger(GraphUtil.class);
private static final LoggerBoolean debug = new LoggerBoolean();
private static final LoggerBoolean trace = new LoggerBoolean();
static {
LoggerUtil.attachObserver(LOG, debug, trace);
}
public enum Members {
ID,
VERTEX_CLASS,
VERTICES,
EDGE_CLASS,
EDGES,
}
/**
* Remove all of the edges that are not incident to the given vertex
* @param graph
* @param v
* @return Returns the set of edges that were removed
*/
public static <V extends AbstractVertex, E extends AbstractEdge> Collection<E> removeEdgesWithoutVertex(IGraph<V, E> graph, V...vertices) {
if (debug.val) LOG.debug("Removing edges that are not incident to " + Arrays.toString(vertices));
Set<E> toRemove = new HashSet<E>();
for (E e : graph.getEdges()) {
boolean found = false;
for (V v : vertices) {
if (graph.isIncident(v, e)) {
found = true;
break;
}
} // FOR
if (found == false) toRemove.add(e);
} // FOR
for (E e : toRemove) {
graph.removeEdge(e);
} // FOR
return (toRemove);
}
/**
* Remove any edges where the source and destination are the same vertex
* @param graph
* @return Returns the set of edges that were removed
*/
public static <V extends AbstractVertex, E extends AbstractEdge> Collection<E> removeLoopEdges(IGraph<V, E> graph) {
Set<E> toRemove = new HashSet<E>();
for (E e : graph.getEdges()) {
Collection<V> vertices = graph.getIncidentVertices(e);
if (vertices == null) continue;
if (vertices.size() == 1) {
toRemove.add(e);
}
else if (CollectionUtil.first(vertices) == CollectionUtil.get(vertices, 1)) {
toRemove.add(e);
}
} // FOR
for (E e : toRemove) {
graph.removeEdge(e);
} // FOR
return (toRemove);
}
/**
*
* @param graph
* @return Returns the set of vertices that were removed
*/
public static <V extends AbstractVertex, E extends AbstractEdge> Collection<V> removeDisconnectedVertices(IGraph<V, E> graph) {
Set<V> toRemove = new HashSet<V>();
for (V v : graph.getVertices()) {
if (graph.getIncidentEdges(v).isEmpty()) {
toRemove.add(v);
}
} // FOR
for (V v : toRemove) {
graph.removeVertex(v);
}
return (toRemove);
}
/**
* Remove duplicate edges between every unique pair of vertices
* @param graph
*/
public static <V extends AbstractVertex, E extends AbstractEdge> void removeDuplicateEdges(IGraph<V, E> graph) {
Set<E> toRemove = new HashSet<E>();
for (V v0 : graph.getVertices()) {
for (V v1 : graph.getVertices()) {
Collection<E> edges = graph.findEdgeSet(v0, v1);
if (edges == null || edges.size() <= 1) continue;
toRemove.clear();
boolean first = true;
for (E e : edges) {
if (first) {
first = false;
} else {
toRemove.add(e);
}
} // FOR
for (E e : toRemove) {
graph.removeEdge(e);
} // FOR
} // FOR
} // FOR
}
/**
*
* @param <V>
* @param <E>
* @param graph
* @param path0
* @param path1
* @return
*/
public static <V extends AbstractVertex, E extends AbstractEdge> String comparePathsDebug(IGraph<V, E> graph, List<V> path0, List<V> path1) {
StringBuilder sb = new StringBuilder();
final String match = "\u2713";
final String conflict = "X";
int ctr0 = 0;
int cnt0 = path0.size();
int ctr1 = 0;
int cnt1 = path1.size();
while (ctr0 < cnt0 || ctr1 < cnt1) {
V v0 = null;
if (ctr0 < cnt0) v0 = path0.get(ctr0++);
V v1 = null;
if (ctr1 < cnt1) v1 = path1.get(ctr1++);
String status;
if (v0 != null && v1 != null) {
status = (v0.equals(v1) ? match : conflict);
} else {
status = conflict;
}
sb.append(String.format("| %s | %-50s | %-50s |\n", status, v0, v1));
sb.append(StringUtil.repeat("-", 110)).append("\n");
} // FOR
return (sb.toString());
}
/**
* For a given graph, write out a serialized form of it to the target output_path
* @param <V>
* @param <E>
* @param graph
* @param output_path
* @throws Exception
*/
public static <V extends AbstractVertex, E extends AbstractEdge> void save(IGraph<V, E> graph, File output_path) throws IOException {
if (debug.val) LOG.debug("Writing out graph to '" + output_path + "'");
JSONStringer stringer = new JSONStringer();
try {
stringer.object();
GraphUtil.serialize(graph, stringer);
stringer.endObject();
} catch (Exception ex) {
throw new IOException(ex);
}
String json = stringer.toString();
try {
FileUtil.writeStringToFile(output_path, JSONUtil.format(json));
} catch (Exception ex) {
throw new IOException(ex);
}
if (debug.val) LOG.debug("Graph was written out to '" + output_path + "'");
}
/**
* For a given graph, write out its contents in serialized form into the provided Stringer
* @param <V>
* @param <E>
* @param graph
* @param stringer
* @throws JSONException
*/
public static <V extends AbstractVertex, E extends AbstractEdge> void serialize(IGraph<V, E> graph, JSONStringer stringer) throws JSONException {
GraphUtil.serialize(graph, null, null, stringer);
}
/**
* For a given graph, write out its contents in serialized form into the provided Stringer
* Can provide sets of vertices or edges to not be serialized out
* @param <V>
* @param <E>
* @param graph
* @param ignore_v
* @param ignore_e
* @param stringer
* @throws JSONException
*/
@SuppressWarnings("unchecked")
public static <V extends AbstractVertex, E extends AbstractEdge> void serialize(IGraph<V, E> graph, Collection<V> ignore_v, Collection<E> ignore_e, JSONStringer stringer) throws JSONException {
int e_cnt = 0;
int v_cnt = 0;
int e_skipped = 0;
// Graph ID
stringer.key(Members.ID.name()).value(graph.getGraphId());
// Vertices
assert(graph.getVertexCount() > 0) : "Graph has no vertices";
stringer.key(Members.VERTICES.name()).array();
Class<V> v_class = null;
Set<Long> all_vertices = new HashSet<Long>();
for (V v : graph.getVertices()) {
if (ignore_v != null && ignore_v.contains(v)) continue;
if (v_class == null) {
v_class = (Class<V>)v.getClass();
if (debug.val) LOG.debug("Discovered vertex class: " + v_class.getName());
}
stringer.object();
v.toJSON(stringer);
stringer.endObject();
all_vertices.add(v.getElementId());
v_cnt++;
if (trace.val) LOG.trace("V [" + v.getElementId() + "]");
} // FOR
stringer.endArray();
stringer.key(Members.VERTEX_CLASS.name()).value(v_class.getName());
if (debug.val) LOG.debug("# of Vertices: " + v_cnt);
// Edges
if (graph.getEdgeCount() > 0) {
stringer.key(Members.EDGES.name()).array();
Class<E> e_class = null;
for (E e : graph.getEdges()) {
if (ignore_e != null && ignore_e.contains(e)) continue;
if (e_class == null) {
e_class = (Class<E>)e.getClass();
if (debug.val) LOG.debug("Discovered edge class: " + e_class.getName());
}
// Thread synchronization issue
// This is an attempt to prevent us from writing out edges that have vertices
// that were added in between the time that we originally wrote out the list of vertices
V v0 = graph.getSource(e);
V v1 = graph.getDest(e);
if (v0 == null) {
Pair<V> pair = graph.getEndpoints(e);
v0 = pair.getFirst();
v1 = pair.getSecond();
}
assert(v0 != null) : e;
assert(v1 != null) : e;
if (ignore_v != null && (ignore_v.contains(v0) || ignore_v.contains(v1))) continue;
if (all_vertices.contains(v0.getElementId()) && all_vertices.contains(v1.getElementId())) {
if (trace.val) LOG.trace(String.format("E [%d] %d => %d", e.getElementId(), v0.getElementId(), v1.getElementId()));
stringer.object();
e.toJSON(stringer);
stringer.endObject();
e_cnt++;
} else {
e_skipped++;
}
} // FOR
stringer.endArray();
stringer.key(Members.EDGE_CLASS.name()).value(e_class.getName());
}
if (e_skipped > 0) LOG.warn(String.format("Skipped %d out of %d edges", e_skipped, graph.getEdgeCount()));
if (debug.val) LOG.debug("# of Edges: " + e_cnt);
return;
}
/**
* Given a graph and a path to a serialized file, load in all of the vertices+edges into the graph
* @param <V>
* @param <E>
* @param graph
* @param catalog_db
* @param path
* @throws Exception
*/
public static <V extends AbstractVertex, E extends AbstractEdge> void load(IGraph<V, E> graph, Database catalog_db, File path) throws IOException {
if (debug.val) LOG.debug("Loading in serialized graph from '" + path + "'");
String contents = FileUtil.readFile(path);
if (contents.isEmpty()) {
throw new IOException("The workload statistics file '" + path + "' is empty");
}
try {
GraphUtil.deserialize(graph, catalog_db, new JSONObject(contents));
} catch (Exception ex) {
throw new IOException(ex);
}
if (debug.val) LOG.debug("Graph loading is complete");
return;
}
/**
* Deserialize the provided JSONObject into the Graph object
* @param <V>
* @param <E>
* @param graph
* @param catalog_db
* @param jsonObject
* @throws Exception
*/
@SuppressWarnings("unchecked")
public static <V extends AbstractVertex, E extends AbstractEdge> int deserialize(IGraph<V, E> graph, Database catalog_db, JSONObject jsonObject) throws JSONException {
// Graph Id
int id = jsonObject.getInt(Members.ID.name());
// Vertices
String v_className = jsonObject.getString(Members.VERTEX_CLASS.name());
// 2011-07-13: Fix for MarkovVertex
v_className = v_className.replace("markov.Vertex", "markov.MarkovVertex");
Class<V> v_class = (Class<V>)ClassUtil.getClass(v_className);
if (debug.val) LOG.debug("Vertex class is '" + v_class.getName() + "'");
JSONArray jsonArray = jsonObject.getJSONArray(Members.VERTICES.name());
for (int i = 0, cnt = jsonArray.length(); i < cnt; i++) {
V vertex = null;
try {
vertex = v_class.newInstance();
} catch (Exception ex) {
LOG.fatal("Failed to create new instance of " + v_class.getName());
throw new JSONException(ex);
}
JSONObject jsonVertex = jsonArray.getJSONObject(i);
vertex.fromJSON(jsonVertex, catalog_db);
graph.addVertex(vertex);
} // FOR
// Edges
// If the EDGE_CLASS is missing, don't bother loading any edges
if (jsonObject.has(Members.EDGE_CLASS.name())) {
String e_className = jsonObject.getString(Members.EDGE_CLASS.name());
// 2011-07-13: Fix for MarkovVertex
e_className = e_className.replace("markov.Edge", "markov.MarkovEdge");
Class<E> e_class = (Class<E>)ClassUtil.getClass(e_className);
if (debug.val) LOG.debug("Edge class is '" + v_class.getName() + "'");
jsonArray = jsonObject.getJSONArray(Members.EDGES.name());
for (int i = 0, cnt = jsonArray.length(); i < cnt; i++) {
E edge = ClassUtil.newInstance(e_class, new Object[] { graph }, new Class<?>[]{ IGraph.class });
JSONObject jsonEdge = jsonArray.getJSONObject(i);
edge.fromJSON(jsonEdge, catalog_db);
} // FOR
}
return (id);
}
/**
*
* @param <V>
* @param <E>
* @param graph
*/
@SuppressWarnings("unchecked")
public static <V extends AbstractVertex, E extends AbstractEdge> void visualizeGraph(IGraph<V, E> graph) {
GraphVisualizationPanel.createFrame(graph).setVisible(true);
ThreadUtil.sleep(10000);
}
}