1
package org.hisp.dhis.expression;
4
* Copyright (c) 2004-2007, University of Oslo
7
* Redistribution and use in source and binary forms, with or without
8
* modification, are permitted provided that the following conditions are met:
9
* * Redistributions of source code must retain the above copyright notice, this
10
* list of conditions and the following disclaimer.
11
* * Redistributions in binary form must reproduce the above copyright notice,
12
* this list of conditions and the following disclaimer in the documentation
13
* and/or other materials provided with the distribution.
14
* * Neither the name of the HISP project nor the names of its contributors may
15
* be used to endorse or promote products derived from this software without
16
* specific prior written permission.
18
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
import static org.hisp.dhis.expression.Expression.SEPARATOR;
31
import static org.hisp.dhis.system.util.MathUtils.calculateExpression;
33
import java.util.Collection;
34
import java.util.HashSet;
35
import java.util.Iterator;
38
import java.util.regex.Matcher;
39
import java.util.regex.Pattern;
41
import org.hisp.dhis.dataelement.CalculatedDataElement;
42
import org.hisp.dhis.dataelement.DataElement;
43
import org.hisp.dhis.dataelement.DataElementCategoryOptionCombo;
44
import org.hisp.dhis.dataelement.DataElementCategoryOptionComboService;
45
import org.hisp.dhis.dataelement.DataElementService;
46
import org.hisp.dhis.dataelement.Operand;
47
import org.hisp.dhis.datavalue.DataValue;
48
import org.hisp.dhis.datavalue.DataValueService;
49
import org.hisp.dhis.period.Period;
50
import org.hisp.dhis.source.Source;
51
import org.hisp.dhis.system.util.MathUtils;
54
* @author Margrethe Store
55
* @author Lars Helge Overland
56
* @version $Id: DefaultExpressionService.java 6463 2008-11-24 12:05:46Z larshelg $
58
public class DefaultExpressionService
59
implements ExpressionService
61
private static final String NULL_REPLACEMENT = "0";
63
// -------------------------------------------------------------------------
65
// -------------------------------------------------------------------------
67
private ExpressionStore expressionStore;
69
public void setExpressionStore( ExpressionStore expressionStore )
71
this.expressionStore = expressionStore;
74
private DataElementService dataElementService;
76
public void setDataElementService( DataElementService dataElementService )
78
this.dataElementService = dataElementService;
81
private DataValueService dataValueService;
83
public void setDataValueService( DataValueService dataValueService )
85
this.dataValueService = dataValueService;
88
private DataElementCategoryOptionComboService categoryOptionComboService;
90
public void setCategoryOptionComboService( DataElementCategoryOptionComboService dataElementCategoryOptionComboService )
92
this.categoryOptionComboService = dataElementCategoryOptionComboService;
95
// -------------------------------------------------------------------------
96
// Expression CRUD operations
97
// -------------------------------------------------------------------------
99
public int addExpression( Expression expression )
101
return expressionStore.addExpression( expression );
104
public void deleteExpression( Expression expression )
106
expressionStore.deleteExpression( expression );
109
public Expression getExpression( int id )
111
return expressionStore.getExpression( id );
114
public void updateExpression( Expression expression )
116
expressionStore.updateExpression( expression );
119
public Collection<Expression> getAllExpressions()
121
return expressionStore.getAllExpressions();
124
// -------------------------------------------------------------------------
126
// -------------------------------------------------------------------------
128
public Double getExpressionValue( Expression expression, Period period, Source source )
130
String expressionString = generateExpression( expression.getExpression(), period, source );
132
return expressionString != null ? calculateExpression( expressionString ) : null;
135
public Set<DataElement> getDataElementsInExpression( String expression )
137
Set<DataElement> dataElementsInExpression = null;
139
if ( expression != null )
141
dataElementsInExpression = new HashSet<DataElement>();
143
Matcher matcher = getMatcher( "(\\[\\d+\\" + SEPARATOR + "\\d+\\])", expression );
145
while ( matcher.find() )
147
String replaceString = matcher.group();
149
replaceString = replaceString.replaceAll( "[\\[\\]]", "" );
151
replaceString = replaceString.substring( 0, replaceString.indexOf( SEPARATOR ) );
153
int dataElementId = Integer.parseInt( replaceString );
155
DataElement dataElement = dataElementService.getDataElement( dataElementId );
157
if ( dataElement != null )
159
dataElementsInExpression.add( dataElement );
164
return dataElementsInExpression;
167
public String convertExpression( String expression, Map<Object, Integer> dataElementMapping, Map<Object, Integer> categoryOptionComboMapping )
169
StringBuffer convertedFormula = new StringBuffer();
171
if ( expression != null )
173
Matcher matcher = getMatcher( "(\\[\\d+\\" + SEPARATOR + "\\d+\\])", expression );
175
while ( matcher.find() )
177
String match = matcher.group();
179
match = match.replaceAll( "[\\[\\]]", "" );
181
String dataElementIdString = match.substring( 0, match.indexOf( SEPARATOR ) );
182
String categoryOptionComboIdString = match.substring( match.indexOf( SEPARATOR ) + 1, match.length() );
184
int dataElementId = Integer.parseInt( dataElementIdString );
185
int categoryOptionComboId = Integer.parseInt( categoryOptionComboIdString );
187
dataElementId = dataElementMapping.get( dataElementId );
188
categoryOptionComboId = categoryOptionComboMapping.get( categoryOptionComboId );
190
match = "[" + dataElementId + SEPARATOR + categoryOptionComboId + "]";
192
matcher.appendReplacement( convertedFormula, match );
195
matcher.appendTail( convertedFormula );
198
return convertedFormula.toString();
201
public Set<Operand> getOperandsInExpression( String expression )
203
Set<Operand> operandsInExpression = null;
205
if ( expression != null )
207
operandsInExpression = new HashSet<Operand>();
209
Matcher matcher = getMatcher( "(\\[\\d+\\" + SEPARATOR + "\\d+\\])", expression );
211
while ( matcher.find() )
213
final Operand operand = new Operand();
215
String match = matcher.group().replaceAll( "[\\[\\]]", "" );
217
operand.setDataElementId( Integer.parseInt( match.substring( 0, match.indexOf( SEPARATOR ) ) ) );
218
operand.setOptionComboId( Integer.parseInt( match.substring( match.indexOf( SEPARATOR ) + 1, match.length() ) ) );
220
operandsInExpression.add( operand );
224
return operandsInExpression;
227
public int expressionIsValid( String formula )
229
StringBuffer buffer = new StringBuffer();
231
Matcher matcher = getMatcher( "\\[.+?\\" + SEPARATOR + ".+?\\]", formula );
233
int dataElementId = -1;
234
int categoryOptionComboId = -1;
236
while ( matcher.find() )
238
String match = matcher.group();
240
match = match.replaceAll( "[\\[\\]]", "" );
242
String dataElementIdString = match.substring( 0, match.indexOf( SEPARATOR ) );
243
String categoryOptionComboIdString = match.substring( match.indexOf( SEPARATOR ) + 1, match.length() );
247
dataElementId = Integer.parseInt( dataElementIdString );
249
catch ( NumberFormatException ex )
251
return DATAELEMENT_ID_NOT_NUMERIC;
256
categoryOptionComboId = Integer.parseInt( categoryOptionComboIdString );
258
catch ( NumberFormatException ex )
260
return CATEGORYOPTIONCOMBO_ID_NOT_NUMERIC;
263
if ( dataElementService.getDataElement( dataElementId ) == null )
265
return DATAELEMENT_DOES_NOT_EXIST;
268
if ( categoryOptionComboService.getDataElementCategoryOptionCombo( categoryOptionComboId ) == null )
270
return CATEGORYOPTIONCOMBO_DOES_NOT_EXIST;
273
// -----------------------------------------------------------------
274
// Replacing the operand with 1 in order to later be able to verify
275
// that the formula is mathematically valid
276
// -----------------------------------------------------------------
278
matcher.appendReplacement( buffer, "1" );
281
matcher.appendTail( buffer );
283
if ( MathUtils.expressionHasErrors( buffer.toString() ) )
285
return EXPRESSION_NOT_WELL_FORMED;
291
public String getExpressionDescription( String formula )
293
StringBuffer buffer = null;
295
if ( formula != null )
297
buffer = new StringBuffer();
299
Matcher matcher = getMatcher( "\\[.+?\\" + SEPARATOR + ".+?\\]", formula );
301
int dataElementId = -1;
302
int categoryOptionComboId = -1;
304
DataElement dataElement = null;
305
DataElementCategoryOptionCombo categoryOptionCombo = null;
307
while ( matcher.find() )
309
String replaceString = matcher.group();
311
replaceString = replaceString.replaceAll( "[\\[\\]]", "" );
313
String dataElementIdString = replaceString.substring( 0, replaceString.indexOf( SEPARATOR ) );
314
String optionComboIdString = replaceString.substring( replaceString.indexOf( SEPARATOR ) + 1, replaceString.length() );
318
dataElementId = Integer.parseInt( dataElementIdString );
320
catch ( NumberFormatException ex )
322
throw new IllegalArgumentException( "Data element identifier must be a number: " + replaceString );
327
categoryOptionComboId = Integer.parseInt( optionComboIdString );
329
catch ( NumberFormatException ex )
331
throw new IllegalArgumentException( "Category option combo identifier must be a number: "
335
dataElement = dataElementService.getDataElement( dataElementId );
336
categoryOptionCombo = categoryOptionComboService.getDataElementCategoryOptionCombo( categoryOptionComboId );
338
if ( dataElement == null )
340
throw new IllegalArgumentException( "Identifier does not reference a data element: "
344
if ( categoryOptionCombo == null )
346
throw new IllegalArgumentException( "Identifier does not reference a category option combo: "
347
+ categoryOptionComboId );
350
replaceString = dataElement.getName() + SEPARATOR + categoryOptionComboService.getOptionNames( categoryOptionCombo );
352
if ( replaceString.endsWith( SEPARATOR ) )
354
replaceString = replaceString.substring( 0, replaceString.length() - 1 );
357
matcher.appendReplacement( buffer, replaceString );
360
matcher.appendTail( buffer );
363
return buffer != null ? buffer.toString() : null;
366
public String replaceCDEsWithTheirExpression( String expression )
368
StringBuffer buffer = null;
370
if ( expression != null )
372
buffer = new StringBuffer();
374
Set<DataElement> caclulatedDataElementsInExpression = getDataElementsInExpression( expression );
376
Iterator<DataElement> iterator = caclulatedDataElementsInExpression.iterator();
378
while ( iterator.hasNext() )
380
DataElement dataElement = iterator.next();
382
if ( !(dataElement instanceof CalculatedDataElement) )
388
Matcher matcher = getMatcher( "(\\[\\d+\\" + SEPARATOR + "\\d+\\])", expression );
390
while ( matcher.find() )
392
String replaceString = matcher.group();
394
for ( DataElement dataElement : caclulatedDataElementsInExpression )
396
if ( replaceString.startsWith( "[" + dataElement.getId() + SEPARATOR ) )
398
CalculatedDataElement calculatedDataElement = (CalculatedDataElement) dataElement;
400
replaceString = calculatedDataElement.getExpression().getExpression();
406
matcher.appendReplacement( buffer, replaceString );
409
matcher.appendTail( buffer );
412
return buffer != null ? buffer.toString() : null;
415
public String generateExpression( String expression, Period period, Source source )
417
StringBuffer buffer = null;
419
if ( expression != null )
421
Matcher matcher = getMatcher( "(\\[\\d+\\" + SEPARATOR + "\\d+\\])", expression );
423
buffer = new StringBuffer();
425
while ( matcher.find() )
427
String replaceString = matcher.group();
429
replaceString = replaceString.replaceAll( "[\\[\\]]", "" );
431
String dataElementIdString = replaceString.substring( 0, replaceString.indexOf( SEPARATOR ) );
432
String categoryOptionComboIdString = replaceString.substring( replaceString.indexOf( SEPARATOR ) + 1, replaceString.length() );
434
int dataElementId = Integer.parseInt( dataElementIdString );
435
int optionComboId = Integer.parseInt( categoryOptionComboIdString );
437
DataElement dataElement = dataElementService.getDataElement( dataElementId );
439
DataElementCategoryOptionCombo categoryOptionCombo = categoryOptionComboService.getDataElementCategoryOptionCombo( optionComboId );
441
DataValue dataValue = dataValueService.getDataValue( source, dataElement, period, categoryOptionCombo );
443
if ( dataValue == null )
445
replaceString = NULL_REPLACEMENT;
449
replaceString = String.valueOf( dataValue.getValue() );
452
matcher.appendReplacement( buffer, replaceString );
455
matcher.appendTail( buffer );
458
return buffer != null ? buffer.toString() : null;
461
// -------------------------------------------------------------------------
462
// Supportive methods
463
// -------------------------------------------------------------------------
465
private Matcher getMatcher( String regex, String expression )
467
Pattern pattern = Pattern.compile( regex );
469
return pattern.matcher( expression );