/*
 * 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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@CoverageIgnore
public class TouchCollector {
	private static final Logger logger = LoggerFactory.getLogger(TouchCollector.class);
	/*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.
Params:
  • classa – Class that needs to be registered.
Throws:
/** * 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; try { 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; } } } catch (NoClassDefFoundError ncdfe) { // "Expected", try described fallback } if (!found) { clazz = Class.forName(classa.replace("/", ".")); registerClass(clazz); } } catch (ClassNotFoundException e) { logger.error("Exception when registering class: " + classa, e); throw e; } } public static synchronized void applyTouchesOnProjectData( ProjectData projectData) { logger.debug("=================== START OF REPORT ======================== "); for (Class<?> c : registeredClasses.keySet()) { logger.debug("Report: " + c.getName()); ClassData cd = projectData.getOrCreateClassData(c.getName()); applyTouchesToSingleClassOnProjectData(cd, c); } logger.debug("=================== END OF REPORT ======================== "); } private static void applyTouchesToSingleClassOnProjectData( final ClassData classData, final Class<?> c) { logger.trace("----------- " + maybeCanonicalName(c) + " ---------------- "); 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.error("Cannot apply touches", e); } } private static String maybeCanonicalName(final Class<?> c) { /* observed getCanonicalName throwing a java.lang.InternalError: Malformed class name on the scala generated class name com.twitter.dataproducts.authcache.storage.GnipStream$Visibility$IsExternal$ Conclusion: getCanonicalName is flaky */ try { return c.getCanonicalName(); } catch (Throwable t) { return c.getName(); } } @CoverageIgnore private static class ApplyToClassDataLightClassmapListener implements LightClassmapListener { //private AtomicInteger idProvider=new AtomicInteger(0); 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 void setSource(String source) { logger.debug("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; } } }