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 }