/*******************************************************************************
* Copyright (c) 2004, 2010 BREDEX GmbH.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* BREDEX GmbH - initial API and implementation and/or initial documentation
*******************************************************************************/
package org.eclipse.jubula.client.core.utils;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jubula.client.core.model.IEventExecTestCasePO;
import org.eclipse.jubula.client.core.model.IExecTestCasePO;
import org.eclipse.jubula.client.core.model.INodePO;
import org.eclipse.jubula.client.core.model.IProjectPO;
import org.eclipse.jubula.client.core.model.IControllerPO;
import org.eclipse.jubula.client.core.model.IReusedProjectPO;
import org.eclipse.jubula.client.core.model.ISpecTestCasePO;
import org.eclipse.jubula.client.core.model.ITestCasePO;
import org.eclipse.jubula.client.core.persistence.IExecPersistable;
import org.eclipse.jubula.client.core.persistence.ISpecPersistable;
import org.eclipse.jubula.client.core.persistence.ProjectPM;
import org.eclipse.jubula.tools.internal.exception.JBException;
/**
* The tree traverser traverses a tree of <code>INodePO</code> instances
* top-down. The traversion starts at the root node passed to the constructor.
* The recursion is established by calling
* {@link org.eclipse.jubula.client.core.model.INodePO#getNodeListIterator()}
* on any node. On the way top-down, the <code>operate()</code> method of the
* passed operation is called for any node.
*
* @author BREDEX GmbH
* @created 13.09.2005
*/
public class TreeTraverser {
/** constant for no maximum traversal depth */
public static final int NO_DEPTH_LIMIT = -1;
/**
* The tree operation.
*/
private List<ITreeNodeOperation<INodePO>> m_operations =
new ArrayList<ITreeNodeOperation<INodePO>>();
/**
* The root node.
*/
private INodePO m_rootNode;
/**
* Flag to indicate if the event handlers should be traversed.
*/
private boolean m_traverseEventHandlers = false;
/**
* traverses the specification when project as root is given, default
* is traversing the execution
*/
private boolean m_traverseSpecPart = false;
/**
* traverses the execution when project as root is given, this is the
* default behavior
*/
private boolean m_traverseExecPart = true;
/**
* Traverses reused projects when project as the root is given
*/
private boolean m_traverseReused = true;
/** Traverses through ExecTCs */
private boolean m_traverseIntoExecs = true;
/**
* The maximum traversal depth. <code>NO_DEPTH_LIMIT</code> by default.
*/
private int m_maxDepth = NO_DEPTH_LIMIT;
/**
* already visited Nodes. We do not want to visit the same node twice
*/
private Set<String> m_visited = new HashSet<String>(1001);
/** the progress monitor */
private IProgressMonitor m_monitor;
/**
* The constructor.
*
* @param rootNode
* The node where the traversion starts
*/
public TreeTraverser(INodePO rootNode) {
m_rootNode = rootNode;
setMonitor(new NullProgressMonitor());
}
/**
* The constructor.
*
* @param rootNode
* The node where the traversion starts
* @param operation
* The operation to call on any node
*/
public TreeTraverser(INodePO rootNode,
ITreeNodeOperation<INodePO> operation) {
this(rootNode);
m_operations.add(operation);
}
/**
* The constructor.
*
* @param rootNode
* The node where the traversion starts
* @param operation
* The operation to call on any node
* @param traverseSpecPart
* boolean to indicate if specPart or execPart should be traversed,
* when project is given as root
*/
public TreeTraverser(INodePO rootNode,
ITreeNodeOperation<INodePO> operation,
boolean traverseSpecPart) {
this(rootNode, operation, traverseSpecPart, !traverseSpecPart);
}
/**
* The constructor.
*
* @param rootNode
* The node where the traversion starts
* @param operation
* The operation to call on any node
* @param traverseSpecPart
* boolean to indicate if specPart should be traversed,
* when project is given as root
* @param traverseExecPart
* boolean to indicate if execPart should be traversed,
* when project is given as root
*/
public TreeTraverser(INodePO rootNode,
ITreeNodeOperation<INodePO> operation,
boolean traverseSpecPart,
boolean traverseExecPart) {
this(rootNode);
m_operations.add(operation);
m_traverseSpecPart = traverseSpecPart;
m_traverseExecPart = traverseExecPart;
}
/**
* The constructor.
*
* @param rootNode
* The node where the traversion starts
* @param operation
* The operation to call on any node
* @param traverseSpecPart
* boolean to indicate if specPart or execPart should be traversed,
* when project is given as root
* @param maxTraversalDepth The maximum depth of traversal.
*/
public TreeTraverser(INodePO rootNode,
ITreeNodeOperation<INodePO> operation,
boolean traverseSpecPart, int maxTraversalDepth) {
this(rootNode, operation, traverseSpecPart, !traverseSpecPart);
m_maxDepth = maxTraversalDepth;
}
/**
* Implements the recursive traversion.
*
* @param context
* The context
* @param parent
* The parent node
* @param node
* The current node
*/
protected void traverseImpl(ITreeTraverserContext<INodePO> context,
INodePO parent, INodePO node) {
if (m_maxDepth == NO_DEPTH_LIMIT && !getMonitor().isCanceled()
|| m_maxDepth > context.getCurrentTreePath().size()) {
context.append(node);
final boolean alreadyVisited = alreadyVisited(node);
Set<ITreeNodeOperation<INodePO>> suspendedOps = null;
for (ITreeNodeOperation<INodePO> operation : m_operations) {
boolean continueWork =
operation.operate(context, parent, node, alreadyVisited);
if (!continueWork) {
if (suspendedOps == null) {
suspendedOps = new HashSet<ITreeNodeOperation<INodePO>>(
m_operations.size());
}
suspendedOps.add(operation);
}
}
if (suspendedOps != null) {
m_operations.removeAll(suspendedOps);
}
addToVisited(node);
if (context.isContinue() && !m_operations.isEmpty()) {
if (node instanceof IProjectPO) {
IProjectPO project = (IProjectPO)node;
traverseProject(context, project);
} else {
if (!(node instanceof IExecTestCasePO)
|| m_traverseIntoExecs) {
// ExecTCs' getNodeListIterator returns the corresponding SpecTestCasePO's node list!
for (Iterator<INodePO> it = node.getNodeListIterator();
it.hasNext();) {
traverseImpl(context, node, it.next());
}
}
if (node instanceof IControllerPO) {
for (INodePO child : node.getUnmodifiableNodeList()) {
traverseImpl(context, node, child);
}
}
if (m_traverseEventHandlers
&& node instanceof ITestCasePO) {
ISpecTestCasePO testCase;
if (node instanceof IExecTestCasePO) {
testCase =
((IExecTestCasePO)node).getSpecTestCase();
} else {
testCase = (ISpecTestCasePO)node;
}
if (testCase != null) {
for (Iterator<IEventExecTestCasePO> it =
testCase.getAllEventEventExecTC()
.iterator();
it.hasNext();) {
traverseImpl(context, node, it.next());
}
}
}
for (ITreeNodeOperation<INodePO> operation : m_operations) {
operation.postOperate(context, parent, node,
alreadyVisited);
}
}
}
context.removeLast();
if (suspendedOps != null) {
m_operations.addAll(suspendedOps);
}
}
}
/**
* Traverses a Project.
*
* @param context
* The traversal context.
* @param project
* The Project to traverse.
*/
private void traverseProject(ITreeTraverserContext<INodePO> context,
IProjectPO project) {
if (m_traverseSpecPart) {
traverseLocalSpecPart(context, project);
if (m_traverseReused) {
traverseReusedProjectSpecPart(context, project);
}
}
if (m_traverseExecPart) {
traverseExecPart(context, project);
}
}
/**
* @param context the context
* @param project the project
*/
protected void traverseExecPart(ITreeTraverserContext<INodePO> context,
IProjectPO project) {
for (IExecPersistable exec : project.getExecObjCont()
.getExecObjList()) {
traverseImpl(context, project, exec);
}
}
/**
* @param context the context
* @param project the project
*/
protected void traverseReusedProjectSpecPart(
ITreeTraverserContext<INodePO> context, IProjectPO project) {
for (IReusedProjectPO reused
: project.getUsedProjects()) {
try {
IProjectPO reusedProject =
ProjectPM.loadReusedProjectInMasterSession(
reused);
if (reusedProject != null) {
traverseLocalSpecPart(context, reusedProject);
}
} catch (JBException e) {
// Unable to load Reused Project.
// The Reused Project will not be traversed.
}
}
}
/**
* @param context the context
* @param project the project
*/
protected void traverseLocalSpecPart(
ITreeTraverserContext<INodePO> context, IProjectPO project) {
for (ISpecPersistable specNode
: project.getSpecObjCont().getSpecObjList()) {
traverseImpl(context, project, specNode);
}
}
/**
* Starts the traversion of the tree under the root node passed to the
* constructor. Event handlers are not included during the traversion.
*/
public void traverse() {
traverse(false);
}
/**
* Starts the traversion of the tree under the root node passed to the
* constructor. Event handlers are included during the traversion, if
* <code>traverseEventHandlers</code> is <code>true</code>.
*
* @param traverseEventHandlers
* If <code>true</code>, the event handlers are included
*/
public void traverse(boolean traverseEventHandlers) {
clearVisited();
m_traverseEventHandlers = traverseEventHandlers;
traverseImpl(new TreeTraverserContext<INodePO>(m_rootNode),
null, m_rootNode);
}
/**
*
* @return the tree node operation
*/
protected List<ITreeNodeOperation<INodePO>> getOperations() {
return m_operations;
}
/**
* adds a <code>ITreeNodeOperation</code> to the list of operations
* that are executed on every step
*
* @param op
* <code>ITreeNodeOperation</code>
*/
public void addOperation(ITreeNodeOperation<INodePO> op) {
m_operations.add(op);
}
/**
* marks this node as processed
* @param node the usual suspect
*/
private void addToVisited(INodePO node) {
m_visited.add(node.getGuid());
}
/**
* checks if the node has been processed before
* @param node the usual suspect
* @return true if the node is in the set of already processed nodes
*/
private boolean alreadyVisited(INodePO node) {
return m_visited.contains(node.getGuid());
}
/**
* resets the visited set to empty
*/
private void clearVisited() {
m_visited.clear();
}
/**
* @return the monitor
*/
public IProgressMonitor getMonitor() {
return m_monitor;
}
/**
* @param monitor the monitor to set
*/
public void setMonitor(IProgressMonitor monitor) {
m_monitor = monitor;
}
/**
* @param reused whether to traverse reused projects
*/
public void setTraverseReused(boolean reused) {
m_traverseReused = reused;
}
/**
* @param spec whether to traverse the Spec part (TCB)
*/
public void setTraverseSpecPart(boolean spec) {
m_traverseSpecPart = spec;
}
/**
* @param bool whether to traverse through ExecTCs (that is, into the corresponding SpecTC)
*/
public void setTraverseIntoExecs(boolean bool) {
m_traverseIntoExecs = bool;
}
}