# CoverageTask.java

    /*******************************************************************************
     * Copyright (c) 2009, 2013 Mountainminds GmbH & Co. KG and Contributors
     * 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:
     *    Evgeny Mandrikov - TestNG support
     *    Brock Janiczak - initial API and implementation
     *
     *******************************************************************************/
    package org.jacoco.ant;

    import static java.lang.String.format;

    import java.util.ArrayList;
    import java.util.Collection;

    import org.apache.tools.ant.BuildException;
    import org.apache.tools.ant.Project;
    import org.apache.tools.ant.RuntimeConfigurable;
    import org.apache.tools.ant.Task;
    import org.apache.tools.ant.TaskContainer;
    import org.apache.tools.ant.UnknownElement;

    /**
     * Container task to run Java/JUnit tasks with the JaCoCo agent jar. Coverage
     * will only be applied if all of the following are true:
     * <ul>
     * <li>Exactly one sub task may be present</li>
     * <li>Task must be either Java or JUnit</li>
     * <li>Task must be using a forked VM (so vm args can be passed)</li>
     * </ul>
     */
    public class CoverageTask extends AbstractCoverageTask implements TaskContainer {

    	private final Collection<TaskEnhancer> taskEnhancers = new ArrayList<TaskEnhancer>();
    	private Task childTask;

    	/**
    	 * Creates a new default coverage task
    	 */
    	public CoverageTask() {
    		super();
    		taskEnhancers.add(new JavaLikeTaskEnhancer("java"));
    		taskEnhancers.add(new JavaLikeTaskEnhancer("junit"));
    		taskEnhancers.add(new TestNGTaskEnhancer("testng"));
    	}

    	/**
    	 * Add child task to this container and reconfigure it to run with coverage
    	 * enabled
    	 */
    	public void addTask(final Task task) {
    		if (childTask != null) {
    			throw new BuildException(
    					"Only one child task can be supplied to the coverge task",
    					getLocation());
    		}

    		this.childTask = task;

    		final String subTaskTypeName = task.getTaskType();

    		final TaskEnhancer enhancer = findEnhancerForTask(subTaskTypeName);
    		if (enhancer == null) {
    			throw new BuildException(format(
    					"%s is not a valid child of the coverage task",
    					subTaskTypeName), getLocation());
    		}

    		if (isEnabled()) {
    			log(format("Enhancing %s with coverage", childTask.getTaskName()));
    			enhancer.enhanceTask(task);
    		}

    		task.maybeConfigure();
    	}

    	private TaskEnhancer findEnhancerForTask(final String taskName) {
    		for (final TaskEnhancer enhancer : taskEnhancers) {
    			if (enhancer.supportsTask(taskName)) {
    				return enhancer;
    			}
    		}

    		return null;
    	}

    	/**
    	 * Executes subtask and performs any required cleanup
    	 */
    	@Override
    	public void execute() throws BuildException {
    		if (childTask == null) {
    			throw new BuildException(
    					"A child task must be supplied for the coverage task",
    					getLocation());
    		}

    		childTask.execute();
    	}

    	/**
    	 * Task enhancer for TestNG. TestNG task always run in a forked VM and has
    	 * nested jvmargs elements
    	 */
    	private class TestNGTaskEnhancer extends JavaLikeTaskEnhancer {

    		public TestNGTaskEnhancer(final String supportedTaskName) {
    			super(supportedTaskName);
    		}

    		@Override
    		public void enhanceTask(final Task task) {
    			addJvmArgs(task);
    		}

    	}

    	/**
    	 * Basic task enhancer that can handle all 'java like' tasks. That is, tasks
    	 * that have a top level fork attribute and nested jvmargs elements
    	 */
    	private class JavaLikeTaskEnhancer implements TaskEnhancer {

    		private final String supportedTaskName;

    		public JavaLikeTaskEnhancer(final String supportedTaskName) {
    			this.supportedTaskName = supportedTaskName;
    		}

    		public boolean supportsTask(final String taskname) {
    			return taskname.equals(supportedTaskName);
    		}

    		public void enhanceTask(final Task task) {
    			final RuntimeConfigurable configurableWrapper = task
    					.getRuntimeConfigurableWrapper();

    			final String forkValue = getProject().replaceProperties(
    					(String) configurableWrapper.getAttributeMap().get("fork"));

    			if (!Project.toBoolean(forkValue)) {
    				throw new BuildException(
    						"Coverage can only be applied on a forked VM",
    						getLocation());
    			}

    			addJvmArgs(task);
    		}

    		public void addJvmArgs(final Task task) {
    			final UnknownElement el = new UnknownElement("jvmarg");
    			el.setTaskName("jvmarg");
    			el.setQName("jvmarg");

    			final RuntimeConfigurable runtimeConfigurableWrapper = el
    					.getRuntimeConfigurableWrapper();
    			runtimeConfigurableWrapper.setAttribute("value",
    					getLaunchingArgument());

    			task.getRuntimeConfigurableWrapper().addChild(
    					runtimeConfigurableWrapper);

    			((UnknownElement) task).addChild(el);
    		}
    	}

    	/**
    	 * The task enhancer is responsible for potentially reconfiguring a task to
    	 * support running with code coverage enabled
    	 */
    	private interface TaskEnhancer {
    		/**
    		 * @param taskname
    		 *            Task type to enhance
    		 * @return <code>true</code> if this enhancer is capable of enhancing
    		 *         the requested task type
    		 */
    		public boolean supportsTask(String taskname);

    		/**
    		 * Attempt to enhance the supplied task with coverage information. This
    		 * operation may fail if the task is being executed in the current VM
    		 *
    		 * @param task
    		 *            Task instance to enhance (usually an
    		 *            {@link UnknownElement})
    		 * @throws BuildException
    		 *             Thrown if this enhancer can handle this type of task, but
    		 *             this instance can not be enhanced for some reason.
    		 */
    		public void enhanceTask(Task task) throws BuildException;
    	}
    }