/*
* This is free software, licensed under the Gnu Public License (GPL) get a copy from <http://www.gnu.org/licenses/gpl.html>
*
* @version $Id: DependencyResolver.java,v 1.3 2005-06-18 04:58:13 hzeller Exp $
*
* @author <a href="mailto:martin.grotzke@javakaffee.de">Martin Grotzke</a>
*/
package henplus.util;
import henplus.logging.Logger;
import henplus.sqlmodel.ColumnFkInfo;
import henplus.sqlmodel.Table;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Resolves dependencies between a given set of tables in respect to their foreign keys.<br>
* Created on: Sep 20, 2004<br>
*
* @author <a href="mailto:martin.grotzke@javakaffee.de">Martin Grotzke</a>
* @version $Id: DependencyResolver.java,v 1.3 2005-06-18 04:58:13 hzeller Exp $
*/
public final class DependencyResolver {
private final Iterator<Table> _tableIter;
private Set<List<Table>> _cyclicDependencies;
/**
* @param tableIter
* An <code>Iterator</code> over <code>Table</code>s.
*/
public DependencyResolver(final Iterator<Table> tableIter) {
_tableIter = tableIter;
}
/**
* @param tables
* A <code>Set</code> of <code>Table</code> objects.
*/
public DependencyResolver(final Set<Table> tables) {
_tableIter = tables != null ? tables.iterator() : null;
}
/**
* @return
*
*/
public ResolverResult sortTables() {
final LinkedHashMap<String, Table> resolved = new LinkedHashMap<String, Table>();
Map<String, Table> unresolved = null;
// first run: separate tables with and without dependencies
while (_tableIter.hasNext()) {
final Table t = _tableIter.next();
if (t == null) {
continue;
}
final Set<ColumnFkInfo> fks = t.getForeignKeys();
// no dependency / foreign key?
Logger.debug("[sortTables] put %s to resolved.", t);
if (fks == null) {
resolved.put(t.getName(), t);
} else {
// dependency fulfilled?
boolean nodep = true;
final Iterator<ColumnFkInfo> iter2 = fks.iterator();
while (iter2.hasNext() && nodep) {
final ColumnFkInfo fk = iter2.next();
if (!resolved.containsKey(fk.getPkTable())) {
nodep = false;
}
}
if (nodep) {
resolved.put(t.getName(), t);
} else {
if (unresolved == null) {
unresolved = new HashMap<String, Table>();
}
unresolved.put(t.getName(), t);
}
}
}
// second run: we check remaining deps
if (unresolved != null) {
for (Table t : unresolved.values()) {
resolveDep(t, null, resolved, unresolved);
}
}
// do we need a second run?
// unresolved = cleanUnresolved( resolved, unresolved );
// add all unresolved/conflicting tables to the resulting list
final Collection<Table> result = resolved.values();
if (unresolved != null) {
for (Table table : unresolved.values()) {
if (!result.contains(table)) {
result.add(table);
}
}
}
return new ResolverResult(result, _cyclicDependencies);
}
/**
* @return
*
*/
/*
* Martin: needed ? private Set restructureDeps() { Set deps = null; if (
* cyclicDependencies != null ) { deps = new HashSet(); Iterator iter =
* cyclicDependencies.iterator(); while ( iter.hasNext() ) deps.add(
* ((ListMap)iter.next()).valuesList() ); } return deps; }
*/
/**
* @param resolved
* @param unresolved
* @return A Map which contains all yet unresolved Tables mapped to their names.
*/
/*
* Martin: needed ? private Map cleanUnresolved( Map resolved, Map
* unresolved ) { Map result = null;
*
* if ( unresolved != null ) { Iterator iter =
* unresolved.keySet().iterator(); while ( iter.hasNext() ) { // type
* element = (type) iter.next();
*
* } }
*
* return null; }
*/
/**
* @param t
* @param cyclePath
* The path of tables which have cyclic dependencies
* @param resolved
* @param unresolved
*/
private void resolveDep(final Table t, List<Table> cyclePath, final Map<String, Table> resolved,
final Map<String, Table> unresolved) {
Logger.debug("[resolveDep] >>> Starting for t: %s and cyclePath: %s", t, cyclePath);
// if the current table is no more in the unresolved collection
if (t == null || resolved.containsKey(t.getName())) {
return;
}
boolean nodep = false;
boolean firstrun = true;
final Set<ColumnFkInfo> fks = t.getForeignKeys();
final Iterator<ColumnFkInfo> iter = fks.iterator();
while (iter.hasNext()) {
final ColumnFkInfo fk = iter.next();
Logger.debug("[resolveDep] FK -> %s: %s", fk.getPkTable(), resolved.containsKey(fk.getPkTable()));
if (!resolved.containsKey(fk.getPkTable())) {
final Table inner = unresolved.get(fk.getPkTable());
// if there's yet a cycle with the two tables inner following t
// then proceed to the next FK and ignore this potential cycle
if (duplicateCycle(t, inner)) {
continue;
}
if (cyclePath != null && cyclePath.contains(inner)) {
cyclePath.add(t);
// create a new list for the detected cycle to add to the
// cyclicDeps, the former one (cyclePath) is used further on
final List<Table> cycle = new ArrayList<Table>(cyclePath);
cycle.add(inner);
if (_cyclicDependencies == null) {
_cyclicDependencies = new HashSet<List<Table>>();
}
Logger.debug("[resolveDep] +++ Putting cyclePath: %s", cycle);
_cyclicDependencies.add(cycle);
continue;
} else {
if (cyclePath == null) {
Logger.debug("[resolveDep] Starting cyclePath with: %s", t);
cyclePath = new ArrayList<Table>();
}
cyclePath.add(t);
}
resolveDep(inner, cyclePath, resolved, unresolved);
if (resolved.containsKey(fk.getPkTable())) {
nodep = (firstrun || nodep);
firstrun = false;
}
} else {
nodep = (firstrun || nodep);
firstrun = false;
}
}
if (nodep && !resolved.containsKey(t.getName())) {
Logger.debug("[resolveDep] put %s to resolved.", t);
resolved.put(t.getName(), t);
}
}
/**
* Tests if there's yet a cycle (stored in cyclicDependencies) with the given tables t and inner, whith inner following t.
*
* @param t
* @param inner
* @return
*/
private boolean duplicateCycle(final Table t, final Table inner) {
boolean result = false;
if (_cyclicDependencies != null) {
final Iterator<List<Table>> iter = _cyclicDependencies.iterator();
while (iter.hasNext() && !result) {
final List<Table> path = iter.next();
if (path.contains(t)) {
final int tIdx = path.indexOf(t);
if (path.size() > tIdx + 1 && inner.equals(path.get(tIdx + 1))) {
result = true;
}
}
}
}
return result;
}
public class ResolverResult {
private final Collection<Table> _tables;
private final Set<? extends Collection<Table>> _cyclicDependencies;
public ResolverResult(final Collection<Table> tables, final Set<? extends Collection<Table>> cyclicDependencies) {
_tables = tables;
_cyclicDependencies = cyclicDependencies;
}
/**
* @return Returns the cyclicDependencies: a <code>Set</code> holding <code>List</code>s of <code>CycleEntry</code> objects,
* where each list represents the path of a cyclic dependency.
*/
public Set<? extends Collection<Table>> getCyclicDependencies() {
return _cyclicDependencies;
}
/**
* @return Returns the tables.
*/
public Collection<Table> getTables() {
return _tables;
}
}
public class CycleEntry {
private final Table _table;
private final ColumnFkInfo _fk;
public CycleEntry(final Table table, final ColumnFkInfo fk) {
_table = table;
_fk = fk;
}
/**
* @return Returns the fk.
*/
public ColumnFkInfo getFk() {
return _fk;
}
/**
* @return Returns the table.
*/
public Table getTable() {
return _table;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("CycleEntry [");
sb.append("table: ").append(_table.getName());
sb.append(", fk: ").append(_fk.toString());
sb.append("]");
return sb.toString();
}
@Override
public boolean equals(final Object other) {
boolean result = false;
if (other == this) {
result = true;
} else if (other instanceof CycleEntry) {
final CycleEntry ce = (CycleEntry) other;
if (_table == null && ce.getTable() == null && _fk == null && ce.getFk() == null) {
result = true;
} else if (_table.equals(ce.getTable()) && _fk.equals(ce.getFk())) {
result = true;
}
}
return result;
}
@Override
public int hashCode() {
return ObjectUtil.nullSafeHashCode(_table) ^ ObjectUtil.nullSafeHashCode(_fk);
}
}
}