001 /** 002 * www.jcoverage.com 003 * Copyright (C)2003 jcoverage ltd. 004 * 005 * This file is part of jcoverage. 006 * 007 * jcoverage is free software; you can redistribute it and/or modify 008 * it under the terms of the GNU General Public License as published 009 * by the Free Software Foundation; either version 2 of the License, 010 * or (at your option) any later version. 011 * 012 * jcoverage is distributed in the hope that it will be useful, but 013 * WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * General Public License for more details. 016 * 017 * You should have received a copy of the GNU General Public License 018 * along with jcoverage; if not, write to the Free Software 019 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 020 * USA 021 * 022 */ 023 package com.jcoverage.coverage; 024 025 import com.jcoverage.util.JavaClassHelper; 026 import com.jcoverage.util.ClassHelper; 027 028 import java.io.File; 029 import java.io.FileInputStream; 030 import java.io.InputStream; 031 import java.io.IOException; 032 033 import java.util.ResourceBundle; 034 035 import org.apache.log4j.Logger; 036 037 import org.apache.bcel.classfile.JavaClass; 038 039 /** 040 * Add coverage instrumentation to existing classes. 041 */ 042 public class Instrument { 043 static final Logger logger=Logger.getLogger(Instrument.class); 044 045 File destinationDirectory=null; 046 String ignoreRegex=null; 047 File baseDir=null; 048 049 /** 050 * @param fi a file 051 * @return true if the specified file has "class" as its extension, 052 * false otherwise. 053 */ 054 boolean isClass(File fi) { 055 if(logger.isDebugEnabled()) { 056 logger.debug("fi: "+fi.getName()); 057 } 058 059 return fi.getName().endsWith(".class"); 060 } 061 062 /** 063 * @param jc a compiled Java class 064 * @return true if the specified class implements the interface 065 * @link HasBeenInstrumented, otherwise false. 066 */ 067 boolean isAlreadyInstrumented(JavaClass jc) { 068 if(logger.isDebugEnabled()) { 069 logger.debug("jc: "+jc.getClassName()); 070 } 071 072 String[] interfaceNames=jc.getInterfaceNames(); 073 for(int i=0;i<interfaceNames.length;i++) { 074 if(logger.isDebugEnabled()) { 075 logger.debug(jc.getClassName()+" implements "+interfaceNames[i]); 076 } 077 078 if(interfaceNames[i].equals(HasBeenInstrumented.class.getName())) { 079 if(logger.isInfoEnabled()) { 080 logger.info(jc.getClassName()+" has already been instrumented"); 081 } 082 return true; 083 } 084 } 085 return false; 086 } 087 088 /** 089 * @param jc a compiled Java class 090 * @return true if the class represented by <code>jc</code> is an 091 * interface. 092 */ 093 boolean isInterface(JavaClass jc) { 094 return !jc.isClass(); 095 } 096 097 /** 098 * Add coverage instrumentation to the specified Java class. 099 * @param clazz a Java class file. 100 */ 101 void instrument(File clazz) { 102 if(logger.isDebugEnabled()) { 103 logger.debug("name: "+clazz.getName()); 104 } 105 106 InputStream is=null; 107 108 try { 109 is=new FileInputStream(clazz); 110 JavaClass jc=JavaClassHelper.newJavaClass(is,clazz.getName()); 111 is.close(); 112 is=null; 113 114 if(isInterface(jc)||isAlreadyInstrumented(jc)) { 115 if(destinationDirectory!=null) { 116 /** 117 * It is not normally necessary to do anything with an 118 * interface or class that has already been 119 * instrumented. However, if a destination directory has 120 * been specified we copy it to the destination directory, 121 * so that on subsequent invocations of "ant" the files will 122 * be seen as being upto date, and will not require 123 * instrumentation. 124 */ 125 File outputDirectory=new File(destinationDirectory,ClassHelper.getPackageName(jc.getClassName()).replace('.','/')); 126 outputDirectory.mkdirs(); 127 jc.dump(new File(outputDirectory,ClassHelper.getBaseName(jc.getClassName())+".class")); 128 } 129 return; 130 } 131 132 if(logger.isInfoEnabled()) { 133 logger.info("instrumenting "+jc.getClassName()); 134 } 135 136 InstrumentClassGen instrument=new InstrumentClassGen(jc,ignoreRegex); 137 instrument.addInstrumentation(); 138 139 if(logger.isDebugEnabled()) { 140 JavaClassHelper.dump(instrument.getClassGen().getJavaClass()); 141 } 142 143 if(destinationDirectory==null) { 144 instrument.getClassGen().getJavaClass().dump(clazz); 145 } else { 146 File outputDirectory=new File(destinationDirectory,ClassHelper.getPackageName(jc.getClassName()).replace('.','/')); 147 outputDirectory.mkdirs(); 148 instrument.getClassGen().getJavaClass().dump(new File(outputDirectory,ClassHelper.getBaseName(jc.getClassName())+".class")); 149 } 150 151 InstrumentationInternal i=(InstrumentationInternal)InstrumentationFactory.getInstance().newInstrumentation(jc.getClassName()); 152 153 if(instrument.getSourceLineNumbers().isEmpty()) { 154 logger.warn("no source line numbers found for: "+jc.getClassName()+", compile with debug=\"yes\"."); 155 } 156 157 i.setSourceLineNumbers(instrument.getSourceLineNumbers()); 158 i.setSourceFileName(jc.getSourceFileName()); 159 i.setSourceLineNumbersByMethod(instrument.getMethodLineNumbers()); 160 i.setConditionalsByMethod(instrument.getMethodConditionals()); 161 i.setMethodNamesAndSignatures(instrument.getMethodNamesAndSignatures()); 162 } catch(IOException ex) { 163 if(logger.isDebugEnabled()) { 164 logger.debug(ex); 165 } 166 167 if(is!=null) { 168 try { 169 is.close(); 170 } catch(IOException whileClosing) { 171 if(logger.isDebugEnabled()) { 172 logger.debug(whileClosing); 173 } 174 } 175 } 176 177 throw new CoverageRuntimeException(ex); 178 } 179 } 180 181 void addInstrumentation(File fi) { 182 if(logger.isDebugEnabled()) { 183 logger.debug("fi: "+fi.getName()); 184 } 185 186 if(fi.isDirectory()) { 187 File[] contents=fi.listFiles(); 188 for(int i=0;i<contents.length;i++) { 189 addInstrumentation(contents[i]); 190 } 191 } 192 193 if(isClass(fi)) { 194 instrument(fi); 195 } 196 } 197 198 void addInstrumentation(String arg) { 199 if(logger.isDebugEnabled()) { 200 logger.debug("arg: "+arg); 201 } 202 203 if(baseDir==null) { 204 addInstrumentation(new File(arg)); 205 } else { 206 addInstrumentation(new File(baseDir,arg)); 207 } 208 } 209 210 void addInstrumentation(String[] args) { 211 for(int i=0;i<args.length;i++) { 212 if(args[i].equals("-d")) { 213 destinationDirectory=new File(args[++i]); 214 continue; 215 } else if(args[i].equals("-basedir")) { 216 baseDir=new File(args[++i]); 217 continue; 218 } else if(args[i].equals("-ignore")) { 219 ignoreRegex=args[++i]; 220 continue; 221 } 222 223 addInstrumentation(args[i]); 224 } 225 } 226 227 public static void main(String[] args) { 228 Instrument instrument=new Instrument(); 229 instrument.addInstrumentation(args); 230 } 231 }