Package org.libreplan.business.planner.entities.allocationalgorithms

Source Code of org.libreplan.business.planner.entities.allocationalgorithms.UntilFillingHoursAllocator$HoursPerAllocationCalculator

/*
* 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.planner.entities.allocationalgorithms;

import static org.libreplan.business.workingday.EffortDuration.zero;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.lang.Validate;
import org.joda.time.LocalDate;
import org.libreplan.business.calendars.entities.ICalendar;
import org.libreplan.business.calendars.entities.ThereAreHoursOnWorkHoursCalculator.CapacityResult;
import org.libreplan.business.common.ProportionalDistributor;
import org.libreplan.business.planner.entities.DayAssignment;
import org.libreplan.business.planner.entities.ResourceAllocation;
import org.libreplan.business.planner.entities.ResourceAllocation.Direction;
import org.libreplan.business.planner.entities.Task;
import org.libreplan.business.util.Pair;
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;

public abstract class UntilFillingHoursAllocator {

    private final Direction direction;

    private final Task task;

    private List<ResourcesPerDayModification> allocations;

    private Map<ResourcesPerDayModification, List<DayAssignment>> resultAssignments = new HashMap<ResourcesPerDayModification, List<DayAssignment>>();


    public UntilFillingHoursAllocator(Direction direction, Task task,
            List<ResourcesPerDayModification> allocations) {
        this.direction = direction;
        this.task = task;
        this.allocations = allocations;
        initializeResultsMap();
    }

    private void initializeResultsMap() {
        for (ResourcesPerDayModification r : allocations) {
            resultAssignments.put(r, new ArrayList<DayAssignment>());
        }
    }

    public IntraDayDate untilAllocating(EffortDuration effortToAllocate) {
        final IntraDayDate dateFromWhichToAllocate = direction
                .getDateFromWhichToAllocate(task);
        List<EffortPerAllocation> effortPerAllocation = effortPerAllocation(
                dateFromWhichToAllocate, effortToAllocate);
        if (effortPerAllocation.isEmpty()) {
            return null;
        }
        return untilAllocating(dateFromWhichToAllocate, effortPerAllocation);
    }

    private IntraDayDate untilAllocating(final IntraDayDate dateFromWhichToAllocate,
            List<EffortPerAllocation> effortPerAllocation) {
        IntraDayDate currentResult = dateFromWhichToAllocate;
        for (EffortPerAllocation each : effortPerAllocation) {
            IntraDayDate candidate = untilAllocating(dateFromWhichToAllocate,
                    each.allocation, each.duration);
            currentResult = pickCurrentOrCandidate(currentResult, candidate);
        }
        setAssignmentsForEachAllocation(currentResult);
        return currentResult;
    }

    private IntraDayDate pickCurrentOrCandidate(IntraDayDate current,
            IntraDayDate candidate) {
        if (direction == Direction.BACKWARD) {
            return IntraDayDate.min(current, candidate);
        }
        return IntraDayDate.max(current, candidate);
    }

    private List<EffortPerAllocation> effortPerAllocation(
            IntraDayDate dateFromWhichToAllocate, EffortDuration toBeAssigned) {
        return new HoursPerAllocationCalculator(allocations)
                .calculateEffortsPerAllocation(dateFromWhichToAllocate,
                        toBeAssigned);
    }

    public interface IAssignmentsCreator {

        List<? extends DayAssignment> createAssignmentsAtDay(PartialDay day,
                EffortDuration limit, ResourcesPerDay resourcesPerDay);
    }

    /**
     *
     * @param dateFromWhichToAllocate
     * @param resourcesPerDayModification
     * @param effortRemaining
     * @return the moment on which the allocation would be completed
     */
    private IntraDayDate untilAllocating(IntraDayDate dateFromWhichToAllocate,
            ResourcesPerDayModification resourcesPerDayModification,
            EffortDuration effortRemaining) {
        EffortDuration taken = zero();
        EffortDuration biggestLastAssignment = zero();
        IntraDayDate current = dateFromWhichToAllocate;
        IAssignmentsCreator assignmentsCreator = resourcesPerDayModification
                .createAssignmentsCreator();
        while (effortRemaining.compareTo(zero()) > 0) {
            PartialDay day = calculateDay(current);
            Pair<EffortDuration, EffortDuration> pair = assignForDay(
                    resourcesPerDayModification, assignmentsCreator, day,
                    effortRemaining);
            taken = pair.getFirst();
            biggestLastAssignment = pair.getSecond();
            effortRemaining = effortRemaining.minus(taken);

            if (effortRemaining.compareTo(zero()) > 0) {
                current = nextDay(current);
            }
        }
        IntraDayDate finish = adjustFinish(resourcesPerDayModification, taken,
                biggestLastAssignment, current);
        // We have to do it now, so the other allocations take it into account.
        // At the end it's done again with the right end date.
        setNewDataForAllocation(resourcesPerDayModification, resultAssignments
                .get(resourcesPerDayModification), finish);
        return finish;
    }

    private IntraDayDate adjustFinish(
            ResourcesPerDayModification resourcesPerDayModification,
            EffortDuration allocatedLastDay,
            EffortDuration biggestLastAssignment,
            IntraDayDate end) {
        IntraDayDate result;
        ResourcesPerDay adjustBy = allocatedLastDay
                .equals(biggestLastAssignment) ? resourcesPerDayModification
                .getGoal() : ResourcesPerDay.amount(1);
        if (isForwardScheduling()) {
            result = plusEffort(end, allocatedLastDay);
            if (!resourcesPerDayModification
                    .thereAreMoreSpaceAvailableAt(result)) {
                result = nextDay(result);
            } else {
                result = end.increaseBy(adjustBy, biggestLastAssignment);
            }
        } else {
            result = minusEffort(end, allocatedLastDay,
                    resourcesPerDayModification
                    .getBeingModified().getAllocationCalendar(), adjustBy);
        }
        return result;
    }

    private IntraDayDate nextDay(IntraDayDate current) {
        if (isForwardScheduling()) {
            return current.nextDayAtStart();
        } else {
            if (current.isStartOfDay()) {
                return current.previousDayAtStart();
            } else {
                return IntraDayDate.startOfDay(current.getDate());
            }
        }
    }

    private PartialDay calculateDay(IntraDayDate current) {
        if (isForwardScheduling()) {
            return dayStartingAt(current);
        } else {
            return dayEndingAt(current);
        }
    }

    private PartialDay dayStartingAt(IntraDayDate start) {
        return new PartialDay(start, nextDay(start));
    }

    private PartialDay dayEndingAt(IntraDayDate current) {
        if (!current.isStartOfDay()) {
            return new PartialDay(IntraDayDate.startOfDay(current.getDate()),
                    current);
        }
        return PartialDay.wholeDay(current.getDate().minusDays(1));
    }

    protected boolean isForwardScheduling() {
        return Direction.FORWARD == direction;
    }

    private IntraDayDate plusEffort(IntraDayDate current, EffortDuration taken) {
        return IntraDayDate.create(current.getDate(),
                taken.plus(current.getEffortDuration()));
    }

    private IntraDayDate minusEffort(IntraDayDate current,
            EffortDuration taken, ICalendar calendar, ResourcesPerDay adjustBy) {
        if (!current.isStartOfDay()) {
            return current.decreaseBy(adjustBy,
                    taken);
        } else {
            LocalDate day = current.getDate().minusDays(1);
            EffortDuration effortAtDay = calendar
                    .asDurationOn(PartialDay.wholeDay(day),
                            ResourcesPerDay.amount(1));
            return IntraDayDate.create(day, effortAtDay).decreaseBy(adjustBy,
                    taken);
        }
    }

    private void setAssignmentsForEachAllocation(IntraDayDate resultDate) {
        for (Entry<ResourcesPerDayModification, List<DayAssignment>> entry : resultAssignments
                .entrySet()) {
            setNewDataForAllocation(entry.getKey(), entry.getValue(),
                    resultDate);
        }
    }

    private <T extends DayAssignment> void setNewDataForAllocation(
            ResourcesPerDayModification modification,
            List<T> value,
            IntraDayDate resultDate) {
        @SuppressWarnings("unchecked")
        ResourceAllocation<T> allocation = (ResourceAllocation<T>) modification
                .getBeingModified();
        ResourcesPerDay resourcesPerDay = modification.getGoal();
        setNewDataForAllocation(allocation, resultDate,
                resourcesPerDay, value);
    }

    protected Direction getDirection() {
        return direction;
    }

    protected abstract <T extends DayAssignment> void setNewDataForAllocation(
            ResourceAllocation<T> allocation, IntraDayDate resultDate,
            ResourcesPerDay resourcesPerDay, List<T> dayAssignments);

    protected abstract CapacityResult thereAreAvailableHoursFrom(
            IntraDayDate dateFromWhichToAllocate,
            ResourcesPerDayModification resourcesPerDayModification,
            EffortDuration remainingDuration);

    protected abstract void markUnsatisfied(
            ResourcesPerDayModification allocationAttempt,
            CapacityResult capacityResult);

    /**
     *
     * @param resourcesPerDayModification
     * @return a pair composed of the effort assigned and the biggest assignment
     *         done.
     */
    private Pair<EffortDuration, EffortDuration> assignForDay(
            ResourcesPerDayModification resourcesPerDayModification,
            IAssignmentsCreator assignmentsCreator,
            PartialDay day, EffortDuration remaining) {
        List<? extends DayAssignment> newAssignments = assignmentsCreator
                .createAssignmentsAtDay(day, remaining,
                        resourcesPerDayModification.getGoal());
        resultAssignments.get(resourcesPerDayModification).addAll(
                newAssignments);
        return Pair.create(DayAssignment.sum(newAssignments),
                getMaxAssignment(newAssignments));
    }

    private EffortDuration getMaxAssignment(
            List<? extends DayAssignment> newAssignments) {
        if (newAssignments.isEmpty()) {
            return zero();
        }
        DayAssignment max = Collections.max(newAssignments,
                DayAssignment.byDurationComparator());
        return max.getDuration();
    }

    private static class EffortPerAllocation {
        final EffortDuration duration;

        final ResourcesPerDayModification allocation;

        private EffortPerAllocation(EffortDuration duration,
                ResourcesPerDayModification allocation) {
            this.duration = duration;
            this.allocation = allocation;
        }

        public static List<EffortPerAllocation> wrap(
                List<ResourcesPerDayModification> allocations,
                List<EffortDuration> durations) {
            Validate.isTrue(durations.size() == allocations.size());
            int i = 0;
            List<EffortPerAllocation> result = new ArrayList<EffortPerAllocation>();
            for(i = 0; i < allocations.size(); i++){
                result.add(new EffortPerAllocation(durations.get(i),
                        allocations.get(i)));
            }
            return result;
        }
    }

    private class HoursPerAllocationCalculator {
        private List<ResourcesPerDayModification> allocations;

        private HoursPerAllocationCalculator(
                List<ResourcesPerDayModification> allocations) {
            this.allocations = new ArrayList<ResourcesPerDayModification>(
                    allocations);
        }

        public List<EffortPerAllocation> calculateEffortsPerAllocation(
                IntraDayDate dateFromWhichToAllocate, EffortDuration toAssign) {
            do {
                List<EffortDuration> durations = divideEffort(toAssign);
                List<EffortPerAllocation> result = EffortPerAllocation.wrap(
                        allocations, durations);
                List<ResourcesPerDayModification> unsatisfied = getUnsatisfied(
                        dateFromWhichToAllocate, result);
                if (unsatisfied.isEmpty()) {
                    return result;
                }
                allocations.removeAll(unsatisfied);
            } while (!allocations.isEmpty());
            return Collections.emptyList();
        }

        private List<ResourcesPerDayModification> getUnsatisfied(
                IntraDayDate dateFromWhichToAllocate,
                List<EffortPerAllocation> hoursPerAllocations) {
            List<ResourcesPerDayModification> cannotSatisfy = new ArrayList<ResourcesPerDayModification>();
            for (EffortPerAllocation each : hoursPerAllocations) {
                CapacityResult capacityResult = thereAreAvailableHoursFrom(
                        dateFromWhichToAllocate, each.allocation, each.duration);
                if (!capacityResult.thereIsCapacityAvailable()) {
                    cannotSatisfy.add(each.allocation);
                    markUnsatisfied(each.allocation, capacityResult);
                }
            }
            return cannotSatisfy;
        }

        private List<EffortDuration> divideEffort(EffortDuration toBeDivided) {
            ProportionalDistributor distributor = ProportionalDistributor
                    .create(createShares());
            int[] secondsDivided = distributor.distribute(toBeDivided
                    .getSeconds());
            return asDurations(secondsDivided);
        }

        private int[] createShares() {
            int[] result = new int[allocations.size()];
            for (int i = 0; i < result.length; i++) {
                result[i] = normalize(allocations.get(i).getGoal()
                        .getAmount());
            }
            return result;
        }

        private List<EffortDuration> asDurations(int[] secondsDivided) {
            List<EffortDuration> result = new ArrayList<EffortDuration>();
            for (int each : secondsDivided) {
                result.add(EffortDuration.seconds(each));
            }
            return result;
        }

        /**
         * Returns a normalized amount for {@link ProportionalDistributor}. For
         * example, for 2.03, 203 is returned.
         *
         * @param amount
         * @return
         */
        private int normalize(BigDecimal amount) {
            return amount.movePointRight(2).intValue();
        }

    }

}
TOP

Related Classes of org.libreplan.business.planner.entities.allocationalgorithms.UntilFillingHoursAllocator$HoursPerAllocationCalculator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.