Coverage Report - net.sourceforge.cobertura.instrument.Main
 
Classes in this File Line Coverage Branch Coverage Complexity
Main
79%
157/198
69%
50/72
6.111
 
 1  
 /*
 2  
  * Cobertura - http://cobertura.sourceforge.net/
 3  
  *
 4  
  * Copyright (C) 2003 jcoverage ltd.
 5  
  * Copyright (C) 2005 Mark Doliner
 6  
  * Copyright (C) 2005 Joakim Erdfelt
 7  
  * Copyright (C) 2005 Grzegorz Lukasik
 8  
  * Copyright (C) 2006 John Lewis
 9  
  * Copyright (C) 2006 Jiri Mares 
 10  
  * Contact information for the above is given in the COPYRIGHT file.
 11  
  *
 12  
  * Cobertura is free software; you can redistribute it and/or modify
 13  
  * it under the terms of the GNU General Public License as published
 14  
  * by the Free Software Foundation; either version 2 of the License,
 15  
  * or (at your option) any later version.
 16  
  *
 17  
  * Cobertura is distributed in the hope that it will be useful, but
 18  
  * WITHOUT ANY WARRANTY; without even the implied warranty of
 19  
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 20  
  * General Public License for more details.
 21  
  *
 22  
  * You should have received a copy of the GNU General Public License
 23  
  * along with Cobertura; if not, write to the Free Software
 24  
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 25  
  * USA
 26  
  */
 27  
 
 28  
 package net.sourceforge.cobertura.instrument;
 29  
 
 30  
 import java.io.ByteArrayOutputStream;
 31  
 import java.io.File;
 32  
 import java.io.FileInputStream;
 33  
 import java.io.FileNotFoundException;
 34  
 import java.io.FileOutputStream;
 35  
 import java.io.IOException;
 36  
 import java.io.InputStream;
 37  
 import java.io.OutputStream;
 38  
 import java.util.ArrayList;
 39  
 import java.util.Collection;
 40  
 import java.util.Iterator;
 41  
 import java.util.List;
 42  
 import java.util.Vector;
 43  
 import java.util.zip.ZipEntry;
 44  
 import java.util.zip.ZipInputStream;
 45  
 import java.util.zip.ZipOutputStream;
 46  
 
 47  
 import net.sourceforge.cobertura.coveragedata.CoverageDataFileHandler;
 48  
 import net.sourceforge.cobertura.coveragedata.ProjectData;
 49  
 import net.sourceforge.cobertura.util.ArchiveUtil;
 50  
 import net.sourceforge.cobertura.util.CommandLineBuilder;
 51  
 import net.sourceforge.cobertura.util.Header;
 52  
 import net.sourceforge.cobertura.util.IOUtil;
 53  
 import net.sourceforge.cobertura.util.RegexUtil;
 54  
 
 55  
 import org.apache.log4j.Logger;
 56  
 import org.objectweb.asm.ClassReader;
 57  
 import org.objectweb.asm.ClassWriter;
 58  
 
 59  
 /**
 60  
  * <p>
 61  
  * Add coverage instrumentation to existing classes.
 62  
  * </p>
 63  
  *
 64  
  * <h3>What does that mean, exactly?</h3>
 65  
  * <p>
 66  
  * It means Cobertura will look at each class you give it.  It
 67  
  * loads the bytecode into memory.  For each line of source,
 68  
  * Cobertura adds a few extra instructions.  These instructions 
 69  
  * do the following:
 70  
  * </p>
 71  
  * 
 72  
  * <ol>
 73  
  * <li>Get an instance of the ProjectData class.</li>
 74  
  * <li>Call a method in this ProjectData class that increments
 75  
  * a counter for this line of code.
 76  
  * </ol>
 77  
  *
 78  
  * <p>
 79  
  * After every line in a class has been "instrumented," Cobertura
 80  
  * edits the bytecode for the class one more time and adds
 81  
  * "implements net.sourceforge.cobertura.coveragedata.HasBeenInstrumented" 
 82  
  * This is basically just a flag used internally by Cobertura to
 83  
  * determine whether a class has been instrumented or not, so
 84  
  * as not to instrument the same class twice.
 85  
  * </p>
 86  
  */
 87  4
 public class Main
 88  
 {
 89  
 
 90  4
         private static final Logger logger = Logger.getLogger(Main.class);
 91  
 
 92  4
         private File destinationDirectory = null;
 93  
 
 94  4
         private Collection ignoreRegexes = new Vector();
 95  
 
 96  4
         private Collection ignoreBranchesRegexes = new Vector();
 97  
 
 98  4
         private ClassPattern classPattern = new ClassPattern();
 99  
 
 100  4
         private ProjectData projectData = null;
 101  
 
 102  
         /**
 103  
          * @param entry A zip entry.
 104  
          * @return True if the specified entry has "class" as its extension,
 105  
          * false otherwise.
 106  
          */
 107  
         private static boolean isClass(ZipEntry entry)
 108  
         {
 109  19
                 return entry.getName().endsWith(".class");
 110  
         }
 111  
 
 112  
         private boolean addInstrumentationToArchive(CoberturaFile file, InputStream archive,
 113  
                         OutputStream output) throws Exception
 114  
         {
 115  1
                 ZipInputStream zis = null;
 116  1
                 ZipOutputStream zos = null;
 117  
 
 118  
                 try
 119  
                 {
 120  1
                         zis = new ZipInputStream(archive);
 121  1
                         zos = new ZipOutputStream(output);
 122  1
                         return addInstrumentationToArchive(file, zis, zos);
 123  
                 }
 124  
                 finally
 125  
                 {
 126  1
                         zis = (ZipInputStream)IOUtil.closeInputStream(zis);
 127  1
                         zos = (ZipOutputStream)IOUtil.closeOutputStream(zos);
 128  1
                 }
 129  
         }
 130  
 
 131  
         private boolean addInstrumentationToArchive(CoberturaFile file, ZipInputStream archive,
 132  
                         ZipOutputStream output) throws Exception
 133  
         {
 134  
                 /*
 135  
                  * "modified" is returned and indicates that something was instrumented.
 136  
                  * If nothing is instrumented, the original entry will be used by the
 137  
                  * caller of this method.
 138  
                  */
 139  2
                 boolean modified = false;
 140  
                 ZipEntry entry;
 141  22
                 while ((entry = archive.getNextEntry()) != null)
 142  
                 {
 143  
                         try
 144  
                         {
 145  20
                                 String entryName = entry.getName();
 146  
 
 147  
                                 /*
 148  
                                  * If this is a signature file then don't copy it,
 149  
                                  * but don't set modified to true.  If the only
 150  
                                  * thing we do is strip the signature, just use
 151  
                                  * the original entry.
 152  
                                  */
 153  20
                                 if (ArchiveUtil.isSignatureFile(entry.getName()))
 154  
                                 {
 155  0
                                         continue;
 156  
                                 }
 157  20
                                 ZipEntry outputEntry = new ZipEntry(entry.getName());
 158  20
                                 outputEntry.setComment(entry.getComment());
 159  20
                                 outputEntry.setExtra(entry.getExtra());
 160  20
                                 outputEntry.setTime(entry.getTime());
 161  20
                                 output.putNextEntry(outputEntry);
 162  
 
 163  
                                 // Read current entry
 164  20
                                 byte[] entryBytes = IOUtil
 165  
                                                 .createByteArrayFromInputStream(archive);
 166  
 
 167  
                                 // Instrument embedded archives if a classPattern has been specified
 168  20
                                 if ((classPattern.isSpecified()) && ArchiveUtil.isArchive(entryName))
 169  
                                 {
 170  1
                                         Archive archiveObj = new Archive(file, entryBytes);
 171  1
                                         addInstrumentationToArchive(archiveObj);
 172  1
                                         if (archiveObj.isModified())
 173  
                                         {
 174  1
                                                 modified = true;
 175  1
                                                 entryBytes = archiveObj.getBytes();
 176  1
                                                 outputEntry.setTime(System.currentTimeMillis());
 177  
                                         }
 178  1
                                 }
 179  19
                                 else if (isClass(entry) && classPattern.matches(entryName))
 180  
                                 {
 181  
                                         try
 182  
                                         {
 183  
                                                 // Instrument class
 184  4
                                                 ClassReader cr = new ClassReader(entryBytes);
 185  4
                                                 ClassWriter cw = new ClassWriter(true);
 186  4
                                                 ClassInstrumenter cv = new ClassInstrumenter(projectData,
 187  
                                                                 cw, ignoreRegexes, ignoreBranchesRegexes);
 188  4
                                                 cr.accept(cv, false);
 189  
         
 190  
                                                 // If class was instrumented, get bytes that define the
 191  
                                                 // class
 192  4
                                                 if (cv.isInstrumented())
 193  
                                                 {
 194  4
                                                         logger.debug("Putting instrumented entry: "
 195  
                                                                         + entry.getName());
 196  4
                                                         entryBytes = cw.toByteArray();
 197  4
                                                         modified = true;
 198  4
                                                         outputEntry.setTime(System.currentTimeMillis());
 199  
                                                 }
 200  
                                         }
 201  0
                                         catch (Throwable t)
 202  
                                         {
 203  0
                                                 if (entry.getName().endsWith("_Stub.class"))
 204  
                                                 {
 205  
                                                         //no big deal - it is probably an RMI stub, and they don't need to be instrumented
 206  0
                                                         logger.debug("Problems instrumenting archive entry: " + entry.getName(), t);
 207  0
                                                 }
 208  
                                                 else
 209  
                                                 {
 210  0
                                                         logger.warn("Problems instrumenting archive entry: " + entry.getName(), t);
 211  
                                                 }
 212  4
                                         }
 213  
                                 }
 214  
 
 215  
                                 // Add entry to the output
 216  20
                                 output.write(entryBytes);
 217  20
                                 output.closeEntry();
 218  20
                                 archive.closeEntry();
 219  
                         }
 220  0
                         catch (Exception e)
 221  
                         {
 222  0
                                 logger.warn("Problems with archive entry: " + entry.getName(), e);
 223  
                         }
 224  0
                         catch (Throwable t)
 225  
                         {
 226  0
                                 logger.warn("Problems with archive entry: " + entry.getName(), t);
 227  20
                         }
 228  20
                         output.flush();
 229  20
                 }
 230  2
                 return modified;
 231  
         }
 232  
 
 233  
         private void addInstrumentationToArchive(Archive archive) throws Exception
 234  
         {
 235  1
                 InputStream in = null;
 236  1
                 ByteArrayOutputStream out = null;
 237  
                 try
 238  
                 {
 239  1
                         in = archive.getInputStream();
 240  1
                         out = new ByteArrayOutputStream();
 241  1
                         boolean modified = addInstrumentationToArchive(archive.getCoberturaFile(), in, out);
 242  
 
 243  1
                         if (modified)
 244  
                         {
 245  1
                                 out.flush();
 246  1
                                 byte[] bytes = out.toByteArray();
 247  1
                                 archive.setModifiedBytes(bytes);
 248  
                         }
 249  
                 }
 250  
                 finally
 251  
                 {
 252  1
                         in = IOUtil.closeInputStream(in);
 253  1
                         out = (ByteArrayOutputStream)IOUtil.closeOutputStream(out);
 254  1
                 }
 255  1
         }
 256  
 
 257  
         private void addInstrumentationToArchive(CoberturaFile archive)
 258  
         {
 259  1
                 logger.debug("Instrumenting archive " + archive.getAbsolutePath());
 260  
 
 261  1
                 File outputFile = null;
 262  1
                 ZipInputStream input = null;
 263  1
                 ZipOutputStream output = null;
 264  1
                 boolean modified = false;
 265  
                 try
 266  
                 {
 267  
                         // Open archive
 268  
                         try
 269  
                         {
 270  1
                                 input = new ZipInputStream(new FileInputStream(archive));
 271  
                         }
 272  0
                         catch (FileNotFoundException e)
 273  
                         {
 274  0
                                 logger.warn("Cannot open archive file: "
 275  
                                                 + archive.getAbsolutePath(), e);
 276  
                                 return;
 277  1
                         }
 278  
 
 279  
                         // Open output archive
 280  
                         try
 281  
                         {
 282  
                                 // check if destination folder is set
 283  1
                                 if (destinationDirectory != null)
 284  
                                 {
 285  
                                         // if so, create output file in it
 286  1
                                         outputFile = new File(destinationDirectory, archive.getPathname());
 287  1
                                 }
 288  
                                 else
 289  
                                 {
 290  
                                         // otherwise create output file in temporary location
 291  0
                                         outputFile = File.createTempFile(
 292  
                                                         "CoberturaInstrumentedArchive", "jar");
 293  0
                                         outputFile.deleteOnExit();
 294  
                                 }
 295  1
                                 output = new ZipOutputStream(new FileOutputStream(outputFile));
 296  
                         }
 297  0
                         catch (IOException e)
 298  
                         {
 299  0
                                 logger.warn("Cannot open file for instrumented archive: "
 300  
                                                 + archive.getAbsolutePath(), e);
 301  
                                 return;
 302  1
                         }
 303  
 
 304  
                         // Instrument classes in archive
 305  
                         try
 306  
                         {
 307  1
                                 modified = addInstrumentationToArchive(archive, input, output);
 308  
                         }
 309  0
                         catch (Throwable e)
 310  
                         {
 311  0
                                 logger.warn("Cannot instrument archive: "
 312  
                                                 + archive.getAbsolutePath(), e);
 313  
                                 return;
 314  1
                         }
 315  
                 }
 316  
                 finally
 317  
                 {
 318  1
                         input = (ZipInputStream)IOUtil.closeInputStream(input);
 319  1
                         output = (ZipOutputStream)IOUtil.closeOutputStream(output);
 320  1
                 }
 321  
 
 322  
                 // If destination folder was not set, overwrite orginal archive with
 323  
                 // instrumented one
 324  1
                 if (modified && (destinationDirectory == null))
 325  
                 {
 326  
                         try
 327  
                         {
 328  0
                                 logger.debug("Moving " + outputFile.getAbsolutePath() + " to "
 329  
                                                 + archive.getAbsolutePath());
 330  0
                                 IOUtil.moveFile(outputFile, archive);
 331  
                         }
 332  0
                         catch (IOException e)
 333  
                         {
 334  0
                                 logger.warn("Cannot instrument archive: "
 335  
                                                 + archive.getAbsolutePath(), e);
 336  0
                                 return;
 337  0
                         }
 338  
                 }
 339  1
                 if ((destinationDirectory != null) && (!modified))
 340  
                 {
 341  0
                         outputFile.delete();
 342  
                 }
 343  1
         }
 344  
 
 345  
         private void addInstrumentationToSingleClass(File file)
 346  
         {
 347  10
                 logger.debug("Instrumenting class " + file.getAbsolutePath());
 348  
 
 349  10
                 InputStream inputStream = null;
 350  
                 ClassWriter cw;
 351  
                 ClassInstrumenter cv;
 352  
                 try
 353  
                 {
 354  10
                         inputStream = new FileInputStream(file);
 355  10
                         ClassReader cr = new ClassReader(inputStream);
 356  10
                         cw = new ClassWriter(true);
 357  10
                         cv = new ClassInstrumenter(projectData, cw, ignoreRegexes, ignoreBranchesRegexes);
 358  10
                         cr.accept(cv, false);
 359  
                 }
 360  0
                 catch (Throwable t)
 361  
                 {
 362  0
                         logger.warn("Unable to instrument file " + file.getAbsolutePath(),
 363  
                                         t);
 364  
                         return;
 365  
                 }
 366  
                 finally
 367  
                 {
 368  10
                         inputStream = IOUtil.closeInputStream(inputStream);
 369  10
                 }
 370  
 
 371  10
                 OutputStream outputStream = null;
 372  
                 try
 373  
                 {
 374  10
                         if (cv.isInstrumented())
 375  
                         {
 376  
                                 // If destinationDirectory is null, then overwrite
 377  
                                 // the original, uninstrumented file.
 378  
                                 File outputFile;
 379  9
                                 if (destinationDirectory == null)
 380  0
                                         outputFile = file;
 381  
                                 else
 382  9
                                         outputFile = new File(destinationDirectory, cv
 383  
                                                         .getClassName().replace('.', File.separatorChar)
 384  
                                                         + ".class");
 385  
 
 386  9
                                 File parentFile = outputFile.getParentFile();
 387  9
                                 if (parentFile != null)
 388  
                                 {
 389  9
                                         parentFile.mkdirs();
 390  
                                 }
 391  
 
 392  9
                                 byte[] instrumentedClass = cw.toByteArray();
 393  9
                                 outputStream = new FileOutputStream(outputFile);
 394  9
                                 outputStream.write(instrumentedClass);
 395  
                         }
 396  
                 }
 397  0
                 catch (Throwable t)
 398  
                 {
 399  0
                         logger.warn("Unable to instrument file " + file.getAbsolutePath(),
 400  
                                         t);
 401  
                         return;
 402  
                 }
 403  
                 finally
 404  
                 {
 405  10
                         outputStream = IOUtil.closeOutputStream(outputStream);
 406  10
                 }
 407  10
         }
 408  
 
 409  
         // TODO: Don't attempt to instrument a file if the outputFile already
 410  
         //       exists and is newer than the input file, and the output and
 411  
         //       input file are in different locations?
 412  
         private void addInstrumentation(CoberturaFile coberturaFile)
 413  
         {
 414  13
                 if (coberturaFile.isClass() && classPattern.matches(coberturaFile.getPathname()))
 415  
                 {
 416  10
                         addInstrumentationToSingleClass(coberturaFile);
 417  10
                 }
 418  3
                 else if (coberturaFile.isDirectory())
 419  
                 {
 420  0
                         String[] contents = coberturaFile.list();
 421  0
                         for (int i = 0; i < contents.length; i++)
 422  
                         {
 423  0
                                 File relativeFile = new File(coberturaFile.getPathname(), contents[i]);
 424  0
                                 CoberturaFile relativeCoberturaFile = new CoberturaFile(coberturaFile.getBaseDir(),
 425  
                                                 relativeFile.toString());
 426  
                                 //recursion!
 427  0
                                 addInstrumentation(relativeCoberturaFile);
 428  
                         }
 429  
                 }
 430  13
         }
 431  
 
 432  
         private void parseArguments(String[] args)
 433  
         {
 434  4
                 File dataFile = CoverageDataFileHandler.getDefaultDataFile();
 435  
 
 436  
                 // Parse our parameters
 437  4
                 List filePaths = new ArrayList();
 438  4
                 String baseDir = null;
 439  37
                 for (int i = 0; i < args.length; i++)
 440  
                 {
 441  33
                         if (args[i].equals("--basedir"))
 442  4
                                 baseDir = args[++i];
 443  29
                         else if (args[i].equals("--datafile"))
 444  4
                                 dataFile = new File(args[++i]);
 445  25
                         else if (args[i].equals("--destination"))
 446  4
                                 destinationDirectory = new File(args[++i]);
 447  21
                         else if (args[i].equals("--ignore"))
 448  
                         {
 449  1
                                 RegexUtil.addRegex(ignoreRegexes, args[++i]);
 450  1
                         }
 451  20
                         else if (args[i].equals("--ignoreBranches"))
 452  
                         {
 453  0
                                 RegexUtil.addRegex(ignoreBranchesRegexes, args[++i]);
 454  0
                         }
 455  20
                         else if (args[i].equals("--includeClasses"))
 456  
                         {
 457  2
                                 classPattern.addIncludeClassesRegex(args[++i]);
 458  2
                         }
 459  18
                         else if (args[i].equals("--excludeClasses"))
 460  
                         {
 461  4
                                 classPattern.addExcludeClassesRegex(args[++i]);
 462  4
                         }
 463  
                         else
 464  
                         {
 465  14
                                 CoberturaFile coberturaFile = new CoberturaFile(baseDir, args[i]);
 466  14
                                 filePaths.add(coberturaFile);
 467  
                         }
 468  
                 }
 469  
 
 470  
                 // Load coverage data
 471  4
                 if (dataFile.isFile())
 472  0
                         projectData = CoverageDataFileHandler.loadCoverageData(dataFile);
 473  4
                 if (projectData == null)
 474  4
                         projectData = new ProjectData();
 475  
                 
 476  
                 // Instrument classes
 477  4
                 System.out.println("Instrumenting "        + filePaths.size() + " "
 478  
                                 + (filePaths.size() == 1 ? "file" : "files")
 479  
                                 + (destinationDirectory != null ? " to "
 480  
                                                 + destinationDirectory.getAbsoluteFile() : ""));
 481  
 
 482  4
                 Iterator iter = filePaths.iterator();
 483  18
                 while (iter.hasNext())
 484  
                 {
 485  14
                         CoberturaFile coberturaFile = (CoberturaFile)iter.next();
 486  14
                         if (coberturaFile.isArchive())
 487  
                         {
 488  1
                                 addInstrumentationToArchive(coberturaFile);
 489  1
                         }
 490  
                         else
 491  
                         {
 492  13
                                 addInstrumentation(coberturaFile);
 493  
                         }
 494  14
                 }
 495  
 
 496  
                 // Save coverage data
 497  4
                 CoverageDataFileHandler.saveCoverageData(projectData, dataFile);
 498  4
         }
 499  
 
 500  
         public static void main(String[] args)
 501  
         {
 502  4
                 Header.print(System.out);
 503  
 
 504  4
                 long startTime = System.currentTimeMillis();
 505  
 
 506  4
                 Main main = new Main();
 507  
 
 508  
                 try {
 509  4
                         args = CommandLineBuilder.preprocessCommandLineArguments( args);
 510  0
                 } catch( Exception ex) {
 511  0
                         System.err.println( "Error: Cannot process arguments: " + ex.getMessage());
 512  0
                         System.exit(1);
 513  4
                 }
 514  4
                 main.parseArguments(args);
 515  
 
 516  4
                 long stopTime = System.currentTimeMillis();
 517  4
                 System.out.println("Instrument time: " + (stopTime - startTime) + "ms");
 518  4
         }
 519  
 
 520  
 }