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    }