DepthFirstSearch.java
- /*
- *
- * The DbUnit Database Testing Framework
- * Copyright (C)2005-2008, DbUnit.org
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- *
- */
- package org.dbunit.util.search;
- import java.util.HashSet;
- import java.util.Iterator;
- import java.util.LinkedHashSet;
- import java.util.Set;
- import java.util.SortedSet;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.dbunit.util.CollectionsHelper;
- /**
- * Search using depth-first algorithm.<br>
- * <br>
- * An instance of this class must be used only once, as it maintains the
- * internal state of the search.<br>
- * <br>
- *
- * @author gommma (gommma AT users.sourceforge.net)
- * @author Last changed by: $Author$
- * @version $Revision$ $Date$
- * @since 2.4.0
- */
- public class DepthFirstSearch implements ISearchAlgorithm {
- // nodes that were already scanned during the search
- private Set scannedNodes;
- private Set reverseScannedNodes;
- protected final Logger logger = LoggerFactory.getLogger(getClass());
-
- // result of the search
- private LinkedHashSet result;
-
- // input of the search
- private Set nodesFrom;
- // callback used to help the search
- private ISearchCallback callback;
- // flag, as one instance cannot be used more than once
- private boolean searching = false;
-
- /**
- * The search depth to be used when recursing through the child nodes
- */
- private int searchDepth = Integer.MAX_VALUE;
-
- /**
- * Creates a new depth-first algorithm using the maximum search depth for recursing over the nodes.
- */
- public DepthFirstSearch()
- {
- super();
- }
- /**
- * Creates a new depth-first algorithm
- * @param searchDepth The search depth to be used when traversing the nodes recursively. Must be > 0.
- * @since 2.4
- */
- public DepthFirstSearch(int searchDepth)
- {
- super();
- if(searchDepth<=0){
- throw new IllegalArgumentException("The searchDepth must be > 0. Given: " + searchDepth);
- }
- this.searchDepth = searchDepth;
- }
- /**
- * Alternative option to search() that takes an array of nodes as input (instead of a Set)
- * @see ISearchAlgorithm
- */
- public Set search(Object[] nodesFrom, ISearchCallback callback)
- throws SearchException
- {
- if(logger.isDebugEnabled())
- logger.debug("search(nodesFrom={}, callback={}) - start", nodesFrom, callback);
- return search(CollectionsHelper.objectsToSet(nodesFrom), callback);
- }
-
- /**
- * @see ISearchAlgorithm
- */
- public Set search(Set nodesFrom, ISearchCallback callback)
- throws SearchException {
- if(logger.isDebugEnabled())
- logger.debug("search(nodesFrom={}, callback={}) - start", nodesFrom, callback);
- synchronized (this) {
- if (searching) {
- throw new IllegalStateException("already searching/searched");
- }
- this.searching = true;
- }
- // set of tables that will be returned (i.e, the declared tables and its
- // dependencies)
- this.result = new LinkedHashSet();
- // callback used to help the search
- this.callback = callback;
-
- this.nodesFrom = new LinkedHashSet();
-
- int sizeNodesFromBefore = 0;
- int sizeResultBefore = 0;
- boolean keepSearching = true;
- this.scannedNodes = new HashSet();
- this.reverseScannedNodes = new HashSet();
- this.scannedNodes = new HashSet();
- do {
-
- // In a traditional depth-first search, the getEdges() method should return only
- // edges where this node is the 'from' vertex, as the graph is known in advance.
- // But in our case, the graph is built 'on the fly', so it's possible that the
- // getEdges() also returns edges where the node is the 'to' vertex.
- // So, before we do the "real" search, we need to do a reverse search to find out
- // all the nodes that should be part of the input.
- Iterator iterator = nodesFrom.iterator();
- while (iterator.hasNext()) {
- Object node = iterator.next();
- reverseSearch(node, 0);
- }
- // this.nodesFrom = nodesFrom;
- // now that the input is adjusted, do the search
- iterator = this.nodesFrom.iterator();
-
- while (iterator.hasNext()) {
- Object node = iterator.next();
- search(node, 0);
- }
-
- nodesFrom = new HashSet(this.result);
-
- // decides if we continue searching
- boolean sizesDontMatch = this.result.size() != this.nodesFrom.size();
- boolean resultChanged = this.result.size() != sizeResultBefore;
- boolean nodesFromChanged = this.nodesFrom.size() != sizeNodesFromBefore;
- sizeNodesFromBefore = this.nodesFrom.size();
- sizeResultBefore = this.result.size();
- keepSearching = sizesDontMatch && ( resultChanged || nodesFromChanged );
-
- } while ( keepSearching );
-
- return this.result;
- }
- /**
- * This is the real depth first search algorithm, which is called recursively.
- *
- * @param node node where the search starts
- * @param currentSearchDepth the search depth in the recursion
- * @return true if the node has been already searched before
- * @throws Exception if an exception occurs while getting the edges
- */
- private boolean search(Object node, int currentSearchDepth) throws SearchException {
- if ( this.logger.isDebugEnabled() ) {
- this.logger.debug( "search:" + node );
- }
- if (this.scannedNodes.contains(node)) {
- if ( this.logger.isDebugEnabled() ) {
- this.logger.debug( "already searched; returning true" );
- }
- return true;
- }
- if (!this.callback.searchNode(node)) {
- if ( this.logger.isDebugEnabled() ) {
- this.logger.debug( "Callback handler blocked search for node " + node );
- }
- return true;
- }
-
- if ( this.logger.isDebugEnabled() ) {
- this.logger.debug("Pushing " + node);
- }
- this.scannedNodes.add(node);
- if(currentSearchDepth < this.searchDepth) {
- // first, search the nodes the node depends on
- SortedSet edges = this.callback.getEdges(node);
- if (edges != null) {
- Iterator iterator = edges.iterator();
- while (iterator.hasNext()) {
- // and recursively search these nodes
- IEdge edge = (IEdge) iterator.next();
- Object toNode = edge.getTo();
- search(toNode, currentSearchDepth++);
- }
- }
- }
- // finally, add the node to the result
- if ( this.logger.isDebugEnabled() ) {
- this.logger.debug( "Adding node " + node + " to the final result" );
- }
- // notify the callback a node was added
- this.callback.nodeAdded(node);
- result.add(node);
-
- return false;
- }
- /**
- * Do a reverse search (i.e, searching the other way of the edges) in order
- * to adjust the input before the real search.
- * @param node node where the search starts
- * @param currentSearchDepth the search depth in the recursion
- * @return true if the node has been already reverse-searched before
- * @throws Exception if an exception occurs while getting the edges
- */
- private boolean reverseSearch(Object node, int currentSearchDepth) throws SearchException {
- if ( this.logger.isDebugEnabled() ) {
- this.logger.debug( "reverseSearch:" + node );
- }
- if (this.reverseScannedNodes.contains(node)) {
- if ( this.logger.isDebugEnabled() ) {
- this.logger.debug( "already searched; returning true" );
- }
- return true;
- }
-
- if (!this.callback.searchNode(node)) {
- if ( this.logger.isDebugEnabled() ) {
- this.logger.debug( "callback handler blocked reverse search for node " + node );
- }
- return true;
- }
-
- if ( this.logger.isDebugEnabled() ) {
- this.logger.debug("Pushing (reverse) " + node);
- }
- this.reverseScannedNodes.add(node);
- if(currentSearchDepth < this.searchDepth) {
- // first, search the nodes the node depends on
- SortedSet edges = this.callback.getEdges(node);
- if (edges != null) {
- Iterator iterator = edges.iterator();
- while (iterator.hasNext()) {
- // and recursively search these nodes if we find a match
- IEdge edge = (IEdge) iterator.next();
- Object toNode = edge.getTo();
- if ( toNode.equals(node) ) {
- Object fromNode = edge.getFrom();
- reverseSearch(fromNode, currentSearchDepth++);
- }
- }
- }
- }
-
- // finally, add the node to the input
- this.nodesFrom.add(node);
- return false;
- }
-
-
- }