/** * Copyright (C) 2008-2010, Squale Project - http://www.squale.org * * This file is part of Squale. * * Squale is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of the * License, or any later version. * * Squale 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 Lesser General Public License * along with Squale. If not, see <http://www.gnu.org/licenses/>. */ package org.squale.squalix.tools.cobertura; import java.io.File; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.squale.jraf.commons.exception.JrafDaoException; import org.squale.squalecommon.daolayer.result.MeasureDAOImpl; import org.squale.squalecommon.enterpriselayer.businessobject.component.ClassBO; import org.squale.squalecommon.enterpriselayer.businessobject.component.MethodBO; import org.squale.squalecommon.enterpriselayer.businessobject.component.PackageBO; import org.squale.squalecommon.enterpriselayer.businessobject.result.cobertura.AbstractCoberturaMetricsBO; import org.squale.squalecommon.enterpriselayer.businessobject.result.cobertura.CoberturaClassMetricsBO; import org.squale.squalecommon.enterpriselayer.businessobject.result.cobertura.CoberturaMethodMetricsBO; import org.squale.squalecommon.enterpriselayer.businessobject.result.cobertura.CoberturaPackageMetricsBO; import org.squale.squalecommon.enterpriselayer.businessobject.result.cobertura.CoberturaProjectMetricsBO; import org.squale.squalix.tools.abstractgenerictask.AbstractGenericTask; import org.squale.squalix.util.parser.JavaParser; import org.squale.squalix.util.repository.ComponentRepository; /** * <p> * A coberturaTask object represents the concrete part of the task which is in charge of the code coverage metrics * computation and recovering. * </p> * <p> * It describes the degree to which the source code of a program has been tested. It is a form of testing that inspects * the code directly and is therefore a form of white box testing.<br /> * Please refer to <a href="http://en.wikipedia.org/wiki/Code_coverage">the Wikipedia definition</a> for more * information. * </p> * <p> * The coberturaTask in Squale has to : * <ul> * <li>launch Cobertura tool if not launched</li> * <li>recover the result files</li> * <li>parse the result files</li> * <li>push the parsed results into the DB</li> * </ul> * </p> */ public class CoberturaTask extends AbstractGenericTask { /** * Instance of MeasureDAO used during the processing */ private static final MeasureDAOImpl MEASURE_DAO = MeasureDAOImpl.getInstance(); /** Logger */ private static final Log LOGGER = LogFactory.getLog( CoberturaTask.class ); /** Cobertura Parser */ private CoberturaParser coberturaParser; /** Cobertura result file */ private CoberturaProjectMetricsBO parsedResults; /** Repository of component */ private ComponentRepository repository; /** Java parser */ private JavaParser jParser; /** * List of ClassMetrics */ private List<List<AbstractCoberturaMetricsBO>> classesMetrics = new ArrayList<List<AbstractCoberturaMetricsBO>>(); /** * List of sorted out methods */ private List<CoberturaMethodMetricsBO> sortedMethods = new ArrayList<CoberturaMethodMetricsBO>(); /** Default constructor */ public CoberturaTask() { /* * All task default constructors must give a name to a "mName attribute" while instantiating. This allows for * example automatic logging by the super class. */ mName = "CoberturaTask"; coberturaParser = new CoberturaParser(); jParser = new JavaParser( mProject ); } /** * <p> * Each class which subclasses the {@link AbstractGenericTask} must implement the * {@link AbstractGenericTask#parseResults(List)}. This method is automatically called in the * {@link AbstractGenericTask#execute()} method. * </p> * * @param results a {@link List} of {@link File}(s) that has to be parsed * @throws JrafDaoException is initially thrown by the persistResults method (if an error occurs while saving the * value in DB) */ public void parseResults( List<File> results ) throws JrafDaoException { // Logging to indicate that Squale has started CoberturaTask parsing LOGGER.info( new String( CoberturaMessages.getMessage( "cobertura.task.beginsParsing", mProject.getName() ) ) ); // Iterating the list to recover result file(s) parsedResults = new CoberturaProjectMetricsBO(); for ( Iterator<File> iterator = results.iterator(); iterator.hasNext(); ) { // Creating a File on each iteration File file = (File) iterator.next(); // If the file exists if ( file.exists() ) { LOGGER.info( new String( CoberturaMessages.getMessage( "cobertura.task.parsingFileName", file.getName() ) ) ); // Parsing the content of if it List<AbstractCoberturaMetricsBO> packageList = coberturaParser.parse( file ).getPackages(); for (AbstractCoberturaMetricsBO abstractCoberturaMetricsBO : packageList) { if(abstractCoberturaMetricsBO instanceof CoberturaPackageMetricsBO) { parsedResults.addPackage((CoberturaPackageMetricsBO)abstractCoberturaMetricsBO); } } } else { // Logging a "no file found" error and cancelling the task LOGGER.error( new String( CoberturaMessages.getMessage( "cobertura.task.noFileFound", file.getName() ) ) ); mStatus = FAILED; } } // Logging to indicate end of parsing LOGGER.info( new String( CoberturaMessages.getMessage( "cobertura.task.parsingSuccessfull", mProject.getName() ) ) ); this.extractProjectMetrics(); } /** * <p> * Extract the metrics associated to each level (Package, class, method) so as to save collections * </p> * * @throws JrafDaoException is thrown if an error occurs when manipulating the data */ private void extractProjectMetrics() throws JrafDaoException { LOGGER.info( new String( CoberturaMessages.getMessage( "cobertura.task.startExtracting", mProject.getName() ) ) ); // Instance of the repository which will be used repository = new ComponentRepository( mProject, getSession() ); // Setting name, audit and component parsedResults.setAudit( this.getAudit() ); parsedResults.setComponent( this.getProject() ); parsedResults.setTaskName( this.getName() ); // Persisting project level values MEASURE_DAO.create( getSession(), parsedResults ); LOGGER.info( new String( CoberturaMessages.getMessage( "cobertura.task.projectLevelPushed", mProject.getName() ) ) ); // Starting processing of package level metrics extractChildrenMetrics( parsedResults ); } /** * <p> * This method is used to extract and iterate the measurements of the package results * </p> * * @param pProjectMetrics the project level result that contains the package level measurements * @throws JrafDaoException is thrown if an error occurs when creating the values in DB or while persisting the * values */ public void extractChildrenMetrics( CoberturaProjectMetricsBO pProjectMetrics ) throws JrafDaoException { LOGGER.info( new String( CoberturaMessages.getMessage( "cobertura.task.packageLevelStarted", mProject.getName() ) ) ); // Getting the list of packages List<AbstractCoberturaMetricsBO> packagesResults = pProjectMetrics.getPackages(); // Iterating to get single package metrics and set the value of audit and reference properties List<AbstractCoberturaMetricsBO> listToRemove = new ArrayList<AbstractCoberturaMetricsBO>(); for ( Iterator<AbstractCoberturaMetricsBO> iterator = packagesResults.iterator(); iterator.hasNext(); ) { // Getting a package CoberturaPackageMetricsBO singlePackageMetrics = (CoberturaPackageMetricsBO) iterator.next(); // Creating the packageBO object thanks to the getPackage method of the JavaParser PackageBO packageBO = jParser.getPackage( singlePackageMetrics.getName() ); // Setting the audit reference singlePackageMetrics.setAudit( this.getAudit() ); // Setting the task reference singlePackageMetrics.setTaskName( this.getName() ); // Setting the mComponent property if(!singlePackageMetrics.getName().equals( "" )) { singlePackageMetrics.setComponent( repository.persisteComponent( packageBO ) ); } else { listToRemove.add( singlePackageMetrics ); } // Adding the classes related to the packages to the list for post-processing classesMetrics.add( singlePackageMetrics.getClasses() ); } // Remove all package which have a name equal to "", because they shouldn't be persit packagesResults.removeAll( listToRemove ); // Persisting the package level values MEASURE_DAO.saveAll( getSession(), packagesResults ); LOGGER.info( new String( CoberturaMessages.getMessage( "cobertura.task.packageLevelPushed", mProject.getName() ) ) ); this.extractClassMetrics( classesMetrics ); } /** * <p> * This method is used to extract and iterate the measurements of the class results * </p> * * @param pClassesMetrics List of classes associated to the packages * @throws JrafDaoException is thrown if an error occurs when creating the values in DB or while persisting the * values */ public void extractClassMetrics( List<List<AbstractCoberturaMetricsBO>> pClassesMetrics ) throws JrafDaoException { LOGGER.info( new String( CoberturaMessages.getMessage( "cobertura.task.classLevelStarted", mProject.getName() ) ) ); // Getting the list of classes List<AbstractCoberturaMetricsBO> classesResults = new ArrayList<AbstractCoberturaMetricsBO>(); // Getting the lists of classes one by one for ( List<AbstractCoberturaMetricsBO> classesList : pClassesMetrics ) { for ( AbstractCoberturaMetricsBO classMetricsBO : classesList ) { // Instance of classMetricsBO CoberturaClassMetricsBO singleClassMetrics = (CoberturaClassMetricsBO) classMetricsBO; // Creating the classBO object thanks to the getClass method of the JavaParser ClassBO classBO = jParser.getClass( singleClassMetrics.getName() ); // Setting the audit reference singleClassMetrics.setAudit( this.getAudit() ); // Setting the task reference singleClassMetrics.setTaskName( this.getName() ); // Setting the mComponent property singleClassMetrics.setComponent( repository.persisteComponent( classBO ) ); // Adding to the list classesResults.add( singleClassMetrics ); // Getting the list of associated methods List<AbstractCoberturaMetricsBO> unsortedMethodMetrics = singleClassMetrics.getMethods(); // Preparing the list of methods for ( AbstractCoberturaMetricsBO abstractCoberturaMetricsBO : unsortedMethodMetrics ) { // Instance of methodBO CoberturaMethodMetricsBO singleMethodMetrics = (CoberturaMethodMetricsBO) abstractCoberturaMetricsBO; // Checking if the method comes from an anonymous class or is an early init. If not executing // the statements if ( !this.isIdentifiable( singleClassMetrics ) && !this.isEarlyInit( singleMethodMetrics ) && !this.isIdentifiable( singleMethodMetrics ) ) { // Recombining the name of the method and its signature String originalMethodName = singleClassMetrics.getName() + '.' + jParser.getConstructorFromByte( singleMethodMetrics.getName(), singleClassMetrics.getName() ) + '(' + jParser.getSignatureFromBytecode( singleMethodMetrics.getSignature() ) + ')'; // Setting the name of the method to the original one singleMethodMetrics.setName( originalMethodName ); sortedMethods.add( singleMethodMetrics ); } } } } // Persisting the package level values MEASURE_DAO.saveAll( getSession(), classesResults ); this.extractMethodMetrics( sortedMethods ); LOGGER.info( new String( CoberturaMessages.getMessage( "cobertura.task.classLevelPushed", mProject.getName() ) ) ); } /** * <p> * This method is used to extract and iterate the measurements of method results * </p> * * @param pMethodMetrics List of sorted out CoberturaMetricsBO * @throws JrafDaoException is thrown if an error occurs when creating the values in DB or while persisting the * values */ public void extractMethodMetrics( List<CoberturaMethodMetricsBO> pMethodMetrics ) throws JrafDaoException { LOGGER.info( new String( CoberturaMessages.getMessage( "cobertura.task.methodLevelStarted", mProject.getName() ) ) ); // Getting the list of methods List<CoberturaMethodMetricsBO> methodToPush = new ArrayList<CoberturaMethodMetricsBO>(); // Getting the method for ( AbstractCoberturaMetricsBO abstractCoberturaMetricsBO : pMethodMetrics ) { // Instance of methodBO CoberturaMethodMetricsBO singleMethodMetrics = (CoberturaMethodMetricsBO) abstractCoberturaMetricsBO; // Setting the audit reference */ singleMethodMetrics.setAudit( this.getAudit() ); // Setting the task reference */ singleMethodMetrics.setTaskName( this.getName() ); // Creating the methodBO object thanks to the getMethod method of the JavaParser MethodBO methodBO = jParser.getMethod( singleMethodMetrics.getName(), "" ); // Setting the mComponent singleMethodMetrics.setComponent( repository.persisteComponent( methodBO ) ); // Checking if the component already exists in the map of components if ( null != repository.getComponent( methodBO ) ) { // Adding to the list used to collect the methods to push methodToPush.add( singleMethodMetrics ); } } // Persisting the method level values MEASURE_DAO.saveAll( getSession(), methodToPush ); LOGGER.info( new String( CoberturaMessages.getMessage( "cobertura.task.methodLevelPushed", mProject.getName() ) ) ); } /** * <p> * Before aggregating metrics and pushing them in DB one must check if a method or a class could be identified in * case of regular audits. As Squale purposes are to audit projects over a middle to long time period it is for now * not possible to integrate method from anonymous class or static class calls. * </p> * <p> * This method checks if the audited method comes from an anonymous class or is a static class call so as to decide * whether or not to save data in DB. * </p> * * @param pMetricBO the metrics that has to be checked * @return a boolean==true if the parameter comes from an anonymous origin such as an anonymous inner class */ public boolean isIdentifiable( AbstractCoberturaMetricsBO pMetricBO ) { boolean boo = false; /* if the name attribute of the parameter contains a $ symbol */ if ( pMetricBO.getName().contains( "$" ) ) { boo = true; } return boo; } /** * <p> * While compiling java source code sometimes early "inits" are done by the compiler so as to ensure that resources * are available (e.g for STATIC properties declared in a class). * </p> * <p> * In those cases the related byte-code is "'<'clinit'>'" (stands for "<'class init'>"). * </p> * * @param pMetricBO the metric name that has to be tested * @return a {@link Boolean}==true if the tested metric has an early init origin */ public boolean isEarlyInit( AbstractCoberturaMetricsBO pMetricBO ) { boolean boo = false; /* Checking if the pMetric name equals <clinit> */ if ( pMetricBO.getName().equals( "<clinit>" ) ) { boo = true; } return boo; } }