1/*******************************************************************************
2 * Copyright (c) 2009, 2015 Mountainminds GmbH & Co. KG and Contributors
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 *    Brock Janiczak - initial API and implementation
10 *
11 *******************************************************************************/
12package org.jacoco.ant;
13
14import static java.lang.String.format;
15
16import java.io.File;
17import java.io.FileOutputStream;
18import java.io.InputStream;
19import java.io.OutputStream;
20import java.util.Iterator;
21
22import org.apache.tools.ant.BuildException;
23import org.apache.tools.ant.Task;
24import org.apache.tools.ant.types.Resource;
25import org.apache.tools.ant.types.ResourceCollection;
26import org.apache.tools.ant.types.resources.Union;
27import org.apache.tools.ant.util.FileUtils;
28import org.jacoco.core.instr.Instrumenter;
29import org.jacoco.core.runtime.OfflineInstrumentationAccessGenerator;
30
31/**
32 * Task for offline instrumentation of class files.
33 */
34public class InstrumentTask extends Task {
35
36	private File destdir;
37
38	private final Union files = new Union();
39
40	private boolean removesignatures = true;
41
42	/**
43	 * Sets the location of the instrumented classes.
44	 *
45	 * @param destdir
46	 *            destination folder for instrumented classes
47	 */
48	public void setDestdir(final File destdir) {
49		this.destdir = destdir;
50	}
51
52	/**
53	 * Sets whether signatures should be removed from JAR files.
54	 *
55	 * @param removesignatures
56	 *            <code>true</code> if signatures should be removed
57	 */
58	public void setRemovesignatures(final boolean removesignatures) {
59		this.removesignatures = removesignatures;
60	}
61
62	/**
63	 * This task accepts any number of class file resources.
64	 *
65	 * @param resources
66	 *            Execution data resources
67	 */
68	public void addConfigured(final ResourceCollection resources) {
69		files.add(resources);
70	}
71
72	@Override
73	public void execute() throws BuildException {
74		if (destdir == null) {
75			throw new BuildException("Destination directory must be supplied",
76					getLocation());
77		}
78		int total = 0;
79		final Instrumenter instrumenter = new Instrumenter(
80				new OfflineInstrumentationAccessGenerator());
81		instrumenter.setRemoveSignatures(removesignatures);
82		final Iterator<?> resourceIterator = files.iterator();
83		while (resourceIterator.hasNext()) {
84			final Resource resource = (Resource) resourceIterator.next();
85			if (resource.isDirectory()) {
86				continue;
87			}
88			total += instrument(instrumenter, resource);
89		}
90		log(format("Instrumented %s classes to %s", Integer.valueOf(total),
91				destdir.getAbsolutePath()));
92	}
93
94	private int instrument(final Instrumenter instrumenter,
95			final Resource resource) {
96		final File file = new File(destdir, resource.getName());
97		file.getParentFile().mkdirs();
98		try {
99			InputStream input = null;
100			OutputStream output = null;
101			try {
102				input = resource.getInputStream();
103				output = new FileOutputStream(file);
104				return instrumenter.instrumentAll(input, output,
105						resource.getName());
106			} finally {
107				FileUtils.close(input);
108				FileUtils.close(output);
109			}
110		} catch (final Exception e) {
111			file.delete();
112			throw new BuildException(format("Error while instrumenting %s",
113					resource), e, getLocation());
114		}
115	}
116}
117