=== added file 'dhis-2/dhis-support/dhis-support-commons/src/main/java/org/hisp/dhis/commons/math/ExpressionFunctions.java' --- dhis-2/dhis-support/dhis-support-commons/src/main/java/org/hisp/dhis/commons/math/ExpressionFunctions.java 1970-01-01 00:00:00 +0000 +++ dhis-2/dhis-support/dhis-support-commons/src/main/java/org/hisp/dhis/commons/math/ExpressionFunctions.java 2015-07-10 11:32:13 +0000 @@ -0,0 +1,67 @@ +package org.hisp.dhis.commons.math; + +/* + * Copyright (c) 2004-2015, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +public class ExpressionFunctions +{ + public static final String NAMESPACE = "d2"; + + /** + * Function which will return zero if the argument is a negative number. + * + * @param value the value. + * @return a Double. + */ + public static Double zing( String value ) + { + if ( value == null ) + { + throw new IllegalArgumentException( "Argument is null: " + value ); + } + + return Math.max( 0d, Double.valueOf( value ) ); + } + + /** + * Function which will return one if the argument is zero or a positive + * number, and zero if not. + * + * @param value the value. + * @return a Double. + */ + public static Double oizp( String value ) + { + if ( value == null ) + { + throw new IllegalArgumentException( "Argument is null: " + value ); + } + + return ( Double.valueOf( value ) >= 0d ) ? 1d : 0d; + } +} === modified file 'dhis-2/dhis-support/dhis-support-commons/src/main/java/org/hisp/dhis/commons/math/OneIfZeroOrPositiveFunction.java' --- dhis-2/dhis-support/dhis-support-commons/src/main/java/org/hisp/dhis/commons/math/OneIfZeroOrPositiveFunction.java 2015-05-28 18:23:26 +0000 +++ dhis-2/dhis-support/dhis-support-commons/src/main/java/org/hisp/dhis/commons/math/OneIfZeroOrPositiveFunction.java 2015-07-10 11:32:13 +0000 @@ -47,6 +47,6 @@ @Override public Double eval( double arg ) { - return ( arg >= 0 ) ? 1d : 0d; + return ( arg >= 0d ) ? 1d : 0d; } } === modified file 'dhis-2/dhis-support/dhis-support-commons/src/main/java/org/hisp/dhis/commons/util/ExpressionUtils.java' --- dhis-2/dhis-support/dhis-support-commons/src/main/java/org/hisp/dhis/commons/util/ExpressionUtils.java 2015-06-15 13:44:20 +0000 +++ dhis-2/dhis-support/dhis-support-commons/src/main/java/org/hisp/dhis/commons/util/ExpressionUtils.java 2015-07-10 11:32:13 +0000 @@ -33,8 +33,11 @@ import org.apache.commons.jexl2.JexlEngine; import org.apache.commons.jexl2.JexlException; import org.apache.commons.jexl2.MapContext; +import org.hisp.dhis.commons.math.ExpressionFunctions; +import java.util.HashMap; import java.util.Map; +import java.util.regex.Pattern; /** * @author Lars Helge Overland @@ -43,8 +46,14 @@ { private static final JexlEngine JEXL = new JexlEngine(); + private static final Pattern NUMERIC_PATTERN = Pattern.compile( "^(-?0|-?[1-9]\\d*)(\\.\\d+)?(E(-)?\\d+)?$" ); + static { + Map functions = new HashMap<>(); + functions.put( ExpressionFunctions.NAMESPACE, ExpressionFunctions.class ); + + JEXL.setFunctions( functions ); JEXL.setCache( 512 ); JEXL.setSilent( false ); } @@ -67,6 +76,32 @@ } /** + * Evaluates the given expression. The given variables will be substituted + * in the expression. Converts the result of the evaluation to a Double. + * Throws an IllegalStateException if the result could not be converted to + * a Double + * + * @param expression the expression. + * @param vars the variables, can be null. + * @return the result of the evaluation. + */ + public static Double evaluateToDouble( String expression, Map vars ) + { + Expression exp = JEXL.createExpression( expression ); + + JexlContext context = vars != null ? new MapContext( vars ) : new MapContext(); + + Object result = exp.evaluate( context ); + + if ( result == null || !isNumeric( String.valueOf( result ) ) ) + { + throw new IllegalStateException( "Result must be not null and numeric: " + result ); + } + + return Double.valueOf( String.valueOf( result ) ); + } + + /** * Evaluates the given expression to true or false. The given variables will * be substituted in the expression. * @@ -102,4 +137,15 @@ return false; } } + + /** + * Indicates whether the given value is numeric. + * + * @param value the value. + * @return true or false. + */ + public static boolean isNumeric( String value ) + { + return NUMERIC_PATTERN.matcher( value ).matches(); + } } === modified file 'dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/util/MathUtils.java' --- dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/util/MathUtils.java 2015-05-28 18:21:56 +0000 +++ dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/util/MathUtils.java 2015-07-10 11:32:13 +0000 @@ -28,17 +28,15 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import static org.hisp.dhis.i18n.locale.LocaleManager.DHIS_STANDARD_LOCALE; - import java.math.BigDecimal; import java.math.MathContext; import java.util.List; +import java.util.Locale; import java.util.Random; import java.util.regex.Pattern; import org.apache.commons.validator.routines.DoubleValidator; import org.apache.commons.validator.routines.IntegerValidator; -import org.hisp.dhis.datavalue.DataValue; import org.hisp.dhis.expression.Operator; import org.hisp.dhis.commons.math.OneIfZeroOrPositiveFunction; import org.hisp.dhis.commons.math.ZeroIfNegativeFunction; @@ -51,6 +49,8 @@ { public static final Double ZERO = new Double( 0 ); + private static final Locale LOCALE = new Locale( "en" ); + private static DoubleValidator DOUBLE_VALIDATOR = new DoubleValidator(); private static IntegerValidator INT_VALIDATOR = new IntegerValidator(); @@ -334,7 +334,7 @@ */ public static boolean isNumeric( String value ) { - return value != null && DOUBLE_VALIDATOR.isValid( value, DHIS_STANDARD_LOCALE ) && NUMERIC_PATTERN.matcher( value ).matches(); + return value != null && DOUBLE_VALIDATOR.isValid( value, LOCALE ) && NUMERIC_PATTERN.matcher( value ).matches(); } /** @@ -346,7 +346,7 @@ */ public static boolean isNumericLenient( String value ) { - return value != null && DOUBLE_VALIDATOR.isValid( value, DHIS_STANDARD_LOCALE ) && NUMERIC_LENIENT_PATTERN.matcher( value ).matches(); + return value != null && DOUBLE_VALIDATOR.isValid( value, LOCALE ) && NUMERIC_LENIENT_PATTERN.matcher( value ).matches(); } /** @@ -458,7 +458,7 @@ */ public static boolean isBool( String value ) { - return value != null && ( value.equals( DataValue.TRUE ) || value.equals( DataValue.FALSE ) ); + return value != null && ( value.equals( "true" ) || value.equals( "false" ) ); } /** === modified file 'dhis-2/dhis-support/dhis-support-system/src/test/java/org/hisp/dhis/system/util/ExpressionUtilsTest.java' --- dhis-2/dhis-support/dhis-support-system/src/test/java/org/hisp/dhis/system/util/ExpressionUtilsTest.java 2015-06-15 13:44:20 +0000 +++ dhis-2/dhis-support/dhis-support-system/src/test/java/org/hisp/dhis/system/util/ExpressionUtilsTest.java 2015-07-10 11:32:13 +0000 @@ -30,6 +30,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; import java.util.HashMap; import java.util.Map; @@ -42,6 +43,37 @@ */ public class ExpressionUtilsTest { + private static final double DELTA = 0.01; + + @Test + public void testEvaluateToDouble() + { + assertEquals( 3d, ExpressionUtils.evaluateToDouble( "3", null ), DELTA ); + assertEquals( 3.45, ExpressionUtils.evaluateToDouble( "3.45", null ), DELTA ); + assertEquals( 5d, ExpressionUtils.evaluateToDouble( "2 + 3", null ), DELTA ); + assertEquals( 15.6, ExpressionUtils.evaluateToDouble( "12.4 + 3.2", null ), DELTA ); + assertEquals( 3d, ExpressionUtils.evaluateToDouble( "d2:zing('3')", null ), DELTA ); + assertEquals( 2d, ExpressionUtils.evaluateToDouble( "d2:zing('-3') + 2.0", null ), DELTA ); + assertEquals( 4d, ExpressionUtils.evaluateToDouble( "d2:zing('-1') + 4 + d2:zing('-2')", null ), DELTA ); + assertEquals( 0d, ExpressionUtils.evaluateToDouble( "d2:oizp('-4')", null ), DELTA ); + assertEquals( 1d, ExpressionUtils.evaluateToDouble( "d2:oizp('0')", null ), DELTA ); + assertEquals( 2d, ExpressionUtils.evaluateToDouble( "d2:oizp('-4') + d2:oizp('0') + d2:oizp('3.0')", null ), DELTA ); + } + + @Test + public void testEvaluateToDoubleWithVars() + { + Map vars = new HashMap(); + + vars.put( "v1", "4" ); + vars.put( "v2", "-5" ); + + assertEquals( 7d, ExpressionUtils.evaluateToDouble( "v1 + 3", vars ), DELTA ); + assertEquals( 4d, ExpressionUtils.evaluateToDouble( "d2:zing(v1)", vars ), DELTA ); + assertEquals( 0d, ExpressionUtils.evaluateToDouble( "d2:zing(v2)", vars ), DELTA ); + assertEquals( 4d, ExpressionUtils.evaluateToDouble( "d2:zing(v1) + d2:zing(v2)", vars ), DELTA ); + } + @Test public void testIsTrue() {