------------------------------------------------------------ revno: 22116 committer: Lars Helge Overland <larshe...@gmail.com> 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" ); + } } }
_______________________________________________ Mailing list: https://launchpad.net/~dhis2-devs Post to : dhis2-devs@lists.launchpad.net Unsubscribe : https://launchpad.net/~dhis2-devs More help : https://help.launchpad.net/ListHelp