/** * 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.rsm; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.StringTokenizer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.squale.jraf.commons.exception.JrafDaoException; import org.squale.jraf.spi.persistence.ISession; import org.squale.squalecommon.daolayer.result.MeasureDAOImpl; import org.squale.squalecommon.enterpriselayer.businessobject.component.AuditBO; import org.squale.squalecommon.enterpriselayer.businessobject.component.ProjectBO; import org.squale.squalecommon.enterpriselayer.businessobject.result.rsm.RSMClassMetricsBO; import org.squale.squalecommon.enterpriselayer.businessobject.result.rsm.RSMMethodMetricsBO; import org.squale.squalecommon.enterpriselayer.businessobject.result.rsm.RSMProjectMetricsBO; import org.squale.squalix.core.TaskData; import org.squale.squalix.util.csv.CSVParser; import org.squale.squalix.util.parser.LanguageParser; import org.squale.squalix.util.repository.ComponentRepository; /** * Objet charg� de faire persister les r�sultats RSM */ public class RSMPersistor { /** Le template pour le rapport de classe */ private String mClassTemplate = "csv.template.class"; /** Le template pour le rapport de classe */ private String mMethodTemplate = "csv.template.method"; /** * Nombre de m�thodes */ private int mNumberOfMethods = 0; /** * Nombre de classes */ private int mNumberOfClasses = 0; /** * Nombre de lignes de commentaires sur le projet */ private int mComments = 0; /** * Nombre de lignes de commentaires sur le projet */ private int mSLOC = 0; /** le nom de la tache r�el, pour diff�rencier java et cpp */ private String mTaskName; /** * Configuration */ private RSMConfiguration mConfiguration; /** * Chemin du fichier � parser */ private String mReportFileName = null; /** * Logger */ private static final Log LOGGER = LogFactory.getLog( RSMPersistor.class ); /** * Audit durant lequel l'analyse est effectu�e */ private AuditBO mAudit = null; /** * Projet sur lequel est r�alis�e l'analyse. */ private ProjectBO mProject = null; /** * Session Persistance */ private ISession mSession = null; /** les param�tres temporaires */ private TaskData mDatas; /** le parser CSV */ private CSVParser mParser; /** l'adaptateur permettant de relier les r�sultats RSM au composants associ�s */ private RSMAdaptator mAdaptator; /** pour faciliter le stockage */ private ComponentRepository mRepository; /** Le marqueur pour pr�processer le rapport g�n�r� et en extraire les m�triques de classes */ private final static String CLASS_LOCATOR = "Class,"; /** Le marqueur pour pr�processer le rapport g�n�r� et en extraire les m�triques de m�thodes */ private final static String METHOD_LOCATOR = "Function,"; /** Marqueur pour signaler la fin de la zone doublon */ private final static String FILE_LOCATOR = "File,"; /** Marqueur pour signaler le d�but de la zone doublon */ private final static String STOP_WRITE = "ProjectFunctionMetrics"; /** Marqueur pour signaler la fin de la zone doublon */ private final static String RESTART_WRITE = "ProjectClassMetrics"; /** * Cha�ne contenant l'expression r�guli�re permettant de effacer les param�tres 3 � 5 de la ligne m�thode */ private String mREGEXPREMOVEPARAMS = ",[^,]*\\([^,]*\\),[^,]*,[^,]*"; /** * Cha�ne contenant l'expression r�guli�re permettant de effacer les param�tres function point attach� aux champs LOC, ELOC, LLOC */ private String mREGEXPREMOVEFUNCTIONPOINT = ",([^,]*)/[^,]*"; /** * Constructeur. * * @param pConfiguration configuration du framework. * @param pAudit audit encadrant l'ex�cution. * @param pSession la session de persistance utilis�e par la t�che. * @param pDatas la liste des param�tres temporaires du projet * @param pTaskName le nom de la tache (pour diff�rencier java et cpp) * @param pLanguageParser le parser de language pour persister les donn�es * @throws JrafDaoException si une session de peristance ne peut �tre cr��e. */ public RSMPersistor( final RSMConfiguration pConfiguration, final AuditBO pAudit, final ISession pSession, TaskData pDatas, String pTaskName, LanguageParser pLanguageParser ) throws JrafDaoException { mSession = pSession; mConfiguration = pConfiguration; mAudit = pAudit; mDatas = pDatas; mProject = pConfiguration.getProject(); mRepository = new ComponentRepository( mProject, mSession ); mTaskName = pTaskName; mAdaptator = new RSMAdaptator( pLanguageParser, mRepository ); } /** * Parse le rapport pour obtenir tous les m�triques * * @param pFileName chemin du fichier rapport. * @param pDatas les donn�es * @throws Exception si un probl�me de parsing apparait. * @return le nombre de m�triques */ public int parseReport( final String pFileName, TaskData pDatas ) throws Exception { mReportFileName = pFileName; LOGGER.info( RSMMessages.getString( "logs.debug.report_parsing_class" ) + mReportFileName ); mParser = new CSVParser( RSMMessages.getString( "csv.config.file" ) ); // effecue les diff�rents pr�processing managePreProcess( pFileName ); int nbClassResults = parseReportForClassMetrics( mConfiguration.getClassReportPath() ); int nbMethodsResults = parseMethodReport( mConfiguration.getMethodsReportPath(), pDatas ); return nbClassResults + nbMethodsResults; } /** * Effectur les diff�rents pr�process n�cessaires * * @param pFileName le nom du fichier g�n�r� par RSM * @throws IOException en cas de problemes lors du pr�-processing */ private void managePreProcess( String pFileName ) throws IOException { // On effectue un pr�processing du fichier r�sultat car le format CSV renvoy� n'est pas vraiment du CSV, // Il y a des lignes suppl�mentaires et des informations g�nantes pour le parsing eraseDouble( pFileName, mConfiguration.getAuxFile() ); preProcess( mConfiguration.getClassReportPath(), mConfiguration.getAuxFile(), CLASS_LOCATOR ); preProcess( mConfiguration.getMethodsReportPath(), mConfiguration.getAuxFile(), METHOD_LOCATOR ); } /** * @param pCSVOutputFile nom du fichier pars� pour le mettre au format CSV correct * @param pCSVOutputFileAux nom du fichier auxilliaire utilis� pour supprimer les doublons * @param pMarker la chaine rep�re * @throws IOException en cas de problemes avec le pr�processing du fichier */ private void preProcess( String pCSVOutputFile, String pCSVOutputFileAux, String pMarker ) throws IOException { // Ecrit a partir du deuxi�me fichier File f = new File( pCSVOutputFile ); BufferedWriter bw = new BufferedWriter( new FileWriter( pCSVOutputFile ) ); BufferedReader br = new BufferedReader( new FileReader( pCSVOutputFileAux ) ); String line = ""; List results = new ArrayList( 0 ); while ( line != null ) { line = br.readLine(); // Null signifie fin de fichier if ( line != null ) { // Si on a trouv� une ligne commencant par le rep�re pass� en param�tre, on la r�cup�re // Pour plus de suret� on v�rifie �galement que la ligne contient une virgule // Traitement diff�rent suivant les classes ou les fichiers, pour les classes on ne s'occupe // pas du nom du fichier, pour les m�thodes oui if ( pMarker.equals( CLASS_LOCATOR ) ) { if ( isValidCSVLine( line, pMarker ) ) { bw.write( line + "\n" ); } } else { if ( isValidCSVLine( line, pMarker ) ) { results.add( line ); } else if ( isFileLine( line ) ) { // On est sur la ligne indiquant le fichier dans lequel se trouve tous les �l�ments // obtenus aux lignes pr�c�dentes // on �crit les r�sultats pr�c�dents avec le nom du fichier en plus StringTokenizer st = new StringTokenizer( line, "," ); String fileName = ""; int counter = 0; while ( st.hasMoreElements() && counter < 2 ) { fileName = st.nextToken().trim(); counter++; } // on �crit la ligne for ( int i = 0; i < results.size(); i++ ) { // efface le retour � la ligne String result = ( (String) ( results.get( i ) ) ).replaceAll( "\n", "" ); // efface param�tres 3 � 5 et function points result = result.replaceFirst(mREGEXPREMOVEPARAMS, "").replaceAll(mREGEXPREMOVEFUNCTIONPOINT, ",$1"); // Dans ce cas il faut rajouter un " " sinon le parser ne tient pas compte de la colonne if ( result.charAt( result.length() - 1 ) != ' ' ) { bw.write( result + " ," + fileName + "\n" ); } else { bw.write( result + "," + fileName + "\n" ); } } // reset la liste des r�sultats results = new ArrayList( 0 ); } } } } // ferme les buffers bw.close(); br.close(); } /** * @param pLine la ligne courante * @return true si la ligne commence par le marqueur de ligne fichier */ private boolean isFileLine( String pLine ) { return pLine.startsWith( FILE_LOCATOR ); } /** * @param pFilename le nom du fichier g�n�r� par rsm * @param pOutputFileAux nom du fichier auxilliaire utilis� pour supprimer les doublons * @throws IOException en cas d'�chec de lecture */ private void eraseDouble( String pFilename, String pOutputFileAux ) throws IOException { BufferedWriter bw = new BufferedWriter( new FileWriter( pOutputFileAux ) ); BufferedReader br = new BufferedReader( new FileReader( pFilename ) ); String line = ""; // Supprime les lignes redondantes du � l'utilisation (n�cessaire) // de deux options donnant en partie le meme r�sultat boolean write = true; while ( line != null ) { line = br.readLine(); // Null signifie fin de fichier if ( line != null ) { // on enl�ve tous les espaces (il n'est pas cens� y avoir d'espaces dans un fichier CSV) // et dans le fichier renvoy� par RSM il y en a un peu partout // Toutefois on enl�ve pas l'espace qui est entre deux virgules sinon le parser ne compte pas la colonne String properLine = ""; for ( int i = 0; i < line.length(); i++ ) { if ( line.charAt( i ) != ' ' || ( line.charAt( i ) == ' ' && i > 0 && i < ( line.length() - 1 ) && line.charAt( i - 1 ) == ',' && line.charAt( i + 1 ) == ',' ) ) { properLine += line.charAt( i ); } } // Si on a trouv� une ligne commencant par le rep�re pass� en param�tre, on la r�cup�re // Pour plus de suret� on v�rifie �galement que la ligne contient une virgule if ( properLine.startsWith( STOP_WRITE ) ) { write = false; } else { if ( properLine.startsWith( RESTART_WRITE ) ) { write = true; } } if ( write && isValidCSVLine( properLine ) ) { bw.write( properLine + "\n" ); } } } // ferme les buffers bw.close(); br.close(); } /** * Effectue un test plus g�n�ral sans marqueur Sert pour l'�tape 1 du pr�-processing * * @param pLine la ligne � tester * @return true si la ligne est valide pour pr�processing CSV */ private boolean isValidCSVLine( String pLine ) { // On v�rifie que la ligne comporte les �l�ments n�cessaire � l'analyse CSV // Il faut une virgule return commonValidCVSLine( pLine ) && ( pLine.startsWith( CLASS_LOCATOR ) || pLine.startsWith( FILE_LOCATOR ) || pLine.startsWith( METHOD_LOCATOR ) ); } /** * Effectue les tests de pr�processing pour r�cup�rer les donn�es qui interressent en fonction du marqueur Sert pour * l'�tape 2 du pr�-processing * * @param pLine la ligne � tester * @param pMarker le rep�re * @return true si la ligne est valide pour analyse CSV */ private boolean isValidCSVLine( String pLine, String pMarker ) { // On v�rifie que la ligne comporte les �l�ments n�cessaire � l'analyse CSV // Il faut une virgule return commonValidCVSLine( pLine ) // Les contraintes sur la taille && pLine.length() > pMarker.length() && pMarker.equals( pLine.substring( 0, pMarker.length() ) ); } /** * Permet de traiter les conditions communes qu'une ligne doit avoir pour �tre analys�e par CSV * * @param pLine la ligne � v�rifier * @return true si la ligne est partiellement valide pour pr�processing CSV */ private boolean commonValidCVSLine( String pLine ) { // On v�rifie que la ligne comporte les �l�ments n�cessaire � l'analyse CSV // Il faut une virgule return pLine.indexOf( "," ) != -1 // Les mots cl�s interdits && pLine.indexOf( "Average" ) == -1 && pLine.indexOf( "Total" ) == -1 && pLine.indexOf( "Maximum" ) == -1 && pLine.indexOf( "Minimum" ) == -1; } /** * Parse le rapport pour obtenir les m�triques de classe. * * @param pFilename chemin du fichier rapport. * @throws Exception si un probl�me de parsing apparait. * @return le nombre de r�sultats de niveau classe * @roseuid 42B976100269 */ private int parseReportForClassMetrics( final String pFilename ) throws Exception { // R�cup�rer les beans issus du rapport de classes RSM Collection classResults = mParser.parse( RSMMessages.getString( mClassTemplate ), pFilename ); // On ne compte que les classes qui ont une logique Collection classToPersist = new ArrayList( 0 ); // Ajout de la volum�trie dans les beans Iterator it = classResults.iterator(); RSMClassMetricsBO bo = null; while ( it.hasNext() ) { // On adapte chaque bean issu du rapport bo = (RSMClassMetricsBO) it.next(); // On ne compte que les classes qui ont une logique, c'est � dire les classes ayant des m�thodes if ( ( bo.getPublicData().intValue() + bo.getProtectedData().intValue() + bo.getPrivateData().intValue() ) > 0 ) { // rattache le bo rsm � la classe associ�e mAdaptator.adaptClassResult( bo ); // on ajoute pour les donn�es projets les donn�es concernant la classe courante // au niveau des commentaires et du nombre de lignes de code mSLOC += bo.getSloc().intValue(); mComments += bo.getComments().intValue(); bo.setAudit( mAudit ); mNumberOfClasses++; bo.setTaskName( mTaskName ); classToPersist.add( bo ); } } LOGGER.info( RSMMessages.getString( "logs.debug.report_parsing_database" ) ); // On sauvegarde le mesures sur les classes MeasureDAOImpl.getInstance().saveAll( mSession, classToPersist ); mSession.commitTransactionWithoutClose(); mSession.beginTransaction(); LOGGER.info( RSMMessages.getString( "logs.debug.report_parsing_end" ) ); return classResults.size(); } /** * Parse le rapport pour obtenir les m�triques de m�thodes * * @param pFilename chemin du fichier rapport. * @param pDatas les donn�es temporaires * @throws Exception si un probl�me de parsing appara�t. * @return le nombre de r�sultats de niveau m�thode r�cup�r�s. * @roseuid 42B9761F015F */ private int parseMethodReport( final String pFilename, TaskData pDatas ) throws Exception { // R�cup�rer les beans issus du rapport de m�thodes RSM Collection methodResults = mParser.parse( RSMMessages.getString( mMethodTemplate ), pFilename ); RSMMethodMetricsBO bo = null; String name = null; // On cr�e une nouvelle collection contenant les beans � faire effectivement persister List resultsToPersist = new ArrayList(); // Adaptation des beans Iterator it = methodResults.iterator(); while ( it.hasNext() ) { bo = (RSMMethodMetricsBO) it.next(); // rattache le bo rsm � la classe associ�e bo.setAudit( mAudit ); // Le nom du fichier est mis en relatif par rapport � la racine du projet String completFileName = bo.getFileName(); if (completFileName.indexOf( "vobs" ) != -1) { String fileName = completFileName.substring( completFileName.indexOf( "vobs" ), completFileName.length() ); bo.setFileName( fileName ); } else { bo.setFileName( completFileName ); } bo.setTaskName( mTaskName ); mNumberOfMethods++; // Probl�me RSM avec le polymorphisme, si plusieurs m�thodes de meme nom pr�sentes // dans le fichier on ne les sauvegarde pas boolean canAdd = mAdaptator.adaptMethodResult( bo ); if ( canAdd ) { resultsToPersist.add( bo ); } } // La collection des r�sultats de m�thodes est persist�e LOGGER.info( RSMMessages.getString( "logs.debug.report_parsing_database" ) ); MeasureDAOImpl.getInstance().saveAll( mSession, resultsToPersist ); mSession.commitTransactionWithoutClose(); mSession.beginTransaction(); LOGGER.info( RSMMessages.getString( "logs.debug.report_parsing_end" ) ); return methodResults.size(); } /** * Cr�e et fait persister les r�sultats de niveau projet. * * @roseuid 42E09E5201AB */ public void persistProjectResult() { LOGGER.info( RSMMessages.getString( "logs.debug.project_database" ) ); RSMProjectMetricsBO metrics = new RSMProjectMetricsBO(); // Cr�ation des m�triques de niveau projet metrics.setComponent( mProject ); metrics.setAudit( mAudit ); metrics.setTaskName( mTaskName ); metrics.setNumberOfClasses( new Integer( mNumberOfClasses ) ); metrics.setNumberOfMethods( new Integer( mNumberOfMethods ) ); metrics.setSloc( new Integer( mSLOC ) ); metrics.setComments( new Integer( mComments ) ); try { MeasureDAOImpl.getInstance().create( mSession, metrics ); } catch ( JrafDaoException e ) { LOGGER.error( e, e ); } } }