/* * This file is part of LibrePlan * * Copyright (C) 2012 Igalia, S.L. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.libreplan.business.planner.entities; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.Collections; import java.util.Map; import java.util.Map.Entry; import java.util.SortedMap; import java.util.TreeMap; import org.joda.time.LocalDate; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; /** * @author Manuel Rego Casasnovas <mrego@igalia.com> * @author Diego Pino GarcĂ­a <dpino@igalia.com> * * Calculates generic 'Earned Value' indicators (those calculated out of * BCWP, ACWP and BCWS */ @Component @Scope(BeanDefinition.SCOPE_SINGLETON) public class EarnedValueCalculator implements IEarnedValueCalculator { @Override public SortedMap<LocalDate, BigDecimal> calculateCostVariance( SortedMap<LocalDate, BigDecimal> bcwp, SortedMap<LocalDate, BigDecimal> acwp) { return substract(bcwp, acwp); } @Override public SortedMap<LocalDate, BigDecimal> calculateScheduleVariance( SortedMap<LocalDate, BigDecimal> bcwp, SortedMap<LocalDate, BigDecimal> bcws) { return substract(bcwp, bcws); } @Override public SortedMap<LocalDate, BigDecimal> calculateBudgetAtCompletion( SortedMap<LocalDate, BigDecimal> bcws) { SortedMap<LocalDate, BigDecimal> result = new TreeMap<LocalDate, BigDecimal>(); BigDecimal value = Collections.max(bcws.values()); for (LocalDate day : bcws.keySet()) { result.put(day, value); } return result; } @Override public SortedMap<LocalDate, BigDecimal> calculateEstimateAtCompletion( SortedMap<LocalDate, BigDecimal> acwp, SortedMap<LocalDate, BigDecimal> bcwp, SortedMap<LocalDate, BigDecimal> bac) { return multiply(divide(acwp, bcwp, BigDecimal.ZERO), bac); } @Override public SortedMap<LocalDate, BigDecimal> calculateVarianceAtCompletion( SortedMap<LocalDate, BigDecimal> bac, SortedMap<LocalDate, BigDecimal> eac) { return substract(bac, eac); } @Override public SortedMap<LocalDate, BigDecimal> calculateEstimatedToComplete( SortedMap<LocalDate, BigDecimal> eac, SortedMap<LocalDate, BigDecimal> acwp) { return substract(eac, acwp); } @Override public SortedMap<LocalDate, BigDecimal> calculateCostPerformanceIndex( SortedMap<LocalDate, BigDecimal> bcwp, SortedMap<LocalDate, BigDecimal> acwp) { return divide(bcwp, acwp, BigDecimal.ZERO); } @Override public SortedMap<LocalDate, BigDecimal> calculateSchedulePerformanceIndex( SortedMap<LocalDate, BigDecimal> bcwp, SortedMap<LocalDate, BigDecimal> bcws) { return divide(bcwp, bcws, BigDecimal.ZERO); } private SortedMap<LocalDate, BigDecimal> substract( SortedMap<LocalDate, BigDecimal> minuend, SortedMap<LocalDate, BigDecimal> subtrahend) { final SortedMap<LocalDate, BigDecimal> result = new TreeMap<LocalDate, BigDecimal>(); forValuesAtSameKey(minuend, subtrahend, substractionOperation(result)); return result; } public static <K, V> void forValuesAtSameKey(Map<K, V> a, Map<K, V> b, IOperation<K, V> onSameKey) { for (Entry<K, V> each : a.entrySet()) { V aValue = each.getValue(); V bValue = b.get(each.getKey()); onSameKey.operate(each.getKey(), aValue, bValue); } } private static IOperation<LocalDate, BigDecimal> substractionOperation( final SortedMap<LocalDate, BigDecimal> result) { return notNullOperands(new IOperation<LocalDate, BigDecimal>() { @Override public void operate(LocalDate key, BigDecimal minuedValue, BigDecimal subtrahendValue) { result.put(key, minuedValue.subtract(subtrahendValue)); } @Override public void undefinedFor(LocalDate key) { } }); } public static <K, V> IOperation<K, V> notNullOperands( final IOperation<K, V> operation) { return new PreconditionChecker<K, V>(operation) { @Override protected boolean isOperationDefinedFor(K key, V a, V b) { return a != null && b != null; } }; } public interface IOperation<K, V> { public void operate(K key, V a, V b); public void undefinedFor(K key); } protected static abstract class PreconditionChecker<K, V> implements IOperation<K, V> { private final IOperation<K, V> decorated; protected PreconditionChecker(IOperation<K, V> decorated) { this.decorated = decorated; } @Override public void operate(K key, V a, V b) { if (isOperationDefinedFor(key, a, b)) { decorated.operate(key, a, b); } else { decorated.undefinedFor(key); } } protected abstract boolean isOperationDefinedFor(K key, V a, V b); @Override public void undefinedFor(K key) { decorated.undefinedFor(key); } } private static SortedMap<LocalDate, BigDecimal> multiply( Map<LocalDate, BigDecimal> firstFactor, Map<LocalDate, BigDecimal> secondFactor) { final SortedMap<LocalDate, BigDecimal> result = new TreeMap<LocalDate, BigDecimal>(); forValuesAtSameKey(firstFactor, secondFactor, multiplicationOperation(result)); return result; } private static IOperation<LocalDate, BigDecimal> multiplicationOperation( final SortedMap<LocalDate, BigDecimal> result) { return notNullOperands(new IOperation<LocalDate, BigDecimal>() { @Override public void operate(LocalDate key, BigDecimal a, BigDecimal b) { result.put(key, a.multiply(b)); } @Override public void undefinedFor(LocalDate key) { result.put(key, BigDecimal.ZERO); } }); } private static SortedMap<LocalDate, BigDecimal> divide( Map<LocalDate, BigDecimal> dividend, Map<LocalDate, BigDecimal> divisor, final BigDecimal defaultIfNotComputable) { final TreeMap<LocalDate, BigDecimal> result = new TreeMap<LocalDate, BigDecimal>(); forValuesAtSameKey(dividend, divisor, divisionOperation(result, defaultIfNotComputable)); return result; } private static IOperation<LocalDate, BigDecimal> divisionOperation( final TreeMap<LocalDate, BigDecimal> result, final BigDecimal defaultIfNotComputable) { return notNullOperands(secondOperandNotZero(new IOperation<LocalDate, BigDecimal>() { @Override public void operate(LocalDate key, BigDecimal dividendValue, BigDecimal divisorValue) { result.put(key, dividendValue.divide(divisorValue, RoundingMode.HALF_UP)); } @Override public void undefinedFor(LocalDate key) { result.put(key, defaultIfNotComputable); } })); } public static <K> IOperation<K, BigDecimal> secondOperandNotZero( final IOperation<K, BigDecimal> operation) { return new PreconditionChecker<K, BigDecimal>(operation) { @Override protected boolean isOperationDefinedFor(K key, BigDecimal a, BigDecimal b) { return b.signum() != 0; } }; } }