001    /*
002     * Cobertura - http://cobertura.sourceforge.net/
003     *
004     * Copyright (C) 2005 Mark Doliner 
005     * Copyright (C) 2006 Jiri Mares 
006     * 
007     * Cobertura 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     * Cobertura 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 Cobertura; 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 net.sourceforge.cobertura.instrument;
024    
025    import java.util.Collection;
026    
027    import net.sourceforge.cobertura.coveragedata.ClassData;
028    import net.sourceforge.cobertura.coveragedata.ProjectData;
029    
030    import org.apache.log4j.Logger;
031    import org.objectweb.asm.ClassAdapter;
032    import org.objectweb.asm.ClassVisitor;
033    import org.objectweb.asm.MethodVisitor;
034    import org.objectweb.asm.Opcodes;
035    
036    class ClassInstrumenter extends ClassAdapter
037    {
038    
039            private static final Logger logger = Logger
040                            .getLogger(ClassInstrumenter.class);
041    
042            private final static String hasBeenInstrumented = "net/sourceforge/cobertura/coveragedata/HasBeenInstrumented";
043    
044            private Collection ignoreRegexs;
045    
046            private Collection ignoreBranchesRegexs;
047    
048            private ProjectData projectData;
049    
050            private ClassData classData;
051    
052            private String myName;
053    
054            private boolean instrument = false;
055    
056            public String getClassName()
057            {
058                    return this.myName;
059            }
060    
061            public boolean isInstrumented()
062            {
063                    return instrument;
064            }
065    
066            public ClassInstrumenter(ProjectData projectData, final ClassVisitor cv,
067                            final Collection ignoreRegexs, final Collection ignoreBranchesRegexes)
068            {
069                    super(cv);
070                    this.projectData = projectData;
071                    this.ignoreRegexs = ignoreRegexs;
072                    this.ignoreBranchesRegexs = ignoreBranchesRegexs;
073            }
074    
075            private boolean arrayContains(Object[] array, Object key)
076            {
077                    for (int i = 0; i < array.length; i++)
078                    {
079                            if (array[i].equals(key))
080                                    return true;
081                    }
082    
083                    return false;
084            }
085    
086            /**
087             * @param name In the format
088             *             "net/sourceforge/cobertura/coverage/ClassInstrumenter"
089             */
090            public void visit(int version, int access, String name, String signature,
091                            String superName, String[] interfaces)
092            {
093                    this.myName = name.replace('/', '.');
094                    this.classData = this.projectData.getOrCreateClassData(this.myName);
095                    this.classData.setContainsInstrumentationInfo();
096    
097                    // Do not attempt to instrument interfaces or classes that
098                    // have already been instrumented
099                    if (((access & Opcodes.ACC_INTERFACE) != 0)
100                                    || arrayContains(interfaces, hasBeenInstrumented))
101                    {
102                            super.visit(version, access, name, signature, superName,
103                                                            interfaces);
104                    }
105                    else
106                    {
107                            instrument = true;
108    
109                            // Flag this class as having been instrumented
110                            String[] newInterfaces = new String[interfaces.length + 1];
111                            System.arraycopy(interfaces, 0, newInterfaces, 0,
112                                                            interfaces.length);
113                            newInterfaces[newInterfaces.length - 1] = hasBeenInstrumented;
114    
115                            super.visit(version, access, name, signature, superName,
116                                            newInterfaces);
117                    }
118            }
119    
120            /**
121             * @param source In the format "ClassInstrumenter.java"
122             */
123            public void visitSource(String source, String debug)
124            {
125                    super.visitSource(source, debug);
126                    classData.setSourceFileName(source);
127            }
128    
129            public MethodVisitor visitMethod(final int access, final String name,
130                            final String desc, final String signature,
131                            final String[] exceptions)
132            {
133                    MethodVisitor mv = cv.visitMethod(access, name, desc, signature,
134                                    exceptions);
135    
136                    if (!instrument)
137                            return mv;
138    
139                    return mv == null ? null : new FirstPassMethodInstrumenter(classData, mv,
140                                    this.myName, access, name, desc, signature, exceptions, ignoreRegexs, 
141                                    ignoreBranchesRegexs);
142            }
143    
144            public void visitEnd()
145            {
146                    if (instrument && classData.getNumberOfValidLines() == 0)
147                            logger.warn("No line number information found for class "
148                                            + this.myName
149                                            + ".  Perhaps you need to compile with debug=true?");
150            }
151    
152    }