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 }