org.dbunit.util.search.DepthFirstSearch.java Source code

Java tutorial

Introduction

Here is the source code for org.dbunit.util.search.DepthFirstSearch.java

Source

/*
 *
 * 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.Set;
import java.util.SortedSet;

import org.apache.commons.collections.set.ListOrderedSet;
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 ListOrderedSet 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 ListOrderedSet 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 ListOrderedSet 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 ListOrderedSet();

        // callback used to help the search
        this.callback = callback;

        this.nodesFrom = new ListOrderedSet();

        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;

    }

}