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.tp;
027
028import java.util.HashMap;
029import java.util.HashSet;
030import java.util.Iterator;
031import java.util.LinkedList;
032import java.util.List;
033import java.util.Map;
034import java.util.Set;
035import java.util.SortedMap;
036import java.util.TreeMap;
037import java.util.concurrent.atomic.AtomicInteger;
038
039
040import net.sourceforge.cobertura.coveragedata.ClassData;
041import net.sourceforge.cobertura.coveragedata.ProjectData;
042import net.sourceforge.cobertura.instrument.pass2.BuildClassMapClassVisitor;
043
044import org.apache.log4j.Logger;
045import org.objectweb.asm.Label;
046
047/**
048 * <p>This class is a container for informations gathered during class analyzing done by {@link BuildClassMapClassVisitor}.</p>
049 * 
050 * 
051 * @author piotr.tabor@gmail.com
052 */
053public class ClassMap {
054        private static final Logger logger=Logger.getLogger(ClassMap.class);
055        /**
056         * Simple name of source-file that was used to generate that value
057         */
058        private String source;
059        
060        /**
061         * We map every eventId that is connected to instruction that created touch-point to the touch-point  
062         */
063        private final Map<Integer,TouchPointDescriptor> eventId2touchPointDescriptor=new HashMap<Integer, TouchPointDescriptor>();
064        
065        /**
066         * Contains map of label into set of {@link JumpTouchPointDescriptor} or {@link SwitchTouchPointDescriptor} that the label could be destination of
067         * 
068         * <p>The labels used here are {@link Label} created during {@link BuildClassMapClassVisitor} pass. Don't try to compare it with labels created by other instrumentation passes.
069         * Instead you should use eventId and {@link #eventId2label} to get the label created in the first pass and lookup using the label.</p>  
070         */
071        private final Map<Label, Set<TouchPointDescriptor>> label2sourcePoints=new HashMap<Label, Set<TouchPointDescriptor>>();
072        
073        /**
074         * Maps eventId to code label from BuildClassMapClassInstrumenter pass     
075         */
076        private final Map<Integer,Label> eventId2label = new HashMap<Integer, Label>();
077        
078        /**
079         * List of line numbers (not lineIds) of lines that are not allowed to contain touch-point. This
080         * lines was probably excluded from coverage using 'ignore' stuff.  
081         */
082        private final Set<Integer> blockedLines = new HashSet<Integer>();
083
084        /**
085         * List of touch-points stored in given line. 
086         */
087        private final SortedMap<Integer, List<TouchPointDescriptor>> line2touchPoints=new TreeMap<Integer, List<TouchPointDescriptor>>();       
088        
089        /**
090         * Set of eventIds that has bean already registered.  
091         */
092        private final Set<Integer> alreadyRegisteredEvents = new HashSet<Integer>();
093        
094        /*from duplicate to origin*/
095        private final Map<Label,Label> labelDuplicates2orginMap=new HashMap<Label, Label>();
096        private final Map<Label,Set<Label>> labelDuplicates2duplicateMap=new HashMap<Label, Set<Label>>();
097        
098        private String className;
099
100        private int maxCounterId=0;
101        
102        public void setSource(String source) {
103                this.source = source;
104        }
105        
106        public void registerNewJump(int eventId,int currentLine, Label destinationLabel) {
107                if (alreadyRegisteredEvents.add(eventId)){
108                        logger.debug(className+":"+currentLine+": Registering JUMP ("+eventId+") to "+destinationLabel);
109                        JumpTouchPointDescriptor descriptor=new JumpTouchPointDescriptor(eventId,currentLine/*,destinationLabel*/);
110                        eventId2touchPointDescriptor.put(eventId, descriptor);
111                        getOrCreateSourcePoints(destinationLabel).add(descriptor);
112                        getOrCreateLineTouchPoints(currentLine).add(descriptor);
113                }else{
114                        logger.debug(className+":"+currentLine+": NOT registering (already done) JUMP ("+eventId+") to "+destinationLabel);
115                }
116        }
117
118        private List<TouchPointDescriptor>  getOrCreateLineTouchPoints(int currentLine) {
119                List<TouchPointDescriptor> res=line2touchPoints.get(currentLine);
120                if (res==null){
121                        res=new LinkedList<TouchPointDescriptor>();
122                        line2touchPoints.put(currentLine,res);
123                }
124                return res;             
125        }
126
127        private Set<TouchPointDescriptor> getOrCreateSourcePoints(Label label) {
128                Set<TouchPointDescriptor> res=label2sourcePoints.get(label);
129                if (res==null){
130                        res=new HashSet<TouchPointDescriptor>();
131                        label2sourcePoints.put(label,res);                      
132                }
133                return res;
134        }
135
136        public void registerNewLabel(int eventId,int currentLine, Label label) {
137                logger.debug(className+":"+currentLine+": Registering label ("+eventId+") "+label);
138                if (alreadyRegisteredEvents.add(eventId)){
139                        eventId2label.put(eventId,label);
140                        putIntoDuplicatesMaps(label,label);
141                }else{
142                        putIntoDuplicatesMaps(label, eventId2label.get(eventId));
143                }
144        }
145        
146        public void putIntoDuplicatesMaps(Label label,Label orgin){
147                labelDuplicates2orginMap.put(label, orgin); //For coherancy
148                Set<Label> list=labelDuplicates2duplicateMap.get(orgin);
149                if(list==null){
150                        list=new HashSet<Label>();
151                        labelDuplicates2duplicateMap.put(orgin,list);
152                }
153                list.add(label);
154        }
155
156        public void registerLineNumber(int eventId,int currentLine, Label label,String methodName, String methodSignature) {
157                logger.debug(className+":"+currentLine+": Registering line ("+eventId+") "+label);
158                if (alreadyRegisteredEvents.add(eventId)){
159                        if (!blockedLines.contains(currentLine)){
160                                LineTouchPointDescriptor line=new LineTouchPointDescriptor(eventId, currentLine,methodName,methodSignature);
161                                eventId2label.put(eventId, label);
162                                eventId2touchPointDescriptor.put(eventId, line);
163                                getOrCreateLineTouchPoints(currentLine).add(line);
164                        }               
165                }
166        }
167        
168        public void unregisterLine(int eventId,int currentLine) {
169                if (alreadyRegisteredEvents.add(eventId)){
170                        blockedLines.add(currentLine);
171                        List<TouchPointDescriptor> res=line2touchPoints.get(currentLine);
172                        if(res!=null){
173                                Iterator<TouchPointDescriptor> iter=res.iterator();
174                                while(iter.hasNext()){
175                                        TouchPointDescriptor desc=iter.next();
176                                        if(desc instanceof LineTouchPointDescriptor){
177                                                iter.remove();
178                                                eventId2touchPointDescriptor.remove(desc.getEventId());
179                                                eventId2label.remove(desc.getEventId());
180                                        }
181                                }                                       
182                        }               
183                }
184        }
185
186        public void registerSwitch(int eventId,int currentLine, Label def, Label[] labels, String conditionType) {
187                if (alreadyRegisteredEvents.add(eventId)){
188                        SwitchTouchPointDescriptor swi=new SwitchTouchPointDescriptor(eventId, currentLine,def, labels, conditionType);
189                        eventId2touchPointDescriptor.put(eventId, swi);
190                        getOrCreateLineTouchPoints(currentLine).add(swi);
191                        getOrCreateSourcePoints(def).add(swi);
192                        for(Label l:labels){
193//                              System.out.println("Registering label to switch:"+l);
194                                getOrCreateSourcePoints(l).add(swi);
195                        }
196                }
197        }
198        
199//======================= data retrieval =====================================================  
200
201
202        public Integer getCounterIdForJumpTrue(int eventId) {
203                JumpTouchPointDescriptor jumpTouchPointDescriptor=(JumpTouchPointDescriptor) eventId2touchPointDescriptor.get(eventId);
204                if (jumpTouchPointDescriptor!=null){
205                        return jumpTouchPointDescriptor.getCounterIdForTrue();
206                }
207                return null;
208        }
209
210        public Integer getCounterIdForJumpFalse(int eventId) {
211                JumpTouchPointDescriptor jumpTouchPointDescriptor=(JumpTouchPointDescriptor) eventId2touchPointDescriptor.get(eventId);
212                if (jumpTouchPointDescriptor!=null){
213                        return jumpTouchPointDescriptor.getCounterIdForFalse();
214                }
215                return null;
216        }
217
218        public boolean isJumpDestinationLabel(int eventId) {
219                Label label_local=eventId2label.get(eventId);
220                logger.debug("Label found for eventId:"+eventId+":"+label_local);
221                if (labelDuplicates2duplicateMap.containsKey(label_local)) {
222                        for (Label label:labelDuplicates2duplicateMap.get(label_local)){
223                                if (label!=null){
224                                        Set<TouchPointDescriptor> res=label2sourcePoints.get(label);
225                                        logger.debug("label2sourcePoints.get("+label+"):"+res);
226                                        if(res!=null){
227                                                for(TouchPointDescriptor r:res){
228                                                        if(r instanceof JumpTouchPointDescriptor){
229                                                                return true;
230                                                        }
231                                                }
232                                        }
233                                }
234                        }
235                }
236                return false;
237        }
238
239        public Integer getCounterIdForSwitch(int eventId) {
240                SwitchTouchPointDescriptor point=(SwitchTouchPointDescriptor)eventId2touchPointDescriptor.get(eventId);
241                if (point!=null){
242                        return point.getCounterId();
243                }
244                return null;
245        }
246
247        public Integer getCounterIdForLineEventId(int eventId) {
248                LineTouchPointDescriptor point=(LineTouchPointDescriptor)eventId2touchPointDescriptor.get(eventId);
249                if (point!=null){
250                        return point.getCounterId();
251                }
252                return null;
253        }
254
255        /**
256         * Returns map:   switchCounterId --> counterId
257         * @param labelEventId
258         * @return
259         */
260        public Map<Integer, Integer> getBranchLabelDescriptorsForLabelEvent(int labelEventId) {
261                Label label_local=eventId2label.get(labelEventId);              
262                if (label_local!=null){                 
263                        if (labelDuplicates2duplicateMap.containsKey(label_local)) {
264                                for (Label label:labelDuplicates2duplicateMap.get(label_local)){                        
265                                        Set<TouchPointDescriptor> list=label2sourcePoints.get(label);
266                                        if(list!=null){
267                                                Map<Integer,Integer> res=new HashMap<Integer, Integer>();
268                                                for(TouchPointDescriptor r:list){
269                                                        if(r instanceof SwitchTouchPointDescriptor){
270                                                                SwitchTouchPointDescriptor swi=(SwitchTouchPointDescriptor)r;
271                                                                res.put(swi.getCounterId(), swi.getCounterIdForLabel(label));
272                                                        }
273                                                }
274                                                return res;
275                                        }                       
276                                }
277                        }
278                }               
279                return null;
280        }       
281        
282        /**
283         * Iterates over all touch-points created during class analysis and assigns
284         * hit-counter identifiers to each of the touchpoint (some of them needs mode then one
285         * hit-counter).
286         * 
287         * <p>This class assign hit-counter ids to each touch-point and upgrades maxCounterId to
288         * reflect the greatest assigned Id. 
289         */
290        public void assignCounterIds(){
291                AtomicInteger idGenerator=new AtomicInteger(0);
292                for(List<TouchPointDescriptor> tpd:line2touchPoints.values()){
293                        for(TouchPointDescriptor t:tpd){
294                                t.assignCounters(idGenerator);
295                        }
296                }
297                maxCounterId=idGenerator.get();
298        }
299        
300        public int getMaxCounterId() {
301                return maxCounterId;
302        }
303
304        public String getClassName() {
305                return className;
306        }
307        
308        public void setClassName(String className) {
309                this.className = className;
310        }
311        
312        public String getSource() {
313                return source;
314        }
315
316        public List<TouchPointDescriptor> getTouchPointsInLineOrder() {
317                LinkedList<TouchPointDescriptor> res=new LinkedList<TouchPointDescriptor>();
318                for(List<TouchPointDescriptor> tpd:line2touchPoints.values()){
319                        for(TouchPointDescriptor t:tpd){
320                                if(tpd instanceof LineTouchPointDescriptor){
321                                        res.add(t);
322                                }
323                        }
324                        for(TouchPointDescriptor t:tpd){
325                                if(!(tpd instanceof LineTouchPointDescriptor)){
326                                        res.add(t);
327                                }
328                        }
329                }
330                return res;
331        }
332        
333        /**
334         * Upgrades {@link ProjectData} to contain all information fount in class during class instrumentation.
335         * 
336         * <p>I don't like the idea o creating sar file during the instrumentation, but we need to do it, 
337         * to be compatible with tools that expact that (such a cobertura-maven-plugin)</p>
338         * @param projectData
339         */
340        public ClassData applyOnProjectData(ProjectData projectData, boolean instrumented){
341                ClassData classData=projectData.getOrCreateClassData(className.replace('/','.'));
342                if(source!=null){
343                        classData.setSourceFileName(source);
344                }
345                if (instrumented){
346                        classData.setContainsInstrumentationInfo();
347                        int lastLine=0;
348                        int jumpsInLine=0;
349                        int toucesInLine=0;
350                        
351                        for(TouchPointDescriptor tpd:getTouchPointsInLineOrder()){
352                                if(tpd.getLineNumber()!=lastLine){
353                                        jumpsInLine=0;
354                                        toucesInLine=0;
355                                        lastLine=tpd.getLineNumber();
356                                }
357                                if(tpd instanceof LineTouchPointDescriptor){
358                                        classData.addLine(tpd.getLineNumber(), ((LineTouchPointDescriptor) tpd).getMethodName(), ((LineTouchPointDescriptor) tpd).getMethodSignature());
359                                }else if(tpd instanceof JumpTouchPointDescriptor){
360                                        classData.addLineJump(tpd.getLineNumber(), jumpsInLine++);
361                                }else if(tpd instanceof SwitchTouchPointDescriptor){
362                                        int countersCnt=((SwitchTouchPointDescriptor)tpd).getCountersForLabelsCnt();
363                                        //TODO(ptab): instead of Integer.MAX_VALUE should be length of Enum.
364                                        classData.addLineSwitch(tpd.getLineNumber(), toucesInLine++,0, countersCnt-2, Integer.MAX_VALUE);
365                                }               
366                        }                       
367                }
368                return classData;
369        }
370
371}