001 /*
002 * Cobertura - http://cobertura.sourceforge.net/
003 *
004 * Copyright (C) 2003 jcoverage ltd.
005 * Copyright (C) 2005 Mark Doliner <thekingant@users.sourceforge.net>
006 * Copyright (C) 2005 Joakim Erdfelt <joakim@erdfelt.net
007 *
008 * Cobertura is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License as published
010 * by the Free Software Foundation; either version 2 of the License,
011 * or (at your option) any later version.
012 *
013 * Cobertura is distributed in the hope that it will be useful, but
014 * WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
016 * General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with Cobertura; if not, write to the Free Software
020 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
021 * USA
022 */
023
024 package net.sourceforge.cobertura.coverage;
025
026 import java.io.BufferedReader;
027 import java.io.File;
028 import java.io.FileInputStream;
029 import java.io.FileOutputStream;
030 import java.io.FileReader;
031 import java.io.IOException;
032 import java.io.InputStream;
033 import java.io.OutputStream;
034 import java.util.ArrayList;
035 import java.util.List;
036 import java.util.regex.Pattern;
037
038 import org.apache.log4j.Logger;
039 import org.objectweb.asm.ClassReader;
040 import org.objectweb.asm.ClassWriter;
041
042 /**
043 * <p>
044 * Add coverage instrumentation to existing classes.
045 * </p>
046 *
047 * <h3>What does that mean, exactly?</h3>
048 * <p>
049 * It means Cobertura will look at each class you give it. It
050 * loads the bytecode into memory. For each line of source,
051 * Cobertura adds a few extra instructions. These instructions
052 * do the following:
053 * </p>
054 *
055 * <ol>
056 * <li>Get an instance of the CoverageData class.</li>
057 * <li>Call a method in this CoverageData class that increments
058 * a counter for this line of code.
059 * </ol>
060 *
061 * <p>
062 * After every line in a class has been "instrumented," Cobertura
063 * edits the bytecode for the class one more time and adds an
064 * "implements net.sourceforge.cobertura.coverage.HasBeenInstrumented" This
065 * is basically just a flag used internally by Cobertura to
066 * determine whether a class has been instrumented or not, so
067 * as not to instrument the same class twice.
068 * </p>
069 */
070 public class Main
071 {
072
073 private static final Logger logger = Logger.getLogger(Main.class);
074
075 private File destinationDirectory = null;
076 private File baseDir = null;
077 private Pattern ignoreRegexp = null;
078
079 /**
080 * @param file A file.
081 * @return True if the specified file has "class" as its extension,
082 * false otherwise.
083 */
084 private static boolean isClass(File file)
085 {
086 return file.getName().endsWith(".class");
087 }
088
089 // TODO: Don't attempt to instrument a file if the outputFile already
090 // exists and is newer than the input file.
091 private void addInstrumentation(File file)
092 {
093 if (file.isDirectory())
094 {
095 File[] contents = file.listFiles();
096 for (int i = 0; i < contents.length; i++)
097 addInstrumentation(contents[i]);
098 return;
099 }
100
101 if (!isClass(file))
102 {
103 return;
104 }
105
106 if (logger.isDebugEnabled())
107 {
108 logger.debug("instrumenting " + file.getAbsolutePath());
109 }
110
111 InputStream inputStream = null;
112 OutputStream outputStream = null;
113 try
114 {
115 inputStream = new FileInputStream(file);
116 ClassReader cr = new ClassReader(inputStream);
117 ClassWriter cw = new ClassWriter(true);
118 ClassInstrumenter cv = new ClassInstrumenter(cw, ignoreRegexp); /* pass in regexp */
119 cr.accept(cv, false);
120 byte[] instrumentedClass = cw.toByteArray();
121
122 if (cv.isInstrumented())
123 {
124 File outputFile = new File(destinationDirectory, cv
125 .getClassName().replace('.', File.separatorChar)
126 + ".class");
127 outputFile.getParentFile().mkdirs();
128 outputStream = new FileOutputStream(outputFile);
129 outputStream.write(instrumentedClass);
130 }
131 }
132 catch (IOException e)
133 {
134 logger
135 .warn("Unable to instrument file "
136 + file.getAbsolutePath());
137 logger.info(e);
138 }
139 finally
140 {
141 if (inputStream != null)
142 {
143 try
144 {
145 inputStream.close();
146 }
147 catch (IOException e)
148 {
149 }
150 }
151 if (outputStream != null)
152 {
153 try
154 {
155 outputStream.close();
156 }
157 catch (IOException e)
158 {
159 }
160 }
161 }
162 }
163
164 private void addInstrumentation(String filename)
165 {
166 if (logger.isDebugEnabled())
167 logger.debug("filename: " + filename);
168
169 if (baseDir == null)
170 addInstrumentation(new File(filename));
171 else
172 addInstrumentation(new File(baseDir, filename));
173 }
174
175 private void parseArguments(String[] args)
176 {
177 for (int i = 0; i < args.length; i++)
178 {
179 if (args[i].equals("-d"))
180 destinationDirectory = new File(args[++i]);
181 else if (args[i].equals("-basedir"))
182 baseDir = new File(args[++i]);
183 else if (args[i].equals("-ignore"))
184 {
185 String regex = args[++i];
186 this.ignoreRegexp = Pattern.compile(regex);
187 }
188 else
189 addInstrumentation(args[i]);
190 }
191 }
192
193 public static void main(String[] args)
194 {
195 long startTime = System.currentTimeMillis();
196
197 Main main = new Main();
198
199 boolean hasCommandsFile = false;
200 String commandsFileName = null;
201 for (int i = 0; i < args.length; i++)
202 {
203 if (args[i].equals("-commandsfile"))
204 {
205 hasCommandsFile = true;
206 commandsFileName = args[++i];
207 }
208 }
209
210 if (hasCommandsFile)
211 {
212 List arglist = new ArrayList();
213 BufferedReader bufferedReader = null;
214
215 try
216 {
217 bufferedReader = new BufferedReader(new FileReader(
218 commandsFileName));
219 String line;
220
221 while ((line = bufferedReader.readLine()) != null)
222 arglist.add(line);
223
224 }
225 catch (IOException e)
226 {
227 logger.fatal("Unable to read temporary commands file "
228 + commandsFileName + ".");
229 logger.info(e);
230 }
231 finally
232 {
233 if (bufferedReader != null)
234 {
235 try
236 {
237 bufferedReader.close();
238 }
239 catch (IOException e)
240 {
241 }
242 }
243 }
244
245 args = (String[])arglist.toArray(new String[arglist.size()]);
246 }
247
248 main.parseArguments(args);
249
250 long stopTime = System.currentTimeMillis();
251 System.out.println("Instrument time: " + (stopTime - startTime)
252 + "ms");
253 }
254 }