001/*
002 * The Apache Software License, Version 1.1
003 *
004 * Copyright (C) 2000-2002 The Apache Software Foundation.  All rights
005 * reserved.
006 * Copyright (C) 2003 jcoverage ltd.
007 * Copyright (C) 2005 Mark Doliner
008 * Copyright (C) 2005 Joakim Erdfelt
009 * Copyright (C) 2005 Grzegorz Lukasik
010 * Copyright (C) 2005 Alexei Yudichev
011 * Copyright (C) 2006 John Lewis
012 * Copyright (C) 2006 Jiri Mares 
013 * Copyright (C) 2008 Scott Frederick
014 * Copyright (C) 2010 Tad Smith
015 * Copyright (C) 2010 Piotr Tabor 
016 *
017 * Redistribution and use in source and binary forms, with or without
018 * modification, are permitted provided that the following conditions
019 * are met:
020 *
021 * 1. Redistributions of source code must retain the above copyright
022 *    notice, this list of conditions and the following disclaimer.
023 *
024 * 2. Redistributions in binary form must reproduce the above copyright
025 *    notice, this list of conditions and the following disclaimer in
026 *    the documentation and/or other materials provided with the
027 *    distribution.
028 *
029 * 3. The end-user documentation included with the redistribution, if
030 *    any, must include the following acknowlegement:
031 *       "This product includes software developed by the
032 *        Apache Software Foundation (http://www.apache.org/)."
033 *    Alternately, this acknowlegement may appear in the software itself,
034 *    if and wherever such third-party acknowlegements normally appear.
035 *
036 * 4. The names "Ant" and "Apache Software
037 *    Foundation" must not be used to endorse or promote products derived
038 *    from this software without prior written permission. For written
039 *    permission, please contact apache@apache.org.
040 *
041 * 5. Products derived from this software may not be called "Apache"
042 *    nor may "Apache" appear in their names without prior written
043 *    permission of the Apache Group.
044 *
045 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
046 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
047 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
048 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
049 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
050 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
051 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
052 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
053 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
054 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
055 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
056 * SUCH DAMAGE.
057 * ====================================================================
058 *
059 * This software consists of voluntary contributions made by many
060 * individuals on behalf of the Apache Software Foundation.  For more
061 * information on the Apache Software Foundation, please see
062 * <http://www.apache.org/>.
063 */
064
065package net.sourceforge.cobertura.ant;
066
067import java.io.File;
068import java.io.IOException;
069import java.util.ArrayList;
070import java.util.HashMap;
071import java.util.List;
072
073import net.sourceforge.cobertura.util.CommandLineBuilder;
074
075import org.apache.tools.ant.BuildException;
076import org.apache.tools.ant.Project;
077import org.apache.tools.ant.types.FileSet;
078import org.apache.tools.ant.types.Path;
079
080
081public class InstrumentTask extends CommonMatchingTask
082{
083
084        private String dataFile = null;
085
086        private File toDir = null;
087
088        final List<Ignore> ignoreRegexs = new ArrayList<Ignore>();
089
090        final List<IgnoreBranches> ignoreBranchesRegexs = new ArrayList<IgnoreBranches>();
091        
092        final List<IgnoreMethodAnnotation> ignoreMethodAnnotations = new ArrayList<IgnoreMethodAnnotation>();
093
094        final List<IncludeClasses> includeClassesRegexs = new ArrayList<IncludeClasses>();
095
096        final List<ExcludeClasses> excludeClassesRegexs = new ArrayList<ExcludeClasses>();
097
098        boolean ignoreTrivial = false;
099
100        private Integer forkedJVMDebugPort;
101        
102        private Path instrumentationClasspath = null;
103        
104        boolean threadsafeRigorous = false;
105
106        final private HashMap<String, FileSet> fileSetMap = new HashMap<String, FileSet>();
107
108        public InstrumentTask() {
109                super(net.sourceforge.cobertura.instrument.Main.class.getCanonicalName());
110        }
111
112        public Ignore createIgnore() {
113                Ignore ignoreRegex = new Ignore();
114                ignoreRegexs.add(ignoreRegex);
115                return ignoreRegex;
116        }
117
118        public IgnoreBranches createIgnoreBranches() {
119                IgnoreBranches ignoreBranchesRegex = new IgnoreBranches();
120                ignoreBranchesRegexs.add(ignoreBranchesRegex);
121                return ignoreBranchesRegex;
122        }
123
124        
125        public IgnoreMethodAnnotation createIgnoreMethodAnnotation() {
126                IgnoreMethodAnnotation ignoreAnnotation = new IgnoreMethodAnnotation();
127                ignoreMethodAnnotations.add(ignoreAnnotation);
128                return ignoreAnnotation;
129        }
130        
131        
132        public IncludeClasses createIncludeClasses() {
133                IncludeClasses includeClassesRegex = new IncludeClasses();
134                includeClassesRegexs.add(includeClassesRegex);
135                return includeClassesRegex;
136        }
137
138        public ExcludeClasses createExcludeClasses() {
139                ExcludeClasses excludeClassesRegex = new ExcludeClasses();
140                excludeClassesRegexs.add(excludeClassesRegex);
141                return excludeClassesRegex;
142        }
143
144        public Path createInstrumentationClasspath() {
145                if (instrumentationClasspath == null) {
146                        instrumentationClasspath = new Path(getProject());
147                }
148                return instrumentationClasspath.createPath();
149        }
150
151        /*
152         * TODO: Is the following method needed to use a classpath ref?  If so,
153         *       test it and uncomment it.
154         */
155        /*
156        public void setInstrumentationClasspathRef(Reference r)
157        {
158                createInstrumentationClasspath().setRefid(r);
159        }
160        */
161
162        public void execute() throws BuildException {
163                CommandLineBuilder builder = null;
164                try {
165                        builder = new CommandLineBuilder();
166                        if (dataFile != null)
167                                builder.addArg("--datafile", dataFile);
168                        if (toDir != null)
169                                builder.addArg("--destination", toDir.getAbsolutePath());
170
171                        for (int i = 0; i < ignoreRegexs.size(); i++) {
172                                Ignore ignoreRegex = (Ignore)ignoreRegexs.get(i);
173                                builder.addArg("--ignore", ignoreRegex.getRegex());
174                        }
175
176                        for (int i = 0; i < ignoreBranchesRegexs.size(); i++) {
177                                IgnoreBranches ignoreBranchesRegex = (IgnoreBranches)ignoreBranchesRegexs.get(i);
178                                builder.addArg("--ignoreBranches", ignoreBranchesRegex.getRegex());
179                        }
180
181                        for (int i = 0; i < ignoreMethodAnnotations.size(); i++) {
182                                IgnoreMethodAnnotation ignoreMethodAnn = (IgnoreMethodAnnotation)ignoreMethodAnnotations.get(i);
183                                builder.addArg("--ignoreMethodAnnotation", ignoreMethodAnn.getAnnotationName());
184                        }
185                        
186                        for (int i = 0; i < includeClassesRegexs.size(); i++) {
187                                IncludeClasses includeClassesRegex = (IncludeClasses)includeClassesRegexs.get(i);
188                                builder.addArg("--includeClasses", includeClassesRegex.getRegex());
189                        }
190
191                        for (int i = 0; i < excludeClassesRegexs.size(); i++) {
192                                ExcludeClasses excludeClassesRegex = (ExcludeClasses)excludeClassesRegexs.get(i);
193                                builder.addArg("--excludeClasses", excludeClassesRegex.getRegex());
194                        }
195
196                        if (ignoreTrivial)
197                                builder.addArg("--ignoreTrivial");
198                        
199                        if (threadsafeRigorous)
200                                builder.addArg("--threadsafeRigorous");
201                        
202                        if (failOnError)
203                                builder.addArg("--failOnError");
204
205                        if (instrumentationClasspath != null) {
206                                processInstrumentationClasspath();
207                        }
208                        createArgumentsForFilesets(builder);
209
210                        builder.saveArgs();
211                } catch (IOException ioe) {
212                        getProject().log("Error creating commands file.", Project.MSG_ERR);
213                        throw new BuildException("Unable to create the commands file.", ioe);
214                }
215
216                // Execute GPL licensed code in separate virtual machine
217                getJava().createArg().setValue("--commandsfile");
218                getJava().createArg().setValue(builder.getCommandLineFile());
219                if (forkedJVMDebugPort != null && forkedJVMDebugPort.intValue() > 0) {
220                        getJava().createJvmarg().setValue("-Xdebug");
221                        getJava().createJvmarg().setValue("-Xrunjdwp:transport=dt_socket,address=" + forkedJVMDebugPort + ",server=y,suspend=y");
222                }
223                AntUtil.transferCoberturaDataFileProperty(getJava());
224                if (getJava().executeJava() != 0) {
225                        throw new BuildException(
226                                        "Error instrumenting classes. See messages above.");
227                }
228
229                builder.dispose();
230        }
231
232        private void processInstrumentationClasspath() {
233                if (includeClassesRegexs.size() == 0)
234                {
235                        throw new BuildException("'includeClasses' is required when 'instrumentationClasspath' is used");
236                }
237
238                String[] sources = instrumentationClasspath.list();
239                for (int i = 0; i < sources.length; i++) {
240                        File fileOrDir = new File(sources[i]);
241                        if (fileOrDir.exists()) {
242                                if (fileOrDir.isDirectory()) {
243                                        createFilesetForDirectory(fileOrDir);
244                                } else {
245                                        addFileToFilesets(fileOrDir);
246                                }
247                        }
248                }
249        }
250
251        private void addFileToFilesets(File file) {
252                File dir = file.getParentFile();
253                String filename = file.getName();
254                FileSet fileSet = getFileSet(dir);
255                fileSet.createInclude().setName(filename);
256        }
257
258        private FileSet getFileSet(File dir) {
259                String key = dir.getAbsolutePath();
260                FileSet fileSet = (FileSet)fileSetMap.get(key);
261                if (fileSet == null) {
262                fileSet = new FileSet();
263                fileSet.setProject(getProject());
264                fileSet.setDir(dir);
265
266                // Now add the new fileset to the map and to the fileSets list 
267                fileSetMap.put(key, fileSet);
268                addFileset(fileSet);
269                }
270                return fileSet;
271        }
272
273        private void createFilesetForDirectory(File dir) {
274                FileSet fileSet = getFileSet(dir);
275                fileSet.createInclude().setName("**/*.class");
276        }
277
278        public void setDataFile(String dataFile) {
279                this.dataFile = dataFile;
280        }
281
282        public void setToDir(File toDir) {
283                this.toDir = toDir;
284        }
285
286        public void setIgnoreTrivial(boolean ignoreTrivial) {
287                this.ignoreTrivial = ignoreTrivial;
288        }
289        
290        public void setThreadsafeRigorous(boolean threadsafeRigorous) {
291                this.threadsafeRigorous = threadsafeRigorous;
292        }
293        
294        public void setForkedJVMDebugPort(Integer forkedJVMDebugPort) {
295                this.forkedJVMDebugPort = forkedJVMDebugPort;
296        }
297}