DepthFirstSearch.java

  1. /*
  2.  *
  3.  * The DbUnit Database Testing Framework
  4.  * Copyright (C)2005-2008, DbUnit.org
  5.  *
  6.  * This library is free software; you can redistribute it and/or
  7.  * modify it under the terms of the GNU Lesser General Public
  8.  * License as published by the Free Software Foundation; either
  9.  * version 2.1 of the License, or (at your option) any later version.
  10.  *
  11.  * This library is distributed in the hope that it will be useful,
  12.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14.  * Lesser General Public License for more details.
  15.  *
  16.  * You should have received a copy of the GNU Lesser General Public
  17.  * License along with this library; if not, write to the Free Software
  18.  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  19.  *
  20.  */

  21. package org.dbunit.util.search;

  22. import java.util.HashSet;
  23. import java.util.Iterator;
  24. import java.util.LinkedHashSet;
  25. import java.util.Set;
  26. import java.util.SortedSet;

  27. import org.slf4j.Logger;
  28. import org.slf4j.LoggerFactory;
  29. import org.dbunit.util.CollectionsHelper;

  30. /**
  31.  * Search using depth-first algorithm.<br>
  32.  * <br>
  33.  * An instance of this class must be used only once, as it maintains the
  34.  * internal state of the search.<br>
  35.  * <br>
  36.  *
  37.  * @author gommma (gommma AT users.sourceforge.net)
  38.  * @author Last changed by: $Author$
  39.  * @version $Revision$ $Date$
  40.  * @since 2.4.0
  41.  */
  42. public class DepthFirstSearch implements ISearchAlgorithm {

  43.   // nodes that were already scanned during the search
  44.   private Set scannedNodes;
  45.   private Set reverseScannedNodes;

  46.   protected final Logger logger = LoggerFactory.getLogger(getClass());
  47.  
  48.   // result of the search
  49.   private LinkedHashSet result;
  50.  
  51.   // input of the search
  52.   private Set nodesFrom;

  53.   // callback used to help the search
  54.   private ISearchCallback callback;

  55.   // flag, as one instance cannot be used more than once
  56.   private boolean searching = false;
  57.  
  58.     /**
  59.      * The search depth to be used when recursing through the child nodes
  60.      */
  61.     private int searchDepth = Integer.MAX_VALUE;

  62.  
  63.     /**
  64.      * Creates a new depth-first algorithm using the maximum search depth for recursing over the nodes.
  65.      */
  66.     public DepthFirstSearch()
  67.     {
  68.         super();
  69.     }

  70.     /**
  71.      * Creates a new depth-first algorithm
  72.      * @param searchDepth The search depth to be used when traversing the nodes recursively. Must be > 0.
  73.      * @since 2.4
  74.      */
  75.     public DepthFirstSearch(int searchDepth)
  76.     {
  77.         super();
  78.         if(searchDepth<=0){
  79.             throw new IllegalArgumentException("The searchDepth must be > 0. Given: " + searchDepth);
  80.         }
  81.         this.searchDepth = searchDepth;
  82.     }

  83.   /**
  84.    * Alternative option to search() that takes an array of nodes as input (instead of a Set)
  85.    * @see ISearchAlgorithm
  86.    */
  87.   public Set search(Object[] nodesFrom, ISearchCallback callback)
  88.       throws SearchException
  89.   {
  90.       if(logger.isDebugEnabled())
  91.           logger.debug("search(nodesFrom={}, callback={}) - start", nodesFrom, callback);

  92.     return search(CollectionsHelper.objectsToSet(nodesFrom), callback);
  93.   }
  94.  
  95.   /**
  96.    * @see ISearchAlgorithm
  97.    */
  98.   public Set search(Set nodesFrom, ISearchCallback callback)
  99.       throws SearchException {
  100.       if(logger.isDebugEnabled())
  101.           logger.debug("search(nodesFrom={}, callback={}) - start", nodesFrom, callback);

  102.     synchronized (this) {
  103.       if (searching) {
  104.         throw new IllegalStateException("already searching/searched");
  105.       }
  106.       this.searching = true;
  107.     }

  108.     // set of tables that will be returned (i.e, the declared tables and its
  109.     // dependencies)
  110.     this.result = new LinkedHashSet();

  111.     // callback used to help the search
  112.     this.callback = callback;
  113.        
  114.     this.nodesFrom = new LinkedHashSet();
  115.    
  116.     int sizeNodesFromBefore = 0;
  117.     int sizeResultBefore = 0;
  118.     boolean keepSearching = true;
  119.     this.scannedNodes = new HashSet();
  120.     this.reverseScannedNodes = new HashSet();
  121.     this.scannedNodes = new HashSet();
  122.     do {
  123.      
  124.       // In a traditional depth-first search, the getEdges() method should return only
  125.       // edges where this node is the 'from' vertex, as the graph is known in advance.
  126.       // But in our case, the graph is built 'on the fly', so it's possible that the
  127.       // getEdges() also returns edges where the node is the 'to' vertex.
  128.       // So, before we do the "real" search, we need to do a reverse search to find out
  129.       // all the nodes that should be part of the input.
  130.       Iterator iterator = nodesFrom.iterator();      
  131.       while (iterator.hasNext()) {
  132.         Object node = iterator.next();
  133.         reverseSearch(node, 0);
  134.       }
  135. //        this.nodesFrom = nodesFrom;

  136.         // now that the input is adjusted, do the search
  137.       iterator = this.nodesFrom.iterator();
  138.      
  139.       while (iterator.hasNext()) {
  140.         Object node = iterator.next();
  141.         search(node, 0);
  142.       }
  143.      
  144.       nodesFrom = new HashSet(this.result);
  145.      
  146.       // decides if we continue searching
  147.       boolean sizesDontMatch = this.result.size() != this.nodesFrom.size();
  148.       boolean resultChanged = this.result.size() != sizeResultBefore;
  149.       boolean nodesFromChanged = this.nodesFrom.size() != sizeNodesFromBefore;
  150.       sizeNodesFromBefore = this.nodesFrom.size();
  151.       sizeResultBefore = this.result.size();
  152.       keepSearching = sizesDontMatch && ( resultChanged || nodesFromChanged );
  153.      
  154.     } while ( keepSearching );
  155.    
  156.     return this.result;

  157.   }

  158.   /**
  159.    * This is the real depth first search algorithm, which is called recursively.
  160.    *
  161.    * @param node node where the search starts
  162.    * @param currentSearchDepth the search depth in the recursion
  163.    * @return true if the node has been already searched before
  164.    * @throws Exception if an exception occurs while getting the edges
  165.    */
  166.   private boolean search(Object node, int currentSearchDepth) throws SearchException {
  167.     if ( this.logger.isDebugEnabled() ) {
  168.       this.logger.debug( "search:" + node );
  169.     }
  170.     if (this.scannedNodes.contains(node)) {
  171.       if ( this.logger.isDebugEnabled() ) {
  172.         this.logger.debug( "already searched; returning true" );
  173.       }
  174.       return true;
  175.     }
  176.     if (!this.callback.searchNode(node)) {
  177.       if ( this.logger.isDebugEnabled() ) {
  178.         this.logger.debug( "Callback handler blocked search for node " + node );
  179.       }
  180.       return true;
  181.     }
  182.    
  183.     if ( this.logger.isDebugEnabled() ) {
  184.       this.logger.debug("Pushing " + node);      
  185.     }
  186.     this.scannedNodes.add(node);

  187.     if(currentSearchDepth < this.searchDepth) {
  188.         // first, search the nodes the node depends on
  189.         SortedSet edges = this.callback.getEdges(node);
  190.         if (edges != null) {
  191.             Iterator iterator = edges.iterator();
  192.             while (iterator.hasNext()) {
  193.               // and recursively search these nodes
  194.               IEdge edge = (IEdge) iterator.next();
  195.               Object toNode = edge.getTo();
  196.               search(toNode, currentSearchDepth++);
  197.             }
  198.           }
  199.     }

  200.     // finally, add the node to the result
  201.     if ( this.logger.isDebugEnabled() ) {
  202.       this.logger.debug( "Adding node " + node + " to the final result" );
  203.     }
  204.     // notify the callback a node was added
  205.     this.callback.nodeAdded(node);
  206.     result.add(node);
  207.    
  208.     return false;
  209.   }

  210.   /**
  211.    * Do a reverse search (i.e, searching the other way of the edges) in order
  212.    * to adjust the input before the real search.
  213.    * @param node node where the search starts
  214.    * @param currentSearchDepth the search depth in the recursion
  215.    * @return true if the node has been already reverse-searched before
  216.    * @throws Exception if an exception occurs while getting the edges
  217.    */
  218.   private boolean reverseSearch(Object node, int currentSearchDepth) throws SearchException {
  219.     if ( this.logger.isDebugEnabled() ) {
  220.       this.logger.debug( "reverseSearch:" + node );
  221.     }
  222.     if (this.reverseScannedNodes.contains(node)) {
  223.       if ( this.logger.isDebugEnabled() ) {
  224.         this.logger.debug( "already searched; returning true" );
  225.       }
  226.       return true;
  227.     }
  228.    
  229.     if (!this.callback.searchNode(node)) {
  230.       if ( this.logger.isDebugEnabled() ) {
  231.         this.logger.debug( "callback handler blocked reverse search for node " + node );
  232.       }
  233.       return true;
  234.     }
  235.    
  236.     if ( this.logger.isDebugEnabled() ) {
  237.       this.logger.debug("Pushing (reverse) " + node);      
  238.     }
  239.     this.reverseScannedNodes.add(node);

  240.     if(currentSearchDepth < this.searchDepth) {
  241.         // first, search the nodes the node depends on
  242.         SortedSet edges = this.callback.getEdges(node);    
  243.         if (edges != null) {
  244.           Iterator iterator = edges.iterator();
  245.           while (iterator.hasNext()) {
  246.             // and recursively search these nodes if we find a match
  247.             IEdge edge = (IEdge) iterator.next();
  248.             Object toNode = edge.getTo();
  249.             if ( toNode.equals(node) ) {
  250.               Object fromNode = edge.getFrom();
  251.               reverseSearch(fromNode, currentSearchDepth++);
  252.             }
  253.           }
  254.         }
  255.     }
  256.    
  257.     // finally, add the node to the input
  258.     this.nodesFrom.add(node);

  259.     return false;

  260.   }
  261.  
  262.  
  263. }