/*
* $Id$
*
* Copyright 2006, The jCoderZ.org Project. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials
* provided with the distribution.
* * Neither the name of the jCoderZ.org Project nor the names of
* its contributors may be used to endorse or promote products
* derived from this software without specific prior written
* permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.jcoderz.phoenix.report;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.bind.JAXBException;
import org.jcoderz.phoenix.report.jaxb.Item;
import org.jcoderz.phoenix.report.jaxb.ObjectFactory;
import com.vladium.emma.data.ClassDescriptor;
import com.vladium.emma.data.DataFactory;
import com.vladium.emma.data.ICoverageData;
import com.vladium.emma.data.IMergeable;
import com.vladium.emma.data.IMetaData;
import com.vladium.emma.data.MethodDescriptor;
import com.vladium.emma.data.ICoverageData.DataHolder;
/**
* Reads the coverage report generated by emma (http://emma.sourceforge.net/).
*
* @author Andreas Mandel
*/
public class EmmaReportReader
extends AbstractReportReader
{
/** JAXB context path. */
public static final String JCOVERAGE_JAXB_CONTEXT_PATH
= "org.jcoderz.phoenix.coverage.jaxb";
private static final String CLASSNAME
= EmmaReportReader.class.getName();
private static final Logger logger = Logger.getLogger(CLASSNAME);
/**
* Used if all branches of a line are covered. If there are 2
* branches in a line 1/2 of this value is assigned as hit
* counter.
*/
private static final int EMMA_FULL_PERCENTAGE = 100;
private IMetaData mEmmaMetaData;
private ICoverageData mEmmaCoverageData;
EmmaReportReader ()
throws JAXBException
{
super(JCOVERAGE_JAXB_CONTEXT_PATH);
}
/** {@inheritDoc} */
public final void parse (File f)
throws JAXBException
{
try
{
final IMergeable[] emmaReport = DataFactory.load(f);
mEmmaMetaData = (IMetaData) emmaReport[DataFactory.TYPE_METADATA];
mEmmaCoverageData = (ICoverageData)
emmaReport[DataFactory.TYPE_COVERAGEDATA];
if (mEmmaMetaData == null)
{
logger.warning(
"Read no meta data from emma in file '" + f + "'.");
}
if (mEmmaCoverageData == null)
{
logger.warning(
"Read no coverage info from emma in file '" + f + "'.");
}
}
catch (IOException e)
{
throw new JAXBException("Cannot read Emma report at '"
+ f + "'.", e);
}
}
/** {@inheritDoc} */
public final Map<ResourceInfo, List<Item>> getItems ()
throws JAXBException
{
final Map<ResourceInfo, List<Item>> itemMap
= new HashMap<ResourceInfo, List<Item>>();
final Iterator<ClassDescriptor> i = mEmmaMetaData.iterator();
while (i.hasNext())
{
final ClassDescriptor clazz = i.next();
final String srcFileName = clazz.getSrcFileName();
final String fileName;
if (srcFileName != null)
{
fileName = srcFileName.substring(
0, srcFileName.lastIndexOf('.'));
}
else
{ // fall back if data is not available.
fileName = clazz.getClassVMName().substring(
clazz.getClassVMName().lastIndexOf('/') + 1);
}
final String classname
= clazz.getClassVMName().substring(
clazz.getClassVMName().lastIndexOf('/') + 1);
final ResourceInfo source
= ResourceInfo.lookup(
clazz.getPackageVMName().replaceAll("/", "."), fileName);
if (source != null)
{
if (logger.isLoggable(Level.FINER))
{
logger.finer(
"Processing coverage info for resource " + source);
}
processClazz(itemMap, source, clazz,
mEmmaCoverageData == null
? null : mEmmaCoverageData.getCoverage(clazz));
}
else
{
if (logger.isLoggable(Level.FINER))
{
logger.finer(
"Ignoring coverage info for class "
+ clazz.getPackageVMName().replaceAll("/", ".") + "."
+ classname + "@" + clazz.getSrcFileName());
}
}
}
return itemMap;
}
private void processClazz (Map<ResourceInfo, List<Item>> itemMap,
ResourceInfo source, ClassDescriptor clazz, DataHolder coverage)
throws JAXBException
{
logger.fine("Processing class '" + clazz.getName() + "'");
final Map<Integer, CoverageDetail> lineCoverage
= collectLineCoverage(clazz, coverage);
final List<Item> itemList
= createItemEntries(lineCoverage);
if (!itemList.isEmpty())
{
if (itemMap.containsKey(source))
{
final List<Item> l = itemMap.get(source);
l.addAll(itemList);
}
else
{
itemMap.put(source, itemList);
}
}
}
/**
* Collect all counters per line number mapping.
* Emma has a block view on the source but we need a line
* by line info.
* @param clazz the static info from emma
* @param coverage the dynamic coverage data
* @return a map mapping from line number to coverage data.
*/
private Map<Integer, CoverageDetail> collectLineCoverage (
final ClassDescriptor clazz, DataHolder coverage)
{
final MethodDescriptor[] methods = clazz.getMethods();
final Map<Integer, CoverageDetail> lineCoverage
= new HashMap<Integer, CoverageDetail>();
for (int methodNr = 0; methodNr < methods.length; methodNr++)
{
final MethodDescriptor method = methods[methodNr];
if (method.getBlockSizes() != null
&& method.getBlockMap() != null)
{
boolean[] methodCoverage = null;
if (coverage != null && coverage.m_coverage.length > methodNr)
{
methodCoverage = coverage.m_coverage[methodNr];
}
final int[][] map = method.getBlockMap();
for (int blockNr = 0;
blockNr < map.length; blockNr++)
{
final int[] blockLines = map[blockNr];
if (methodCoverage != null
&& methodCoverage.length > blockNr
&& methodCoverage[blockNr])
{
markCovered(lineCoverage, blockLines);
}
else
{
markNotCovered(lineCoverage, blockLines);
}
}
}
}
return lineCoverage;
}
/**
* Creates finding report entries out of the line info.
* @param lineCoverage map from line number to its coverage info.
* @return a list of finding items.
* @throws JAXBException the the object creation for the jaxb
* objects fails.
*/
private List<Item> createItemEntries (
final Map<Integer, CoverageDetail> lineCoverage)
throws JAXBException
{
final List<Item> itemList = new ArrayList<Item>();
for (Entry<Integer, CoverageDetail> entry : lineCoverage.entrySet())
{
final CoverageDetail c = entry.getValue();
final Item item = new ObjectFactory().createItem();
item.setOrigin(Origin.COVERAGE);
if (c.mNotVisitedBranches > 0)
{
final int branches
= c.mVisitedBranches + c.mNotVisitedBranches;
item.setCounter(
(EMMA_FULL_PERCENTAGE * c.mVisitedBranches)
/ branches);
}
else
{
item.setCounter(EMMA_FULL_PERCENTAGE);
}
item.setLine(entry.getKey());
item.setSeverity(Severity.COVERAGE);
item.setFindingType("coverage"); // FIXME: use type
itemList.add(item);
}
return itemList;
}
private void markCovered (Map<Integer, CoverageDetail> lineCoverage,
int[] lines)
{
for (int line : lines)
{
getLine(lineCoverage, line).mVisitedBranches++;
}
}
private void markNotCovered (Map<Integer, CoverageDetail> lineCoverage,
int[] lines)
{
for (int line : lines)
{
getLine(lineCoverage, line).mNotVisitedBranches++;
}
}
private CoverageDetail getLine (
Map<Integer, CoverageDetail> lineCoverage, int line)
{
CoverageDetail result = lineCoverage.get(line);
if (result == null)
{
result = new CoverageDetail();
lineCoverage.put(line, result);
}
return result;
}
private static class CoverageDetail
{
private int mNotVisitedBranches;
private int mVisitedBranches;
}
}