/* * This file is part of LibrePlan * * Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e * Desenvolvemento Tecnolóxico de Galicia * Copyright (C) 2010-2011 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.calendars.entities; import static org.libreplan.business.workingday.EffortDuration.zero; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import org.joda.time.LocalDate; import org.libreplan.business.calendars.entities.AvailabilityTimeLine.EndOfTime; import org.libreplan.business.calendars.entities.AvailabilityTimeLine.FixedPoint; import org.libreplan.business.calendars.entities.AvailabilityTimeLine.Interval; import org.libreplan.business.calendars.entities.AvailabilityTimeLine.StartOfTime; import org.libreplan.business.workingday.EffortDuration; import org.libreplan.business.workingday.IntraDayDate; import org.libreplan.business.workingday.IntraDayDate.PartialDay; import org.libreplan.business.workingday.ResourcesPerDay; /** * @author Óscar González Fernández <ogonzalez@igalia.com> */ public class ThereAreHoursOnWorkHoursCalculator { // Not instantiableisCapacityAvailable private ThereAreHoursOnWorkHoursCalculator() { } public static abstract class CapacityResult { public final boolean thereIsCapacityAvailable() { return match(new IMatcher<Boolean>() { @Override public Boolean on(CapacityAvailable result) { return true; } @Override public Boolean on(ThereAreNoValidPeriods result) { return false; } @Override public Boolean on(ValidPeriodsDontHaveCapacity result) { return false; } @Override public Boolean on(ResourcesPerDayIsZero result) { return false; } }); } public interface IMatcher<T> { public T on(CapacityAvailable result); public T on(ThereAreNoValidPeriods result); public T on(ValidPeriodsDontHaveCapacity result); public T on(ResourcesPerDayIsZero result); } public abstract <T> T match(IMatcher<T> matcher); } public static class CapacityAvailable extends CapacityResult { private CapacityAvailable() { } @Override public <T> T match(IMatcher<T> matcher) { return matcher.on(this); } } public static class ThereAreNoValidPeriods extends CapacityResult { private final ICalendar specifiedCalendar; private final AvailabilityTimeLine specifiedAdditionalAvailability; private ThereAreNoValidPeriods(ICalendar specifiedCalendar, AvailabilityTimeLine specifiedAdditionalAvailability) { this.specifiedCalendar = specifiedCalendar; this.specifiedAdditionalAvailability = specifiedAdditionalAvailability; } public ICalendar getSpecifiedCalendar() { return specifiedCalendar; } public AvailabilityTimeLine getSpecifiedAdditionalAvailability() { return specifiedAdditionalAvailability; } @Override public <T> T match(IMatcher<T> matcher) { return matcher.on(this); } } public static class ValidPeriodsDontHaveCapacity extends CapacityResult { private final List<Interval> validPeriods; private final EffortDuration sumReached; private final EffortDuration effortNeeded; private ValidPeriodsDontHaveCapacity( Collection<? extends Interval> validPeriods, EffortDuration sumReached, EffortDuration effortNeeded) { this.validPeriods = Collections .unmodifiableList(new ArrayList<Interval>(validPeriods)); this.sumReached = sumReached; this.effortNeeded = effortNeeded; } public List<Interval> getValidPeriods() { return validPeriods; } public EffortDuration getSumReached() { return sumReached; } public EffortDuration getEffortNeeded() { return effortNeeded; } @Override public <T> T match(IMatcher<T> matcher) { return matcher.on(this); } } public static class ResourcesPerDayIsZero extends CapacityResult { private ResourcesPerDayIsZero() { } @Override public <T> T match(IMatcher<T> matcher) { return matcher.on(this); } } /** * Calculates if there are enough hours */ public static CapacityResult thereIsAvailableCapacityFor( ICalendar calendar, AvailabilityTimeLine availability, ResourcesPerDay resourcesPerDay, EffortDuration effortToAllocate) { if (effortToAllocate.isZero()) { return new CapacityAvailable(); } if (resourcesPerDay.isZero()) { return new ResourcesPerDayIsZero(); } AvailabilityTimeLine realAvailability = calendar.getAvailability() .and(availability); List<Interval> validPeriods = realAvailability.getValidPeriods(); if (validPeriods.isEmpty()) { return new ThereAreNoValidPeriods(calendar, availability); } Interval last = getLast(validPeriods); Interval first = validPeriods.get(0); final boolean isOpenEnded = last.getEnd().equals(EndOfTime.create()) || first.getStart().equals(StartOfTime.create()); if (isOpenEnded) { return new CapacityAvailable(); } return thereIsCapacityOn(calendar, effortToAllocate, resourcesPerDay, validPeriods); } private static Interval getLast(List<Interval> validPeriods) { return validPeriods.get(validPeriods.size() - 1); } private static CapacityResult thereIsCapacityOn(ICalendar calendar, EffortDuration effortToAllocate, ResourcesPerDay resourcesPerDay, List<Interval> validPeriods) { EffortDuration sum = zero(); for (Interval each : validPeriods) { FixedPoint start = (FixedPoint) each.getStart(); FixedPoint end = (FixedPoint) each.getEnd(); EffortDuration pending = effortToAllocate.minus(sum); sum = sum.plus(sumDurationUntil(calendar, pending, resourcesPerDay, start.getDate(), end.getDate())); if (sum.compareTo(effortToAllocate) >= 0) { return new CapacityAvailable(); } } return new ValidPeriodsDontHaveCapacity(validPeriods, sum, effortToAllocate); } private static EffortDuration sumDurationUntil(ICalendar calendar, EffortDuration maximum, ResourcesPerDay resourcesPerDay, LocalDate start, LocalDate end) { return sunDurationUntil(calendar, maximum, resourcesPerDay, IntraDayDate.startOfDay(start), IntraDayDate.startOfDay(end)); } private static EffortDuration sunDurationUntil(ICalendar calendar, EffortDuration maximum, ResourcesPerDay resourcesPerDay, IntraDayDate start, IntraDayDate end) { EffortDuration result = zero(); for (PartialDay current : start.daysUntil(end)) { result = result.plus(calendar.asDurationOn(current, resourcesPerDay)); if (result.compareTo(maximum) >= 0) { return maximum; } } return result; } }