/**
* 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.javancss;
/*
* Some parts of the code are inspired by the MOJO javancss plug-in :
* http://mojo.codehaus.org/javancss-maven-plugin/
* Copyright 2004-2005 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javancss.Javancss;
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.AbstractComplexComponentBO;
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.component.ProjectBO;
import org.squale.squalecommon.enterpriselayer.businessobject.component.parameters.ListParameterBO;
import org.squale.squalecommon.enterpriselayer.businessobject.component.parameters.ParametersConstants;
import org.squale.squalecommon.enterpriselayer.businessobject.result.ErrorBO;
import org.squale.squalecommon.enterpriselayer.businessobject.result.javancss.JavancssClassMetricsBO;
import org.squale.squalecommon.enterpriselayer.businessobject.result.javancss.JavancssMethodMetricsBO;
import org.squale.squalecommon.enterpriselayer.businessobject.result.javancss.JavancssPackageMetricsBO;
import org.squale.squalecommon.enterpriselayer.businessobject.result.javancss.JavancssProjectMetricsBO;
import org.squale.squalix.core.AbstractTask;
import org.squale.squalix.core.TaskData;
import org.squale.squalix.core.TaskException;
import org.squale.squalix.core.exception.ConfigurationException;
import org.squale.squalix.util.buildpath.BuildProjectPath;
import org.squale.squalix.util.file.FileUtility;
import org.squale.squalix.util.methodBO.MethodBOHelper;
import org.squale.squalix.util.parser.JavaParser;
import org.squale.squalix.util.repository.ComponentRepository;
/**
* This Class is the task for the javancss tool.
*/
public class JavancssTask
extends AbstractTask
{
/** Logger */
private static final Log LOGGER = LogFactory.getLog( JavancssTask.class );
/** Javancss configuration */
private JavancssConfig configuration;
/** java extension file */
public static final String[] JAVA_FILE_EXTENSION = { ".java" };
/** java parser */
private JavaParser parser;
/** repository of component */
private ComponentRepository repository;
/** path to the project view */
private String viewPath;
/** list of sources */
private List sources;
/** list of sources file to parse (take into account inclusion and exclusion) */
private List includedFileNames = new ArrayList( 0 );
/** The javancss results */
private JavancssResult parsingResult;
/** Path of the .xml report */
private String outputFilename;
/** Number of classes */
private int numberOfClasses;
/** Map of classes results to persist */
private Map<String, JavancssClassMetricsBO> classMap = new HashMap<String, JavancssClassMetricsBO>();
/** Map of package results to persist */
private Map<String, JavancssPackageMetricsBO> packageMap = new HashMap<String, JavancssPackageMetricsBO>();
/** List of method results to persist */
private List<JavancssMethodMetricsBO> methodList = new ArrayList<JavancssMethodMetricsBO>();
/**
* Defaults constructor
*/
public JavancssTask()
{
mName = "JavancssTask";
}
/**
* Method call by the task launcher. It execute the task : initialization, execution of javancss and record of the
* result in the database
*
* @throws TaskException All exception happen during the task
*/
public void execute()
throws TaskException
{
try
{
initialize();
if ( mStatus == RUNNING )
{
LOGGER.info( JavancssMessages.getString( "javancss.initialize.end" ) );
doJavaNcss();
}
if ( mStatus == RUNNING )
{
LOGGER.info( JavancssMessages.getString( "javancss.toolexecution.end" ) );
persisteMeasures();
}
}
catch ( ConfigurationException e )
{
throw new TaskException( e );
}
catch ( JrafDaoException e )
{
throw new TaskException( e );
}
finally
{
// Deletion of the file result
if ( outputFilename != null )
{
File xmlResult = new File( outputFilename );
if ( xmlResult.exists() )
{
affectFileSystemSize( xmlResult, false );
xmlResult.delete();
}
}
}
}
/**
* Initialization of the Javanccs task. Create the parser and repository object. Get the configuration parameters
* sources and viewpath. Create the list of classes to analyze (take into account inclusion and exclusion).
*
* @throws ConfigurationException Exception happen if some element configuration (sources or viwpath) are missing.
*/
private void initialize()
throws ConfigurationException
{
parser = new JavaParser( mProject );
repository = new ComponentRepository( mProject, getSession() );
config();
fillKnownClasses();
sources =
(List) ( (ListParameterBO) mProject.getParameters().getParameters().get( ParametersConstants.SOURCES ) ).getParameters();
if ( sources == null )
{
String message =
JavancssMessages.getString( "javancss.exception.configuration.sources_not_found" )
+ ParametersConstants.SOURCES;
initError( message );
LOGGER.error( message );
mStatus = CANCELLED;
}
else
{
viewPath = (String) mData.getData( TaskData.VIEW_PATH );
if ( null == viewPath )
{
String message =
JavancssMessages.getString( "javancss.exception.configuration.viewpath_not_found" )
+ TaskData.VIEW_PATH;
initError( message );
LOGGER.error( message );
mStatus = CANCELLED;
}
else
{
includedFileNames =
FileUtility.getIncludedFiles(
viewPath,
BuildProjectPath.buildProjectPath( viewPath, sources ),
(ListParameterBO) mProject.getParameter( ParametersConstants.INCLUDED_PATTERNS ),
(ListParameterBO) mProject.getParameter( ParametersConstants.EXCLUDED_PATTERNS ),
(ListParameterBO) mProject.getParameter( ParametersConstants.EXCLUDED_DIRS ),
JAVA_FILE_EXTENSION );
}
}
}
/**
* This method do the configuration of javancss. That means get back the path for the result file.
*
* @throws ConfigurationException Exception happen if the xml configuration file is not found.
*/
private void config()
throws ConfigurationException
{
configuration = new JavancssConfig();
try
{
configuration.parse( new FileInputStream( "config/javancss-config.xml" ) );
outputFilename = configuration.getResultFilePath();
File outputFile = new File( outputFilename );
File ouputDirectory = outputFile.getParentFile();
if ( !ouputDirectory.exists() )
{
ouputDirectory.mkdir();
}
}
catch ( FileNotFoundException e )
{
String message = JavancssMessages.getString( "javancss.exception.configuration.xml_file_not_found" );
initError( message );
LOGGER.error( message );
mStatus = CANCELLED;
}
}
/**
* This method use the repository for fill the mknownClasses of the parser
*/
private void fillKnownClasses()
{
Set allClasses = ( (Map) repository.getClasses() ).keySet();
Iterator it = allClasses.iterator();
while ( it.hasNext() )
{
String pathClas = it.next().toString().replaceAll( "<=>", "." );
parser.addKnownClass( pathClas );
}
}
/**
* This methods launch the tool javancss and do the parsing of the result file.
*
* @throws TaskException Exception due to problem of result file creation/existence/reading
*/
private void doJavaNcss()
throws TaskException
{
String[] args = getCommandLineArgument();
String rcsHeader = JavancssMessages.getString( "javancss.rcsheader" );
Javancss lau = new Javancss( args, rcsHeader );
JavancssParser par = new JavancssParser();
try
{
InputStream inp = new FileInputStream( outputFilename );
parsingResult = par.parsing( inp );
parsingResult.getProjectMetrics().setLines( lau.getLOC() );
}
catch ( FileNotFoundException e )
{
String message = JavancssMessages.getString( "javancss.exception.result.file_not_exist" );
throw new TaskException( message );
}
}
/**
* This method create the option command line to use with javancss. It returns a table of string.
*
* @return The option command line to use with javancss
*/
private String[] getCommandLineArgument()
{
List argumentList = new ArrayList();
// creation of package level metrics
argumentList.add( "-package" );
// creation of classes level metrics
argumentList.add( "-object" );
// creation of methods level metrics
argumentList.add( "-function" );
// the kind of output is xml
argumentList.add( "-xml" );
// Creation of a file with the result
argumentList.add( "-out" );
// Path of the file to create
argumentList.add( outputFilename );
// Add of all the file to analyze
for ( int i = 0; i < includedFileNames.size(); i++ )
{
argumentList.add( new File( (String) includedFileNames.get( i ) ).getPath() );
}
return (String[]) argumentList.toArray( new String[argumentList.size()] );
}
/**
* This methods execute the recording of the result of the javancss tool in the database.
*
* @throws TaskException exception due to bad result from findFileName
* @throws JrafDaoException exception due to problem during the record in database
*/
private void persisteMeasures()
throws TaskException, JrafDaoException
{
// Completion of the package level BO and recording of new components
ArrayList packageResults = parsingResult.getPackageResult();
for ( int i = 0; i < packageResults.size(); i++ )
{
JavancssPackageMetricsBO packageMetrics = (JavancssPackageMetricsBO) packageResults.get( i );
//Test if the package name is not "." (See issue #217)
if ( !packageMetrics.getComponentName().equals( "." ) )
{
PackageBO packBO = parser.getPackage( packageMetrics.getComponentName() );
packageMetrics.setAudit( getAudit() );
packageMetrics.setComponent( repository.persisteComponent( packBO ) );
packageMetrics.setTaskName( getName() );
packageMap.put( repository.buildKey( packageMetrics.getComponent() ), packageMetrics );
}
}
// Completion of classes level results and creation of new components
completeClassesMeasures();
// Completion of methods level results and creation of new components
completeMethodsMeasures();
MeasureDAOImpl dao = MeasureDAOImpl.getInstance();
// Save method results
dao.saveAll( getSession(), methodList );
// Completion and recording of project level results
JavancssProjectMetricsBO projectResults = parsingResult.getProjectMetrics();
projectResults.setCommentsLines( projectResults.getJavadocsLines().intValue()
+ projectResults.getMultiCommentsLines().intValue() + projectResults.getSingleCommentsLines().intValue() );
projectResults.setLoc( projectResults.getCommentsLines().intValue() + projectResults.getNcss().intValue() );
projectResults.setClassNumber( numberOfClasses );
projectResults.setMethodNumber( parsingResult.getMethodsResult().size() );
projectResults.setAudit( getAudit() );
projectResults.setComponent( getProject() );
projectResults.setTaskName( getName() );
dao.create( getSession(), projectResults );
// Save package level results
dao.saveAll( getSession(), packageMap.values() );
// Save class level results
dao.saveAll( getSession(), classMap.values() );
LOGGER.info( JavancssMessages.getString( "javancss.persistence.end" ) );
}
/**
* This method complete the classBO linked to the measure.
*
* @throws TaskException Exception Happen during the search of the path of the class
* @throws JrafDaoException Exception happen during the the persistence
*/
private void completeClassesMeasures()
throws TaskException, JrafDaoException
{
ArrayList classesResults = parsingResult.getClassesResult();
List pathFile = FileUtility.cutPath( includedFileNames, viewPath );
for ( int i = 0; i < classesResults.size(); i++ )
{
JavancssClassMetricsBO classesMetrics = (JavancssClassMetricsBO) classesResults.get( i );
String name = classesMetrics.getComponentName();
ClassBO classBO = parser.getClass( name );
// Searching of the complete path of the class
String nameToFind = name.replace( '.', File.separatorChar ) + JAVA_FILE_EXTENSION[0];
List result = FileUtility.findFileName( pathFile, nameToFind );
if ( result.size() <= 1 )
{
if ( result.size() == 1 )
{
classBO.setFileName( (String) result.get( 0 ) );
}
else
{
String message = JavancssMessages.getString( "javancss.info.findfilename.noMatch", nameToFind );
LOGGER.info( message );
initError( message, ErrorBO.CRITICITY_LOW );
}
parser.addKnownClass( name );
classesMetrics.setAudit( getAudit() );
classesMetrics.setComponent( repository.persisteComponent( classBO ) );
classesMetrics.setTaskName( getName() );
numberOfClasses = numberOfClasses + classesMetrics.getClasses().intValue() + 1;
classMap.put( repository.buildKey( classesMetrics.getComponent() ), classesMetrics );
}
else
{
String message =
JavancssMessages.getString( "javancss.exception.findfilename.tooManyMatch", nameToFind );
LOGGER.error( message );
throw new TaskException( message );
}
}
}
/**
* This method do the persistence of the method level metric for javancss results
*
* @throws JrafDaoException Exception happen during the persistence
* @throws TaskException exception happened during the persistence in the base
*/
private void completeMethodsMeasures()
throws JrafDaoException, TaskException
{
ArrayList methodsResults = parsingResult.getMethodsResult();
Collection allMethods = repository.getMethods().values();
for ( int i = 0; i < methodsResults.size(); i++ )
{
JavancssMethodMetricsBO methodMetrics = (JavancssMethodMetricsBO) methodsResults.get( i );
String methPathName = methodMetrics.getComponentName();
/*
* If the method give by javancss is inside an anonymous class, the method results is not persist. But the
* CCN of the method is take into account in maxVg and sumVg ; and this method is take into account in the
* total number of methods
*/
if ( !methPathName.contains( "$" ) )
{
ifMethodIsNotInsideAnonym( methPathName, methodMetrics, allMethods );
}
else
{
ifMethodIsInsideAnonym( methPathName, methodMetrics );
}
}
}
/**
* This method do the persistence of the measure when the method linked to this measure is not inside an anonymous
* class
*
* @param methPathName The path name of the method link to the metric to persist
* @param methodMetrics The metrics bo to persist
* @param allMethods The repository of all existent methods
* @throws JrafDaoException exception happened during the persistence in the base
*/
private void ifMethodIsNotInsideAnonym( String methPathName, JavancssMethodMetricsBO methodMetrics,
Collection allMethods )
throws JrafDaoException
{
ArrayList methodMatch = new ArrayList();
MethodBO methBO = parser.getMethod( methPathName, "" );
MethodBO repoMethBO = (MethodBO) repository.getComponent( methBO );
if ( repoMethBO == null )
{
// Searching of the complete path name of the classes which contain the method
AbstractComplexComponentBO compoBo = (AbstractComplexComponentBO) methBO.getParent();
while ( ( compoBo.getParent() instanceof ClassBO ) )
{
compoBo = compoBo.getParent();
}
ClassBO classBo = (ClassBO) repository.getComponent( compoBo );
methBO.setLongFileName( classBo.getFileName() );
( (ClassBO) methBO.getParent() ).setFileName( classBo.getFileName() );
// As javancss doesn't give the full name for arguments type, we search correspondence between the
// method name give by javancss and those we have in the database.
// If there is no match found, then we don't modify methBO, and we persist it.
methodMatch = MethodBOHelper.searchMethodBO( methBO, allMethods, repository );
if ( methodMatch.size() == 1 )
{
methBO = (MethodBO) methodMatch.get( 0 );
}
else if ( methodMatch.size() > 1 )
{
String message = JavancssMessages.getString( "javancss.info.method.manyMatch", methBO.getName() );
LOGGER.info( message );
initError( message, ErrorBO.CRITICITY_LOW );
}
}
else
{
methBO = repoMethBO;
}
methodMetrics.setAudit( getAudit() );
methodMetrics.setComponent( methBO );
methodMetrics.setTaskName( getName() );
int ccn = methodMetrics.getCcn().intValue();
AbstractComplexComponentBO compo = methodMetrics.getComponent().getParent();
computeClass( ccn, compo );
methodMetrics.setComponent( repository.persisteComponent( methBO ) );
methodList.add( methodMetrics );
}
/**
* This method do the persistence of the measure when the method linked to the measure is inside an anonymous class
*
* @param methPathName The path name of the method link to the metric to persist
* @param methodMetrics The metrics BO to use
*/
private void ifMethodIsInsideAnonym( String methPathName, JavancssMethodMetricsBO methodMetrics )
{
String pathName = methPathName;
int ccn = methodMetrics.getCcn().intValue();
// we cut the pathName of the anonymous class in order to get the class which contains the anonymous class
pathName = pathName.substring( 0, pathName.lastIndexOf( '$' ) );
ClassBO repoclazzBO = null;
do
{
pathName = pathName.substring( 0, pathName.lastIndexOf( "." ) );
ClassBO clazzBO = parser.getClass( pathName );
repoclazzBO = (ClassBO) repository.getComponent( clazzBO );
}
while ( repoclazzBO == null && pathName.contains( "." ) );
computeClass( ccn, repoclazzBO );
}
/**
* Compute of the maxVg and sumVg for the class level result
*
* @param component The JavancssMethodMetricsBO associate to the method parent of the class
* @param ccn The cyclomatic complexity of the method
*/
private void computeClass( int ccn, AbstractComplexComponentBO component )
{
AbstractComplexComponentBO compo = component;
while ( !( compo.getParent() instanceof PackageBO ) && !( compo.getParent() instanceof ProjectBO) )
{
compo = compo.getParent();
}
JavancssClassMetricsBO metricClassCompo = classMap.get( repository.buildKey( compo ) );
if ( metricClassCompo.getSumVg() == null )
{
metricClassCompo.setSumVg( ccn );
metricClassCompo.setMaxVg( ccn );
}
else
{
metricClassCompo.setSumVg( metricClassCompo.getSumVg().intValue() + ccn );
if ( ccn > metricClassCompo.getMaxVg().intValue() )
{
metricClassCompo.setMaxVg( ccn );
}
}
computePackage( ccn, compo );
}
/**
* Compute of the maxVg and sumVg for the package level result
*
* @param ccn The ccn value to test
* @param component The parent ClassBO
*/
private void computePackage( int ccn, AbstractComplexComponentBO component )
{
if (component.getParent() instanceof PackageBO)
{
PackageBO compo = (PackageBO) component.getParent();
JavancssPackageMetricsBO metricPackageCompo = packageMap.get( repository.buildKey( compo ) );
if ( metricPackageCompo.getSumVg() == null )
{
metricPackageCompo.setSumVg( ccn );
metricPackageCompo.setMaxVg( ccn );
}
else
{
metricPackageCompo.setSumVg( metricPackageCompo.getSumVg().intValue() + ccn );
if ( ccn > metricPackageCompo.getMaxVg().intValue() )
{
metricPackageCompo.setMaxVg( ccn );
}
}
}
computeProject( ccn );
}
/**
* Compute of the maxVg and sumVg for the project level result
*
* @param ccn The new value of ccn to test
*/
private void computeProject( int ccn )
{
JavancssProjectMetricsBO projectMetricResults = parsingResult.getProjectMetrics();
if ( projectMetricResults.getSumVg() == null )
{
projectMetricResults.setSumVg( ccn );
projectMetricResults.setMaxVg( ccn );
}
else
{
projectMetricResults.setSumVg( projectMetricResults.getSumVg().intValue() + ccn );
if ( ccn > projectMetricResults.getMaxVg().intValue() )
{
projectMetricResults.setMaxVg( ccn );
}
}
}
}