TargetTasksExplorer.java

// $Id$
/*
 * ====================================================================
 * Copyright (c) 2002-2004, Christophe Labouisse All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package net.ggtools.grand.ant;

import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import net.ggtools.grand.ant.AntTargetNode.SourceElement;
import net.ggtools.grand.log.LoggerManager;

import org.apache.commons.logging.Log;
import org.apache.tools.ant.RuntimeConfigurable;
import org.apache.tools.ant.Target;
import org.apache.tools.ant.Task;

/**
 * A class to recursively explore the tasks of a target to rebuild the source
 * code.
 *
 * @author Christophe Labouisse
 */
class TargetTasksExplorer {
    /**
     * Field log.
     */
    private static final Log LOG =
            LoggerManager.getLog(TargetTasksExplorer.class);

    /**
     * Field textElements.
     */
    private List<SourceElement> textElements;

    /**
     * Creates a new TargetTasksExplorer instance for a specific project.
     *
     * @param antProject
     *            associated project.
     */
    TargetTasksExplorer(final AntProject antProject) {
    }

    /**
     * Rebuild a node source by exploring.
     *
     * @param node AntTargetNode
     * @param target Target
     */
    public void exploreTarget(final AntTargetNode node, final Target target) {
        LOG.trace("Exploring target " + target.getName());
        textElements = new LinkedList<SourceElement>();
        addText("<target name=\"", AntTargetNode.SOURCE_MARKUP);
        addText(target.getName(), AntTargetNode.SOURCE_ATTRIBUTE);
        addText("\"", AntTargetNode.SOURCE_MARKUP);

        if (node.getIfCondition() != null) {
            addText(" if=\"", AntTargetNode.SOURCE_MARKUP);
            addText(node.getIfCondition(), AntTargetNode.SOURCE_ATTRIBUTE);
            addText("\"", AntTargetNode.SOURCE_MARKUP);
        }

        if (node.getUnlessCondition() != null) {
            addText(" unless=\"", AntTargetNode.SOURCE_MARKUP);
            addText(node.getUnlessCondition(), AntTargetNode.SOURCE_ATTRIBUTE);
            addText("\"", AntTargetNode.SOURCE_MARKUP);
        }

        final List<String> dependencies = Collections.list(target.getDependencies());
        if (!dependencies.isEmpty()) {
            addText(" depends=\"", AntTargetNode.SOURCE_MARKUP);
            for (String dependency : dependencies) {
                addText(dependency, AntTargetNode.SOURCE_ATTRIBUTE);
                if (dependencies.indexOf(dependency) != dependencies.size() - 1) {
                    addText(", ", AntTargetNode.SOURCE_ATTRIBUTE);
                }
            }
            addText("\"", AntTargetNode.SOURCE_MARKUP);
        }

        if (target.getDescription() != null) {
            addText(" description=\"", AntTargetNode.SOURCE_MARKUP);
            addText(target.getDescription(), AntTargetNode.SOURCE_ATTRIBUTE);
            addText("\"", AntTargetNode.SOURCE_MARKUP);
        }

        final Task[] taskArray = target.getTasks();
        final boolean hasChildren = taskArray.length > 0;

        if (hasChildren) {
            addText(">\n", AntTargetNode.SOURCE_MARKUP);
        } else {
            addText(" />\n", AntTargetNode.SOURCE_MARKUP);
        }

        for (final Task task : taskArray) {
            exploreTask(task.getRuntimeConfigurableWrapper(), 1);
        }

        if (hasChildren) {
            addText("</target>", AntTargetNode.SOURCE_MARKUP);
        }

        // Merge contiguous source elements of the same style.
        SourceElement previous = null;

        for (final Iterator<SourceElement> iter = textElements.iterator(); iter.hasNext();) {
            final SourceElement element = iter.next();
            if (previous == null) {
                previous = element;
            } else {
                if (previous.getStyle() == element.getStyle()) {
                    previous.setText(previous.getText().concat(element.getText()));
                    iter.remove();
                } else {
                    previous = element;
                }
            }
        }
        node.setRichSource(textElements.toArray(new SourceElement[0]));

        final StringBuilder buffer = new StringBuilder();
        for (SourceElement element : textElements) {
            buffer.append(element.getText());
        }
        node.setSource(buffer.toString());
    }

    /**
     * Method exploreTask.
     * @param wrapper RuntimeConfigurable
     * @param level int
     */
    private void exploreTask(final RuntimeConfigurable wrapper, final int level) {
        indent(level);
        addText("<", AntTargetNode.SOURCE_MARKUP);
        addText(wrapper.getElementTag(), AntTargetNode.SOURCE_MARKUP);
        final Map<String, Object> attributes = wrapper.getAttributeMap();
        for (final Map.Entry<String, Object> entry : attributes.entrySet()) {
            addText(" ", AntTargetNode.SOURCE_MARKUP);
            addText(entry.getKey(), AntTargetNode.SOURCE_MARKUP);
            addText("=\"", AntTargetNode.SOURCE_MARKUP);
            addText((String) entry.getValue(), AntTargetNode.SOURCE_ATTRIBUTE);
            addText("\"", AntTargetNode.SOURCE_MARKUP);
        }

        final List<RuntimeConfigurable> children = Collections.list(wrapper.getChildren());
        final boolean hasChildren = !children.isEmpty();

        final String trimmedText = wrapper.getText().toString().trim();
        final boolean hasText = !trimmedText.isEmpty();

        final boolean hasNestedElements = hasChildren || hasText;

        // TODO process text elements.
        if (hasNestedElements) {
            addText(">", AntTargetNode.SOURCE_MARKUP);
            if (hasChildren) {
                addText("\n", AntTargetNode.SOURCE_MARKUP);
            }
        } else {
            addText(" />\n", AntTargetNode.SOURCE_MARKUP);
        }

        for (final RuntimeConfigurable childWrapper : children) {
            exploreTask(childWrapper, level + 1);
        }

        if (hasText) {
            addText(trimmedText, AntTargetNode.SOURCE_TEXT);
        }

        if (hasNestedElements) {
            if (!hasText) {
                indent(level);
            }
            addText("</", AntTargetNode.SOURCE_MARKUP);
            addText(wrapper.getElementTag(), AntTargetNode.SOURCE_MARKUP);
            addText(">\n", AntTargetNode.SOURCE_MARKUP);
        }
    }

    /**
     * Method addText.
     * @param text String
     * @param style int
     */
    private void addText(final String text, final int style) {
        textElements.add(new SourceElement(text, style));
    }

    /**
     * Method indent.
     * @param level int
     */
    private void indent(final int level) {
        final StringBuilder buffer = new StringBuilder(level * 4);
        for (int i = 0; i < level; i++) {
            buffer.append("   ");
        }
        addText(buffer.toString(), AntTargetNode.SOURCE_TEXT);
    }
}