mirror of https://github.com/apache/openjpa.git
OPENJPA-235
git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@544918 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
4d23f445c6
commit
5463700d6c
|
@ -214,9 +214,11 @@ public class JDBCConfigurationImpl
|
||||||
updateManagerPlugin = addPlugin("jdbc.UpdateManager", true);
|
updateManagerPlugin = addPlugin("jdbc.UpdateManager", true);
|
||||||
aliases = new String[]{
|
aliases = new String[]{
|
||||||
"default",
|
"default",
|
||||||
"org.apache.openjpa.jdbc.kernel.OperationOrderUpdateManager",
|
"org.apache.openjpa.jdbc.kernel.ConstraintUpdateManager",
|
||||||
"operation-order",
|
"operation-order",
|
||||||
"org.apache.openjpa.jdbc.kernel.OperationOrderUpdateManager",
|
"org.apache.openjpa.jdbc.kernel.OperationOrderUpdateManager",
|
||||||
|
"constraint",
|
||||||
|
"org.apache.openjpa.jdbc.kernel.ConstraintUpdateManager",
|
||||||
};
|
};
|
||||||
updateManagerPlugin.setAliases(aliases);
|
updateManagerPlugin.setAliases(aliases);
|
||||||
updateManagerPlugin.setDefault(aliases[0]);
|
updateManagerPlugin.setDefault(aliases[0]);
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 () + "]";
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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>
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue