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.pass1;
027
028import java.util.HashMap;
029import java.util.LinkedHashMap;
030import java.util.LinkedList;
031import java.util.List;
032import java.util.Map;
033import java.util.concurrent.atomic.AtomicInteger;
034
035import net.sourceforge.cobertura.instrument.ContextMethodAwareMethodAdapter;
036
037import org.apache.log4j.Logger;
038import org.objectweb.asm.Label;
039import org.objectweb.asm.MethodVisitor;
040
041/**
042 * Detects duplicates in a single analyzed method of ASM code. Applies found information about
043 * duplicates into {@link #duplicatesCollector} structure.
044 * 
045 * See {@link DetectDuplicatedCodeClassVisitor} about reasons for the class.
046 * 
047 * @author piotr.tabor@gmail.com
048 */
049public class DetectDuplicatedCodeMethodVisitor extends ContextMethodAwareMethodAdapter{
050        private final Logger logger=Logger.getLogger(DetectDuplicatedCodeClassVisitor.class);
051        /**
052         * Map of (lineNumber -> (list of pairs: lineId,{@link CodeFootstamp} for the lineId))). 
053         */
054        private final Map<Integer,List<LineIdWithCodeFootstamp>> line2label2codefootstamp=new LinkedHashMap<Integer, List<LineIdWithCodeFootstamp>>();
055        
056        /**
057         * Map of (lineNumber -> (duplicate lineId -> origin lineId)). This structure is filled with a new reasults at the {@link #visitEnd()} method.    
058         */
059        private final Map<Integer,Map<Integer,Integer>> duplicatesCollector;    
060
061        /**
062         * Represents pair of lineId and {@link CodeFootstamp} connected with that {@link #lineId} 
063         */
064        private static class LineIdWithCodeFootstamp{
065                private Integer lineId;
066                private CodeFootstamp footstamp;
067                public LineIdWithCodeFootstamp(Integer lineId, CodeFootstamp footstamp) {
068                        super();
069                        this.lineId = lineId;
070                        this.footstamp = footstamp;
071                }               
072        }
073        
074        /**
075         * {@link CodeFootstamp} of currently analyzed block of code. We will append to that variable
076         * events that we will see in the current block of code. 
077         */
078        private CodeFootstamp currentLineFootstamp;             
079
080        public DetectDuplicatedCodeMethodVisitor(MethodVisitor mv,Map<Integer,Map<Integer,Integer>> duplicatesCollector, String className, String methodName, String methodSignature,AtomicInteger lineIdGenerator) {
081                super(mv,className, methodName, methodSignature,lineIdGenerator);
082                this.duplicatesCollector=duplicatesCollector;
083        }
084        
085//---------------- Visit event and put it into footstamp methods ---------------------- 
086        
087        /**
088         * <p>Starts a new block and assigns a new {@link #currentLineFootstamp}.</p>
089         * 
090         * <p>Put's the new {@link LineIdWithCodeFootstamp} into {@link #line2label2codefootstamp} 
091         */
092        @Override
093        public void visitLineNumber(int lineNumber, Label label) {
094                super.visitLineNumber(lineNumber, label);
095                
096                currentLineFootstamp=new CodeFootstamp();                               
097                List<LineIdWithCodeFootstamp> footstamps=(line2label2codefootstamp.get(lineNumber));
098                if(footstamps==null){
099                        footstamps=new LinkedList<LineIdWithCodeFootstamp>();
100                        line2label2codefootstamp.put(lineNumber, footstamps);
101                }
102                footstamps.add(new LineIdWithCodeFootstamp(lastLineId, currentLineFootstamp));          
103        }
104        
105        @Override
106        public void visitLabel(Label label) {
107                if (currentLineFootstamp!=null){
108                        currentLineFootstamp.visitLabel(label);
109                }
110                super.visitLabel(label);
111        }
112        
113        @Override
114        public void visitFieldInsn(int access, String name, String description, String signature) {
115                if (currentLineFootstamp!=null){
116                        currentLineFootstamp.visitFieldInsn(access,name,description,signature);
117                }
118                super.visitFieldInsn(access, name, description, signature);
119        }
120        
121        @Override
122        public void visitInsn(int opCode) {
123                if (currentLineFootstamp!=null){
124                        currentLineFootstamp.visitInsn(opCode);
125                }
126                super.visitInsn(opCode);
127        }
128        
129        @Override
130        public void visitIntInsn(int opCode, int variable) {
131                if (currentLineFootstamp!=null){
132                        currentLineFootstamp.visitIntInsn(opCode,variable);
133                }
134                super.visitIntInsn(opCode, variable);
135        }
136        
137        @Override
138        public void visitIincInsn(int opCode, int variable) {
139                if (currentLineFootstamp!=null){
140                        currentLineFootstamp.visitIintInsn(opCode,variable);
141                }
142                super.visitIincInsn(opCode, variable);
143        }
144        
145        @Override
146        public void visitJumpInsn(int opCode, Label label) {
147                if (currentLineFootstamp!=null){
148                        currentLineFootstamp.visitJumpInsn(opCode,label);
149                }
150                super.visitJumpInsn(opCode, label);
151        }
152        @Override
153        public void visitLdcInsn(Object obj) {
154                if (currentLineFootstamp!=null){
155                        currentLineFootstamp.visitLdcInsn(obj);
156                }
157                super.visitLdcInsn(obj);
158        }
159        
160        @Override
161        public void visitMethodInsn(int opCode, String className, String methodName, String description) {
162                if (currentLineFootstamp!=null){
163                        currentLineFootstamp.visitMethodInsn(opCode,className,methodName,description);
164                }
165                super.visitMethodInsn(opCode, className, methodName, description);
166        }
167        @Override
168        public void visitMultiANewArrayInsn(String type, int arg1) {
169                if (currentLineFootstamp!=null){
170                        currentLineFootstamp.visitMultiANewArrayInsn(type,arg1);
171                }
172                super.visitMultiANewArrayInsn(type, arg1);
173        }
174        @Override
175        public void visitLookupSwitchInsn(Label arg0, int[] arg1, Label[] arg2) {
176                if (currentLineFootstamp!=null){
177                        currentLineFootstamp.visitLookupSwitchInsn(arg0,arg1,arg2);
178                }
179                super.visitLookupSwitchInsn(arg0, arg1, arg2);
180        }
181        
182        @Override
183        public void visitTableSwitchInsn(int arg0, int arg1, Label arg2,Label[] arg3) {
184                if (currentLineFootstamp!=null){
185                        currentLineFootstamp.visitTableSwitchInsn(arg0,arg1,arg2,arg3);
186                }
187                super.visitTableSwitchInsn(arg0, arg1, arg2, arg3);
188        }
189        
190        @Override
191        public void visitEnd() {
192                super.visitEnd();
193                putDuplicatedLinesIntoMap(duplicatesCollector);
194        }
195
196        /**
197         * Analyzes (compare) all footstamps stored in {@link #line2label2codefootstamp} and add found duplicated
198         * into {@link #duplicatesCollector}
199         */
200        public void putDuplicatedLinesIntoMap(Map<Integer,Map<Integer,Integer>> res){
201                for(Map.Entry<Integer, List<LineIdWithCodeFootstamp>> l:line2label2codefootstamp.entrySet()){
202                        Map<Integer,Integer> r=putDuplicates(l.getValue());
203                        if(r!=null){
204                                res.put(l.getKey(), r);
205                        }
206
207                        if(logger.isDebugEnabled()){
208                                for(LineIdWithCodeFootstamp pair:l.getValue()){
209                                        logger.debug("SIGNATURE:"+l.getKey()+":"+pair.lineId+":"+pair.footstamp);
210                                }                       
211                        }
212                }
213        }
214
215        /**
216         * <p>Analyzes (compares) a list of duplicates connected to the line with the same number and 
217         * add found duplicated into {@link #duplicatesCollector}</p>
218         * 
219         * @param listOfFootstamps - list of footstamps connected with a source-code line with the same number
220         * 
221         * @return A map (duplicate lineId -> origin lineId ) of duplicates found in the listOfFootstams, or NULL
222         *  if no such duplicates has been found. 
223         */
224        private Map<Integer, Integer> putDuplicates(List<LineIdWithCodeFootstamp> listOfFootstamps) {
225                Map<CodeFootstamp,Integer> reversedMap = new HashMap<CodeFootstamp, Integer>();
226                Map<Integer,Integer> result = new HashMap<Integer, Integer>();
227                for (LineIdWithCodeFootstamp lcf : listOfFootstamps){
228                        lcf.footstamp.finalize();
229                        if (lcf.footstamp.isMeaningful()) {
230                                Integer found = reversedMap.get(lcf.footstamp);
231                                if (found != null){
232                                        result.put(lcf.lineId, found);
233                                } else {
234                                        reversedMap.put(lcf.footstamp, lcf.lineId);
235                                }
236                        }
237                }               
238                return result.size() > 0 ? result : null;
239        }
240
241}