dhis2-devs team mailing list archive
-
dhis2-devs team
-
Mailing list archive
-
Message #43628
[Branch ~dhis2-devs-core/dhis2/trunk] Rev 22116: JEP functions, code style
------------------------------------------------------------
revno: 22116
committer: Lars Helge Overland <larshelge@xxxxxxxxx>
branch nick: dhis2
timestamp: Wed 2016-03-02 22:51:09 +0100
message:
JEP functions, code style
modified:
dhis-2/dhis-api/src/main/java/org/hisp/dhis/expression/ExpressionService.java
dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/expression/DefaultExpressionService.java
dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/ArithmeticMean.java
dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/Count.java
dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/CustomFunctions.java
dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/MaxValue.java
dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/MedianValue.java
dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/MinValue.java
dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/StandardDeviation.java
dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/VectorSum.java
--
lp:dhis2
https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk
Your team DHIS 2 developers is subscribed to branch lp:dhis2.
To unsubscribe from this branch go to https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk/+edit-subscription
=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/expression/ExpressionService.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/expression/ExpressionService.java 2016-01-13 12:54:38 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/expression/ExpressionService.java 2016-03-02 21:51:09 +0000
@@ -391,5 +391,4 @@
* @param indicators the collection of Indicators.
*/
List<DataElementOperand> getOperandsInIndicators( List<Indicator> indicators );
-
}
=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/expression/DefaultExpressionService.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/expression/DefaultExpressionService.java 2016-01-08 19:07:20 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/expression/DefaultExpressionService.java 2016-03-02 21:51:09 +0000
@@ -89,991 +89,1031 @@
* @author Lars Helge Overland
*/
public class DefaultExpressionService
-implements ExpressionService
+ implements ExpressionService
{
- private static final Log log = LogFactory.getLog( DefaultExpressionService.class );
-
- // -------------------------------------------------------------------------
- // Dependencies
- // -------------------------------------------------------------------------
-
- private GenericStore<Expression> expressionStore;
-
- public void setExpressionStore( GenericStore<Expression> expressionStore )
- {
- this.expressionStore = expressionStore;
- }
-
- private DataElementService dataElementService;
-
- public void setDataElementService( DataElementService dataElementService )
- {
- this.dataElementService = dataElementService;
- }
-
- private ConstantService constantService;
-
- public void setConstantService( ConstantService constantService )
- {
- this.constantService = constantService;
- }
-
- private DataElementCategoryService categoryService;
-
- public void setCategoryService( DataElementCategoryService categoryService )
- {
- this.categoryService = categoryService;
- }
-
- private OrganisationUnitGroupService organisationUnitGroupService;
-
- public void setOrganisationUnitGroupService( OrganisationUnitGroupService organisationUnitGroupService )
- {
- this.organisationUnitGroupService = organisationUnitGroupService;
- }
-
- private DimensionService dimensionService;
-
- public void setDimensionService( DimensionService dimensionService )
- {
- this.dimensionService = dimensionService;
- }
-
- private IdentifiableObjectManager idObjectManager;
-
- public void setIdObjectManager( IdentifiableObjectManager idObjectManager )
- {
- this.idObjectManager = idObjectManager;
- }
-
- // -------------------------------------------------------------------------
- // Expression CRUD operations
- // -------------------------------------------------------------------------
-
- @Override
- @Transactional
- public int addExpression( Expression expression )
- {
- return expressionStore.save( expression );
- }
-
- @Override
- @Transactional
- public void deleteExpression( Expression expression )
- {
- expressionStore.delete( expression );
- }
-
- @Override
- @Transactional
- public Expression getExpression( int id )
- {
- return expressionStore.get( id );
- }
-
- @Override
- @Transactional
- public void updateExpression( Expression expression )
- {
- expressionStore.update( expression );
- }
-
- @Override
- @Transactional
- public List<Expression> getAllExpressions()
- {
- return expressionStore.getAll();
- }
-
- // -------------------------------------------------------------------------
- // Business logic
- // -------------------------------------------------------------------------
-
- @Override
- public Double getIndicatorValue( Indicator indicator, Period period, Map<? extends DimensionalItemObject, Double> valueMap,
- Map<String, Double> constantMap, Map<String, Integer> orgUnitCountMap )
- {
- if ( indicator == null || indicator.getExplodedNumeratorFallback() == null || indicator.getExplodedDenominatorFallback() == null )
- {
- return null;
- }
-
- Integer days = period != null ? period.getDaysInPeriod() : null;
-
- final String denominatorExpression = generateExpression( indicator.getExplodedDenominatorFallback(),
- valueMap, constantMap, orgUnitCountMap, days, NEVER_SKIP );
-
- if ( denominatorExpression == null )
- {
- return null;
- }
-
- final double denominatorValue = calculateExpression( denominatorExpression );
-
- if ( !isEqual( denominatorValue, 0d ) )
- {
- final String numeratorExpression = generateExpression( indicator.getExplodedNumeratorFallback(),
- valueMap, constantMap, orgUnitCountMap, days, NEVER_SKIP );
-
- if ( numeratorExpression == null )
- {
- return null;
- }
-
- final double numeratorValue = calculateExpression( numeratorExpression );
-
- final double annualizationFactor = period != null ? DateUtils.getAnnualizationFactor( indicator, period.getStartDate(), period.getEndDate() ) : 1d;
- final double factor = indicator.getIndicatorType().getFactor();
- final double aggregatedValue = ( numeratorValue / denominatorValue ) * factor * annualizationFactor;
-
- return aggregatedValue;
- }
-
- return null;
- }
-
- @Override
- public Double getExpressionValue( Expression expression, Map<? extends DimensionalItemObject, Double> valueMap,
- Map<String, Double> constantMap, Map<String, Integer> orgUnitCountMap, Integer days )
- {
- return getExpressionValue( expression, valueMap, constantMap, orgUnitCountMap, days, null, null );
- }
-
- @Override
- public Double getExpressionValue( Expression expression, Map<? extends DimensionalItemObject, Double> valueMap,
- Map<String, Double> constantMap, Map<String, Integer> orgUnitCountMap, Integer days,
- Set<DataElementOperand> incompleteValues)
- {
- return getExpressionValue( expression, valueMap, constantMap, orgUnitCountMap, days, incompleteValues, null );
- }
-
- @Override
- public Double getExpressionValue( Expression expression, Map<? extends DimensionalItemObject, Double> valueMap,
- Map<String, Double> constantMap, Map<String, Integer> orgUnitCountMap, Integer days,
- Set<DataElementOperand> incompleteValues,
- ListMap<String, Double> aggregateMap)
- {
- String expressionString = generateExpression( expression.getExplodedExpressionFallback(), valueMap, constantMap,
- orgUnitCountMap, days, expression.getMissingValueStrategy(), incompleteValues, aggregateMap );
-
- Double result = expressionString != null ? calculateExpression( expressionString ) : null;
-
- log.debug("getExpressionValue("+expression.getExpression()+") ==> '"+expressionString+"' ==> "+result);
-
- return result;
- }
-
- @Override
- @Transactional
- public Set<DataElement> getDataElementsInExpression( String expression )
- {
- return getDataElementsInExpressionInternal( OPERAND_PATTERN, expression );
- }
-
- private Set<DataElement> getDataElementsInExpressionInternal( Pattern pattern, String expression )
- {
- Set<DataElement> dataElements = new HashSet<>();
-
- if ( expression != null )
- {
- final Matcher matcher = pattern.matcher( expression );
-
- while ( matcher.find() )
- {
- final DataElement dataElement = dataElementService.getDataElement( matcher.group( 1 ) );
-
- if ( dataElement != null )
- {
- dataElements.add( dataElement );
- }
- }
- }
-
- return dataElements;
- }
-
- @Override
- @Transactional
- public Set<DataElementCategoryOptionCombo> getOptionCombosInExpression( String expression )
- {
- Set<DataElementCategoryOptionCombo> optionCombosInExpression = new HashSet<>();
-
- if ( expression != null )
- {
- final Matcher matcher = OPTION_COMBO_OPERAND_PATTERN.matcher( expression );
-
- while ( matcher.find() )
- {
- DataElementCategoryOptionCombo categoryOptionCombo = categoryService.
- getDataElementCategoryOptionCombo( matcher.group( 2 ) );
-
- if ( categoryOptionCombo != null )
- {
- optionCombosInExpression.add( categoryOptionCombo );
- }
- }
- }
-
- return optionCombosInExpression;
- }
-
- @Override
- @Transactional
- public Set<DataElementOperand> getOperandsInExpression( String expression )
- {
- Set<DataElementOperand> operandsInExpression = new HashSet<>();
-
- if ( expression != null )
- {
- final Matcher matcher = OPTION_COMBO_OPERAND_PATTERN.matcher( expression );
-
- while ( matcher.find() )
- {
- DataElementOperand operand = DataElementOperand.getOperand( matcher.group() );
-
- if ( operand.getOptionComboId() != null )
- {
- operandsInExpression.add( operand );
- }
- }
- }
-
- return operandsInExpression;
- }
-
- @Override
- @Transactional
- public Set<String> getAggregatesInExpression( String expression )
- {
- Pattern prefix=CustomFunctions.getAggregatePrefixPattern();
- Set<String> aggregates = new HashSet<>();
-
- if ( expression != null )
- {
- final Matcher matcher = prefix.matcher(expression);
- int scan=0, len=expression.length();
-
- while ((scan<len)&&( matcher.find(scan) ))
- {
- int start=matcher.end();
- int end=Expression.matchExpression(expression,start);
- if (end<0) {
- log.warn("Bad expression starting at "+start+" in "+expression);}
- if (end>0) {
- aggregates.add(expression.substring(start,end));
- scan=end+1;}
- else scan=start+1;
- }
- }
-
- return aggregates;
- }
-
- @Override
- @Transactional
- public Set<DataElement> getDataElementsInIndicators( Collection<Indicator> indicators )
- {
- Set<DataElement> dataElements = new HashSet<>();
-
- for ( Indicator indicator : indicators )
- {
- dataElements.addAll( getDataElementsInExpression( indicator.getNumerator() ) );
- dataElements.addAll( getDataElementsInExpression( indicator.getDenominator() ) );
- }
-
- return dataElements;
- }
-
- @Override
- @Transactional
- public Set<DataElement> getDataElementTotalsInIndicators( Collection<Indicator> indicators )
- {
- Set<DataElement> dataElements = new HashSet<>();
-
- for ( Indicator indicator : indicators )
- {
- dataElements.addAll( getDataElementsInExpressionInternal( DATA_ELEMENT_TOTAL_PATTERN, indicator.getNumerator() ) );
- dataElements.addAll( getDataElementsInExpressionInternal( DATA_ELEMENT_TOTAL_PATTERN, indicator.getDenominator() ) );
- }
-
- return dataElements;
- }
-
- @Override
- @Transactional
- public Set<DataElement> getDataElementWithOptionCombosInIndicators( Collection<Indicator> indicators )
- {
- Set<DataElement> dataElements = new HashSet<>();
-
- for ( Indicator indicator : indicators )
- {
- dataElements.addAll( getDataElementsInExpressionInternal( OPTION_COMBO_OPERAND_PATTERN, indicator.getNumerator() ) );
- dataElements.addAll( getDataElementsInExpressionInternal( OPTION_COMBO_OPERAND_PATTERN, indicator.getDenominator() ) );
- }
-
- return dataElements;
- }
-
- @Override
- public Set<DimensionalItemObject> getDimensionalItemObjectsInExpression( String expression )
- {
- Set<DimensionalItemObject> dimensionItems = Sets.newHashSet();
-
- if ( expression == null || expression.isEmpty() )
- {
- return dimensionItems;
- }
-
- Matcher matcher = VARIABLE_PATTERN.matcher( expression );
-
- while ( matcher.find() )
- {
- String dimensionItem = matcher.group( 2 );
-
- DimensionalItemObject dimensionItemObject = dimensionService.getDataDimensionalItemObject( dimensionItem );
-
- if ( dimensionItemObject != null )
- {
- dimensionItems.add( dimensionItemObject );
- }
- }
-
- return dimensionItems;
- }
-
- @Override
- public Set<DimensionalItemObject> getDimensionalItemObjectsInIndicators( Collection<Indicator> indicators )
- {
- Set<DimensionalItemObject> items = Sets.newHashSet();
-
- for ( Indicator indicator : indicators )
- {
- items.addAll( getDimensionalItemObjectsInExpression( indicator.getNumerator() ) );
- items.addAll( getDimensionalItemObjectsInExpression( indicator.getDenominator() ) );
- }
-
- return items;
- }
-
- @Override
- public Set<OrganisationUnitGroup> getOrganisationUnitGroupsInExpression( String expression )
- {
- Set<OrganisationUnitGroup> groupsInExpression = new HashSet<>();
-
- if ( expression != null )
- {
- final Matcher matcher = OU_GROUP_PATTERN.matcher( expression );
-
- while ( matcher.find() )
- {
- final OrganisationUnitGroup group = organisationUnitGroupService.getOrganisationUnitGroup( matcher.group( 1 ) );
-
- if ( group != null )
- {
- groupsInExpression.add( group );
- }
- }
- }
-
- return groupsInExpression;
- }
-
- @Override
- public Set<OrganisationUnitGroup> getOrganisationUnitGroupsInIndicators( Collection<Indicator> indicators )
- {
- Set<OrganisationUnitGroup> groups = new HashSet<>();
-
- if ( indicators != null )
- {
- for ( Indicator indicator : indicators )
- {
- groups.addAll( getOrganisationUnitGroupsInExpression( indicator.getNumerator() ) );
- groups.addAll( getOrganisationUnitGroupsInExpression( indicator.getDenominator() ) );
- }
- }
-
- return groups;
- }
-
- @Override
- @Transactional
- public void filterInvalidIndicators( List<Indicator> indicators )
- {
- if ( indicators != null )
- {
- Iterator<Indicator> iterator = indicators.iterator();
-
- while ( iterator.hasNext() )
- {
- Indicator indicator = iterator.next();
-
- if ( !expressionIsValid( indicator.getNumerator() ).isValid() ||
- !expressionIsValid( indicator.getDenominator() ).isValid() )
- {
- iterator.remove();
- log.warn( "Indicator is invalid: " + indicator + ", " + indicator.getNumerator() + ", " + indicator.getDenominator() );
- }
- }
- }
- }
-
- @Override
- @Transactional
- public ExpressionValidationOutcome expressionIsValid( String expression )
- {
- if ( expression == null || expression.isEmpty() )
- {
- return ExpressionValidationOutcome.EXPRESSION_IS_EMPTY;
- }
-
- // ---------------------------------------------------------------------
- // Operands
- // ---------------------------------------------------------------------
-
- StringBuffer sb = new StringBuffer();
- Matcher matcher = VARIABLE_PATTERN.matcher( expression );
-
- while ( matcher.find() )
- {
- String dimensionItem = matcher.group( 2 );
-
- if ( dimensionService.getDataDimensionalItemObject( dimensionItem ) == null )
- {
- return ExpressionValidationOutcome.DIMENSIONAL_ITEM_OBJECT_DOES_NOT_EXIST;
- }
-
- matcher.appendReplacement( sb, "1.1" );
- }
-
- expression = TextUtils.appendTail( matcher, sb );
-
- // ---------------------------------------------------------------------
- // Constants
- // ---------------------------------------------------------------------
-
- matcher = CONSTANT_PATTERN.matcher( expression );
- sb = new StringBuffer();
-
- while ( matcher.find() )
- {
- String constant = matcher.group( 1 );
-
- if ( idObjectManager.getNoAcl( Constant.class, constant ) == null )
- {
- return ExpressionValidationOutcome.CONSTANT_DOES_NOT_EXIST;
- }
-
- matcher.appendReplacement( sb, "1.1" );
- }
-
- expression = TextUtils.appendTail( matcher, sb );
-
- // ---------------------------------------------------------------------
- // Org unit groups
- // ---------------------------------------------------------------------
-
- matcher = OU_GROUP_PATTERN.matcher( expression );
- sb = new StringBuffer();
-
- while ( matcher.find() )
- {
- String group = matcher.group( 1 );
-
- if ( idObjectManager.getNoAcl( OrganisationUnitGroup.class, group ) == null )
- {
- return ExpressionValidationOutcome.ORG_UNIT_GROUP_DOES_NOT_EXIST;
- }
-
- matcher.appendReplacement( sb, "1.1" );
- }
-
- expression = TextUtils.appendTail( matcher, sb );
-
- // ---------------------------------------------------------------------
- // Days
- // ---------------------------------------------------------------------
-
- expression = expression.replaceAll( DAYS_EXPRESSION, "1.1" );
-
- // ---------------------------------------------------------------------
- // Well-formed expression
- // ---------------------------------------------------------------------
-
- if ( MathUtils.expressionHasErrors( expression ) )
- {
- return ExpressionValidationOutcome.EXPRESSION_IS_NOT_WELL_FORMED;
- }
-
- return ExpressionValidationOutcome.VALID;
- }
-
- @Override
- @Transactional
- public String getExpressionDescription( String expression )
- {
- if ( expression == null || expression.isEmpty() )
- {
- return null;
- }
-
- // ---------------------------------------------------------------------
- // Operands
- // ---------------------------------------------------------------------
-
- StringBuffer sb = new StringBuffer();
- Matcher matcher = VARIABLE_PATTERN.matcher( expression );
-
- while ( matcher.find() )
- {
- String dimensionItem = matcher.group( 2 );
-
- DimensionalItemObject dimensionItemObject = dimensionService.getDataDimensionalItemObject( dimensionItem );
-
- if ( dimensionItemObject == null )
- {
- throw new InvalidIdentifierReferenceException( "Identifier does not reference a dimensional item object: " + dimensionItem );
- }
-
- matcher.appendReplacement( sb, Matcher.quoteReplacement( dimensionItemObject.getDisplayName() ) );
- }
-
- expression = TextUtils.appendTail( matcher, sb );
-
- // ---------------------------------------------------------------------
- // Constants
- // ---------------------------------------------------------------------
-
- sb = new StringBuffer();
- matcher = CONSTANT_PATTERN.matcher( expression );
-
- while ( matcher.find() )
- {
- String co = matcher.group( 1 );
-
- Constant constant = constantService.getConstant( co );
-
- if ( constant == null )
- {
- throw new InvalidIdentifierReferenceException( "Identifier does not reference a constant: " + co );
- }
-
- matcher.appendReplacement( sb, Matcher.quoteReplacement( constant.getDisplayName() ) );
- }
-
- expression = TextUtils.appendTail( matcher, sb );
-
- // ---------------------------------------------------------------------
- // Org unit groups
- // ---------------------------------------------------------------------
-
- sb = new StringBuffer();
- matcher = OU_GROUP_PATTERN.matcher( expression );
-
- while ( matcher.find() )
- {
- String oug = matcher.group( 1 );
-
- OrganisationUnitGroup group = organisationUnitGroupService.getOrganisationUnitGroup( oug );
-
- if ( group == null )
- {
- throw new InvalidIdentifierReferenceException( "Identifier does not reference an organisation unit group: " + oug );
- }
-
- matcher.appendReplacement( sb, Matcher.quoteReplacement( group.getDisplayName() ) );
- }
-
- expression = TextUtils.appendTail( matcher, sb );
-
- // ---------------------------------------------------------------------
- // Days
- // ---------------------------------------------------------------------
-
- sb = new StringBuffer();
- matcher = DAYS_PATTERN.matcher( expression );
-
- while ( matcher.find() )
- {
- matcher.appendReplacement( sb, DAYS_DESCRIPTION );
- }
-
- expression = TextUtils.appendTail( matcher, sb );
-
- return expression;
- }
-
- @Override
- @Transactional
- public void explodeValidationRuleExpressions( Collection<ValidationRule> validationRules )
- {
- if ( validationRules != null && !validationRules.isEmpty() )
- {
- Set<String> dataElementTotals = new HashSet<>();
-
- for ( ValidationRule rule : validationRules )
- {
- dataElementTotals.addAll( RegexUtils.getMatches( DATA_ELEMENT_TOTAL_PATTERN, rule.getLeftSide().getExpression(), 1 ) );
- dataElementTotals.addAll( RegexUtils.getMatches( DATA_ELEMENT_TOTAL_PATTERN, rule.getRightSide().getExpression(), 1 ) );
- }
-
- if ( !dataElementTotals.isEmpty() )
- {
- final ListMap<String, String> dataElementMap = dataElementService.getDataElementCategoryOptionComboMap( dataElementTotals );
-
- if ( !dataElementMap.isEmpty() )
- {
- for ( ValidationRule rule : validationRules )
- {
- rule.getLeftSide().setExplodedExpression( explodeExpression( rule.getLeftSide().getExplodedExpressionFallback(), dataElementMap ) );
- rule.getRightSide().setExplodedExpression( explodeExpression( rule.getRightSide().getExplodedExpressionFallback(), dataElementMap ) );
- }
- }
- }
- }
- }
-
- private String explodeExpression( String expression, ListMap<String, String> dataElementOptionComboMap )
- {
- if ( expression == null || expression.isEmpty() )
- {
- return null;
- }
-
- StringBuffer sb = new StringBuffer();
- Matcher matcher = OPERAND_PATTERN.matcher( expression );
-
- while ( matcher.find() )
- {
- if ( operandIsTotal( matcher ) )
- {
- final StringBuilder replace = new StringBuilder( PAR_OPEN );
-
- String de = matcher.group( 1 );
-
- List<String> cocs = dataElementOptionComboMap.get( de );
-
- for ( String coc : cocs )
- {
- replace.append( EXP_OPEN ).append( de ).append( SEPARATOR ).append(
- coc ).append( EXP_CLOSE ).append( "+" );
- }
-
- replace.deleteCharAt( replace.length() - 1 ).append( PAR_CLOSE );
- matcher.appendReplacement( sb, Matcher.quoteReplacement( replace.toString() ) );
- }
- }
-
- return TextUtils.appendTail( matcher, sb );
- }
-
- @Override
- @Transactional
- public String explodeExpression( String expression )
- {
- if ( expression == null || expression.isEmpty() )
- {
- return null;
- }
-
- StringBuffer sb = new StringBuffer();
- Matcher matcher = OPERAND_PATTERN.matcher( expression );
-
- while ( matcher.find() )
- {
- if ( operandIsTotal( matcher ) )
- {
- final StringBuilder replace = new StringBuilder( PAR_OPEN );
-
- final DataElement dataElement = idObjectManager.getNoAcl( DataElement.class, matcher.group( 1 ) );
-
- final DataElementCategoryCombo categoryCombo = dataElement.getCategoryCombo();
-
- for ( DataElementCategoryOptionCombo categoryOptionCombo : categoryCombo.getOptionCombos() )
- {
- replace.append( EXP_OPEN ).append( dataElement.getUid() ).append( SEPARATOR ).append(
- categoryOptionCombo.getUid() ).append( EXP_CLOSE ).append( "+" );
- }
-
- replace.deleteCharAt( replace.length() - 1 ).append( PAR_CLOSE );
- matcher.appendReplacement( sb, Matcher.quoteReplacement( replace.toString() ) );
- }
- }
-
- return TextUtils.appendTail( matcher, sb );
- }
-
- @Override
- @Transactional
- public void substituteExpressions( Collection<Indicator> indicators, Integer days )
- {
- if ( indicators != null && !indicators.isEmpty() )
- {
- Map<String, Constant> constants = new CachingMap<String, Constant>().
- load( idObjectManager.getAllNoAcl( Constant.class ), c -> c.getUid() );
-
- Map<String, OrganisationUnitGroup> orgUnitGroups = new CachingMap<String, OrganisationUnitGroup>().
- load( idObjectManager.getAllNoAcl( OrganisationUnitGroup.class ), g -> g.getUid() );
-
- for ( Indicator indicator : indicators )
- {
- indicator.setExplodedNumerator( substituteExpression( indicator.getNumerator(), constants, orgUnitGroups, days ) );
- indicator.setExplodedDenominator( substituteExpression( indicator.getDenominator(), constants, orgUnitGroups, days ) );
- }
- }
- }
-
- private String substituteExpression( String expression, Map<String, Constant> constants, Map<String, OrganisationUnitGroup> orgUnitGroups, Integer days )
- {
- if ( expression == null || expression.isEmpty() )
- {
- return null;
- }
-
- // ---------------------------------------------------------------------
- // Constants
- // ---------------------------------------------------------------------
-
- StringBuffer sb = new StringBuffer();
- Matcher matcher = CONSTANT_PATTERN.matcher( expression );
-
- while ( matcher.find() )
- {
- String co = matcher.group( 1 );
-
- Constant constant = constants.get( co );
-
- String replacement = constant != null ? String.valueOf( constant.getValue() ) : NULL_REPLACEMENT;
-
- matcher.appendReplacement( sb, Matcher.quoteReplacement( replacement ) );
- }
-
- expression = TextUtils.appendTail( matcher, sb );
-
- // ---------------------------------------------------------------------
- // Org unit groups
- // ---------------------------------------------------------------------
-
- sb = new StringBuffer();
- matcher = OU_GROUP_PATTERN.matcher( expression );
-
- while ( matcher.find() )
- {
- String oug = matcher.group( 1 );
-
- OrganisationUnitGroup group = orgUnitGroups.get( oug );
-
- String replacement = group != null ? String.valueOf( group.getMembers().size() ) : NULL_REPLACEMENT;
-
- matcher.appendReplacement( sb, replacement );
-
- //TODO sub tree
- }
-
- expression = TextUtils.appendTail( matcher, sb );
-
- // ---------------------------------------------------------------------
- // Days
- // ---------------------------------------------------------------------
-
- sb = new StringBuffer();
- matcher = DAYS_PATTERN.matcher( expression );
-
- while ( matcher.find() )
- {
- String replacement = days != null ? String.valueOf( days ) : NULL_REPLACEMENT;
-
- matcher.appendReplacement( sb, replacement );
- }
-
- return TextUtils.appendTail( matcher, sb );
- }
-
- @Override
- public String generateExpression( String expression, Map<? extends DimensionalItemObject, Double> valueMap,
- Map<String, Double> constantMap, Map<String, Integer> orgUnitCountMap, Integer days, MissingValueStrategy missingValueStrategy )
- {
- return generateExpression( expression, valueMap, constantMap, orgUnitCountMap, days, missingValueStrategy, null, null );
- }
-
- private String generateExpression( String expression, Map<? extends DimensionalItemObject, Double> valueMap,
- Map<String, Double> constantMap, Map<String, Integer> orgUnitCountMap, Integer days,
- MissingValueStrategy missingValueStrategy, Set<DataElementOperand> incompleteValues,
- Map<String, List<Double>> aggregateMap)
- {
- if ( expression == null || expression.isEmpty() )
- {
- return null;
- }
-
- Map<String, Double> dimensionItemValueMap = valueMap.entrySet().stream().
- collect( Collectors.toMap( e -> e.getKey().getDimensionItem(), e -> e.getValue() ) );
-
- Set<String> incompleteItems = incompleteValues != null ? incompleteValues.
- stream().map( i -> i.getDimensionItem() ).collect( Collectors.toSet() ) : Sets.newHashSet();
-
- missingValueStrategy = missingValueStrategy == null ? NEVER_SKIP : missingValueStrategy;
-
- // ---------------------------------------------------------------------
- // Substitute aggregates
- // ---------------------------------------------------------------------
-
- StringBuffer sb = new StringBuffer();
-
- Pattern prefix=CustomFunctions.getAggregatePrefixPattern();
- Matcher matcher = prefix.matcher( expression );
-
- int scan=0, len=expression.length(), tail=0;
- while ((scan<len)&&(matcher.find(scan)))
- {
- int start=matcher.end();
- int end=Expression.matchExpression(expression, start);
- if (end<0) {
- sb.append(expression.substring(scan,start));
- scan=start+1;
- tail=start;
- }
- else if ((aggregateMap==null)||(expression.charAt(start)=='<')) {
- sb.append(expression.substring(scan,end));
- scan=end+1; tail=end;
- }
- else {
- String sub_expression=expression.substring(start,end);
- List<Double> samples = aggregateMap.get( sub_expression );
-
- if (samples == null) {
- if (SKIP_IF_ANY_VALUE_MISSING.equals( missingValueStrategy )) {
- return null;}
- else {}}
- else {
- String literal = ( samples == null) ? ("[]") : (samples.toString());
- sb.append(expression.substring(scan,start));
- sb.append(literal);}
- scan=end; tail=end;
- }
- }
-
- sb.append(expression.substring(tail));
- expression = sb.toString();
-
- // ---------------------------------------------------------------------
- // DimensionalItemObjects
- // ---------------------------------------------------------------------
-
- sb = new StringBuffer();
- matcher = VARIABLE_PATTERN.matcher( expression );
-
- int matchCount = 0;
- int valueCount = 0;
-
- while ( matcher.find() )
- {
- matchCount++;
-
- String dimItem = matcher.group( 2 );
-
- final Double value = dimensionItemValueMap.get( dimItem );
-
- boolean missingValue = value == null || incompleteItems.contains( dimItem );
-
- if ( missingValue && SKIP_IF_ANY_VALUE_MISSING.equals( missingValueStrategy ) )
- {
- return null;
- }
-
- if ( !missingValue )
- {
- valueCount++;
- }
-
- String replacement = value != null ? String.valueOf( value ) : NULL_REPLACEMENT;
-
- matcher.appendReplacement( sb, Matcher.quoteReplacement( replacement ) );
- }
-
- if ( SKIP_IF_ALL_VALUES_MISSING.equals( missingValueStrategy ) && matchCount > 0 && valueCount == 0 )
- {
- return null;
- }
-
- expression = TextUtils.appendTail( matcher, sb );
-
- // ---------------------------------------------------------------------
- // Constants
- // ---------------------------------------------------------------------
-
- sb = new StringBuffer();
- matcher = CONSTANT_PATTERN.matcher( expression );
-
- while ( matcher.find() )
- {
- final Double constant = constantMap != null ? constantMap.get( matcher.group( 1 ) ) : null;
-
- String replacement = constant != null ? String.valueOf( constant ) : NULL_REPLACEMENT;
-
- matcher.appendReplacement( sb, replacement );
- }
-
- expression = TextUtils.appendTail( matcher, sb );
-
- // ---------------------------------------------------------------------
- // Org unit groups
- // ---------------------------------------------------------------------
-
- sb = new StringBuffer();
- matcher = OU_GROUP_PATTERN.matcher( expression );
-
- while ( matcher.find() )
- {
- final Integer count = orgUnitCountMap != null ? orgUnitCountMap.get( matcher.group( 1 ) ) : null;
-
- String replacement = count != null ? String.valueOf( count ) : NULL_REPLACEMENT;
-
- matcher.appendReplacement( sb, replacement );
- }
-
- expression = TextUtils.appendTail( matcher, sb );
-
- // ---------------------------------------------------------------------
- // Days
- // ---------------------------------------------------------------------
-
- sb = new StringBuffer();
- matcher = DAYS_PATTERN.matcher( expression );
-
- while ( matcher.find() )
- {
- String replacement = days != null ? String.valueOf( days ) : NULL_REPLACEMENT;
-
- matcher.appendReplacement( sb, replacement );
- }
-
- return TextUtils.appendTail( matcher, sb );
- }
-
- @Override
- @Transactional
- public List<DataElementOperand> getOperandsInIndicators( List<Indicator> indicators )
- {
- final List<DataElementOperand> operands = new ArrayList<>();
-
- for ( Indicator indicator : indicators )
- {
- Set<DataElementOperand> temp = getOperandsInExpression( indicator.getExplodedNumerator() );
- operands.addAll( temp != null ? temp : new HashSet<DataElementOperand>() );
-
- temp = getOperandsInExpression( indicator.getExplodedDenominator() );
- operands.addAll( temp != null ? temp : new HashSet<DataElementOperand>() );
- }
-
- return operands;
- }
-
- // -------------------------------------------------------------------------
- // Supportive methods
- // -------------------------------------------------------------------------
-
- private boolean operandIsTotal( Matcher matcher )
- {
- return matcher != null && StringUtils.trimToEmpty( matcher.group( 2 ) ).isEmpty();
- }
+ private static final Log log = LogFactory.getLog( DefaultExpressionService.class );
+
+ // -------------------------------------------------------------------------
+ // Dependencies
+ // -------------------------------------------------------------------------
+
+ private GenericStore<Expression> expressionStore;
+
+ public void setExpressionStore( GenericStore<Expression> expressionStore )
+ {
+ this.expressionStore = expressionStore;
+ }
+
+ private DataElementService dataElementService;
+
+ public void setDataElementService( DataElementService dataElementService )
+ {
+ this.dataElementService = dataElementService;
+ }
+
+ private ConstantService constantService;
+
+ public void setConstantService( ConstantService constantService )
+ {
+ this.constantService = constantService;
+ }
+
+ private DataElementCategoryService categoryService;
+
+ public void setCategoryService( DataElementCategoryService categoryService )
+ {
+ this.categoryService = categoryService;
+ }
+
+ private OrganisationUnitGroupService organisationUnitGroupService;
+
+ public void setOrganisationUnitGroupService( OrganisationUnitGroupService organisationUnitGroupService )
+ {
+ this.organisationUnitGroupService = organisationUnitGroupService;
+ }
+
+ private DimensionService dimensionService;
+
+ public void setDimensionService( DimensionService dimensionService )
+ {
+ this.dimensionService = dimensionService;
+ }
+
+ private IdentifiableObjectManager idObjectManager;
+
+ public void setIdObjectManager( IdentifiableObjectManager idObjectManager )
+ {
+ this.idObjectManager = idObjectManager;
+ }
+
+ // -------------------------------------------------------------------------
+ // Expression CRUD operations
+ // -------------------------------------------------------------------------
+
+ @Override
+ @Transactional
+ public int addExpression( Expression expression )
+ {
+ return expressionStore.save( expression );
+ }
+
+ @Override
+ @Transactional
+ public void deleteExpression( Expression expression )
+ {
+ expressionStore.delete( expression );
+ }
+
+ @Override
+ @Transactional
+ public Expression getExpression( int id )
+ {
+ return expressionStore.get( id );
+ }
+
+ @Override
+ @Transactional
+ public void updateExpression( Expression expression )
+ {
+ expressionStore.update( expression );
+ }
+
+ @Override
+ @Transactional
+ public List<Expression> getAllExpressions()
+ {
+ return expressionStore.getAll();
+ }
+
+ // -------------------------------------------------------------------------
+ // Business logic
+ // -------------------------------------------------------------------------
+
+ @Override
+ public Double getIndicatorValue( Indicator indicator, Period period,
+ Map<? extends DimensionalItemObject, Double> valueMap, Map<String, Double> constantMap,
+ Map<String, Integer> orgUnitCountMap )
+ {
+ if ( indicator == null || indicator.getExplodedNumeratorFallback() == null
+ || indicator.getExplodedDenominatorFallback() == null )
+ {
+ return null;
+ }
+
+ Integer days = period != null ? period.getDaysInPeriod() : null;
+
+ final String denominatorExpression = generateExpression( indicator.getExplodedDenominatorFallback(), valueMap,
+ constantMap, orgUnitCountMap, days, NEVER_SKIP );
+
+ if ( denominatorExpression == null )
+ {
+ return null;
+ }
+
+ final double denominatorValue = calculateExpression( denominatorExpression );
+
+ if ( !isEqual( denominatorValue, 0d ) )
+ {
+ final String numeratorExpression = generateExpression( indicator.getExplodedNumeratorFallback(), valueMap,
+ constantMap, orgUnitCountMap, days, NEVER_SKIP );
+
+ if ( numeratorExpression == null )
+ {
+ return null;
+ }
+
+ final double numeratorValue = calculateExpression( numeratorExpression );
+
+ final double annualizationFactor = period != null
+ ? DateUtils.getAnnualizationFactor( indicator, period.getStartDate(), period.getEndDate() ) : 1d;
+ final double factor = indicator.getIndicatorType().getFactor();
+ final double aggregatedValue = (numeratorValue / denominatorValue) * factor * annualizationFactor;
+
+ return aggregatedValue;
+ }
+
+ return null;
+ }
+
+ @Override
+ public Double getExpressionValue( Expression expression, Map<? extends DimensionalItemObject, Double> valueMap,
+ Map<String, Double> constantMap, Map<String, Integer> orgUnitCountMap, Integer days )
+ {
+ return getExpressionValue( expression, valueMap, constantMap, orgUnitCountMap, days, null, null );
+ }
+
+ @Override
+ public Double getExpressionValue( Expression expression, Map<? extends DimensionalItemObject, Double> valueMap,
+ Map<String, Double> constantMap, Map<String, Integer> orgUnitCountMap, Integer days,
+ Set<DataElementOperand> incompleteValues )
+ {
+ return getExpressionValue( expression, valueMap, constantMap, orgUnitCountMap, days, incompleteValues, null );
+ }
+
+ @Override
+ public Double getExpressionValue( Expression expression, Map<? extends DimensionalItemObject, Double> valueMap,
+ Map<String, Double> constantMap, Map<String, Integer> orgUnitCountMap, Integer days,
+ Set<DataElementOperand> incompleteValues, ListMap<String, Double> aggregateMap )
+ {
+ String expressionString = generateExpression( expression.getExplodedExpressionFallback(), valueMap, constantMap,
+ orgUnitCountMap, days, expression.getMissingValueStrategy(), incompleteValues, aggregateMap );
+
+ Double result = expressionString != null ? calculateExpression( expressionString ) : null;
+
+ return result;
+ }
+
+ @Override
+ @Transactional
+ public Set<DataElement> getDataElementsInExpression( String expression )
+ {
+ return getDataElementsInExpressionInternal( OPERAND_PATTERN, expression );
+ }
+
+ private Set<DataElement> getDataElementsInExpressionInternal( Pattern pattern, String expression )
+ {
+ Set<DataElement> dataElements = new HashSet<>();
+
+ if ( expression != null )
+ {
+ final Matcher matcher = pattern.matcher( expression );
+
+ while ( matcher.find() )
+ {
+ final DataElement dataElement = dataElementService.getDataElement( matcher.group( 1 ) );
+
+ if ( dataElement != null )
+ {
+ dataElements.add( dataElement );
+ }
+ }
+ }
+
+ return dataElements;
+ }
+
+ @Override
+ @Transactional
+ public Set<DataElementCategoryOptionCombo> getOptionCombosInExpression( String expression )
+ {
+ Set<DataElementCategoryOptionCombo> optionCombosInExpression = new HashSet<>();
+
+ if ( expression != null )
+ {
+ final Matcher matcher = OPTION_COMBO_OPERAND_PATTERN.matcher( expression );
+
+ while ( matcher.find() )
+ {
+ DataElementCategoryOptionCombo categoryOptionCombo = categoryService
+ .getDataElementCategoryOptionCombo( matcher.group( 2 ) );
+
+ if ( categoryOptionCombo != null )
+ {
+ optionCombosInExpression.add( categoryOptionCombo );
+ }
+ }
+ }
+
+ return optionCombosInExpression;
+ }
+
+ @Override
+ @Transactional
+ public Set<DataElementOperand> getOperandsInExpression( String expression )
+ {
+ Set<DataElementOperand> operandsInExpression = new HashSet<>();
+
+ if ( expression != null )
+ {
+ final Matcher matcher = OPTION_COMBO_OPERAND_PATTERN.matcher( expression );
+
+ while ( matcher.find() )
+ {
+ DataElementOperand operand = DataElementOperand.getOperand( matcher.group() );
+
+ if ( operand.getOptionComboId() != null )
+ {
+ operandsInExpression.add( operand );
+ }
+ }
+ }
+
+ return operandsInExpression;
+ }
+
+ @Override
+ @Transactional
+ public Set<String> getAggregatesInExpression( String expression )
+ {
+ Pattern prefix = CustomFunctions.getAggregatePrefixPattern();
+ Set<String> aggregates = new HashSet<>();
+
+ if ( expression != null )
+ {
+ final Matcher matcher = prefix.matcher( expression );
+
+ int scan = 0, len = expression.length();
+
+ while ( (scan < len) && (matcher.find( scan )) )
+ {
+ int start = matcher.end();
+
+ int end = Expression.matchExpression( expression, start );
+
+ if ( end < 0 )
+ {
+ log.warn( "Bad expression starting at " + start + " in " + expression );
+ }
+ else if ( end > 0 )
+ {
+ aggregates.add( expression.substring( start, end ) );
+ scan = end + 1;
+ }
+ else
+ {
+ scan = start + 1;
+ }
+ }
+ }
+
+ return aggregates;
+ }
+
+ @Override
+ @Transactional
+ public Set<DataElement> getDataElementsInIndicators( Collection<Indicator> indicators )
+ {
+ Set<DataElement> dataElements = new HashSet<>();
+
+ for ( Indicator indicator : indicators )
+ {
+ dataElements.addAll( getDataElementsInExpression( indicator.getNumerator() ) );
+ dataElements.addAll( getDataElementsInExpression( indicator.getDenominator() ) );
+ }
+
+ return dataElements;
+ }
+
+ @Override
+ @Transactional
+ public Set<DataElement> getDataElementTotalsInIndicators( Collection<Indicator> indicators )
+ {
+ Set<DataElement> dataElements = new HashSet<>();
+
+ for ( Indicator indicator : indicators )
+ {
+ dataElements
+ .addAll( getDataElementsInExpressionInternal( DATA_ELEMENT_TOTAL_PATTERN, indicator.getNumerator() ) );
+ dataElements.addAll(
+ getDataElementsInExpressionInternal( DATA_ELEMENT_TOTAL_PATTERN, indicator.getDenominator() ) );
+ }
+
+ return dataElements;
+ }
+
+ @Override
+ @Transactional
+ public Set<DataElement> getDataElementWithOptionCombosInIndicators( Collection<Indicator> indicators )
+ {
+ Set<DataElement> dataElements = new HashSet<>();
+
+ for ( Indicator indicator : indicators )
+ {
+ dataElements.addAll( getDataElementsInExpressionInternal( OPTION_COMBO_OPERAND_PATTERN, indicator.getNumerator() ) );
+ dataElements.addAll( getDataElementsInExpressionInternal( OPTION_COMBO_OPERAND_PATTERN, indicator.getDenominator() ) );
+ }
+
+ return dataElements;
+ }
+
+ @Override
+ public Set<DimensionalItemObject> getDimensionalItemObjectsInExpression( String expression )
+ {
+ Set<DimensionalItemObject> dimensionItems = Sets.newHashSet();
+
+ if ( expression == null || expression.isEmpty() )
+ {
+ return dimensionItems;
+ }
+
+ Matcher matcher = VARIABLE_PATTERN.matcher( expression );
+
+ while ( matcher.find() )
+ {
+ String dimensionItem = matcher.group( 2 );
+
+ DimensionalItemObject dimensionItemObject = dimensionService.getDataDimensionalItemObject( dimensionItem );
+
+ if ( dimensionItemObject != null )
+ {
+ dimensionItems.add( dimensionItemObject );
+ }
+ }
+
+ return dimensionItems;
+ }
+
+ @Override
+ public Set<DimensionalItemObject> getDimensionalItemObjectsInIndicators( Collection<Indicator> indicators )
+ {
+ Set<DimensionalItemObject> items = Sets.newHashSet();
+
+ for ( Indicator indicator : indicators )
+ {
+ items.addAll( getDimensionalItemObjectsInExpression( indicator.getNumerator() ) );
+ items.addAll( getDimensionalItemObjectsInExpression( indicator.getDenominator() ) );
+ }
+
+ return items;
+ }
+
+ @Override
+ public Set<OrganisationUnitGroup> getOrganisationUnitGroupsInExpression( String expression )
+ {
+ Set<OrganisationUnitGroup> groupsInExpression = new HashSet<>();
+
+ if ( expression != null )
+ {
+ final Matcher matcher = OU_GROUP_PATTERN.matcher( expression );
+
+ while ( matcher.find() )
+ {
+ final OrganisationUnitGroup group = organisationUnitGroupService
+ .getOrganisationUnitGroup( matcher.group( 1 ) );
+
+ if ( group != null )
+ {
+ groupsInExpression.add( group );
+ }
+ }
+ }
+
+ return groupsInExpression;
+ }
+
+ @Override
+ public Set<OrganisationUnitGroup> getOrganisationUnitGroupsInIndicators( Collection<Indicator> indicators )
+ {
+ Set<OrganisationUnitGroup> groups = new HashSet<>();
+
+ if ( indicators != null )
+ {
+ for ( Indicator indicator : indicators )
+ {
+ groups.addAll( getOrganisationUnitGroupsInExpression( indicator.getNumerator() ) );
+ groups.addAll( getOrganisationUnitGroupsInExpression( indicator.getDenominator() ) );
+ }
+ }
+
+ return groups;
+ }
+
+ @Override
+ @Transactional
+ public void filterInvalidIndicators( List<Indicator> indicators )
+ {
+ if ( indicators != null )
+ {
+ Iterator<Indicator> iterator = indicators.iterator();
+
+ while ( iterator.hasNext() )
+ {
+ Indicator indicator = iterator.next();
+
+ if ( !expressionIsValid( indicator.getNumerator() ).isValid()
+ || !expressionIsValid( indicator.getDenominator() ).isValid() )
+ {
+ iterator.remove();
+ log.warn( "Indicator is invalid: " + indicator + ", " + indicator.getNumerator() + ", "
+ + indicator.getDenominator() );
+ }
+ }
+ }
+ }
+
+ @Override
+ @Transactional
+ public ExpressionValidationOutcome expressionIsValid( String expression )
+ {
+ if ( expression == null || expression.isEmpty() )
+ {
+ return ExpressionValidationOutcome.EXPRESSION_IS_EMPTY;
+ }
+
+ // ---------------------------------------------------------------------
+ // Operands
+ // ---------------------------------------------------------------------
+
+ StringBuffer sb = new StringBuffer();
+ Matcher matcher = VARIABLE_PATTERN.matcher( expression );
+
+ while ( matcher.find() )
+ {
+ String dimensionItem = matcher.group( 2 );
+
+ if ( dimensionService.getDataDimensionalItemObject( dimensionItem ) == null )
+ {
+ return ExpressionValidationOutcome.DIMENSIONAL_ITEM_OBJECT_DOES_NOT_EXIST;
+ }
+
+ matcher.appendReplacement( sb, "1.1" );
+ }
+
+ expression = TextUtils.appendTail( matcher, sb );
+
+ // ---------------------------------------------------------------------
+ // Constants
+ // ---------------------------------------------------------------------
+
+ matcher = CONSTANT_PATTERN.matcher( expression );
+ sb = new StringBuffer();
+
+ while ( matcher.find() )
+ {
+ String constant = matcher.group( 1 );
+
+ if ( idObjectManager.getNoAcl( Constant.class, constant ) == null )
+ {
+ return ExpressionValidationOutcome.CONSTANT_DOES_NOT_EXIST;
+ }
+
+ matcher.appendReplacement( sb, "1.1" );
+ }
+
+ expression = TextUtils.appendTail( matcher, sb );
+
+ // ---------------------------------------------------------------------
+ // Org unit groups
+ // ---------------------------------------------------------------------
+
+ matcher = OU_GROUP_PATTERN.matcher( expression );
+ sb = new StringBuffer();
+
+ while ( matcher.find() )
+ {
+ String group = matcher.group( 1 );
+
+ if ( idObjectManager.getNoAcl( OrganisationUnitGroup.class, group ) == null )
+ {
+ return ExpressionValidationOutcome.ORG_UNIT_GROUP_DOES_NOT_EXIST;
+ }
+
+ matcher.appendReplacement( sb, "1.1" );
+ }
+
+ expression = TextUtils.appendTail( matcher, sb );
+
+ // ---------------------------------------------------------------------
+ // Days
+ // ---------------------------------------------------------------------
+
+ expression = expression.replaceAll( DAYS_EXPRESSION, "1.1" );
+
+ // ---------------------------------------------------------------------
+ // Well-formed expression
+ // ---------------------------------------------------------------------
+
+ if ( MathUtils.expressionHasErrors( expression ) )
+ {
+ return ExpressionValidationOutcome.EXPRESSION_IS_NOT_WELL_FORMED;
+ }
+
+ return ExpressionValidationOutcome.VALID;
+ }
+
+ @Override
+ @Transactional
+ public String getExpressionDescription( String expression )
+ {
+ if ( expression == null || expression.isEmpty() )
+ {
+ return null;
+ }
+
+ // ---------------------------------------------------------------------
+ // Operands
+ // ---------------------------------------------------------------------
+
+ StringBuffer sb = new StringBuffer();
+ Matcher matcher = VARIABLE_PATTERN.matcher( expression );
+
+ while ( matcher.find() )
+ {
+ String dimensionItem = matcher.group( 2 );
+
+ DimensionalItemObject dimensionItemObject = dimensionService.getDataDimensionalItemObject( dimensionItem );
+
+ if ( dimensionItemObject == null )
+ {
+ throw new InvalidIdentifierReferenceException( "Identifier does not reference a dimensional item object: " + dimensionItem );
+ }
+
+ matcher.appendReplacement( sb, Matcher.quoteReplacement( dimensionItemObject.getDisplayName() ) );
+ }
+
+ expression = TextUtils.appendTail( matcher, sb );
+
+ // ---------------------------------------------------------------------
+ // Constants
+ // ---------------------------------------------------------------------
+
+ sb = new StringBuffer();
+ matcher = CONSTANT_PATTERN.matcher( expression );
+
+ while ( matcher.find() )
+ {
+ String co = matcher.group( 1 );
+
+ Constant constant = constantService.getConstant( co );
+
+ if ( constant == null )
+ {
+ throw new InvalidIdentifierReferenceException( "Identifier does not reference a constant: " + co );
+ }
+
+ matcher.appendReplacement( sb, Matcher.quoteReplacement( constant.getDisplayName() ) );
+ }
+
+ expression = TextUtils.appendTail( matcher, sb );
+
+ // ---------------------------------------------------------------------
+ // Org unit groups
+ // ---------------------------------------------------------------------
+
+ sb = new StringBuffer();
+ matcher = OU_GROUP_PATTERN.matcher( expression );
+
+ while ( matcher.find() )
+ {
+ String oug = matcher.group( 1 );
+
+ OrganisationUnitGroup group = organisationUnitGroupService.getOrganisationUnitGroup( oug );
+
+ if ( group == null )
+ {
+ throw new InvalidIdentifierReferenceException( "Identifier does not reference an organisation unit group: " + oug );
+ }
+
+ matcher.appendReplacement( sb, Matcher.quoteReplacement( group.getDisplayName() ) );
+ }
+
+ expression = TextUtils.appendTail( matcher, sb );
+
+ // ---------------------------------------------------------------------
+ // Days
+ // ---------------------------------------------------------------------
+
+ sb = new StringBuffer();
+ matcher = DAYS_PATTERN.matcher( expression );
+
+ while ( matcher.find() )
+ {
+ matcher.appendReplacement( sb, DAYS_DESCRIPTION );
+ }
+
+ expression = TextUtils.appendTail( matcher, sb );
+
+ return expression;
+ }
+
+ @Override
+ @Transactional
+ public void explodeValidationRuleExpressions( Collection<ValidationRule> validationRules )
+ {
+ if ( validationRules != null && !validationRules.isEmpty() )
+ {
+ Set<String> dataElementTotals = new HashSet<>();
+
+ for ( ValidationRule rule : validationRules )
+ {
+ dataElementTotals.addAll(
+ RegexUtils.getMatches( DATA_ELEMENT_TOTAL_PATTERN, rule.getLeftSide().getExpression(), 1 ) );
+ dataElementTotals.addAll(
+ RegexUtils.getMatches( DATA_ELEMENT_TOTAL_PATTERN, rule.getRightSide().getExpression(), 1 ) );
+ }
+
+ if ( !dataElementTotals.isEmpty() )
+ {
+ final ListMap<String, String> dataElementMap = dataElementService
+ .getDataElementCategoryOptionComboMap( dataElementTotals );
+
+ if ( !dataElementMap.isEmpty() )
+ {
+ for ( ValidationRule rule : validationRules )
+ {
+ rule.getLeftSide().setExplodedExpression(
+ explodeExpression( rule.getLeftSide().getExplodedExpressionFallback(), dataElementMap ) );
+ rule.getRightSide().setExplodedExpression(
+ explodeExpression( rule.getRightSide().getExplodedExpressionFallback(), dataElementMap ) );
+ }
+ }
+ }
+ }
+ }
+
+ private String explodeExpression( String expression, ListMap<String, String> dataElementOptionComboMap )
+ {
+ if ( expression == null || expression.isEmpty() )
+ {
+ return null;
+ }
+
+ StringBuffer sb = new StringBuffer();
+ Matcher matcher = OPERAND_PATTERN.matcher( expression );
+
+ while ( matcher.find() )
+ {
+ if ( operandIsTotal( matcher ) )
+ {
+ final StringBuilder replace = new StringBuilder( PAR_OPEN );
+
+ String de = matcher.group( 1 );
+
+ List<String> cocs = dataElementOptionComboMap.get( de );
+
+ for ( String coc : cocs )
+ {
+ replace.append( EXP_OPEN ).append( de ).append( SEPARATOR ).
+ append( coc ).append( EXP_CLOSE ).append( "+" );
+ }
+
+ replace.deleteCharAt( replace.length() - 1 ).append( PAR_CLOSE );
+ matcher.appendReplacement( sb, Matcher.quoteReplacement( replace.toString() ) );
+ }
+ }
+
+ return TextUtils.appendTail( matcher, sb );
+ }
+
+ @Override
+ @Transactional
+ public String explodeExpression( String expression )
+ {
+ if ( expression == null || expression.isEmpty() )
+ {
+ return null;
+ }
+
+ StringBuffer sb = new StringBuffer();
+ Matcher matcher = OPERAND_PATTERN.matcher( expression );
+
+ while ( matcher.find() )
+ {
+ if ( operandIsTotal( matcher ) )
+ {
+ final StringBuilder replace = new StringBuilder( PAR_OPEN );
+
+ final DataElement dataElement = idObjectManager.getNoAcl( DataElement.class, matcher.group( 1 ) );
+
+ final DataElementCategoryCombo categoryCombo = dataElement.getCategoryCombo();
+
+ for ( DataElementCategoryOptionCombo categoryOptionCombo : categoryCombo.getOptionCombos() )
+ {
+ replace.append( EXP_OPEN ).append( dataElement.getUid() ).append( SEPARATOR ).
+ append( categoryOptionCombo.getUid() ).append( EXP_CLOSE ).append( "+" );
+ }
+
+ replace.deleteCharAt( replace.length() - 1 ).append( PAR_CLOSE );
+ matcher.appendReplacement( sb, Matcher.quoteReplacement( replace.toString() ) );
+ }
+ }
+
+ return TextUtils.appendTail( matcher, sb );
+ }
+
+ @Override
+ @Transactional
+ public void substituteExpressions( Collection<Indicator> indicators, Integer days )
+ {
+ if ( indicators != null && !indicators.isEmpty() )
+ {
+ Map<String, Constant> constants = new CachingMap<String, Constant>()
+ .load( idObjectManager.getAllNoAcl( Constant.class ), c -> c.getUid() );
+
+ Map<String, OrganisationUnitGroup> orgUnitGroups = new CachingMap<String, OrganisationUnitGroup>()
+ .load( idObjectManager.getAllNoAcl( OrganisationUnitGroup.class ), g -> g.getUid() );
+
+ for ( Indicator indicator : indicators )
+ {
+ indicator.setExplodedNumerator( substituteExpression(
+ indicator.getNumerator(), constants, orgUnitGroups, days ) );
+ indicator.setExplodedDenominator( substituteExpression(
+ indicator.getDenominator(), constants, orgUnitGroups, days ) );
+ }
+ }
+ }
+
+ private String substituteExpression( String expression, Map<String, Constant> constants,
+ Map<String, OrganisationUnitGroup> orgUnitGroups, Integer days )
+ {
+ if ( expression == null || expression.isEmpty() )
+ {
+ return null;
+ }
+
+ // ---------------------------------------------------------------------
+ // Constants
+ // ---------------------------------------------------------------------
+
+ StringBuffer sb = new StringBuffer();
+ Matcher matcher = CONSTANT_PATTERN.matcher( expression );
+
+ while ( matcher.find() )
+ {
+ String co = matcher.group( 1 );
+
+ Constant constant = constants.get( co );
+
+ String replacement = constant != null ? String.valueOf( constant.getValue() ) : NULL_REPLACEMENT;
+
+ matcher.appendReplacement( sb, Matcher.quoteReplacement( replacement ) );
+ }
+
+ expression = TextUtils.appendTail( matcher, sb );
+
+ // ---------------------------------------------------------------------
+ // Org unit groups
+ // ---------------------------------------------------------------------
+
+ sb = new StringBuffer();
+ matcher = OU_GROUP_PATTERN.matcher( expression );
+
+ while ( matcher.find() )
+ {
+ String oug = matcher.group( 1 );
+
+ OrganisationUnitGroup group = orgUnitGroups.get( oug );
+
+ String replacement = group != null ? String.valueOf( group.getMembers().size() ) : NULL_REPLACEMENT;
+
+ matcher.appendReplacement( sb, replacement );
+
+ // TODO sub tree
+ }
+
+ expression = TextUtils.appendTail( matcher, sb );
+
+ // ---------------------------------------------------------------------
+ // Days
+ // ---------------------------------------------------------------------
+
+ sb = new StringBuffer();
+ matcher = DAYS_PATTERN.matcher( expression );
+
+ while ( matcher.find() )
+ {
+ String replacement = days != null ? String.valueOf( days ) : NULL_REPLACEMENT;
+
+ matcher.appendReplacement( sb, replacement );
+ }
+
+ return TextUtils.appendTail( matcher, sb );
+ }
+
+ @Override
+ public String generateExpression( String expression, Map<? extends DimensionalItemObject, Double> valueMap,
+ Map<String, Double> constantMap, Map<String, Integer> orgUnitCountMap, Integer days,
+ MissingValueStrategy missingValueStrategy )
+ {
+ return generateExpression( expression, valueMap, constantMap, orgUnitCountMap, days, missingValueStrategy, null,
+ null );
+ }
+
+ private String generateExpression( String expression, Map<? extends DimensionalItemObject, Double> valueMap,
+ Map<String, Double> constantMap, Map<String, Integer> orgUnitCountMap, Integer days,
+ MissingValueStrategy missingValueStrategy, Set<DataElementOperand> incompleteValues,
+ Map<String, List<Double>> aggregateMap )
+ {
+ if ( expression == null || expression.isEmpty() )
+ {
+ return null;
+ }
+
+ Map<String, Double> dimensionItemValueMap = valueMap.entrySet().stream().
+ collect( Collectors.toMap( e -> e.getKey().getDimensionItem(), e -> e.getValue() ) );
+
+ Set<String> incompleteItems = incompleteValues != null ?
+ incompleteValues.stream().map( i -> i.getDimensionItem() ).collect( Collectors.toSet() ) : Sets.newHashSet();
+
+ missingValueStrategy = missingValueStrategy == null ? NEVER_SKIP : missingValueStrategy;
+
+ // ---------------------------------------------------------------------
+ // Substitute aggregates
+ // ---------------------------------------------------------------------
+
+ StringBuffer sb = new StringBuffer();
+
+ Pattern prefix = CustomFunctions.getAggregatePrefixPattern();
+ Matcher matcher = prefix.matcher( expression );
+
+ int scan = 0, len = expression.length(), tail = 0;
+
+ while ( (scan < len) && (matcher.find( scan )) )
+ {
+ int start = matcher.end();
+ int end = Expression.matchExpression( expression, start );
+
+ if ( end < 0 )
+ {
+ sb.append( expression.substring( scan, start ) );
+ scan = start + 1;
+ tail = start;
+ }
+ else if ( ( aggregateMap == null) || ( expression.charAt( start ) == '<' ) )
+ {
+ sb.append( expression.substring( scan, end ) );
+ scan = end + 1;
+ tail = end;
+ }
+ else
+ {
+ String sub_expression = expression.substring( start, end );
+ List<Double> samples = aggregateMap.get( sub_expression );
+
+ if ( samples == null )
+ {
+ if ( SKIP_IF_ANY_VALUE_MISSING.equals( missingValueStrategy ) )
+ {
+ return null;
+ }
+ else
+ {
+ }
+ }
+ else
+ {
+ String literal = (samples == null) ? ("[]") : (samples.toString());
+ sb.append( expression.substring( scan, start ) );
+ sb.append( literal );
+ }
+
+ scan = end;
+ tail = end;
+ }
+ }
+
+ sb.append( expression.substring( tail ) );
+ expression = sb.toString();
+
+ // ---------------------------------------------------------------------
+ // DimensionalItemObjects
+ // ---------------------------------------------------------------------
+
+ sb = new StringBuffer();
+ matcher = VARIABLE_PATTERN.matcher( expression );
+
+ int matchCount = 0;
+ int valueCount = 0;
+
+ while ( matcher.find() )
+ {
+ matchCount++;
+
+ String dimItem = matcher.group( 2 );
+
+ final Double value = dimensionItemValueMap.get( dimItem );
+
+ boolean missingValue = value == null || incompleteItems.contains( dimItem );
+
+ if ( missingValue && SKIP_IF_ANY_VALUE_MISSING.equals( missingValueStrategy ) )
+ {
+ return null;
+ }
+
+ if ( !missingValue )
+ {
+ valueCount++;
+ }
+
+ String replacement = value != null ? String.valueOf( value ) : NULL_REPLACEMENT;
+
+ matcher.appendReplacement( sb, Matcher.quoteReplacement( replacement ) );
+ }
+
+ if ( SKIP_IF_ALL_VALUES_MISSING.equals( missingValueStrategy ) && matchCount > 0 && valueCount == 0 )
+ {
+ return null;
+ }
+
+ expression = TextUtils.appendTail( matcher, sb );
+
+ // ---------------------------------------------------------------------
+ // Constants
+ // ---------------------------------------------------------------------
+
+ sb = new StringBuffer();
+ matcher = CONSTANT_PATTERN.matcher( expression );
+
+ while ( matcher.find() )
+ {
+ final Double constant = constantMap != null ? constantMap.get( matcher.group( 1 ) ) : null;
+
+ String replacement = constant != null ? String.valueOf( constant ) : NULL_REPLACEMENT;
+
+ matcher.appendReplacement( sb, replacement );
+ }
+
+ expression = TextUtils.appendTail( matcher, sb );
+
+ // ---------------------------------------------------------------------
+ // Org unit groups
+ // ---------------------------------------------------------------------
+
+ sb = new StringBuffer();
+ matcher = OU_GROUP_PATTERN.matcher( expression );
+
+ while ( matcher.find() )
+ {
+ final Integer count = orgUnitCountMap != null ? orgUnitCountMap.get( matcher.group( 1 ) ) : null;
+
+ String replacement = count != null ? String.valueOf( count ) : NULL_REPLACEMENT;
+
+ matcher.appendReplacement( sb, replacement );
+ }
+
+ expression = TextUtils.appendTail( matcher, sb );
+
+ // ---------------------------------------------------------------------
+ // Days
+ // ---------------------------------------------------------------------
+
+ sb = new StringBuffer();
+ matcher = DAYS_PATTERN.matcher( expression );
+
+ while ( matcher.find() )
+ {
+ String replacement = days != null ? String.valueOf( days ) : NULL_REPLACEMENT;
+
+ matcher.appendReplacement( sb, replacement );
+ }
+
+ return TextUtils.appendTail( matcher, sb );
+ }
+
+ @Override
+ @Transactional
+ public List<DataElementOperand> getOperandsInIndicators( List<Indicator> indicators )
+ {
+ final List<DataElementOperand> operands = new ArrayList<>();
+
+ for ( Indicator indicator : indicators )
+ {
+ Set<DataElementOperand> temp = getOperandsInExpression( indicator.getExplodedNumerator() );
+ operands.addAll( temp != null ? temp : new HashSet<DataElementOperand>() );
+
+ temp = getOperandsInExpression( indicator.getExplodedDenominator() );
+ operands.addAll( temp != null ? temp : new HashSet<DataElementOperand>() );
+ }
+
+ return operands;
+ }
+
+ // -------------------------------------------------------------------------
+ // Supportive methods
+ // -------------------------------------------------------------------------
+
+ private boolean operandIsTotal( Matcher matcher )
+ {
+ return matcher != null && StringUtils.trimToEmpty( matcher.group( 2 ) ).isEmpty();
+ }
}
=== modified file 'dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/ArithmeticMean.java'
--- dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/ArithmeticMean.java 2016-01-13 12:54:38 +0000
+++ dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/ArithmeticMean.java 2016-03-02 21:51:09 +0000
@@ -53,11 +53,10 @@
public void run( Stack inStack )
throws ParseException
{
-
- // check the stack
checkStack( inStack );
Object param = inStack.pop();
+
if ( param instanceof List )
{
List<Double> vals = CustomFunctions.checkVector( param );
@@ -78,6 +77,8 @@
}
}
else
+ {
throw new ParseException( "Invalid aggregate value in expression" );
+ }
}
}
=== modified file 'dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/Count.java'
--- dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/Count.java 2016-01-13 12:54:38 +0000
+++ dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/Count.java 2016-03-02 21:51:09 +0000
@@ -53,7 +53,6 @@
public void run( Stack inStack )
throws ParseException
{
- // check the stack
checkStack( inStack );
Object param = inStack.pop();
=== modified file 'dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/CustomFunctions.java'
--- dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/CustomFunctions.java 2016-01-13 12:54:38 +0000
+++ dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/CustomFunctions.java 2016-03-02 21:51:09 +0000
@@ -44,7 +44,6 @@
*/
public class CustomFunctions
{
-
private static Boolean init_done = false;
public static Map<String, PostfixMathCommandI> aggregate_functions = new HashMap<String, PostfixMathCommandI>();
@@ -52,7 +51,10 @@
public static void addFunctions( JEP parser )
{
if ( !(init_done) )
+ {
initCustomFunctions();
+ }
+
for ( Entry<String, PostfixMathCommandI> e : aggregate_functions.entrySet() )
{
String fname = e.getKey();
@@ -68,14 +70,20 @@
public static Pattern getAggregatePrefixPattern()
{
if ( !(init_done) )
+ {
initCustomFunctions();
+ }
+
if ( n_aggregates == aggregate_functions.size() )
+ {
return aggregate_prefix;
+ }
else
{
StringBuffer s = new StringBuffer();
int i = 0;
s.append( "(" );
+
for ( String key : aggregate_functions.keySet() )
{
if ( i > 0 )
@@ -84,6 +92,7 @@
i++;
s.append( key );
}
+
s.append( ")\\s*\\(" );
aggregate_prefix = Pattern.compile( s.toString() );
n_aggregates = aggregate_functions.size();
@@ -103,23 +112,34 @@
if ( param instanceof List )
{
List<?> vals = (List<?>) param;
+
for ( Object val : vals )
{
if ( !(val instanceof Double) )
+ {
throw new ParseException( "Non numeric vector" );
+ }
}
+
return (List<Double>) param;
}
else
+ {
throw new ParseException( "Invalid vector argument" );
+ }
}
private synchronized static void initCustomFunctions()
{
if ( init_done )
+ {
return;
+ }
else
+ {
init_done = true;
+ }
+
CustomFunctions.addAggregateFunction( "AVG", new ArithmeticMean() );
CustomFunctions.addAggregateFunction( "STDDEV", new StandardDeviation() );
CustomFunctions.addAggregateFunction( "MEDIAN", new MedianValue() );
=== modified file 'dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/MaxValue.java'
--- dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/MaxValue.java 2016-01-13 12:54:38 +0000
+++ dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/MaxValue.java 2016-03-02 21:51:09 +0000
@@ -53,19 +53,24 @@
public void run( Stack inStack )
throws ParseException
{
- // check the stack
checkStack( inStack );
Object param = inStack.pop();
List<Double> vals = CustomFunctions.checkVector( param );
Double max = null;
+
for ( Double v : vals )
{
if ( max == null )
+ {
max = v;
+ }
else if ( v > max )
+ {
max = v;
+ }
}
+
inStack.push( new Double( max ) );
}
}
=== modified file 'dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/MedianValue.java'
--- dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/MedianValue.java 2016-01-13 12:54:38 +0000
+++ dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/MedianValue.java 2016-03-02 21:51:09 +0000
@@ -53,7 +53,6 @@
public void run( Stack inStack )
throws ParseException
{
- // check the stack
checkStack( inStack );
Object param = inStack.pop();
@@ -65,6 +64,8 @@
inStack.push( new Double( (vals.get( n / 2 ) + vals.get( n / 2 + 1 )) / 2 ) );
}
else
+ {
inStack.push( new Double( vals.get( (n + 1) / 2 ) ) );
+ }
}
}
=== modified file 'dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/MinValue.java'
--- dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/MinValue.java 2016-01-13 12:54:38 +0000
+++ dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/MinValue.java 2016-03-02 21:51:09 +0000
@@ -53,7 +53,6 @@
public void run( Stack inStack )
throws ParseException
{
- // check the stack
checkStack( inStack );
Object param = inStack.pop();
@@ -62,9 +61,13 @@
for ( Double v : vals )
{
if ( min == null )
+ {
min = v;
+ }
else if ( v < min )
+ {
min = v;
+ }
}
inStack.push( new Double( min ) );
}
=== modified file 'dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/StandardDeviation.java'
--- dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/StandardDeviation.java 2016-01-13 12:54:38 +0000
+++ dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/StandardDeviation.java 2016-03-02 21:51:09 +0000
@@ -53,12 +53,12 @@
public void run( Stack inStack )
throws ParseException
{
- // check the stack
checkStack( inStack );
Object param = inStack.pop();
List<Double> vals = CustomFunctions.checkVector( param );
int n = vals.size();
+
if ( n == 0 )
{
inStack.push( new Double( 0 ) );
@@ -66,16 +66,19 @@
else
{
double sum = 0, sum2 = 0, mean, variance;
+
for ( Double v : vals )
{
sum = sum + v;
}
mean = sum / n;
+
for ( Double v : vals )
{
sum2 = sum2 + ((v - mean) * (v - mean));
}
+
variance = sum2 / n;
inStack.push( new Double( Math.sqrt( variance ) ) );
}
=== modified file 'dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/VectorSum.java'
--- dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/VectorSum.java 2016-01-13 12:54:38 +0000
+++ dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/jep/VectorSum.java 2016-03-02 21:51:09 +0000
@@ -53,10 +53,10 @@
public void run( Stack inStack )
throws ParseException
{
- // check the stack
checkStack( inStack );
Object param = inStack.pop();
+
if ( param instanceof List )
{
List<Double> vals = CustomFunctions.checkVector( param );
@@ -76,6 +76,8 @@
}
}
else
+ {
throw new ParseException( "Invalid aggregate value in expression" );
+ }
}
}