package com.canoo.webtest.reporting;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.apache.commons.collections.EnumerationUtils;
import org.apache.commons.collections.IteratorUtils;
import org.apache.commons.lang.ClassUtils;
import org.apache.log4j.Logger;
import org.apache.tools.ant.IntrospectionHelper;
import org.apache.tools.ant.Location;
import org.apache.tools.ant.RuntimeConfigurable;
import org.apache.tools.ant.Task;
/**
* Result holder for the execution of a step (or task) and his children.
*
* @author Marc Guillemot
*/
public class StepResult {
private static final Logger LOG = Logger.getLogger(StepResult.class);
private static String getName(final Task task) {
final String taskName = task.getTaskName();
if (taskName == null) {
return task.getTaskType() + " " + ClassUtils.getShortClassName(task.getClass());
}
return taskName;
}
private final Map<String, String> fAttributes = new TreeMap<String, String>();
private final List<StepResult> fChildren = new ArrayList<StepResult>();
private Date fEndDate;
private List fHtmlParserMessages = Collections.EMPTY_LIST;
private Location fLocation;
private StepResult fParent;
private final Date fStartDate = new Date();
private boolean fSuccessfull;
private String fTaskDescription;
private final String fTaskName;
/**
* Constructs a result for a non executed task (and his children).
* This allows to present information in report
* about configured tasks that haven't been executed due to an error in a step before them.
*
* @param taskWrapper the configurable for the task
*/
@SuppressWarnings("unchecked")
protected StepResult(final RuntimeConfigurable taskWrapper) {
this(taskWrapper.getElementTag());
fAttributes.putAll(taskWrapper.getAttributeMap());
fTaskDescription = (String) fAttributes.get("description");
LOG.debug("Constructing result for non executed task: " + getTaskName());
retrieveNestedText(taskWrapper);
// HACK: Perhaps should we look if the element is a task or not
if ("table".equals(taskWrapper.getElementTag())) {
fEndDate = fStartDate;
fSuccessfull = true;
}
// the task may have children
addLostChildren(IteratorUtils.asIterator(taskWrapper.getChildren()));
}
public StepResult(final String taskName) {
fTaskName = taskName;
}
/**
* Retrieves the nested text configured within the task, if any, taking care
* to remove "noise" as Ant does
*
* @param taskWrapper the current task wrapper
*/
protected void retrieveNestedText(final RuntimeConfigurable taskWrapper) {
LOG.trace("In retrieveNestedText");
final String nestedText = taskWrapper.getText().toString().trim();
final IntrospectionHelper ih = IntrospectionHelper.getHelper(taskWrapper.getClass());
if (nestedText.length() > 0 && ih.supportsCharacters()) {
LOG.debug(taskWrapper.getElementTag() + " supports text: " + ih.getAddTextMethod());
fAttributes.put("nested text", nestedText);
}
}
/**
* Builds the result holder for the given task
*
* @param task the task (probably a {@link org.apache.tools.ant.UnknownElement}
*/
StepResult(final Task task) {
this(getName(task));
retrieveNestedText(task.getRuntimeConfigurableWrapper());
// HACK: use WebtestPropertyHelper instead?
final Map attributeMap = task.getRuntimeConfigurableWrapper().getAttributeMap();
for (final Iterator iterator = attributeMap.entrySet().iterator(); iterator.hasNext();) {
final Map.Entry entry = (Map.Entry) iterator.next();
fAttributes.put((String) entry.getKey(), task.getProject().replaceProperties((String) entry.getValue()));
}
// fAttributes.putAll(task.getRuntimeConfigurableWrapper().getAttributeMap());
fTaskDescription = (String) fAttributes.get("description");
fAttributes.remove("description");
fLocation = task.getLocation();
}
/**
* Receives notification that properties have been expanded in an attribute
*/
void propertiesExpanded(final String originalValue, final String expanded) {
// write expanded values (if any) in place of original values
// (would be interesting to have both)
for (final Map.Entry<String, String> entry : fAttributes.entrySet()) {
final String unexpandedValue = (String) entry.getValue();
if (originalValue.equals(unexpandedValue)) {
LOG.debug("Replacing value with expanded value: " + expanded);
entry.setValue(expanded);
}
}
}
/**
* @param result the child to add
*/
public void addChild(final StepResult result) {
if (result == null)
throw new IllegalArgumentException("child can't be null");
result.fParent = this;
fChildren.add(result);
}
/**
* Adds report information for non executed child tasks
*
* @param iter the iterator over {@link RuntimeConfigurable} child task
*/
protected void addLostChildren(final Iterator iter) {
while (iter.hasNext()) {
final RuntimeConfigurable child = (RuntimeConfigurable) iter.next();
addChild(new StepResult(child));
}
}
/**
* Tries to find the child tasks that haven't been executed and adds them to the current result.
*
* @param task the failing task
*/
protected void addNotExecutedChildren(final Task task) {
final RuntimeConfigurable taskWrapper = task.getRuntimeConfigurableWrapper();
// may happens that no wrapper is available when the task has not been created by ant
// like for some special WebTest tasks (that should probably disappear)
if (taskWrapper == null) {
LOG.debug("No wrapper found for task " + task + ", skipping lost children search");
return;
}
final int iNbKnownChildren = getChildren().size();
final List children = EnumerationUtils.toList(taskWrapper.getChildren());
if (iNbKnownChildren < children.size()) {
addLostChildren(children.listIterator(iNbKnownChildren));
}
}
/**
* Gets the attributes of the task
*
* @return the attributes
*/
public Map<String, String> getAttributes() {
return fAttributes;
}
/**
* @return the children.
*/
public List<StepResult> getChildren() {
return fChildren;
}
/**
* Gets the duration of the task execution.
*
* @return the task duration, <code>-1</code> if the task hasn't be
* executed
*/
public long getDuration() {
if (isCompleted()) {
return getEndDate().getTime() - getStartDate().getTime();
}
return -1;
}
/**
* Gets the date at which the execution of the task finished
*
* @return <code>null</code> if the task has not been completed
*/
public Date getEndDate() {
return fEndDate;
}
public List getHtmlParserMessages() {
return fHtmlParserMessages;
}
/**
* Gets the location of the task
*
* @return <code>null</code> if unknown
*/
public Location getLocation() {
return fLocation;
}
/**
* @return the parent result, <code>null</code> for the top most result
*/
StepResult getParent() {
return fParent;
}
/**
* Gets the date at which the execution of the task started
*
* @return <code>null</code> if the task has not been started
*/
public Date getStartDate() {
return fStartDate;
}
/**
* Gets the description of the task
*
* @return <code>null</code> if unknown or no description set
*/
public String getTaskDescription() {
return fTaskDescription;
}
/**
* @return the task name.
*/
public String getTaskName() {
return fTaskName;
}
/**
* Indicates if the step has been executed (may be successfull or failed)
*
* @return <code>true</code> if executed
*/
public boolean isCompleted() {
return getEndDate() != null;
}
/**
* Indicates if the step has been successfully completed
*
* @return <code>true</code> if successful
*/
public boolean isSuccessful() {
return fSuccessfull;
}
/**
* Informs the result that the task if finished, allowing to stop the timer
*
* @param throwable the exception thrown by the task (if any)
* @param liHtmlParserMessages the list of html parser messages associated to the just finished task
*/
void taskFinished(final Task task, final Throwable throwable, final List liHtmlParserMessages) {
fEndDate = new Date();
fSuccessfull = throwable == null;
fHtmlParserMessages = liHtmlParserMessages;
addNotExecutedChildren(task);
}
/**
* Adds results of the step execution
*
* @param results
*/
void addStepResults(final Map<String, String> results) {
fAttributes.putAll(results);
}
/**
* Gets the task attribute value
* @param key the attribute name (case insensitive)
* @return the value, <code>null</code> if task doesn't have this attribute
*/
String getAttribute(final String key)
{
if (key == null)
return null;
for (final Iterator iter=fAttributes.entrySet().iterator(); iter.hasNext();)
{
final Map.Entry entry = (Map.Entry) iter.next();
if (key.equalsIgnoreCase((String) entry.getKey()))
return (String) entry.getValue();
}
return null;
}
}