001/*
002 * Cobertura - http://cobertura.sourceforge.net/
003 *
004 * Copyright (C) 2011 Piotr Tabor
005 *
006 * Note: This file is dual licensed under the GPL and the Apache
007 * Source License (so that it can be used from both the main
008 * Cobertura classes and the ant tasks).
009 *
010 * Cobertura is free software; you can redistribute it and/or modify
011 * it under the terms of the GNU General Public License as published
012 * by the Free Software Foundation; either version 2 of the License,
013 * or (at your option) any later version.
014 *
015 * Cobertura is distributed in the hope that it will be useful, but
016 * WITHOUT ANY WARRANTY; without even the implied warranty of
017 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
018 * General Public License for more details.
019 *
020 * You should have received a copy of the GNU General Public License
021 * along with Cobertura; if not, write to the Free Software
022 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
023 * USA
024 */
025
026package net.sourceforge.cobertura.instrument.pass2;
027
028import java.util.Collection;
029import java.util.Map;
030import java.util.Set;
031import java.util.regex.Pattern;
032
033import net.sourceforge.cobertura.CoverageIgnore;
034import net.sourceforge.cobertura.instrument.AbstractFindTouchPointsClassInstrumenter;
035import net.sourceforge.cobertura.instrument.FindTouchPointsMethodAdapter;
036import net.sourceforge.cobertura.instrument.HistoryMethodAdapter;
037import net.sourceforge.cobertura.instrument.pass3.CodeProvider;
038import net.sourceforge.cobertura.instrument.tp.ClassMap;
039
040import org.objectweb.asm.AnnotationVisitor;
041import org.objectweb.asm.ClassReader;
042import org.objectweb.asm.ClassVisitor;
043import org.objectweb.asm.MethodVisitor;
044import org.objectweb.asm.Opcodes;
045import org.objectweb.asm.Type;
046
047/**
048 * <p>Analyzes given class. Builds {@link ClassMap} that represents any touch-points and other important information
049 * for instrumentation.</p>
050 * 
051 *  This instrumenter ({@link ClassVisitor}) does not change the bytecode of the class. It makes only analyzys and fills {@link ClassMap}. 
052 * 
053 * @author piotr.tabor@gmail.com
054 */
055public class BuildClassMapClassVisitor extends AbstractFindTouchPointsClassInstrumenter{
056        /**
057         * {@link ClassMap} for the currently analyzed class. 
058         */
059        private final ClassMap classMap = new ClassMap();
060        
061        /**
062         * Information about important 'events' (instructions) are sent into the listener that is internally
063         * responsible for modifying the {@link #classMap} content. 
064         */
065        private final BuildClassMapTouchPointListener touchPointListener = new BuildClassMapTouchPointListener(classMap);       
066        
067        /**
068         * It's flag that signals if the class should be instrumented by cobertura. 
069         * After analyzing the class you can check the field using {@link #shouldBeInstrumented()}.
070         */
071        private boolean toInstrument = true; 
072        
073        private final Set<String> ignoredMethods;
074
075        /**
076         * @param cv                 - a listener for code-instrumentation events 
077         * @param ignoreRegexp       - list of patters of method calls that should be ignored from line-coverage-measurement 
078         * @param duplicatedLinesMap - map of found duplicates in the class. You should use {@link DetectDuplicatedCodeClassVisitor} to find the duplicated lines. 
079         */
080        public BuildClassMapClassVisitor(ClassVisitor cv, Collection<Pattern> ignoreRegexes,Map<Integer, Map<Integer, Integer>> duplicatedLinesMap,
081                        Set<String> ignoredMethods) {
082                super(cv,ignoreRegexes,duplicatedLinesMap);
083                this.ignoredMethods = ignoredMethods;
084        }
085        
086        @Override
087        public AnnotationVisitor visitAnnotation(String name, boolean arg1) {
088                if (Type.getDescriptor(CoverageIgnore.class).equals(name)) {
089                        toInstrument = false;
090                }
091                return super.visitAnnotation(name, arg1);
092        }
093
094        /**
095         * Stores in {@link #classMap} information of className and if the class should be instrumented ({@link #shouldBeInstrumented()}) 
096         */
097        @Override
098        public void visit(int version, int access, String name, String signature,
099                        String parent, String[] interfaces) {
100                classMap.setClassName(name);
101                
102                if ((access & Opcodes.ACC_INTERFACE) != 0) {
103                        toInstrument = false;
104                }
105                super.visit(version, access, name, signature, parent, interfaces);              
106        }
107        
108        /**
109         * Stores in {@link #classMap} information of source filename
110         */
111        @Override
112        public void visitSource(String file, String debug) {
113                classMap.setSource(file);
114                super.visitSource(file, debug);
115        }
116        
117        /**
118         * Analyzes given method and stores  information about all found important places into {@link #classMap} 
119         */
120        @Override
121        public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {         
122                if (((access & Opcodes.ACC_STATIC) != 0) 
123                                && CodeProvider.COBERTURA_INIT_METHOD_NAME.equals(name)) {
124                  toInstrument = false; //  The class has bean already instrumented.
125                }
126                
127                MethodVisitor mv = super.visitMethod(access, name, desc, signature,     exceptions);
128                if (ignoredMethods.contains(name + desc)) {
129                        return mv;
130                }
131                FindTouchPointsMethodAdapter instrumenter = new FindTouchPointsMethodAdapter(
132                                new HistoryMethodAdapter(mv, 4),
133                                classMap.getClassName(),name,desc,eventIdGenerator,duplicatedLinesMap,lineIdGenerator);
134                instrumenter.setTouchPointListener(touchPointListener);
135                instrumenter.setIgnoreRegexp(getIgnoreRegexp());
136                return instrumenter;
137        }
138
139        /**
140         * Returns classMap build for the analyzed map. The classmap is filled after running the analyzer ({@link ClassReader#accept(ClassVisitor, int)}).
141         * 
142         * @return the classmap.
143         */
144        public ClassMap getClassMap(){
145                return classMap;
146        }
147
148        /**
149         * It's flag that signals if the class should be instrumented by Cobertura.
150         */
151        public boolean shouldBeInstrumented() {
152                return toInstrument;
153        }
154}