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.MethodHelper;
026    
027    import java.util.HashMap;
028    import java.util.Map;
029    import java.util.Set;
030    import java.util.TreeSet;
031    
032    import org.apache.bcel.classfile.JavaClass;
033    import org.apache.bcel.classfile.Method;
034    
035    import org.apache.bcel.generic.ClassGen;
036    
037    import org.apache.log4j.Logger;
038    
039    /**
040     * Add coverage instrumentation to an existing class. Instances of
041     * this class are normally created by @see Instrument, as part of the
042     * instrumentation process.
043     */
044    class InstrumentClassGen {
045      static final Logger logger=Logger.getLogger(InstrumentClassGen.class);
046    
047      final ClassGen cg;
048      
049      /**
050       * The set of "real" source line numbers that are present in this
051       * class. That is, those lines of Java source code that do not
052       * represent comments, or other syntax "fluff" (e.g., "} else {"),
053       * or those lines that have been ignored because they match the
054       * ignore regex.
055       */
056      final Set sourceLineNumbers=new TreeSet();
057    
058      /**
059       * The set of method names concatenated with their signature.
060       */
061      final Set methodNamesAndSignatures=new TreeSet();
062    
063      /**
064       * A mapping from method name and signature to the set of line
065       * numbers for that method.
066       */
067      final Map methodLineNumbers=new HashMap();
068    
069      /**
070       * A mapping from method name and signature to the set of
071       * conditionals for that method.
072       * @see Conditional
073       */
074      final Map methodConditionals=new HashMap();
075    
076      final String ignoreRegex;
077    
078      InstrumentClassGen(JavaClass jc,String ignoreRegex) {
079        this.cg=new ClassGen(jc);
080        this.ignoreRegex=ignoreRegex;
081      }
082    
083      /**
084       * Add instrumentation collected by <code>instrument</code> to this
085       * class
086       */
087      private void add(Method method,InstrumentMethodGen instrument) {
088        methodNamesAndSignatures.add(MethodHelper.getMethodNameAndSignature(method));
089        methodLineNumbers.put(MethodHelper.getMethodNameAndSignature(method),instrument.getSourceLineNumbers());
090        methodConditionals.put(MethodHelper.getMethodNameAndSignature(method),instrument.getConditionals());
091        addSourceLineNumbers(instrument.getSourceLineNumbers());
092      }
093    
094      /**
095       * Add instrumentation to a method found in this class.
096       * @param method a method present in the class
097       */
098      void addInstrumentation(Method method) {
099        if(logger.isDebugEnabled()) {
100          logger.debug("adding instrumentation to: "+cg.getClassName()+'.'+method.getName());
101        }
102        InstrumentMethodGen instrument=new InstrumentMethodGen(method,cg,ignoreRegex);
103        instrument.addInstrumentation();
104        add(method,instrument);
105      }
106    
107      /**
108       * Add instrument to all the supplied methods.
109       */
110      void addInstrumentation(Method[] methods) {
111        for(int i=0;i<methods.length;i++) {
112          addInstrumentation(methods[i]);
113        }
114      }
115    
116      /**
117       * Add coverage instrumentation to the class. Once instrumented, the
118       * instrumented class is tagged with a marker interface @see
119       * HasBeenInstrumented to prevent it from being instrumented again.
120       */
121      void addInstrumentation() {
122        if(logger.isDebugEnabled()) {
123          logger.debug("adding instrumentation to: "+getClassGen().getClassName());
124        }
125    
126        addInstrumentation(getClassGen().getMethods());
127        getClassGen().addInterface(HasBeenInstrumented.class.getName());
128      }
129    
130      ClassGen getClassGen() {
131        return cg;
132      }
133    
134      private void addSourceLineNumbers(Set sourceLineNumbers) {
135        this.sourceLineNumbers.addAll(sourceLineNumbers);
136      }
137    
138      /**
139       * @return the set of source line numbers for this class
140       */
141      Set getSourceLineNumbers() {
142        return sourceLineNumbers;
143      }
144    
145      /**
146       * @return a mapping from method name and signature to the set of
147       * line numbers for that method.
148       */
149      Map getMethodLineNumbers() {
150        return methodLineNumbers;
151      }
152    
153      /**
154       * @return a mapping from method name and signature to the set of
155       * conditionals for that method.
156       * @see Conditional
157       */
158      Map getMethodConditionals() {
159        return methodConditionals;
160      }
161    
162      /**
163       * @return the set of method names and signatures that can be found
164       * in this class.
165       */
166      Set getMethodNamesAndSignatures() {
167        return methodNamesAndSignatures;
168      }
169    }