/*
* Cobertura - http://cobertura.sourceforge.net/
*
* Copyright (C) 2010 Piotr Tabor
*
* Note: This file is dual licensed under the GPL and the Apache
* Source License (so that it can be used from both the main
* Cobertura classes and the ant tasks).
*
* Cobertura is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation; either version 2 of the License,
* or (at your option) any later version.
*
* Cobertura is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Cobertura; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*/
package net.sourceforge.cobertura.coveragedata;
import net.sourceforge.cobertura.CoverageIgnore;
import net.sourceforge.cobertura.instrument.pass3.AbstractCodeProvider;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
@CoverageIgnore
public class TouchCollector {
private static final Logger logger = Logger.getLogger(TouchCollector.class
.getCanonicalName());
/*In fact - concurrentHashset*/
private static Map<Class<?>, Integer> registeredClasses = new ConcurrentHashMap<Class<?>, Integer>();
static {
ProjectData.getGlobalProjectData(); // To call ProjectData.initialize();
}
public static synchronized void registerClass(Class<?> classa) {
registeredClasses.put(classa, 0);
}
/**
* This method is only for backward compatibility
*
* Information:
* ASM version 4.1 does not allow for the data type java.lang.Class to be a parameter
* to the method visitLdcInsn which causes issues for anything below .class versions
* 49 and lower. Changing the registered class to use instead a String parameter and
* search for the class in the classpath helped resolve the issue.
* Also as a side note: The replace parameters might enter as "java/lang/String" and
* need to be translated to "java.lang.String" so the forName method can understand it.
*
* @param classa Class that needs to be registered.
* @throws ClassNotFoundException
*/
public static synchronized void registerClass(String classa)
throws ClassNotFoundException {
try {
// If it's not in the system jvm, then search the current thread for the class.
// This is a dirty hack to guarantee that multiple classloaders can invoke cobertura code.
// We try 2 methods to register the classes
// First method we try to call the invoker classloader. If the invoker causes an exception (NoClassDefFound) it
// will then call Thread.currentThread.getContextClassLoader() which gets the current threads classloader and
// checks to see if cobertura code is in there. This is here because there are situations where multiple
// classloaders might be invoked and it requires the check of multiple classloaders.
boolean found = false;
Class<?> clazz = Class.forName(classa.replace("/", "."), false,
Thread.currentThread().getContextClassLoader());
for (Method meth : clazz.getMethods()) {
if (meth.toString().contains("net.sourceforge.cobertura")) {
registerClass(clazz);
found = true;
}
}
if (!found) {
clazz = Class.forName(classa.replace("/", "."));
registerClass(clazz);
}
} catch (ClassNotFoundException e) {
logger.log(Level.SEVERE, "Exception when registering class: "
+ classa, e);
throw e;
}
}
public static synchronized void applyTouchesOnProjectData(
ProjectData projectData) {
logger
.fine("=================== START OF REPORT ======================== ");
for (Class<?> c : registeredClasses.keySet()) {
logger.fine("Report: " + c.getName());
ClassData cd = projectData.getOrCreateClassData(c.getName());
applyTouchesToSingleClassOnProjectData(cd, c);
}
logger
.fine("=================== END OF REPORT ======================== ");
}
private static void applyTouchesToSingleClassOnProjectData(
final ClassData classData, final Class<?> c) {
logger.finer("----------- " + c.getCanonicalName()
+ " ---------------- ");
try {
Method m0 = c
.getDeclaredMethod(AbstractCodeProvider.COBERTURA_GET_AND_RESET_COUNTERS_METHOD_NAME);
m0.setAccessible(true);
final TestUnitInformationHolder[] res = (TestUnitInformationHolder[]) m0
.invoke(null, new Object[]{});
LightClassmapListener lightClassmap = new ApplyToClassDataLightClassmapListener(
classData, res);
Method m = c.getDeclaredMethod(
AbstractCodeProvider.COBERTURA_CLASSMAP_METHOD_NAME,
LightClassmapListener.class);
m.setAccessible(true);
m.invoke(null, lightClassmap);
} catch (Exception e) {
// Ignore exception. We are not using Junit testing method.
// TODO: Fix this so we don't duplicate code and can probably make more efficient.
e.printStackTrace();
}
try {
Method m0 = c
.getDeclaredMethod(AbstractCodeProvider.COBERTURA_GET_AND_RESET_COUNTERS_METHOD_NAME);
m0.setAccessible(true);
final int[] res = (int[]) m0.invoke(null, new Object[]{});
LightClassmapListener lightClassmap = new ApplyToClassDataLightClassmapListener(
classData, res);
Method m = c.getDeclaredMethod(
AbstractCodeProvider.COBERTURA_CLASSMAP_METHOD_NAME,
LightClassmapListener.class);
m.setAccessible(true);
m.invoke(null, lightClassmap);
} catch (Exception e) {
logger.log(Level.SEVERE, "Cannot apply touches", e);
}
}
@CoverageIgnore
private static class ApplyToClassDataLightClassmapListener
implements
LightClassmapListener {
private final ClassData classData;
private final int[] res;
private int currentLine = 0;
private int jumpsInLine = 0;
private int switchesInLine = 0;
private void updateLine(int new_line) {
if (new_line != currentLine) {
currentLine = new_line;
jumpsInLine = 0;
switchesInLine = 0;
}
}
public ApplyToClassDataLightClassmapListener(ClassData cd, int[] res) {
classData = cd;
this.res = res;
}
public ApplyToClassDataLightClassmapListener(ClassData cd,
TestUnitInformationHolder[] results) {
classData = cd;
this.res = new int[results.length];
for (int i = 0; i < results.length; i++) {
this.res[i] = results[i].getNumOfExecutions();
}
}
public void setSource(String source) {
logger.fine("source: " + source);
classData.setSourceFileName(source);
}
public void setClazz(Class<?> clazz) {
}
public void setClazz(String clazz) {
}
public void putLineTouchPoint(int classLine, int counterId,
String methodName, String methodDescription) {
updateLine(classLine);
LineData ld = classData.addLine(classLine, methodName,
methodDescription);
ld.touch(res[counterId]);
}
public void putSwitchTouchPoint(int classLine, int maxBranches,
int... counterIds) {
updateLine(classLine);
LineData ld = getOrCreateLine(classLine);
int switchId = switchesInLine++;
classData.addLineSwitch(classLine, switchId, 0,
counterIds.length - 2, maxBranches);
for (int i = 0; i < counterIds.length; i++) {
ld.touchSwitch(switchId, i - 1, res[counterIds[i]]);
}
}
public void putJumpTouchPoint(int classLine, int trueCounterId,
int falseCounterId) {
updateLine(classLine);
LineData ld = getOrCreateLine(classLine);
int branchId = jumpsInLine++;
classData.addLineJump(classLine, branchId);
ld.touchJump(branchId, true, res[trueCounterId]);
ld.touchJump(branchId, false, res[falseCounterId]);
}
private LineData getOrCreateLine(int classLine) {
LineData ld = classData.getLineData(classLine);
if (ld == null) {
ld = classData.addLine(classLine, null, null);
}
return ld;
}
}
}