OPENJPA-235

git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@544918 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Patrick Linskey 2007-06-06 18:49:30 +00:00
parent 4d23f445c6
commit 5463700d6c
11 changed files with 1507 additions and 1 deletions

View File

@ -214,9 +214,11 @@ public class JDBCConfigurationImpl
updateManagerPlugin = addPlugin("jdbc.UpdateManager", true);
aliases = new String[]{
"default",
"org.apache.openjpa.jdbc.kernel.OperationOrderUpdateManager",
"org.apache.openjpa.jdbc.kernel.ConstraintUpdateManager",
"operation-order",
"org.apache.openjpa.jdbc.kernel.OperationOrderUpdateManager",
"constraint",
"org.apache.openjpa.jdbc.kernel.ConstraintUpdateManager",
};
updateManagerPlugin.setAliases(aliases);
updateManagerPlugin.setDefault(aliases[0]);

View File

@ -0,0 +1,419 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.openjpa.jdbc.kernel;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import org.apache.openjpa.jdbc.meta.ClassMapping;
import org.apache.openjpa.jdbc.schema.Column;
import org.apache.openjpa.jdbc.schema.ForeignKey;
import org.apache.openjpa.jdbc.schema.Table;
import org.apache.openjpa.jdbc.sql.PrimaryRow;
import org.apache.openjpa.jdbc.sql.Row;
import org.apache.openjpa.jdbc.sql.RowImpl;
import org.apache.openjpa.jdbc.sql.RowManager;
import org.apache.openjpa.jdbc.sql.RowManagerImpl;
import org.apache.openjpa.jdbc.sql.SQLExceptions;
import org.apache.openjpa.kernel.OpenJPAStateManager;
import org.apache.openjpa.lib.graph.DepthFirstAnalysis;
import org.apache.openjpa.lib.graph.Edge;
import org.apache.openjpa.lib.graph.Graph;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.util.InternalException;
import org.apache.openjpa.util.OpenJPAException;
/**
* <p>Standard update manager, capable of foreign key constraint evaluation.</p>
*
* @since 1.0.0
*/
public class ConstraintUpdateManager
extends AbstractUpdateManager {
private static final Localizer _loc = Localizer.forPackage
(ConstraintUpdateManager.class);
public boolean orderDirty() {
return false;
}
protected PreparedStatementManager newPreparedStatementManager
(JDBCStore store, Connection conn) {
return new PreparedStatementManagerImpl(store, conn);
}
protected RowManager newRowManager() {
return new RowManagerImpl(false);
}
protected Collection flush(RowManager rowMgr,
PreparedStatementManager psMgr, Collection exceps) {
RowManagerImpl rmimpl = (RowManagerImpl) rowMgr;
// first take care of all secondary table deletes and 'all row' deletes
// (which are probably secondary table deletes), since no foreign
// keys ever rely on secondary table pks
flush(rmimpl.getAllRowDeletes(), psMgr);
flush(rmimpl.getSecondaryDeletes(), psMgr);
// now do any 'all row' updates
flush(rmimpl.getAllRowUpdates(), psMgr);
// analyze foreign keys
Collection inserts = rmimpl.getInserts();
Collection updates = rmimpl.getUpdates();
Collection deletes = rmimpl.getDeletes();
Graph[] graphs = new Graph[2]; // insert graph, delete graph
analyzeForeignKeys(inserts, updates, deletes, rmimpl, graphs);
// flush insert graph, if any
boolean autoAssign = rmimpl.hasAutoAssignConstraints();
try {
flushGraph(graphs[0], psMgr, autoAssign);
} catch (SQLException se) {
exceps = addException(exceps, SQLExceptions.getStore(se, dict));
} catch (OpenJPAException ke) {
exceps = addException(exceps, ke);
}
// flush the rest of the inserts and updates; inserts before updates
// because some update fks might reference pks that have to be inserted
flush(inserts, psMgr);
flush(updates, psMgr);
// flush the delete graph, if any
try {
flushGraph(graphs[1], psMgr, autoAssign);
} catch (SQLException se) {
exceps = addException(exceps, SQLExceptions.getStore(se, dict));
} catch (OpenJPAException ke) {
exceps = addException(exceps, ke);
}
// put the remainder of the deletes after updates because some updates
// may be nulling fks to rows that are going to be deleted
flush(deletes, psMgr);
// take care of all secondary table inserts and updates last, since
// they may rely on previous inserts or updates, but nothing relies
// on them
flush(rmimpl.getSecondaryUpdates(), psMgr);
// flush any left over prepared statements
psMgr.flush();
return exceps;
}
/**
* Analyze foreign key dependencies on the given rows
* and create an insert and a delete graph to execute. The insert
* graph will be flushed before all other rows, and the delete graph will
* be flushed after them.
*/
private void analyzeForeignKeys(Collection inserts, Collection updates,
Collection deletes, RowManagerImpl rowMgr, Graph[] graphs) {
// if there are any deletes, we have to map the insert objects on their
// oids so we'll be able to detect delete-then-insert-same-pk cases
Map insertMap = null;
OpenJPAStateManager sm;
if (!deletes.isEmpty() && !inserts.isEmpty()) {
insertMap = new HashMap((int) (inserts.size() * 1.33 + 1));
for (Iterator itr = inserts.iterator(); itr.hasNext();) {
sm = ((Row) itr.next()).getPrimaryKey();
if (sm != null && sm.getObjectId() != null)
insertMap.put(sm.getObjectId(), sm);
}
}
// first construct the graph for deletes; this may expand to include
// inserts and updates as well if there are any inserts that rely on
// deletes (delete-then-insert-same-pk cases)
PrimaryRow row;
Row row2;
ForeignKey[] fks;
OpenJPAStateManager fkVal;
boolean ignoreUpdates = true;
for (Iterator itr = deletes.iterator(); itr.hasNext();) {
row = (PrimaryRow) itr.next();
if (!row.isValid())
continue;
row2 = getInsertRow(insertMap, rowMgr, row);
if (row2 != null) {
ignoreUpdates = false;
graphs[1] = addEdge(graphs[1], row, (PrimaryRow) row2, null);
}
// now check this row's fks against other deletes
fks = row.getTable().getForeignKeys();
for (int j = 0; j < fks.length; j++) {
// when deleting ref fks they'll just set a where value, so
// check both for fk updates (relation fks) and wheres (ref fks)
fkVal = row.getForeignKeySet(fks[j]);
if (fkVal == null)
fkVal = row.getForeignKeyWhere(fks[j]);
if (fkVal == null)
continue;
row2 = rowMgr.getRow(fks[j].getPrimaryKeyTable(),
Row.ACTION_DELETE, fkVal, false);
if (row2 != null && row2.isValid() && row2 != row)
graphs[1] = addEdge(graphs[1], row, (PrimaryRow) row2,
fks[j]);
}
}
if (ignoreUpdates)
graphs[0] = analyzeAgainstInserts(inserts, rowMgr, graphs[0]);
else {
// put inserts *and updates* in the delete graph; they all rely
// on each other
graphs[1] = analyzeAgainstInserts(updates, rowMgr, graphs[1]);
graphs[1] = analyzeAgainstInserts(inserts, rowMgr, graphs[1]);
}
}
/**
* Check to see if there is an insert for for the same table and primary
* key values as the given delete row.
*/
private Row getInsertRow(Map insertMap, RowManagerImpl rowMgr, Row row) {
if (insertMap == null)
return null;
OpenJPAStateManager sm = row.getPrimaryKey();
if (sm == null)
return null;
// look for a new object whose insert id is the same as this delete one
Object oid = sm.getObjectId();
OpenJPAStateManager nsm = (OpenJPAStateManager) insertMap.get(oid);
if (nsm == null)
return null;
// found new object; get its row
row = rowMgr.getRow(row.getTable(), Row.ACTION_INSERT, nsm, false);
return (row == null || row.isValid()) ? row : null;
}
/**
* Analyze the given rows against the inserts, placing dependencies
* in the given graph.
*/
private Graph analyzeAgainstInserts(Collection rows, RowManagerImpl rowMgr,
Graph graph) {
PrimaryRow row;
Row row2;
ForeignKey[] fks;
Column[] cols;
for (Iterator itr = rows.iterator(); itr.hasNext();) {
row = (PrimaryRow) itr.next();
if (!row.isValid())
continue;
// check this row's fks against inserts; a logical fk to an auto-inc
// column is treated just as actual database fk because the result
// is the same: the pk row has to be inserted before the fk row
fks = row.getTable().getForeignKeys();
for (int j = 0; j < fks.length; j++) {
if (row.getForeignKeySet(fks[j]) == null)
continue;
// see if this row is dependent on another. if it's only
// depenent on itself, see if the fk is logical or deferred, in
// which case it must be an auto-inc because otherwise we
// wouldn't have recorded it
row2 = rowMgr.getRow(fks[j].getPrimaryKeyTable(),
Row.ACTION_INSERT, row.getForeignKeySet(fks[j]), false);
if (row2 != null && row2.isValid() && (row2 != row
|| fks[j].isDeferred() || fks[j].isLogical()))
graph = addEdge(graph, (PrimaryRow) row2, row, fks[j]);
}
// see if there are any relation id columns dependent on
// auto-inc objects
cols = row.getTable().getRelationIdColumns();
for (int j = 0; j < cols.length; j++) {
OpenJPAStateManager sm = row.getRelationIdSet(cols[j]);
if (sm == null)
continue;
row2 = rowMgr.getRow(getBaseTable(sm), Row.ACTION_INSERT,
sm, false);
if (row2 != null && row2.isValid())
graph = addEdge(graph, (PrimaryRow) row2, row, cols[j]);
}
}
return graph;
}
/**
* Return the base table for the given instance.
*/
private static Table getBaseTable(OpenJPAStateManager sm) {
ClassMapping cls = (ClassMapping) sm.getMetaData();
while (cls.getJoinablePCSuperclassMapping() != null)
cls = cls.getJoinablePCSuperclassMapping();
return cls.getTable();
}
/**
* Add an edge between the given rows in the given foreign key graph.
*/
private Graph addEdge(Graph graph, PrimaryRow row1, PrimaryRow row2,
Object fk) {
// delay creation of the graph
if (graph == null)
graph = new Graph();
row1.setDependent(true);
row2.setDependent(true);
graph.addNode(row1);
graph.addNode(row2);
// add an edge from row1 to row2, and set the fk causing the
// dependency as the user object so we can retrieve it when resolving
// circular constraints
Edge edge = new Edge(row1, row2, true);
edge.setUserObject(fk);
graph.addEdge(edge);
return graph;
}
/**
* Flush the given graph of rows in the proper order.
* @param graph The graph of statements to be walked
* @param psMgr The prepared statement manager to use to issue the
* statements
* @param autoAssign Whether any of the rows in the graph have any
* auto-assign constraints
*/
protected void flushGraph(Graph graph, PreparedStatementManager psMgr,
boolean autoAssign)
throws SQLException {
if (graph == null)
return;
DepthFirstAnalysis dfa = newDepthFirstAnalysis(graph, autoAssign);
Collection nodes = dfa.getSortedNodes();
Collection backs = dfa.getEdges(Edge.TYPE_BACK);
// handle circular constraints:
// - if deleted row A has a ciricular fk to deleted row B, then use an
// update statement to null A's fk to B
// - if inserted row A has a circular fk to updated/inserted row B,
// then null the fk in the B row object, and after flushing, use
// an update to set the fk to back to A
Collection insertUpdates = null;
Collection deleteUpdates = null;
PrimaryRow row;
RowImpl update;
Edge edge;
ForeignKey fk;
Column col;
for (Iterator itr = backs.iterator(); itr.hasNext();) {
edge = (Edge) itr.next();
if (edge.getUserObject() == null)
throw new InternalException(_loc.get("del-ins-cycle"));
// use a primary row update to prevent setting pk and fk values
// until after flush, to get latest auto-increment values
row = (PrimaryRow) edge.getTo();
if (row.getAction() == Row.ACTION_DELETE) {
// copy where conditions into new update that nulls the fk
row = (PrimaryRow) edge.getFrom();
update = new PrimaryRow(row.getTable(), Row.ACTION_UPDATE,null);
row.copyInto(update, true);
if (edge.getUserObject() instanceof ForeignKey) {
fk = (ForeignKey) edge.getUserObject();
update.setForeignKey(fk, row.getForeignKeyIO(fk), null);
} else
update.setNull((Column) edge.getUserObject());
if (deleteUpdates == null)
deleteUpdates = new LinkedList();
deleteUpdates.add(update);
} else {
// copy where conditions into new update that sets the fk
update = new PrimaryRow(row.getTable(), Row.ACTION_UPDATE,null);
if (row.getAction() == Row.ACTION_INSERT) {
if (row.getPrimaryKey() == null)
throw new InternalException(_loc.get("ref-cycle"));
update.wherePrimaryKey(row.getPrimaryKey());
} else
row.copyInto(update, true);
if (edge.getUserObject() instanceof ForeignKey) {
fk = (ForeignKey) edge.getUserObject();
update.setForeignKey(fk, row.getForeignKeyIO(fk),
row.getForeignKeySet(fk));
row.clearForeignKey(fk);
} else {
col = (Column) edge.getUserObject();
update.setRelationId(col, row.getRelationIdSet(col),
row.getRelationIdCallback(col));
row.clearRelationId(col);
}
if (insertUpdates == null)
insertUpdates = new LinkedList();
insertUpdates.add(update);
}
}
// flush delete updates to null fks, then all rows in order, then
// the insert updates to set circular fk values
if (deleteUpdates != null)
flush(deleteUpdates, psMgr);
for (Iterator itr = nodes.iterator(); itr.hasNext();)
psMgr.flush((RowImpl) itr.next());
if (insertUpdates != null)
flush(insertUpdates, psMgr);
}
/**
* Create a new {@link DepthFirstAnalysis} suitable for the given graph
* and auto-assign settings.
*/
protected DepthFirstAnalysis newDepthFirstAnalysis(Graph graph,
boolean autoAssign) {
return new DepthFirstAnalysis(graph);
}
/**
* Flush the given collection of secondary rows.
*/
protected void flush(Collection rows, PreparedStatementManager psMgr) {
if (rows.size() == 0)
return;
RowImpl row;
for (Iterator itr = rows.iterator(); itr.hasNext(); ) {
row = (RowImpl) itr.next();
if (row.isValid() && !row.isDependent())
psMgr.flush(row);
}
}
}

View File

@ -0,0 +1,145 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.openjpa.lib.graph;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* <p>Performs a breadth-first walk of a given {@link Graph},
* notifying visitors as it sees each node. See the BFS algorithm
* in the book 'Introduction to Algorithms' by Cormen, Leiserson, and
* Rivest.</p>
* <p/>
* <p>Each {@link GraphVisitor} will be notified when a node
* is colored black (nodeVisited), edge seen (edgeVisited),
* and a node is seen for the first time, i.e. colored gray (nodeSeen).</p>
*
* @author Steve Kim
* @since 1.0.0
* @nojavadoc
*/
public class BreadthFirstWalk {
private final Graph _graph;
private final Set _visitors = new HashSet();
private final List _queue = new LinkedList();
private final Map _nodeInfo = new HashMap();
public BreadthFirstWalk(Graph graph) {
_graph = graph;
}
/**
* Begins the breadth first traversal.
*/
public void walk() {
_queue.clear();
_nodeInfo.clear();
Collection nodes = _graph.getNodes();
for (Iterator itr = nodes.iterator(); itr.hasNext();)
_nodeInfo.put(itr.next(), new NodeInfo());
Object node;
NodeInfo info;
for (Iterator itr = nodes.iterator(); itr.hasNext();) {
node = itr.next();
info = (NodeInfo) _nodeInfo.get(node);
if (info.color == NodeInfo.COLOR_WHITE)
enqueue(node, info);
processQueue();
}
}
/**
* Process the queue to see what data needs to be obtained.
*/
private void processQueue() {
Object node;
Object other;
NodeInfo info;
NodeInfo otherInfo;
Collection edges;
Edge edge;
while (_queue.size() > 0) {
node = _queue.remove(0);
info = (NodeInfo) _nodeInfo.get(node);
visit(node, info);
edges = _graph.getEdgesFrom(node);
for (Iterator itr = edges.iterator(); itr.hasNext();) {
edge = (Edge) itr.next();
edgeVisited(edge);
other = edge.getOther(node);
otherInfo = (NodeInfo) _nodeInfo.get(other);
if (otherInfo.color == NodeInfo.COLOR_WHITE)
enqueue(other, otherInfo);
}
}
}
/**
* Push the given node onto the queue to be processed.
* Notify visitors.
*/
protected void enqueue(Object node, NodeInfo info) {
_queue.add(node);
info.color = NodeInfo.COLOR_GRAY;
for (Iterator i = _visitors.iterator(); i.hasNext();)
((GraphVisitor) i.next()).nodeSeen(node);
}
/**
* Visit the node. Mark the node black and notify visitors.
*/
protected void visit(Object node, NodeInfo info) {
info.color = NodeInfo.COLOR_BLACK;
for (Iterator i = _visitors.iterator(); i.hasNext();)
((GraphVisitor) i.next()).nodeVisited(node);
}
/**
* An edge is seen. Notify visitors.
*/
protected void edgeVisited(Edge edge) {
for (Iterator i = _visitors.iterator(); i.hasNext();)
((GraphVisitor) i.next()).edgeVisited(edge);
}
/**
* add a {@link GraphVisitor} to be notified during breadth first search.
*/
public void addGraphVisitor(GraphVisitor visitor) {
_visitors.add(visitor);
}
/**
* remove a given {@link GraphVisitor} from the listener set.
*/
public void removeGraphVisitor(GraphVisitor visitor) {
_visitors.remove(visitor);
}
}

View File

@ -0,0 +1,215 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.openjpa.lib.graph;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* <p>Performs a depth-first analysis of a given {@link Graph}, caching
* information about the graph's nodes and edges. See the DFS algorithm
* in the book 'Introduction to Algorithms' by Cormen, Leiserson, and
* Rivest. The algorithm has been modified to group sibling nodes without
* connections together during the topological sort.</p>
*
* @author Abe White
* @since 1.0.0
* @nojavadoc
*/
public class DepthFirstAnalysis {
private final Graph _graph;
private final Map _nodeInfo = new HashMap();
private Comparator _comp;
/**
* Constructor. Performs the analysis on the given graph and caches
* the resulting information.
*/
public DepthFirstAnalysis(Graph graph) {
_graph = graph;
// initialize node infos
Collection nodes = graph.getNodes();
for (Iterator itr = nodes.iterator(); itr.hasNext();)
_nodeInfo.put(itr.next(), new NodeInfo());
// visit all nodes -- see intro to algo's book
NodeInfo info;
Object node;
for (Iterator itr = nodes.iterator(); itr.hasNext();) {
node = itr.next();
info = (NodeInfo) _nodeInfo.get(node);
if (info.color == NodeInfo.COLOR_WHITE)
visit(graph, node, info, 0);
}
}
/**
* Visit a node. See Introduction to Algorithms book for details.
*/
private int visit(Graph graph, Object node, NodeInfo info, int time) {
// discover node
info.color = NodeInfo.COLOR_GRAY;
// explore all vertices from that node depth first
Collection edges = graph.getEdgesFrom(node);
Edge edge;
Object other;
NodeInfo otherInfo;
int maxChildTime = time - 1;
int childTime;
for (Iterator itr = edges.iterator(); itr.hasNext();) {
edge = (Edge) itr.next();
other = edge.getOther(node);
otherInfo = (NodeInfo) _nodeInfo.get(other);
if (otherInfo.color == NodeInfo.COLOR_WHITE) {
// undiscovered node; recurse into it
childTime = visit(graph, other, otherInfo, time);
edge.setType(Edge.TYPE_TREE);
} else if (otherInfo.color == NodeInfo.COLOR_GRAY) {
childTime = -1;
edge.setType(Edge.TYPE_BACK);
} else {
childTime = otherInfo.finished;
edge.setType(Edge.TYPE_FORWARD);
}
maxChildTime = Math.max(maxChildTime, childTime);
}
// finished with node
info.color = NodeInfo.COLOR_BLACK;
info.finished = maxChildTime + 1;
return info.finished;
}
/**
* Set the comparator that should be used for ordering groups of nodes
* with the same dependencies.
*/
public void setNodeComparator(Comparator comp) {
_comp = comp;
}
/**
* Return the nodes in topologically-sorted order. This is often used
* to order dependencies. If each graph edge (u, v) represents a
* dependency of v on u, then this method will return the nodes in the
* order that they should be evaluated to satisfy all dependencies. Of
* course, if the graph is cyclic (has back edges), then no such ordering
* is possible, though this method will still return the correct order
* as if edges creating the cycles did not exist.
*/
public List getSortedNodes() {
Map.Entry[] entries = (Map.Entry[]) _nodeInfo.entrySet().
toArray(new Map.Entry[_nodeInfo.size()]);
Arrays.sort(entries, new NodeInfoComparator(_comp));
return new NodeList(entries);
}
/**
* Return all edges of the given type. This method can be used to
* discover all edges that cause cycles in the graph by passing it
* the {@link #EDGE_BACK} edge type.
*/
public Collection getEdges(int type) {
Collection typed = null;
Edge edge;
Object node;
for (Iterator nodes = _graph.getNodes().iterator(); nodes.hasNext();) {
node = nodes.next();
for (Iterator itr = _graph.getEdgesFrom(node).iterator();
itr.hasNext();) {
edge = (Edge) itr.next();
if (edge.getType() == type) {
if (typed == null)
typed = new ArrayList();
typed.add(edge);
}
}
}
return (typed == null) ? Collections.EMPTY_LIST : typed;
}
/**
* Return the logical time that the given node was finished in
* the graph walk, or -1 if the node is not part of the graph.
*/
public int getFinishedTime(Object node) {
NodeInfo info = (NodeInfo) _nodeInfo.get(node);
if (info == null)
return -1;
return info.finished;
}
/**
* Comparator for toplogically sorting entries in the node info map.
*/
private static class NodeInfoComparator
implements Comparator {
private final Comparator _subComp;
public NodeInfoComparator(Comparator subComp) {
_subComp = subComp;
}
public int compare(Object o1, Object o2) {
Map.Entry e1 = (Map.Entry) o1;
Map.Entry e2 = (Map.Entry) o2;
NodeInfo n1 = (NodeInfo) e1.getValue();
NodeInfo n2 = (NodeInfo) e2.getValue();
// reverse finished order
int ret = n2.finished - n1.finished;
if (ret == 0 && _subComp != null)
ret = _subComp.compare(e1.getKey(), e2.getKey());
return ret;
}
}
/**
* List of node-to-nodeinfo entries that exposes just the nodes.
*/
private static class NodeList
extends AbstractList {
private final Map.Entry[] _entries;
public NodeList(Map.Entry[] entries) {
_entries = entries;
}
public Object get(int idx) {
return _entries[idx].getKey();
}
public int size() {
return _entries.length;
}
}
}

View File

@ -0,0 +1,189 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.openjpa.lib.graph;
/**
* <p>A graph edge. Includes the from and to nodes, an arbitrary user object,
* and a weight. Edges can be either directed or undirected.</p>
*
* @author Abe White
* @since 1.0.0
* @nojavadoc
*/
public class Edge {
/**
* An edge (u, v) is a tree edge if node v was first discovered by
* traversing the edge.
*/
public static final int TYPE_TREE = 1;
/**
* An edge (u, v) is a back edge if it creates a cycle back to an
* ancestor in the graph.
*/
public static final int TYPE_BACK = 2;
/**
* An edge (u, v) is a forward edge if it is not a tree or back edge.
*/
public static final int TYPE_FORWARD = 3;
private final Object _from;
private final Object _to;
private final boolean _directed;
private int _type = 0;
private double _weight = 0;
private Object _userObj = null;
/**
* Constructor.
*
* @param from the node the edge comes from
* @param to the node the edge goes to
* @param directed whether the edge is directed
*/
public Edge(Object from, Object to, boolean directed) {
if (from == null)
throw new NullPointerException("from == null");
if (to == null)
throw new NullPointerException("to == null");
_from = from;
_to = to;
_directed = directed;
}
/**
* Constructor.
*
* @param from the node the edge comes from
* @param to the node the edge goes to
* @param directed whether the edge is directed
* @param userObject an associated object
*/
public Edge(Object from, Object to, boolean directed, Object userObject) {
this(from, to, directed);
_userObj = userObject;
}
/**
* Return the node the edge links from.
*/
public Object getFrom() {
return _from;
}
/**
* Return the node the edge links to.
*/
public Object getTo() {
return _to;
}
/**
* Return the node on the opposite end of the given one, or null if the
* given node is not part of this edge.
*/
public Object getOther(Object node) {
if (_to == node)
return _from;
if (_from == node)
return _to;
return null;
}
/**
* Return true if this edge links to the given node. For undirected edges,
* this method returns true if either side is equal to the given node.
*/
public boolean isTo(Object node) {
return _to == node || (!_directed && _from == node);
}
/**
* Return true if this edge links from the given node. For undirected
* edges, this method returns true if either side is equal to the given
* node.
*/
public boolean isFrom(Object node) {
return _from == node || (!_directed && _to == node);
}
/**
* Return whether the edge is directed.
*/
public boolean isDirected() {
return _directed;
}
/**
* Return the weight of the edge.
*/
public double getWeight() {
return _weight;
}
/**
* Set the weight of the edge.
*/
public void setWeight(double weight) {
_weight = weight;
}
/**
* Arbitrary user object associated with the edge.
*/
public Object getUserObject() {
return _userObj;
}
/**
* Arbitrary user object associated with the edge.
*/
public void setUserObject(Object obj) {
_userObj = obj;
}
/**
* Traversal bookkeeping info.
*/
public int getType() {
return _type;
}
/**
* Traversal bookkeeping info.
*/
public void setType(int type) {
_type = type;
}
/**
* Clear traversal info.
*/
public void clearTraversal() {
_type = 0;
}
public String toString() {
return super.toString() + "[from=" + getFrom() + ";to=" + getTo()
+ ";directed=" + isDirected () + ";weight=" + getWeight () + "]";
}
}

View File

@ -0,0 +1,199 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.openjpa.lib.graph;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
/**
* <p>Graph representation using the adjacency list form. See the book
* 'Introduction to Algorithms' by Cormen, Leiserson, and Rivest.</p>
*
* @author Abe White
* @since 1.0.0
* @nojavadoc
*/
public class Graph {
// map each node to list of edges from that node
private final Map _nodes = new HashMap();
/**
* Clear the graph.
*/
public void clear() {
_nodes.clear();
}
/**
* Return true if the graph contains the given node.
*/
public boolean containsNode(Object node) {
return _nodes.containsKey(node);
}
/**
* Return a view of all nodes in the graph.
*/
public Collection getNodes() {
return _nodes.keySet();
}
/**
* Add a node to the graph. Adding a node a second time has no effect.
*/
public void addNode(Object node) {
if (node == null)
throw new NullPointerException("node = null");
if (!containsNode(node))
_nodes.put(node, null);
}
/**
* Remove a node from the graph. All edges to and from the node
* will be cleared.
*
* @return true if the node was removed, false otherwise
*/
public boolean removeNode(Object node) {
boolean rem = containsNode(node);
if (rem) {
Collection edges = getEdgesTo(node);
for (Iterator itr = edges.iterator(); itr.hasNext();)
removeEdge((Edge) itr.next());
_nodes.remove(node);
}
return rem;
}
/**
* Return all edges in the graph.
*/
public Collection getEdges() {
Collection all = new HashSet();
Collection edges;
for (Iterator itr = _nodes.values().iterator(); itr.hasNext();) {
edges = (Collection) itr.next();
if (edges != null)
all.addAll(edges);
}
return all;
}
/**
* Return all the edges from a particular node.
*/
public Collection getEdgesFrom(Object node) {
Collection edges = (Collection) _nodes.get(node);
return (edges == null) ? Collections.EMPTY_LIST : edges;
}
/**
* Return all the edges to a particular node.
*/
public Collection getEdgesTo(Object node) {
Collection edges = getEdges();
Collection to = new ArrayList();
Edge edge;
for (Iterator itr = edges.iterator(); itr.hasNext();) {
edge = (Edge) itr.next();
if (edge.isTo(node))
to.add(edge);
}
return to;
}
/**
* Return all the edges from one node to another.
*/
public Collection getEdges(Object from, Object to) {
Collection edges = getEdgesFrom(from);
Collection matches = new ArrayList(edges.size());
Edge edge;
for (Iterator itr = edges.iterator(); itr.hasNext();) {
edge = (Edge) itr.next();
if (edge.isTo(to))
matches.add(edge);
}
return matches;
}
/**
* Add an edge to the graph.
*/
public void addEdge(Edge edge) {
if (!containsNode(edge.getTo()))
throw new IllegalArgumentException(edge.getTo().toString());
if (!containsNode(edge.getFrom()))
throw new IllegalArgumentException(edge.getFrom().toString());
Collection from = (Collection) _nodes.get(edge.getFrom());
if (from == null) {
from = new ArrayList(3);
_nodes.put(edge.getFrom(), from);
}
from.add(edge);
if (!edge.isDirected() && !edge.getFrom().equals(edge.getTo())) {
Collection to = (Collection) _nodes.get(edge.getTo());
if (to == null) {
to = new ArrayList(3);
_nodes.put(edge.getTo(), to);
}
to.add(edge);
}
}
/**
* Remove an edge from the graph.
*
* @return true if the edge was removed, false if not in the graph
*/
public boolean removeEdge(Edge edge) {
Collection edges = (Collection) _nodes.get(edge.getFrom());
if (edges == null)
return false;
boolean rem = edges.remove(edge);
if (rem && !edge.isDirected()) {
edges = (Collection) _nodes.get(edge.getTo());
if (edges != null)
edges.remove(edge);
}
return rem;
}
/**
* Clear all nodes and edges of the bookkeeping information from their
* last traversal.
*/
public void clearTraversal() {
Collection edges;
for (Iterator vals = _nodes.values().iterator(); vals.hasNext();) {
edges = (Collection) vals.next();
if (edges != null)
for (Iterator ed = edges.iterator(); ed.hasNext();)
((Edge) ed.next()).clearTraversal ();
}
}
}

View File

@ -0,0 +1,47 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.openjpa.lib.graph;
/**
* <p>A helper interface that allows third parties to be notified of
* graph events during graph traversals</p>
*
* @author Steve Kim
* @since 1.0.0
* @nojavadoc
*/
public interface GraphVisitor {
/**
* May not be called. The meaning of this method is dependent
* on the traversal being used. See each appropriate graph
* walker for details.
*/
public void nodeSeen(Object node);
/**
* will only be called once per node
*/
public void nodeVisited(Object node);
/**
* may visit the node twice (both sides)
*/
public void edgeVisited(Edge edge);
}

View File

@ -0,0 +1,35 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.openjpa.lib.graph;
/**
* <p>Struct used to track graph node information during traversal.</p>
*
* @author Abe White
* @since 1.0.0
*/
class NodeInfo {
public static final int COLOR_WHITE = 0;
public static final int COLOR_GRAY = 1;
public static final int COLOR_BLACK = 2;
public int finished = 0;
public int color = COLOR_WHITE;
}

View File

@ -0,0 +1,27 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you 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.
-->
<html>
<body>
<p><strong>Graph Abstraction</strong></p>
<p>
This package provides a graph abstraction and graph-related algorithms.
</p>
</body>
</html>

View File

@ -0,0 +1,80 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.openjpa.lib.graph;
import java.util.Collection;
import java.util.Iterator;
import org.apache.openjpa.lib.test.AbstractTestCase;
/**
* <p>Tests the {@link DepthFirstAnalysis} type.</p>
*
* @author Abe White
*/
public class TestDepthFirstAnalysis
extends AbstractTestCase {
private DepthFirstAnalysis _dfa = null;
public void setUp() {
Graph graph = new Graph();
Object node1 = new Object();
Object node2 = new Object();
Object node3 = new Object();
Object node4 = new Object();
graph.addNode(node1);
graph.addNode(node2);
graph.addNode(node3);
graph.addNode(node4);
graph.addEdge(new Edge(node1, node2, true));
graph.addEdge(new Edge(node2, node3, true));
graph.addEdge(new Edge(node3, node1, true));
graph.addEdge(new Edge(node3, node4, true));
graph.addEdge(new Edge(node2, node2, true));
_dfa = new DepthFirstAnalysis(graph);
}
public void testNodeSorting() {
Collection nodes = _dfa.getSortedNodes();
assertEquals(4, nodes.size());
int time = Integer.MAX_VALUE;
Object node;
for (Iterator itr = nodes.iterator(); itr.hasNext();) {
node = itr.next();
assertTrue(time >= _dfa.getFinishedTime(node));
time = _dfa.getFinishedTime(node);
}
}
public void testEdgeTyping() {
Collection edges = _dfa.getEdges(Edge.TYPE_BACK);
assertEquals(2, edges.size());
Iterator itr = edges.iterator();
Edge edge0 = (Edge) itr.next();
Edge edge1 = (Edge) itr.next();
assertTrue((edge0.getTo() == edge0.getFrom())
|| edge1.getTo() == edge1.getFrom());
}
public static void main(String[] args) {
main(TestDepthFirstAnalysis.class);
}
}

View File

@ -0,0 +1,148 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.openjpa.lib.graph;
import java.util.Collection;
import java.util.Iterator;
import org.apache.openjpa.lib.test.AbstractTestCase;
/**
* <p>Tests the {@link Graph} type, and in so doing implicitly tests the
* {@link Edge} as well.</p>
*
* @author Abe White
*/
public class TestGraph
extends AbstractTestCase {
private Graph _graph = new Graph();
private Object _node1 = new Object();
private Object _node2 = new Object();
private Object _node3 = new Object();
private Edge _edge1 = new Edge(_node1, _node2, true);
private Edge _edge2 = new Edge(_node2, _node3, true);
private Edge _edge3 = new Edge(_node1, _node3, false);
private Edge _edge4 = new Edge(_node2, _node2, false);
public void setUp() {
_graph.addNode(_node1);
_graph.addNode(_node2);
_graph.addNode(_node3);
_graph.addEdge(_edge1);
_graph.addEdge(_edge2);
_graph.addEdge(_edge3);
_graph.addEdge(_edge4);
}
/**
* Tests adding and retrieving nodes and edges.
*/
public void testAddRetrieve() {
assertEquals(3, _graph.getNodes().size());
assertEquals(4, _graph.getEdges().size());
Collection edges = _graph.getEdgesFrom(_node1);
assertEquals(2, edges.size());
Iterator itr = edges.iterator();
Edge edge0 = (Edge) itr.next();
Edge edge1 = (Edge) itr.next();
assertTrue((edge0 == _edge1 && edge1 == _edge3)
|| (edge0 == _edge3 && edge1 == _edge1));
edges = _graph.getEdgesTo(_node1);
assertEquals(1, edges.size());
assertEquals(_edge3, edges.iterator().next());
edges = _graph.getEdges(_node1, _node3);
assertEquals(1, edges.size());
assertEquals(_edge3, edges.iterator().next());
edges = _graph.getEdges(_node3, _node1);
assertEquals(1, edges.size());
assertEquals(_edge3, edges.iterator().next());
edges = _graph.getEdgesFrom(_node2);
assertEquals(2, edges.size());
itr = edges.iterator();
edge0 = (Edge) itr.next();
edge1 = (Edge) itr.next();
assertTrue((edge0 == _edge2 && edge1 == _edge4)
|| (edge0 == _edge4 && edge1 == _edge2));
edges = _graph.getEdgesTo(_node2);
assertEquals(2, edges.size());
itr = edges.iterator();
edge0 = (Edge) itr.next();
edge1 = (Edge) itr.next();
assertTrue((edge0 == _edge1 && edge1 == _edge4)
|| (edge0 == _edge4 && edge1 == _edge1));
edges = _graph.getEdges(_node2, _node2);
assertEquals(1, edges.size());
assertEquals(_edge4, edges.iterator().next());
edges = _graph.getEdgesFrom(_node3);
assertEquals(1, edges.size());
assertEquals(_edge3, edges.iterator().next());
}
/**
* Test removing edges.
*/
public void testRemoveEdges() {
assertTrue(_graph.removeEdge(_edge2));
Collection edges = _graph.getEdgesFrom(_node2);
assertEquals(1, edges.size());
assertEquals(_edge4, edges.iterator().next());
assertTrue(_graph.removeEdge(_edge3));
edges = _graph.getEdgesFrom(_node1);
assertEquals(1, edges.size());
assertEquals(_edge1, edges.iterator().next());
edges = _graph.getEdgesTo(_node1);
assertEquals(0, edges.size());
edges = _graph.getEdgesTo(_node3);
assertEquals(0, edges.size());
edges = _graph.getEdgesFrom(_node3);
assertEquals(0, edges.size());
}
/**
* Test removing nodes.
*/
public void testRemoveNodes() {
assertTrue(_graph.removeNode(_node3));
Collection edges = _graph.getEdges();
assertEquals(2, edges.size());
Iterator itr = edges.iterator();
Edge edge0 = (Edge) itr.next();
Edge edge1 = (Edge) itr.next();
assertTrue((edge0 == _edge1 && edge1 == _edge4)
|| (edge0 == _edge4 && edge1 == _edge1));
edges = _graph.getEdgesFrom(_node1);
assertEquals(1, edges.size());
assertEquals(_edge1, edges.iterator().next());
edges = _graph.getEdgesTo(_node1);
assertEquals(0, edges.size());
}
public static void main(String[] args) {
main(TestGraph.class);
}
}