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    }