/** * 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.squalecommon.enterpriselayer.facade.rule; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.Iterator; import org.apache.commons.beanutils.BeanUtils; import org.python.core.PyException; import org.python.core.PyFloat; import org.python.core.PyInteger; import org.python.core.PyNone; import org.python.core.PyObject; import org.python.util.PythonInterpreter; import org.squale.squalecommon.enterpriselayer.businessobject.result.MeasureBO; import org.squale.squalecommon.enterpriselayer.businessobject.rule.AbstractFormulaBO; /** * Interpr�te de formule */ public class FormulaInterpreter { /** Derni�re formule compil�e � des fins d'optimisation */ private long mLastFormulaComputed; /** Interpreteur python */ private PythonInterpreter mInterpreter; /** * Obtention d'un interpr�teur Jython * * @return interpr�teur */ public PythonInterpreter getInterpreter() { if ( mInterpreter == null ) { mInterpreter = new PythonInterpreter(); mInterpreter.exec( "from math import *\n" ); } return mInterpreter; } /** * V�rification de la syntaxe d'une formule * * @param pFormula formule * @throws FormulaException si erreur */ public void checkSyntax( AbstractFormulaBO pFormula ) throws FormulaException { FormulaConverter converter = new FormulaConverter(); String result = converter.convertFormula( pFormula ); PythonInterpreter inter = getInterpreter(); try { inter.exec( result ); } catch ( PyException e ) { throw new FormulaException( e ); } // Test des param�tres utilis�s MeasureBOFactory factory = new MeasureBOFactory(); MeasureBO[] measures = new MeasureBO[pFormula.getMeasureKinds().size()]; Iterator measureKinds = pFormula.getMeasureKinds().iterator(); int i = 0; while ( measureKinds.hasNext() ) { measures[i] = factory.createMeasure( pFormula.getComponentLevel(), (String) measureKinds.next() ); i++; } checkParameters( pFormula, measures ); } /** * Evaluation d'une formule * * @param pFormula formule * @param pMeasures mesures * @throws FormulaException si erreur * @return note attribu�e */ public Number evaluate( AbstractFormulaBO pFormula, MeasureBO[] pMeasures ) throws FormulaException { Number result = null; try { result = evaluateWithoutAdapt( pFormula, pMeasures ); // Ajustement du r�sultat if ( null != result ) { double value = result.doubleValue(); // Ajustement de la valeur dans l'intervale autoris� final int minValue = 0; final int maxValue = 3; if ( value < minValue ) { value = minValue; } else if ( value > maxValue ) { value = maxValue; } // Conversion de la valeur en type result = new Double( value ); } } catch ( Throwable t ) { // Renvoi d'une exception si la couche Jython rencontre une erreur throw new FormulaException( t ); } return result; } /** * @param pFormula la formule * @param pMeasures mesures * @return le r�sultat une fois la mesure appliqu�e * @throws FormulaException si erreur */ public Number evaluateWithoutAdapt( AbstractFormulaBO pFormula, MeasureBO[] pMeasures ) throws FormulaException { Number result = null; try { // Calcul PyObject r = calculate( pFormula, pMeasures ); // Conversion du r�sultat result = convertResult( r ); } catch ( Throwable t ) { // Renvoi d'une exception si la couche Jython rencontre une erreur throw new FormulaException( t ); } return result; } /** * @param pFormula la formule * @param pMeasures mesures * @return le r�sulat en phyton une fois la formule appliqu�e * @throws FormulaException si erreur */ private PyObject calculate( AbstractFormulaBO pFormula, MeasureBO[] pMeasures ) throws FormulaException { // Obtention d'un interpr�te Python PythonInterpreter inter = getInterpreter(); PyObject result = null; try { // Compilation de la formule correspondate compileFormula( pFormula ); // Optimisation du traitement des cha�nes StringBuffer function = new StringBuffer(); function.append( FormulaConverter.FUNCTION_PREFIX ); long formulaId = pFormula.getId(); if ( formulaId < 0 ) { formulaId = 0; } function.append( formulaId ); function.append( '(' ); // Traitement de chacun des param�tres for ( int i = 0; i < pMeasures.length; i++ ) { // Chaque param�tre suit une r�gle de nommage String param = "measure" + i; inter.set( param, pMeasures[i] ); // Cas de plusieurs param�tres if ( i > 0 ) { function.append( ',' ); } function.append( param ); } function.append( ')' ); // Appel de la fonction result = inter.eval( function.toString() ); } catch ( Throwable t ) { // Renvoi d'une exception si la couche Jython rencontre une erreur throw new FormulaException( t ); } return result; } /** * Compilation d'une formule Une optimisation est faite pour �viter de recompiler plusieurs fois la m�me formule * * @param pFormula formule � compiler */ private void compileFormula( AbstractFormulaBO pFormula ) { if ( mLastFormulaComputed != pFormula.getId() ) { FormulaConverter converter = new FormulaConverter(); String formula = converter.convertFormula( pFormula ); PythonInterpreter inter = getInterpreter(); inter.exec( formula ); mLastFormulaComputed = pFormula.getId(); } } /** * Conversion d'un r�sultat Python La conversion s'op�re sur les objets de type PyFloat ou PyInteger, un autre objet * g�n�re une erreur * * @param pResult objet � convertir * @return valeur correspondante ou null si la conversion ne peut se faire * @throws FormulaException si erreur dans le type */ public Double convertResult( PyObject pResult ) throws FormulaException { Double result; // Les fonctions peuvent renvoyer null comme type PYTHON // on ne traite donc que les objets de type num�rique // Attention en Jython, isNumbeType renvoie toujours true if ( pResult.isNumberType() ) { double value; // Traitement du cas entier if ( pResult instanceof PyInteger ) { value = ( (PyInteger) pResult ).getValue(); } else if ( pResult instanceof PyFloat ) { value = ( (PyFloat) pResult ).getValue(); } else { // Le type attendue n'est pas num�rique throw new FormulaException( RuleMessages.getString( "result.badtype", new Object[] { pResult } ) ); } // Conversion de la valeur en type result = new Double( value ); } else if ( pResult instanceof PyNone ) { // Un r�sultat vide est g�n�r� dans le cas d'un trigger non activ� par exemple result = null; } else { // Pour le cas o� isNumberType renvoie false ce qui est peu probable // pour la version actuelle // Le type attendue n'est pas num�rique throw new FormulaException( RuleMessages.getString( "result.badtype", new Object[] { pResult } ) ); } return result; } /** * V�rification des param�tres * * @param pFormula formule * @param pMeasures mesure * @throws FormulaException si erreur * @return true si les param�tres sont corrects */ private boolean checkParameters( AbstractFormulaBO pFormula, MeasureBO[] pMeasures ) throws FormulaException { boolean result = true; // Construction d'une map avec les types de mesure et les mesures HashMap map = new HashMap(); Iterator measureKinds = pFormula.getMeasureKinds().iterator(); int i = 0; // Parcours des mesures while ( measureKinds.hasNext() ) { map.put( measureKinds.next(), pMeasures[i] ); i++; } // R�cup�ration des attributs utilis�s par la formule ParameterExtraction extracter = new ParameterExtraction(); Iterator attributes = extracter.getFormulaParameters( pFormula ).iterator(); // On v�rifie que les param�tres utilis�s dans la formule correspondent bien // � des m�triques existantes while ( attributes.hasNext() ) { FormulaParameter parameter = (FormulaParameter) attributes.next(); Object measure = map.get( parameter.getMeasureKind() ); // Si la mesure n'existe pas on l�ve une exception if ( measure == null ) { // La m�trique est inconnue, on indique ce qui manque throw new FormulaException( RuleMessages.getString( "measure.unknown", new Object[] { parameter.getMeasureKind() } ) ); } else { // On v�rifie que la mesure contient bien une propri�t� avec ce nom if ( getProperty( measure, parameter.getMeasureAttribute() ) == null ) { result = false; } } } return result; } /** * Obtention d'une propri�t� * * @param pMeasure mesure * @param pAttribute attribut * @return propri�t� si elle existe * @throws FormulaException si erreur */ private String getProperty( Object pMeasure, String pAttribute ) throws FormulaException { String value = null; // On pourrait aussi intercepter le type Exception et factoriser // le message d'erreur try { // On essaye d'obtenir la propri�t� correspondante value = BeanUtils.getProperty( pMeasure, pAttribute ); } catch ( IllegalAccessException e ) { // Renvoi d'une exception encapul�e throw new FormulaException( RuleMessages.getString( "parameter.unknown", new Object[] { pAttribute } ), e ); } catch ( InvocationTargetException e ) { // Renvoi d'une exception encapul�e throw new FormulaException( RuleMessages.getString( "parameter.unknown", new Object[] { pAttribute } ), e ); } catch ( NoSuchMethodException e ) { // Renvoi d'une exception encapul�e throw new FormulaException( RuleMessages.getString( "parameter.unknown", new Object[] { pAttribute } ), e ); } return value; } }