package ca.sqlpower.object;
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 ca.sqlpower.graph.BreadthFirstSearch;
import ca.sqlpower.graph.GraphModel;
import ca.sqlpower.object.SPObject;
/**
* This graph takes a {@link WabitObject} for its root and makes a graph
* model that represents all of the root's dependencies. The root is
* included in the dependencies. The children can be included or the graph
* can just be of the dependencies. If the children are not included in the
* graph then the parents will replace their children when adding edges.
* <p>
* Example:<br>
* If we have a workspace with a report, chart, query, and 2 data sources
* the parent tree would look like the following
* <pre>
* Workspace
* |
* ---------------------------------------------
* / | | | \
* DS1 DS2 Query Chart Report
* | |
* --------- |
* / | \ |
* Col1 Col2 Col3 Page
* |
* -----------
* / | \
* CB1 CB2 CB3
* | |
* CR1 CR2
* </pre>
* Where a DS is a data source, a CB is a content box and a CR is a content renderer.<br>
* If we said the Query was based on DS1, the Chart was based on the Query, and the Report
* was displaying the Chart in CB1 and the Query in CB2 we would have dependencies between
* the WabitObjects. With dependencies the tree would look like the following<br>
* <img src="doc-files/ProjectGraphModel-1.JPG"><br>
* If a graph is made with the report node as the root you will have a graph of the following
* <pre>
* Report
* |
* |
* |
* Page
* |
* ----------------------------
* / | \
* CB1 CB2 CB3
* | |
* CR1 CR2
* | |
* Chart----------------->Query
* | |
* --------- DS1
* / | \
* Col1 Col2 Col3
* </pre>
* If a graph is made without the children of objects being included in the graph
* the result will look like the following
* <pre>
* Report
* |
* -----------
* / \
* Chart-------->Query
* |
* DS1
* </pre>
* If we look at a graph without the objects children, just the dependencies, with
* the query as the root we will get the graph
* <pre>
* Query
* |
* DS1
* </pre>
* If we then reverse the polarity on the graph above we get
* <pre>
* Query
* |
* -----------
* / \
* Chart CR2
* |
* CR1
* </pre>
*/
public class WorkspaceGraphModel implements GraphModel<SPObject, WorkspaceGraphModelEdge> {
/**
* This list will contain all of the nodes in the entire graph.
*/
private final Set<SPObject> nodes;
/**
* This list will map each node to a list of inbound edges on the node.
*/
private final Map<SPObject, Set<WorkspaceGraphModelEdge>> inboundEdges;
/**
* This list will map each node to a list of outbound edges on the node.
*/
private final Map<SPObject, Set<WorkspaceGraphModelEdge>> outboundEdges;
/**
* This node is the starting point when creating the graph.
* @see #WorkspaceGraphModel(WabitObject, WabitObject, boolean, boolean)
*/
private final SPObject graphStartNode;
private WorkspaceGraphModel(Set<SPObject> nodes, Map<SPObject, Set<WorkspaceGraphModelEdge>> inboundEdges, Map<SPObject, Set<WorkspaceGraphModelEdge>> outboundEdges, SPObject graphStartNode) {
this.nodes = nodes;
this.inboundEdges = inboundEdges;
this.outboundEdges = outboundEdges;
this.graphStartNode = graphStartNode;
}
/**
* @param root
* The Wabit object that is the highest ancestor of all of the
* objects in the graph. This node is used to create an initial
* graph ensuring no nodes are missed when reversing polarity.
* This will normally be the WabitWorkspace.
* @param graphStartNode
* The Wabit object to start creating the graph model from. This
* will be the starting point for finding edges and nodes. Only
* objects that are dependencies or children, if the child flag
* is set, recursively will be included in the graph. If the
* entire workspace is desired to be made into a graph this
* should be the workspace object. This must be a
* child/grandchild/etc of the root.
* @param showOnlyDependencies
* If false the children of objects will be included in the graph
* and their parent/child relationship will be included as an
* edge. If true only the dependency lines will be stored when
* creating the graph not the parent/child edges. The children
* will still be traversed if this is true but the edge created
* for the dependency will link the child traversed from's
* ancestor to the object the child depends on directly not its
* ancestor. The ancestor to link to will always be a child of
* the root object given.
* @param reversePolarity
* If false the edges in the graph will go from the parent object
* to the child object for parent/child edges, and dependencies
* edges will go from the object that is dependent on an object
* to the object being depended on. If this is true then the
* edges in the graph will be reversed so the children of objects
* will have an edge pointing to their parent and objects being
* depended on will point to the objects that depend on them.
*/
public WorkspaceGraphModel(SPObject root, SPObject graphStartNode,
boolean showOnlyDependencies, boolean reversePolarity) {
this(root, graphStartNode, showOnlyDependencies, reversePolarity, false);
}
/**
* @param includeChildren
* If true the children of the {@link #graphStartNode} will be
* included in the graph, even if the relationships are inverted.
*/
public WorkspaceGraphModel(SPObject root, SPObject graphStartNode,
boolean showOnlyDependencies, boolean reversePolarity, boolean includeChildren) {
//The graph made by this constructor is done in two steps:
//1) create an initial graph of the whole workspace and if necessary reverse
// polarity. Child nodes will be hidden if we are showing only dependencies.
//2) do a BFS on the graph starting at the start node to find what nodes should
// stay in the graph, ie connected, and remove the rest.
nodes = new HashSet<SPObject>();
outboundEdges = new HashMap<SPObject, Set<WorkspaceGraphModelEdge>>();
inboundEdges = new HashMap<SPObject, Set<WorkspaceGraphModelEdge>>();
this.graphStartNode = graphStartNode;
createGraph(root, showOnlyDependencies, reversePolarity);
BreadthFirstSearch<SPObject, WorkspaceGraphModelEdge> bfs =
new BreadthFirstSearch<SPObject, WorkspaceGraphModelEdge>();
Set<SPObject> connectedObjects = new HashSet<SPObject>(bfs.performSearch(this, graphStartNode));
if (includeChildren) {
connectedObjects.addAll(performChildSearch(graphStartNode, connectedObjects));
}
restrictGraph(connectedObjects);
}
/**
* @param parent
* The parent that we want to ensure its children are included in
* the graph.
* @param existingChildren
* The set of objects already to be included in the graph. This
* set exists for performance reasons in case the child is
* already included.
*/
private Set<SPObject> performChildSearch(SPObject parent, Set<SPObject> existingObjects) {
Set<SPObject> connectedObjects = new HashSet<SPObject>();
BreadthFirstSearch<SPObject, WorkspaceGraphModelEdge> bfs =
new BreadthFirstSearch<SPObject, WorkspaceGraphModelEdge>();
//Merges the existing objects with the recently found objects to pass the correct found
//collection to the recursive call.
Set<SPObject> foundObjects = new HashSet<SPObject>(existingObjects);
for (SPObject child : parent.getChildren()) {
if (!foundObjects.contains(child)) {
List<SPObject> newNodes = bfs.performSearch(this, child);
connectedObjects.addAll(newNodes);
foundObjects.addAll(newNodes);
}
Set<SPObject> newNodes = performChildSearch(child, foundObjects);
connectedObjects.addAll(newNodes);
foundObjects.addAll(newNodes);
}
return connectedObjects;
}
void restrictGraph(Set<SPObject> connectedObjects) {
List<SPObject> removedObjects = new ArrayList<SPObject>();
for (SPObject node : nodes) {
if (!connectedObjects.contains(node)) {
removedObjects.add(node);
}
}
nodes.removeAll(removedObjects);
for (SPObject removedNode : removedObjects) {
inboundEdges.remove(removedNode);
if (outboundEdges.get(removedNode) != null) {
for (WorkspaceGraphModelEdge edge : outboundEdges.get(removedNode)) {
if (inboundEdges.get(edge.getChild()) != null) {
inboundEdges.get(edge.getChild()).remove(edge);
}
}
}
outboundEdges.remove(removedNode);
}
}
void createGraph(SPObject root, boolean showOnlyDependencies,
boolean reversePolarity) {
Queue<SPObject> adjacentNodes = new LinkedList<SPObject>();
Map<SPObject, SPObject> childParentMap = new HashMap<SPObject, SPObject>();
adjacentNodes.add(root);
while (!adjacentNodes.isEmpty()) {
SPObject node = adjacentNodes.remove();
if (nodes.contains(node)) continue;
if (node != root) {
for (SPObject child : node.getChildren()) {
childParentMap.put(child, node);
}
}
List<SPObject> edgeChildren = new ArrayList<SPObject>();
edgeChildren.addAll(node.getDependencies());
edgeChildren.addAll(node.getChildren());
for (SPObject child : edgeChildren) {
adjacentNodes.add(child);
if (showOnlyDependencies && child.getParent() != null &&
child.getParent().equals(node)) continue;
SPObject childNode = child;
SPObject parentNode = node;
if (reversePolarity) {
SPObject temp = parentNode;
parentNode = childNode;
childNode = temp;
}
if (showOnlyDependencies) {
while (childParentMap.get(parentNode) != null) {
parentNode = childParentMap.get(parentNode);
}
}
WorkspaceGraphModelEdge edge = new WorkspaceGraphModelEdge(parentNode, childNode);
Set<WorkspaceGraphModelEdge> childInboundList = inboundEdges.get(childNode);
if (childInboundList == null) {
childInboundList = new HashSet<WorkspaceGraphModelEdge>();
inboundEdges.put(childNode, childInboundList);
}
childInboundList.add(edge);
Set<WorkspaceGraphModelEdge> parentOutboundList = outboundEdges.get(parentNode);
if (parentOutboundList == null) {
parentOutboundList = new HashSet<WorkspaceGraphModelEdge>();
outboundEdges.put(parentNode, parentOutboundList);
}
parentOutboundList.add(edge);
}
if (!showOnlyDependencies) {
nodes.add(node);
} else {
if (!childParentMap.containsKey(node)
|| inboundEdges.get(node) != null
|| outboundEdges.get(node) != null) {
nodes.add(node);
}
}
}
}
public Collection<SPObject> getAdjacentNodes(SPObject node) {
Collection<SPObject> adjacentNodes = new HashSet<SPObject>();
Set<WorkspaceGraphModelEdge> edges = outboundEdges.get(node);
if (edges == null) return Collections.emptySet();
for (WorkspaceGraphModelEdge edge : edges) {
adjacentNodes.add(edge.getChild());
}
return adjacentNodes;
}
public Collection<WorkspaceGraphModelEdge> getEdges() {
Set<WorkspaceGraphModelEdge> allEdges = new HashSet<WorkspaceGraphModelEdge>();
//All of the edged in the inbound map should also be in the outbound map
//so only add one of the two
for (Set<WorkspaceGraphModelEdge> edges : inboundEdges.values()) {
if (edges == null) continue;
allEdges.addAll(edges);
}
return allEdges;
}
public Collection<WorkspaceGraphModelEdge> getInboundEdges(
SPObject node) {
Set<WorkspaceGraphModelEdge> edges = inboundEdges.get(node);
if (edges == null) return Collections.emptyList();
return edges;
}
public Collection<SPObject> getNodes() {
return nodes;
}
public Collection<WorkspaceGraphModelEdge> getOutboundEdges(
SPObject node) {
Set<WorkspaceGraphModelEdge> edges = outboundEdges.get(node);
if (edges == null) return Collections.emptyList();
return edges;
}
public SPObject getGraphStartNode() {
return graphStartNode;
}
/**
* Returns this graph model, with all edges reversed. This has a similar
* effect to the reverse polarity flag in the constructor, but the graph may
* contain a different set of nodes.
*/
public WorkspaceGraphModel invert() {
Map<SPObject, Set<WorkspaceGraphModelEdge>> invertedInboundEdges = new HashMap<SPObject, Set<WorkspaceGraphModelEdge>>();
for (Map.Entry<SPObject, Set<WorkspaceGraphModelEdge>> entry : inboundEdges.entrySet()) {
Set<WorkspaceGraphModelEdge> edges = new HashSet<WorkspaceGraphModelEdge>();
for (WorkspaceGraphModelEdge edge : entry.getValue()) {
edges.add(new WorkspaceGraphModelEdge(edge.getChild(), edge.getParent()));
}
invertedInboundEdges.put(entry.getKey(), edges);
}
Map<SPObject, Set<WorkspaceGraphModelEdge>> invertedOutboundEdges = new HashMap<SPObject, Set<WorkspaceGraphModelEdge>>();
for (Map.Entry<SPObject, Set<WorkspaceGraphModelEdge>> entry : outboundEdges.entrySet()) {
Set<WorkspaceGraphModelEdge> edges = new HashSet<WorkspaceGraphModelEdge>();
for (WorkspaceGraphModelEdge edge : entry.getValue()) {
edges.add(new WorkspaceGraphModelEdge(edge.getChild(), edge.getParent()));
}
invertedOutboundEdges.put(entry.getKey(), edges);
}
return new WorkspaceGraphModel(nodes, invertedOutboundEdges, invertedInboundEdges, graphStartNode);
}
}