org.squashtest.tm.service.internal.testcase.TestCaseCallTreeFinder.java Source code

Java tutorial

Introduction

Here is the source code for org.squashtest.tm.service.internal.testcase.TestCaseCallTreeFinder.java

Source

/**
 *     This file is part of the Squashtest platform.
 *     Copyright (C) 2010 - 2016 Henix, henix.fr
 *
 *     See the NOTICE file distributed with this work for additional
 *     information regarding copyright ownership.
 *
 *     This 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 3 of the License, or
 *     (at your option) any later version.
 *
 *     this software 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 software.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.squashtest.tm.service.internal.testcase;

import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import javax.inject.Inject;

import org.apache.commons.collections.Bag;
import org.apache.commons.collections.bag.HashBag;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.squashtest.tm.domain.NamedReference;
import org.squashtest.tm.domain.NamedReferencePair;
import org.squashtest.tm.domain.library.structures.LibraryGraph;
import org.squashtest.tm.domain.library.structures.LibraryGraph.SimpleNode;
import org.squashtest.tm.service.internal.repository.TestCaseDao;

/**
 *
 * @author Gregory Fouquet
 *
 */
@Component
public class TestCaseCallTreeFinder {

    private static final Logger LOGGER = LoggerFactory.getLogger(TestCaseCallTreeFinder.class);

    @Inject
    private TestCaseDao testCaseDao;

    /**
     *  given the Id of a test case, will compute the subsequent test case call tree.
     *
     * @param rootTcId. Null is not legal and unchecked.
     * @return a set containing the ids of the called test cases, that will not include the calling test case id. Not null, possibly empty.
     */
    public Set<Long> getTestCaseCallTree(Long rootTcId) {

        Set<Long> calleesIds = new HashSet<>();
        List<Long> prevCalleesIds = testCaseDao.findAllDistinctTestCasesIdsCalledByTestCase(rootTcId);

        LOGGER.trace("TestCase #{} directly calls {}", rootTcId, prevCalleesIds);

        prevCalleesIds.remove(rootTcId);// added to prevent infinite cycle in case of inconsistent data

        while (!prevCalleesIds.isEmpty()) {
            // FIXME a tester avant correction : boucle infinie quand il y a un cycle dans les appels de cas de test
            calleesIds.addAll(prevCalleesIds);
            prevCalleesIds = testCaseDao.findAllTestCasesIdsCalledByTestCases(prevCalleesIds);

            LOGGER.trace("TestCase #{} indirectly calls {}", rootTcId, prevCalleesIds);
            prevCalleesIds.remove(rootTcId);// added to prevent infinite cycle in case of inconsistent data
        }

        return calleesIds;
    }

    /**
     * same as {@link #getTestCaseCallTree(Long)}, but for multiple test cases
     *
     * @param tcIds
     * @return
     */
    public Set<Long> getTestCaseCallTree(Collection<Long> tcIds) {

        Set<Long> result = new HashSet<>();

        Collection<Long> process = tcIds;
        List<Long> next;

        while (!process.isEmpty()) {
            next = testCaseDao.findAllTestCasesIdsCalledByTestCases(process);
            next.removeAll(result); // remove results that were already processed

            result.addAll(next);
            process = next;

        }

        return result;
    }

    /**
     * Returns all test cases ids of test casese calling the one matching the given id.
     * The search will go through all the calling hierarchy.
     *
     * @param rootTcId : the id of the potentially called test case we want the callers of.
     * @return : all calling test case (even through multiple call)
     */
    public Set<Long> getTestCaseCallers(Long rootTcId) {

        Set<Long> callerIds = new HashSet<>();
        List<Long> prevCallerIds = testCaseDao.findAllDistinctTestCasesIdsCallingTestCase(rootTcId);

        LOGGER.trace("TestCase #{} directly calls {}", prevCallerIds, rootTcId);

        prevCallerIds.remove(rootTcId);// added to prevent infinite cycle in case of inconsistent data

        while (!prevCallerIds.isEmpty()) {
            // FIXME a tester avant correction : boucle infinie quand il y a un cycle dans les appels de cas de test
            callerIds.addAll(prevCallerIds);
            prevCallerIds = testCaseDao.findAllTestCasesIdsCallingTestCases(prevCallerIds);

            LOGGER.trace("TestCase #{} indirectly calls {}", prevCallerIds, rootTcId);
            prevCallerIds.remove(rootTcId);// added to prevent infinite cycle in case of inconsistent data
        }

        return callerIds;
    }

    /**
     * returns a graph of simple nodes representing the ancestry of the nodes in arguments.
     *
     * @param calledIds
     * @return
     */
    public LibraryGraph<NamedReference, SimpleNode<NamedReference>> getCallerGraph(List<Long> calledIds) {

        // remember which nodes were processed (so that we can spare less DB calls in the worst cases scenarios)
        Set<Long> allIds = new HashSet<>();
        allIds.addAll(calledIds);

        // the temporary result variable
        List<NamedReferencePair> allpairs = new LinkedList<>();

        // a temporary buffer variable
        List<Long> currentCalled = new LinkedList<>(calledIds);

        // phase 1 : data collection
        while (!currentCalled.isEmpty()) {

            List<NamedReferencePair> currentPair = testCaseDao.findTestCaseCallsUpstream(currentCalled);

            allpairs.addAll(currentPair);

            /*
             * collect the caller ids in the currentPair for the next loop, with the following restrictions :
             * 1) if the caller is not null, and
             * 2) if that node was not already processed
             *
             * then we can add that id.
             */

            List<Long> nextCalled = new LinkedList<>();

            for (NamedReferencePair pair : currentPair) {
                // no caller -> no need for further processing
                if (pair.getCaller() == null) {
                    continue;
                }

                Long key = pair.getCaller().getId();
                if (!allIds.contains(key)) {
                    nextCalled.add(key);
                    allIds.add(key);
                }

            }

            currentCalled = nextCalled;

        }

        // phase 2 : make that graph

        LibraryGraph<NamedReference, SimpleNode<NamedReference>> graph = new LibraryGraph<>();

        for (NamedReferencePair pair : allpairs) {
            graph.addEdge(node(pair.getCaller()), node(pair.getCalled()));
        }

        return graph;

    }

    /**
     * returns an extended graph of ALL the test cases that can be reached by the source test cases, navigating on the call test steps
     * in both directions. This graph include the callers and the called test cases, recursively including their callers and test cases
     * etc until all relevant test cases are included in the resulting graph.
     *
     * @param calledIds
     * @return
     */
    // note :  for each node, call steps are searched both upward and downward. As a result every edge will
    // be found twice (once on the up, once on the down). We then must build the graph by using only one edge over two
    // that's why we use a bag here, and then we halve the cardinality.
    public LibraryGraph<NamedReference, SimpleNode<NamedReference>> getExtendedGraph(List<Long> sourceIds) {

        // remember which nodes were processed (so that we can spare less DB calls in the worst cases scenarios)
        Set<Long> treated = new HashSet<>();
        treated.addAll(sourceIds);

        // the temporary result variable
        Bag allpairs = new HashBag();

        // a temporary buffer variable
        List<Long> currentNodes = new LinkedList<>(sourceIds);

        // phase 1 : data collection

        while (!currentNodes.isEmpty()) {

            List<NamedReferencePair> currentPair = testCaseDao.findTestCaseCallsUpstream(currentNodes);
            currentPair.addAll(testCaseDao.findTestCaseCallsDownstream(currentNodes));

            allpairs.addAll(currentPair);

            /*
             * collect the caller ids in the currentPair for the next loop, with the following restrictions :
             * 1) if the "caller" slot of the Object[] is not null,
             * 2) if the "called" slot is not null,
             * 2) if that node was not already processed
             *
             * then we can add that id.
             */

            List<Long> nextNodes = new LinkedList<>();

            for (NamedReferencePair pair : currentPair) {

                // no caller or no called -> no need for further processing
                if (pair.getCaller() == null || pair.getCalled() == null) {
                    continue;
                }

                Long callerkey = pair.getCaller().getId();
                if (!treated.contains(callerkey)) {
                    nextNodes.add(callerkey);
                    treated.add(callerkey);
                }

                Long calledkey = pair.getCalled().getId();
                if (!treated.contains(calledkey)) {
                    nextNodes.add(calledkey);
                    treated.add(calledkey);
                }

            }

            currentNodes = nextNodes;

        }

        // phase 2 : halve the number of edges as explained in the comment above the method
        // every edges will appear two times, except for "boundaries" (ie caller is null or called is null),
        // for which the cardinality is 1.
        for (NamedReferencePair pair : (Set<NamedReferencePair>) allpairs.uniqueSet()) {
            int cardinality = allpairs.getCount(pair);
            if (cardinality > 1) {
                allpairs.remove(pair, cardinality / 2);
            }
        }

        // phase 3 : make that graph
        LibraryGraph<NamedReference, SimpleNode<NamedReference>> graph = new LibraryGraph<>();

        for (NamedReferencePair pair : (Iterable<NamedReferencePair>) allpairs) {
            graph.addEdge(node(pair.getCaller()), node(pair.getCalled()));
        }

        return graph;

    }

    private SimpleNode<NamedReference> node(NamedReference ref) {
        return ref != null ? new SimpleNode<>(ref) : null;
    }

}