/**
* 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.mccabe;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.squale.squalecommon.enterpriselayer.businessobject.result.ErrorBO;
import org.squale.squalix.core.AbstractTask;
import org.squale.squalix.core.TaskData;
import org.squale.squalix.core.TaskException;
import org.squale.squalix.util.file.FileUtility;
import org.squale.squalix.util.process.ProcessErrorHandler;
import org.squale.squalix.util.process.ProcessManager;
import org.squale.squalix.util.process.ProcessOutputHandler;
/**
* Ex�cute l'analyse McCabe. L'environnement McCabe doit �tre correctement initialis� avant le lancement de la t�che, ou
* la commande "cli" appel�e doit pointer vers un script qui le met en place.<br>
* <br>
* Il est important que la g�n�ration des rapports d�bute par celle du niveau m�thode, afin de pouvoir correctement
* cr�er les composants ClassBO si n�cessaire.<br>
* L'ordre des rapports dans le configuration est resp�ct�.
*/
public abstract class AbstractMcCabeTask
extends AbstractTask
implements ProcessErrorHandler, ProcessOutputHandler
{
/**
* Configuration de l'outil d'analyse
*/
protected McCabeConfiguration mConfiguration;
/** Le processManager * */
protected ProcessManager mProcess;
/**
* Logger
*/
private static final Log LOGGER = LogFactory.getLog( AbstractMcCabeTask.class );
/** Buffer pour �crire les erreurs */
protected BufferedWriter mErrorWriter;
/** Nom du composant courant que McCabe parse (sert pour la remont�e des warning) */
protected String mCurrentComponent = "";
/**
* L'analyse compl�te consiste en :
* <ul>
* <li>Lancement du parsing sur l'application</li>
* <li>Cr�ation des rapports</li>
* <li>Cr�ation des beans � partir des rapports</li>
* <li>Transformation des beans en beans persistants</li>
* <li>Persistance des beans</li>
* </ul>
* A la fin de l'analyse on effectue les op�rations de cloture de tache
*
* @throws TaskException Si un probl�me d'ex�cution appara�t.
*/
public void execute()
throws TaskException
{
try
{
initialize();
LOGGER.info( McCabeMessages.getString( "logs.analyzing" ) + mProject.getParent().getName() + " - "
+ mProject.getName() );
int resultParse = parseSource();
if ( 0 == resultParse )
{
// Si le parsing s'est bien d�roul�, on g�n�re les rapports
// Creation de la base de rapports
createSpcFile( mConfiguration );
String reportName = null;
for ( int i = 0; i < mConfiguration.getReports().size(); i++ )
{
// Pour chaque nom de rapport configur�s dans le fichier de configuration
// McCabe, on va le cr�er, le parser et le mapper sur les objets
reportName = (String) mConfiguration.getReports().get( i );
createReport( reportName );
}
// On parse maintenant les rapports
for ( int i = 0; i < mConfiguration.getReports().size(); i++ )
{
// Pour chaque nom de rapport configur�s dans le fichier de configuration
// McCabe, on va le cr�er, le parser et le mapper sur les objets
reportName = (String) mConfiguration.getReports().get( i );
parseReport( reportName );
}
// Une fois que tous les rapports ont �t� g�n�r�s et pars�s,
// on peut g�n�rer les r�sultats de niveau projet
persistProjectResult();
}
// positionne les donn�es sur la taille du file System
affectFileSystemSize( mConfiguration.getSubWorkspace(), true );
// Lance les op�rations de cloture de la t�che
FileUtility.deleteRecursively( mConfiguration.getSubWorkspace() );
if ( 0 != resultParse )
{
// Si le parsing ne s'est pas bien d�roul�, on lance une exception
throw new Exception( McCabeMessages.getString( "exception.parsing_error" ) );
}
if ( mErrorWriter != null )
{
mErrorWriter.close();
}
}
catch ( Exception e )
{
throw new TaskException( e );
}
}
/**
* Pr�pare l'environnement d'ex�cution de l'analyse :
* <ul>
* <li>Cr�ation du dossier destination des r�sultats du parsing</li>
* <li>Cr�ation du fichier config.pcf</li>
* </ul>
*
* @exception Exception si un probleme d'initialisation apparait
*/
protected void initialize()
throws Exception
{
// On r�cup�re la configuration du module McCabe, personnalis�e
// avec les param�tres du projet
mConfiguration =
McCabeConfiguration.build( mProject, McCabeMessages.getString( "configuration.file" ), getData() );
try
{
// On tente de le cr�er si il n'existe pas
mConfiguration.getErrorLogger().getParentFile().mkdirs();
mErrorWriter = new BufferedWriter( new FileWriter( mConfiguration.getErrorLogger() ) );
}
catch ( IOException ioe )
{
// On log l'erreur
LOGGER.warn( "Erreur sur le fichier de log des erreurs : "
+ mConfiguration.getErrorLogger().getAbsolutePath() );
LOGGER.warn( ioe );
}
File workspace = mConfiguration.getWorkspace();
if ( !workspace.exists() || !workspace.isDirectory() || !workspace.canWrite() || !workspace.canRead() )
{
// On va v�rifier que le workspace est disponible
throw new Exception( McCabeMessages.getString( "exception.no_workspace" ) );
}
// On cr�e le fichier de description de l'analyse dans le workspace
McCabePCFFile pcfFile = new McCabePCFFile( mConfiguration, getData() );
createProjectConfigurationFile( pcfFile );
// On cr�e une instance de persisteur et du template utilisant la session d�j� ouverte
setParser();
setPersistor();
LOGGER.info( McCabeMessages.getString( "logs.initialized" ) + mProject.getParent().getName() + " - "
+ mProject.getName() );
}
/**
* Cr�ation du fichier de configuration McCabe Ce fichier de configuration contient la liste des fichiers
*
* @param pFile fichier
* @throws Exception si erreur
*/
protected void createProjectConfigurationFile( McCabePCFFile pFile )
throws Exception
{
pFile.build();
// On copie le PCF sous le nom <projet>.pcf si le nombre de fichiers .pcf dans le workspace
// est < 10
File backupDir =
new File( mConfiguration.getWorkspace() + File.separator + McCabeMessages.getString( "pcf.log.dir" ) );
backupDir.mkdirs();
File[] backupDirFiles = backupDir.listFiles();
if ( backupDirFiles != null )
{
// On tri selon le nom du fichier d�croissant pour ne pas �craser les fichiers qui porte sur le
// m�me projet.
List files = Arrays.asList( backupDirFiles );
Collections.sort( files );
Collections.reverse( files );
// On renomme tous les fichiers avec l'extension ext+1
String fileName;
String newFileName;
File backUpFile = null;
int ext;
for ( int i = 0; i < files.size(); i++ )
{
fileName = ( (File) files.get( i ) ).getName();
// Si un fichier porte l'extension max, on le supprime
if ( fileName.matches( ".*\\." + ( McCabeConfiguration.MAX_PCF_SAVED - 1 ) ) )
{
( (File) files.get( i ) ).delete();
}
else
{
ext = 0;
// On r�cup�re l'extension
int lastDot = fileName.lastIndexOf( "." );
if ( lastDot > 0 )
{
try
{
ext = Integer.parseInt( fileName.substring( lastDot + 1 ) ) + 1;
// On renomme le fichier
newFileName = fileName.substring( 0, lastDot + 1 ) + ext;
( (File) files.get( i ) ).renameTo( new File(
backupDirFiles[i].getParentFile().getAbsoluteFile()
+ File.separator + newFileName ) );
}
catch ( NumberFormatException nbfe )
{
// On renommera le fichier avec l'extension 1 (pour ne pas �craser celui
// qui existe)
backUpFile = (File) files.get( i );
}
}
}
}
// On copie le fichier pcf dans le r�pertoire de sauvegarde
String backupName = backupDir.getAbsolutePath() + File.separator + mProject.getName() + ".pcf";
// On renomme le fichier extension.1 si il existe
if ( backUpFile != null )
{
backUpFile.renameTo( new File( backUpFile.getAbsolutePath() + ".1" ) );
}
FileUtility.copyFile( pFile.getPcfFile(), new File( backupName ) );
}
}
/**
* Cr�e le fichier contenant le chemin vers les mod�les de rapports McCabe et celui avec les param�tres sp�ciaux
* utilisateurs.
*
* @param pConfiguration configuration � utiliser.
* @throws IOException Si un probl�me d'�criture du fichier appara�t.
*/
protected void createSpcFile( final McCabeConfiguration pConfiguration )
throws IOException
{
String spcFileName =
pConfiguration.getSubWorkspace().getAbsolutePath() + File.separator
+ McCabeMessages.getString( "reports_db.name" );
BufferedWriter bw = new BufferedWriter( new FileWriter( spcFileName ) );
bw.write( McCabeMessages.getString( "spc.header" ) );
bw.write( pConfiguration.getReportsPath().getAbsolutePath() );
bw.close();
String userFileName =
pConfiguration.getSubWorkspace().getAbsolutePath() + File.separator
+ McCabeMessages.getString( "user_def.name" );
bw = new BufferedWriter( new FileWriter( userFileName ) );
bw.write( McCabeMessages.getString( "file.user.def.header" ) );
bw.write( pConfiguration.getReposDir().getAbsolutePath() );
bw.newLine();
bw.close();
}
/**
* Parse les fichiers sources afin d'en extraire les m�triques.
*
* @return le r�sultat du process de parsing.
* @throws Exception si un probl�me de parsing appara�t.
*/
protected int parseSource()
throws Exception
{
// Execute le parsing des sources avec McCabe
String[] parseCommand = new String[mConfiguration.getParserCommand().length + 1];
parseCommand[0] = mConfiguration.getCliCommand();
for ( int i = 1; i < parseCommand.length; i++ )
{
// On met en forme la liste des param�tres pour la passer au process
parseCommand[i] = mConfiguration.getParserCommand()[i - 1];
}
LOGGER.info( McCabeMessages.getString( "logs.running_parsing", new Object[] { mProject.getParent().getName(),
mProject.getName() } ) );
mProcess = createProcessManager( parseCommand, mConfiguration.getSubWorkspace(), mConfiguration.getLogger() );
// On veut g�rer les informations lanc�es par le processus en sortie
mProcess.setOutputHandler( this );
// On cherche � avoir un comportement synchrone pour �tre s�r de ne pas
// avoir un �tat des donn�es incoh�rent
int resultParse = mProcess.startProcess( this );
LOGGER.info( McCabeMessages.getString( "logs.return_parsing", new Object[] { mProject.getParent().getName(),
mProject.getName(), new Integer( resultParse ) } ) );
return resultParse;
}
/**
* G�n�re un rapport de m�triques McCabe.
*
* @param pReport le nom de rapport � g�n�rer.
* @throws Exception si un probl�me d'ex�cution appara�t.
*/
protected void createReport( final String pReport )
throws Exception
{
LOGGER.info( McCabeMessages.getString( "logs.create.report", pReport ) );
// Lancement de la cr�ation de tous les rapports
String reportFileName = computeReportFileName( pReport );
ArrayList params = new ArrayList();
params.add( mConfiguration.getCliCommand() );
String param = null;
for ( int j = 0; j < mConfiguration.getMetricsCommand().length; j++ )
{
// Remplacer les param�tres configurables (%...%), ce sont des param�tres
// qui remplacent des cl�s dans les param�tres de configuration
// des analyses McCabe (classpath pour Java,...)
param =
mConfiguration.getMetricsCommand()[j].replaceAll(
McCabeMessages.getString( "key.substition.report_name" ),
pReport );
param = param.replaceAll( McCabeMessages.getString( "key.substition.report_out" ), reportFileName );
params.add( param );
}
String type[] = new String[0];
String modifiedParams[] = (String[]) params.toArray( type );
mProcess = createProcessManager( modifiedParams, mConfiguration.getSubWorkspace(), mConfiguration.getLogger() );
// On veut g�rer les informations lanc�es par le processus en sortie
mProcess.setOutputHandler( this );
int resultMetrics = mProcess.startProcess( this );
if ( resultMetrics != 0 )
{
throw new McCabeException( McCabeMessages.getString( "error.processing" ) + " #" + resultMetrics );
}
}
/**
* Analyse un rapport de m�triques McCabe
*
* @param pReport rapport � parser
* @throws Exception si erreur
*/
protected abstract void parseReport( final String pReport )
throws Exception;
/**
* D�termination du fichier de rapport
*
* @param pReport rapport
* @return nom complet du rapport
* @throws IOException si erreur
*/
protected String computeReportFileName( final String pReport )
throws IOException
{
String reportFileName = pReport + McCabeMessages.getString( "reports_extension" );
reportFileName = mConfiguration.getSubWorkspace().getCanonicalPath() + File.separator + reportFileName;
return reportFileName;
}
/**
* @see org.squale.squalix.util.process.ProcessErrorHandler#processError(java.lang.String)
*/
public void processError( String pErrorMessage )
{
// cette m�thode n'est effectu� que si le message n'est pas � ignorer
if ( !mConfiguration.ignoreMsg( pErrorMessage ) )
{
LOGGER.error( McCabeMessages.getString( "logs.error.tools_error" ) + pErrorMessage );
// Certaines erreurs sonst connues, on va alors essayer de les mapper avec
// un message plus explicite
ErrorBO error = new ErrorBO();
error.setInitialMessage( pErrorMessage );
// On modifie le message en rempla�ant par un message plus explicite si il est d�fini
error.setMessage( mConfiguration.getReplacingMsg( pErrorMessage ) );
// sinon on met un message g�n�rique
if ( pErrorMessage.equals( error.getMessage() ) )
{
error.setMessage( McCabeMessages.getString( "error.processing" ) );
}
error.setLevel( ErrorBO.CRITICITY_FATAL );
if ( !mConfiguration.ignoreMsg( pErrorMessage ) && !mConfiguration.changeErrorMsgToWarning( pErrorMessage ) )
{
mErrors.add( error );
}
// Et on ins�re la cha�ne dans le fichir de log des erreurs si le buffer a �t� cr�e
if ( mErrorWriter != null )
{
try
{
mErrorWriter.write( pErrorMessage + "\n" );
}
catch ( IOException e )
{
// On log juste l'erreur
LOGGER.warn( e );
}
}
}
}
/**
* Ajoute la configuration � la t�che.
*
* @param pConfiguration la configuration � ajouter.
* @roseuid 42DE1C2E01CC
*/
public void setConfiguration( McCabeConfiguration pConfiguration )
{
mConfiguration = pConfiguration;
}
/**
* R�cup�re la configuration.
*
* @return la configuration
*/
public McCabeConfiguration getConfiguration()
{
return mConfiguration;
}
/**
* Cr�e le ProcessManager. On ne fait pas de new mais un set pour impl�menter le pattern IOC pour pouvoir tester sur
* un environnement windows
*
* @param pArguments arguments
* @param pDirectory r�pertoire de lancement
* @param pLogger le logger
* @return le ProcessManager normal
*/
protected ProcessManager createProcessManager( String[] pArguments, File pDirectory, File pLogger )
{
return new ProcessManager( pArguments, null, pDirectory, pLogger );
}
/**
* Modifie le parser.
*/
public abstract void setParser();
/**
* Positionne la classe de persistance.
*/
protected abstract void setPersistor()
throws Exception;
/**
* Calcule et enregistre les m�triques McCabe de niveau projet.
*/
public abstract void persistProjectResult();
/**
* @see org.squale.squalix.util.process.ProcessOutputHandler#processOutput(java.lang.String)
*/
public void processOutput( String pOutputLine )
{
// On filtre une erreur connue "Can't create directory '/OUTILS/McCabe/current/repos'."
// qui ne g�ne en rien le bon d�roulement de McCabe.
if ( mConfiguration != null && !mConfiguration.ignoreMsg( pOutputLine ) )
{
// On ajoute le nom du fichier au message s'il le faut
mConfiguration.addFileNameToMsg( pOutputLine, mCurrentComponent );
// CAS ERREUR
if ( pOutputLine.startsWith( "ERR" ) )
{
// On teste si le message d'erreur ne fait pas parti des messages � consid�rer comme warning
if ( mConfiguration.changeErrorMsgToWarning( pOutputLine ) )
{
// On log un warning
initError( pOutputLine, ErrorBO.CRITICITY_WARNING );
}
else
{
// Il s'agit d'une erreur
initError( pOutputLine, ErrorBO.CRITICITY_FATAL );
mStatus = FAILED;
}
// CAS WARNING
}
else if ( pOutputLine.startsWith( "WARN" ) )
{
initError( pOutputLine, ErrorBO.CRITICITY_WARNING );
}
else if ( pOutputLine.matches( "cli: Processing " + (String) mData.getData( TaskData.VIEW_PATH ) + ".*" ) )
{
// On r�cup�re le nom du fichier en coupant la cha�ne du d�but et le viewPath
mCurrentComponent =
pOutputLine.replaceFirst( "cli: Processing " + (String) mData.getData( TaskData.VIEW_PATH ), "" );
}
// On ne r�cup�re pas les autres traces
}
}
}