001 /*
002 * Cobertura - http://cobertura.sourceforge.net/
003 *
004 * Copyright (C) 2003 jcoverage ltd.
005 * Copyright (C) 2005 Mark Doliner
006 * Copyright (C) 2005 Mark Sinke
007 * Copyright (C) 2006 Jiri Mares
008 *
009 * Cobertura is free software; you can redistribute it and/or modify
010 * it under the terms of the GNU General Public License as published
011 * by the Free Software Foundation; either version 2 of the License,
012 * or (at your option) any later version.
013 *
014 * Cobertura is distributed in the hope that it will be useful, but
015 * WITHOUT ANY WARRANTY; without even the implied warranty of
016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017 * General Public License for more details.
018 *
019 * You should have received a copy of the GNU General Public License
020 * along with Cobertura; if not, write to the Free Software
021 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
022 * USA
023 */
024
025 package net.sourceforge.cobertura.coveragedata;
026
027 import java.io.IOException;
028 import java.io.ObjectInputStream;
029 import java.io.Serializable;
030 import java.util.ArrayList;
031 import java.util.List;
032 import java.util.concurrent.locks.Lock;
033 import java.util.concurrent.locks.ReentrantLock;
034
035 import net.sourceforge.cobertura.util.StringUtil;
036
037 /**
038 * <p>
039 * This class implements HasBeenInstrumented so that when cobertura
040 * instruments itself, it will omit this class. It does this to
041 * avoid an infinite recursion problem because instrumented classes
042 * make use of this class.
043 * </p>
044 */
045 public class LineData
046 implements Comparable, CoverageData, HasBeenInstrumented, Serializable
047 {
048 private static final long serialVersionUID = 4;
049
050 private transient Lock lock;
051
052 private long hits;
053 private List jumps;
054 private List switches;
055 private final int lineNumber;
056 private String methodDescriptor;
057 private String methodName;
058
059 LineData(int lineNumber)
060 {
061 this(lineNumber, null, null);
062 }
063
064 LineData(int lineNumber, String methodName, String methodDescriptor)
065 {
066 this.hits = 0;
067 this.jumps = null;
068 this.lineNumber = lineNumber;
069 this.methodName = methodName;
070 this.methodDescriptor = methodDescriptor;
071 initLock();
072 }
073
074 private void initLock()
075 {
076 lock = new ReentrantLock();
077 }
078
079 /**
080 * This is required because we implement Comparable.
081 */
082 public int compareTo(Object o)
083 {
084 if (!o.getClass().equals(LineData.class))
085 return Integer.MAX_VALUE;
086 return this.lineNumber - ((LineData)o).lineNumber;
087 }
088
089 public boolean equals(Object obj)
090 {
091 if (this == obj)
092 return true;
093 if ((obj == null) || !(obj.getClass().equals(this.getClass())))
094 return false;
095
096 LineData lineData = (LineData)obj;
097 getBothLocks(lineData);
098 try
099 {
100 return (this.hits == lineData.hits)
101 && ((this.jumps == lineData.jumps) || ((this.jumps != null) && (this.jumps.equals(lineData.jumps))))
102 && ((this.switches == lineData.switches) || ((this.switches != null) && (this.switches.equals(lineData.switches))))
103 && (this.lineNumber == lineData.lineNumber)
104 && (this.methodDescriptor.equals(lineData.methodDescriptor))
105 && (this.methodName.equals(lineData.methodName));
106 }
107 finally
108 {
109 lock.unlock();
110 lineData.lock.unlock();
111 }
112 }
113
114 public double getBranchCoverageRate()
115 {
116 if (getNumberOfValidBranches() == 0)
117 return 1d;
118 lock.lock();
119 try
120 {
121 return ((double) getNumberOfCoveredBranches()) / getNumberOfValidBranches();
122 }
123 finally
124 {
125 lock.unlock();
126 }
127 }
128
129 public String getConditionCoverage()
130 {
131 StringBuffer ret = new StringBuffer();
132 if (getNumberOfValidBranches() == 0)
133 {
134 ret.append(StringUtil.getPercentValue(1.0));
135 }
136 else
137 {
138 lock.lock();
139 try
140 {
141 ret.append(StringUtil.getPercentValue(getBranchCoverageRate()));
142 ret.append(" (").append(getNumberOfCoveredBranches()).append("/").append(getNumberOfValidBranches()).append(")");
143 }
144 finally
145 {
146 lock.unlock();
147 }
148 }
149 return ret.toString();
150 }
151
152 public long getHits()
153 {
154 lock.lock();
155 try
156 {
157 return hits;
158 }
159 finally
160 {
161 lock.unlock();
162 }
163 }
164
165 public boolean isCovered()
166 {
167 lock.lock();
168 try
169 {
170 return (getHits() > 0) && ((getNumberOfValidBranches() == 0) || ((1.0 - getBranchCoverageRate()) < 0.0001));
171 }
172 finally
173 {
174 lock.unlock();
175 }
176 }
177
178 public double getLineCoverageRate()
179 {
180 return (getHits() > 0) ? 1 : 0;
181 }
182
183 public int getLineNumber()
184 {
185 return lineNumber;
186 }
187
188 public String getMethodDescriptor()
189 {
190 lock.lock();
191 try
192 {
193 return methodDescriptor;
194 }
195 finally
196 {
197 lock.unlock();
198 }
199 }
200
201 public String getMethodName()
202 {
203 lock.lock();
204 try
205 {
206 return methodName;
207 }
208 finally
209 {
210 lock.unlock();
211 }
212 }
213
214 /**
215 * @see net.sourceforge.cobertura.coveragedata.CoverageData#getNumberOfCoveredBranches()
216 */
217 /*public int getNumberOfCoveredBranches()
218 {
219 if (this.branches == null)
220 return 0;
221 int covered = 0;
222 for (Iterator i = this.branches.iterator(); i.hasNext(); covered += ((BranchData) i.next()).getNumberOfCoveredBranches());
223 return covered;
224 }*/
225
226 public int getNumberOfCoveredLines()
227 {
228 return (getHits() > 0) ? 1 : 0;
229 }
230
231 public int getNumberOfValidBranches()
232 {
233 int ret = 0;
234 lock.lock();
235 try
236 {
237 if (jumps != null)
238 for (int i = jumps.size() - 1; i >= 0; i--)
239 ret += ((JumpData) jumps.get(i)).getNumberOfValidBranches();
240 if (switches != null)
241 for (int i = switches.size() - 1; i >= 0; i--)
242 ret += ((SwitchData) switches.get(i)).getNumberOfValidBranches();
243 return ret;
244 }
245 finally
246 {
247 lock.unlock();
248 }
249 }
250
251 public int getNumberOfCoveredBranches()
252 {
253 int ret = 0;
254 lock.lock();
255 try
256 {
257 if (jumps != null)
258 for (int i = jumps.size() - 1; i >= 0; i--)
259 ret += ((JumpData) jumps.get(i)).getNumberOfCoveredBranches();
260 if (switches != null)
261 for (int i = switches.size() - 1; i >= 0; i--)
262 ret += ((SwitchData) switches.get(i)).getNumberOfCoveredBranches();
263 return ret;
264 }
265 finally
266 {
267 lock.unlock();
268 }
269 }
270
271 public int getNumberOfValidLines()
272 {
273 return 1;
274 }
275
276 public int hashCode()
277 {
278 return this.lineNumber;
279 }
280
281 public boolean hasBranch()
282 {
283 lock.lock();
284 try
285 {
286 return (jumps != null) || (switches != null);
287 }
288 finally
289 {
290 lock.unlock();
291 }
292 }
293
294 public void merge(CoverageData coverageData)
295 {
296 LineData lineData = (LineData)coverageData;
297 getBothLocks(lineData);
298 try
299 {
300 this.hits += lineData.hits;
301 if (lineData.jumps != null)
302 if (this.jumps == null)
303 this.jumps = lineData.jumps;
304 else
305 {
306 for (int i = Math.min(this.jumps.size(), lineData.jumps.size()) - 1; i >= 0; i--)
307 ((JumpData) this.jumps.get(i)).merge((JumpData) lineData.jumps.get(i));
308 for (int i = Math.min(this.jumps.size(), lineData.jumps.size()); i < lineData.jumps.size(); i++)
309 this.jumps.add(lineData.jumps.get(i));
310 }
311 if (lineData.switches != null)
312 if (this.switches == null)
313 this.switches = lineData.switches;
314 else
315 {
316 for (int i = Math.min(this.switches.size(), lineData.switches.size()) - 1; i >= 0; i--)
317 ((SwitchData) this.switches.get(i)).merge((SwitchData) lineData.switches.get(i));
318 for (int i = Math.min(this.switches.size(), lineData.switches.size()); i < lineData.switches.size(); i++)
319 this.switches.add(lineData.switches.get(i));
320 }
321 if (lineData.methodName != null)
322 this.methodName = lineData.methodName;
323 if (lineData.methodDescriptor != null)
324 this.methodDescriptor = lineData.methodDescriptor;
325 }
326 finally
327 {
328 lock.unlock();
329 lineData.lock.unlock();
330 }
331 }
332
333 void addJump(int jumpNumber)
334 {
335 getJumpData(jumpNumber);
336 }
337
338 void addSwitch(int switchNumber, int[] keys)
339 {
340 getSwitchData(switchNumber, new SwitchData(switchNumber, keys));
341 }
342
343 void addSwitch(int switchNumber, int min, int max)
344 {
345 getSwitchData(switchNumber, new SwitchData(switchNumber, min, max));
346 }
347
348 void setMethodNameAndDescriptor(String name, String descriptor)
349 {
350 lock.lock();
351 try
352 {
353 this.methodName = name;
354 this.methodDescriptor = descriptor;
355 }
356 finally
357 {
358 lock.unlock();
359 }
360 }
361
362 void touch()
363 {
364 lock.lock();
365 try
366 {
367 this.hits++;
368 }
369 finally
370 {
371 lock.unlock();
372 }
373 }
374
375 void touchJump(int jumpNumber, boolean branch)
376 {
377 getJumpData(jumpNumber).touchBranch(branch);
378 }
379
380 void touchSwitch(int switchNumber, int branch)
381 {
382 getSwitchData(switchNumber, null).touchBranch(branch);
383 }
384
385 public int getConditionSize() {
386 lock.lock();
387 try
388 {
389 return ((jumps == null) ? 0 : jumps.size()) + ((switches == null) ? 0 :switches.size());
390 }
391 finally
392 {
393 lock.unlock();
394 }
395 }
396
397 public Object getConditionData(int index)
398 {
399 Object branchData = null;
400 lock.lock();
401 try
402 {
403 int jumpsSize = (jumps == null) ? 0 : jumps.size();
404 int switchesSize = (switches == null) ? 0 :switches.size();
405 if (index < jumpsSize)
406 {
407 branchData = jumps.get(index);
408 }
409 else if (index < jumpsSize + switchesSize)
410 {
411 branchData = switches.get(index - jumpsSize);
412 }
413 return branchData;
414 }
415 finally
416 {
417 lock.unlock();
418 }
419 }
420
421 public String getConditionCoverage(int index) {
422 Object branchData = getConditionData(index);
423 if (branchData == null)
424 {
425 return StringUtil.getPercentValue(1.0);
426 }
427 else if (branchData instanceof JumpData)
428 {
429 JumpData jumpData = (JumpData) branchData;
430 return StringUtil.getPercentValue(jumpData.getBranchCoverageRate());
431 }
432 else
433 {
434 SwitchData switchData = (SwitchData) branchData;
435 return StringUtil.getPercentValue(switchData.getBranchCoverageRate());
436
437 }
438 }
439
440 JumpData getJumpData(int jumpNumber)
441 {
442 lock.lock();
443 try
444 {
445 if (jumps == null)
446 {
447 jumps = new ArrayList();
448 }
449 if (jumps.size() <= jumpNumber)
450 {
451 for (int i = jumps.size(); i <= jumpNumber; jumps.add(new JumpData(i++)));
452 }
453 return (JumpData) jumps.get(jumpNumber);
454 }
455 finally
456 {
457 lock.unlock();
458 }
459 }
460
461 SwitchData getSwitchData(int switchNumber, SwitchData data)
462 {
463 lock.lock();
464 try
465 {
466 if (switches == null)
467 {
468 switches = new ArrayList();
469 }
470 if (switches.size() < switchNumber)
471 {
472 for (int i = switches.size(); i < switchNumber; switches.add(new SwitchData(i++)));
473 }
474 if (switches.size() == switchNumber)
475 if (data != null)
476 switches.add(data);
477 else
478 switches.add(new SwitchData(switchNumber));
479 return (SwitchData) switches.get(switchNumber);
480 }
481 finally
482 {
483 lock.unlock();
484 }
485 }
486
487 private void getBothLocks(LineData other) {
488 /*
489 * To prevent deadlock, we need to get both locks or none at all.
490 *
491 * When this method returns, the thread will have both locks.
492 * Make sure you unlock them!
493 */
494 boolean myLock = false;
495 boolean otherLock = false;
496 while ((!myLock) || (!otherLock))
497 {
498 try
499 {
500 myLock = lock.tryLock();
501 otherLock = other.lock.tryLock();
502 }
503 finally
504 {
505 if ((!myLock) || (!otherLock))
506 {
507 //could not obtain both locks - so unlock the one we got.
508 if (myLock)
509 {
510 lock.unlock();
511 }
512 if (otherLock)
513 {
514 other.lock.unlock();
515 }
516 //do a yield so the other threads will get to work.
517 Thread.yield();
518 }
519 }
520 }
521 }
522
523 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
524 {
525 in.defaultReadObject();
526 initLock();
527 }
528 }