org.libreplan.business.planner.limiting.entities.LimitingResourceAllocator.java Source code

Java tutorial

Introduction

Here is the source code for org.libreplan.business.planner.limiting.entities.LimitingResourceAllocator.java

Source

/*
 * This file is part of LibrePlan
 *
 * Copyright (C) 2009-2010 Fundacin para o Fomento da Calidade Industrial e
 *                         Desenvolvemento Tecnolxico 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.limiting.entities;

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

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

import org.joda.time.LocalDate;
import org.libreplan.business.calendars.entities.ResourceCalendar;
import org.libreplan.business.planner.entities.DayAssignment;
import org.libreplan.business.planner.entities.GenericDayAssignment;
import org.libreplan.business.planner.entities.GenericResourceAllocation;
import org.libreplan.business.planner.entities.ResourceAllocation;
import org.libreplan.business.planner.entities.SpecificDayAssignment;
import org.libreplan.business.planner.entities.SpecificResourceAllocation;
import org.libreplan.business.resources.entities.LimitingResourceQueue;
import org.libreplan.business.resources.entities.Resource;
import org.libreplan.business.workingday.EffortDuration;
import org.libreplan.business.workingday.IntraDayDate;
import org.libreplan.business.workingday.IntraDayDate.PartialDay;
import org.libreplan.business.workingday.IntraDayDate.UntilEnd;
import org.libreplan.business.workingday.ResourcesPerDay;

/**
 * Handles all the logic related to allocation of
 * {@link LimitingResourceQueueElement} into {@link LimitingResourceQueue}.
 *
 * The class does not do the allocation itself but provides methods
 * needed to do the allocation of {@link LimitingResourceQueueElement}:
 * <ul>
 *     <li><em>getFirstValidGap</em></li>
 *     <li><em>calculateStartAndEndTime</em></li>
 *     <li><em>generateDayAssignments</em></li>
 * </ul>
 *
 *
 * @author Diego Pino Garcia <dpino@igalia.com>
 */
public class LimitingResourceAllocator {

    private static final class UntilEndAndEffort extends UntilEnd {

        private final EffortDuration goal;

        private EffortDuration currentDuration = zero();

        private UntilEndAndEffort(IntraDayDate endExclusive, EffortDuration goal) {
            super(endExclusive);
            this.goal = goal;
        }

        @Override
        protected boolean hasNext(boolean currentDateIsLessThanEnd) {
            return currentDateIsLessThanEnd || currentDuration.compareTo(goal) <= 0;
        }

        public void setCurrent(EffortDuration currentDuration) {
            this.currentDuration = currentDuration;
        }

        @Override
        public IntraDayDate limitNext(IntraDayDate nextDay) {
            return nextDay;
        }

    }

    private final static ResourcesPerDay ONE_RESOURCE_PER_DAY = ResourcesPerDay.amount(new BigDecimal(1));

    /**
     * Returns first valid gap in queue for element.
     *
     * Returns null if there is not a valid gap.
     * This case can only happen on trying to allocate an element related to a generic resource allocation.
     * It is possible that queue.resource does not hold element.criteria at any interval of time.
     *
     * @param queue search gap inside queue
     * @param element element to fit into queue
     * @return {@link Gap}
     */
    public static Gap getFirstValidGap(LimitingResourceQueue queue, LimitingResourceQueueElement element) {

        final Resource resource = queue.getResource();
        final List<LimitingResourceQueueElement> elements = new LinkedList<>(
                queue.getLimitingResourceQueueElements());
        final int size = elements.size();
        final DateAndHour startTime = getStartTimeBecauseOfGantt(element);

        int pos = 0;

        // Iterate through queue elements
        while (pos <= size) {
            Gap gap = getGapInQueueAtPosition(resource, elements, startTime, pos++);

            if (gap != null) {

                List<Gap> subgaps = getFittingSubgaps(element, gap, resource);

                if (!subgaps.isEmpty()) {
                    return subgaps.get(0);
                }
            }
        }

        // The queue cannot hold this element (queue.resource doesn't meet element.criteria)
        return null;
    }

    private static List<Gap> getFittingSubgaps(LimitingResourceQueueElement element, final Gap gap,
            final Resource resource) {

        List<Gap> result = new ArrayList<>();

        if (isSpecific(element) && gap.canFit(element)) {
            result.add(gap);
        } else if (isGeneric(element)) {
            final List<Gap> gaps = gap.splitIntoGapsSatisfyingCriteria(resource, element.getCriteria());
            for (Gap subgap : gaps) {

                if (subgap.canFit(element)) {
                    result.add(subgap);
                }
            }
        }

        return result;
    }

    public static Gap getFirstValidGapSince(LimitingResourceQueueElement element, LimitingResourceQueue queue,
            DateAndHour since) {

        List<Gap> gaps = getValidGapsForElementSince(element, queue, since);
        return (!gaps.isEmpty()) ? gaps.get(0) : null;
    }

    public static List<Gap> getValidGapsForElementSince(LimitingResourceQueueElement element,
            LimitingResourceQueue queue, DateAndHour since) {

        List<Gap> result = new ArrayList<>();

        final Resource resource = queue.getResource();
        final List<LimitingResourceQueueElement> elements = new LinkedList<>(
                queue.getLimitingResourceQueueElements());
        final int size = elements.size();

        int pos = moveUntil(elements, since);

        // Iterate through queue elements
        while (pos <= size) {
            Gap gap = getGapInQueueAtPosition(resource, elements, since, pos++);

            // The queue cannot hold this element (queue.resource doesn't meet element.criteria)
            if (gap != null) {
                List<Gap> subgaps = getFittingSubgaps(element, gap, resource);
                result.addAll(subgaps);
            }
        }

        return result;
    }

    private static int moveUntil(List<LimitingResourceQueueElement> elements, DateAndHour until) {
        int pos = 0;

        if (elements.size() > 0) {
            // Space between until and first element start time
            LimitingResourceQueueElement first = elements.get(0);
            if (until.isBefore(first.getStartTime())) {
                return 0;
            }

            for (pos = 1; pos < elements.size(); pos++) {
                final LimitingResourceQueueElement each = elements.get(pos);
                final DateAndHour startTime = each.getStartTime();

                if (until.isBefore(startTime) || until.isEquals(startTime)) {
                    return pos;
                }
            }
        }
        return pos;
    }

    private static boolean isGeneric(LimitingResourceQueueElement element) {
        return element.getResourceAllocation() instanceof GenericResourceAllocation;
    }

    private static boolean isSpecific(LimitingResourceQueueElement element) {
        return element.getResourceAllocation() instanceof SpecificResourceAllocation;
    }

    public static DateAndHour getFirstElementTime(List<DayAssignment> dayAssignments) {
        final DayAssignment start = dayAssignments.get(0);
        return new DateAndHour(start.getDay(), start.getDuration().getHours());
    }

    public static DateAndHour getLastElementTime(List<DayAssignment> dayAssignments) {
        final DayAssignment end = dayAssignments.get(dayAssignments.size() - 1);
        return new DateAndHour(end.getDay(), end.getDuration().getHours());
    }

    private static Gap getGapInQueueAtPosition(Resource resource, List<LimitingResourceQueueElement> elements,
            DateAndHour startTimeBecauseOfGantt, int pos) {

        final int size = elements.size();

        // No elements in queue
        if (size == 0) {
            return createLastGap(startTimeBecauseOfGantt, null, resource);
        }

        // Last element
        if (pos == size) {
            return createLastGap(startTimeBecauseOfGantt, elements.get(size - 1), resource);
        }

        LimitingResourceQueueElement next = elements.get(pos);

        // First element
        if (pos == 0 && startTimeBecauseOfGantt.getDate().isBefore(next.getStartDate())) {
            return Gap.create(resource, startTimeBecauseOfGantt, next.getStartTime());
        }

        // In the middle of two elements
        if (pos > 0) {
            LimitingResourceQueueElement previous = elements.get(pos - 1);
            return Gap.create(resource, DateAndHour.max(previous.getEndTime(), startTimeBecauseOfGantt),
                    next.getStartTime());
        }

        return null;
    }

    private static DateAndHour getStartTimeBecauseOfGantt(LimitingResourceQueueElement element) {
        return new DateAndHour(new LocalDate(element.getEarliestStartDateBecauseOfGantt()), 0);
    }

    private static Gap createLastGap(DateAndHour _startTime, LimitingResourceQueueElement lastElement,
            Resource resource) {

        final DateAndHour queueEndTime = (lastElement != null) ? lastElement.getEndTime() : null;
        DateAndHour startTime = DateAndHour.max(_startTime, queueEndTime);

        return Gap.create(resource, startTime, null);
    }

    /**
     * Generates a list of {@link DayAssignment} for {@link Resource} starting from startTime.
     *
     * The returned list is not associated to resourceAllocation.
     *
     * resourceAllocation is passed to know if the list of day assignments
     * should be {@link GenericDayAssignment} or {@link SpecificDayAssignment}.
     *
     * @param resourceAllocation
     * @param resource
     * @param startTime
     * @return {@link List<DayAssignment>}
     */
    public static List<DayAssignment> generateDayAssignments(ResourceAllocation<?> resourceAllocation,
            Resource resource, DateAndHour startTime, DateAndHour endsAfter) {

        List<DayAssignment> assignments = new LinkedList<>();
        ResourceCalendar calendar = resource.getCalendar();
        final EffortDuration totalEffort = hours(resourceAllocation.getIntendedTotalHours());
        EffortDuration effortAssigned = zero();
        UntilEndAndEffort condition = new UntilEndAndEffort(endsAfter.toIntraDayDate(), totalEffort);

        for (PartialDay each : startTime.toIntraDayDate().daysUntil(condition)) {
            EffortDuration effortForDay = EffortDuration.min(calendar.asDurationOn(each, ONE_RESOURCE_PER_DAY),
                    totalEffort);
            DayAssignment dayAssignment = createDayAssignment(resourceAllocation, resource, each.getDate(),
                    effortForDay);
            effortAssigned = effortAssigned.plus(addDayAssignment(assignments, dayAssignment));
            condition.setCurrent(effortAssigned);
        }
        if (effortAssigned.compareTo(totalEffort) > 0) {
            stripStartAssignments(assignments, effortAssigned.minus(totalEffort));
        }
        return new ArrayList<>(assignments);
    }

    private static void stripStartAssignments(List<DayAssignment> assignments, EffortDuration durationSurplus) {
        ListIterator<DayAssignment> listIterator = assignments.listIterator();

        while (listIterator.hasNext() && durationSurplus.compareTo(zero()) > 0) {
            DayAssignment current = listIterator.next();
            EffortDuration durationTaken = min(durationSurplus, current.getDuration());
            durationSurplus = durationSurplus.minus(durationTaken);
            if (durationTaken.compareTo(current.getDuration()) == 0) {
                listIterator.remove();
            } else {
                listIterator.set(current.withDuration(durationTaken));
            }
        }
    }

    private static List<DayAssignment> generateDayAssignmentsStartingFromEnd(
            ResourceAllocation<?> resourceAllocation, Resource resource, DateAndHour endTime) {
        ResourceCalendar calendar = resource.getCalendar();
        List<DayAssignment> result = new ArrayList<>();

        LocalDate date = endTime.getDate();
        EffortDuration totalIntended = hours(resourceAllocation.getIntendedTotalHours());

        // Generate last day assignment
        PartialDay firstDay = new PartialDay(IntraDayDate.startOfDay(date),
                IntraDayDate.create(date, hours(endTime.getHour())));

        EffortDuration effortCanAllocate = min(totalIntended,
                calendar.asDurationOn(firstDay, ONE_RESOURCE_PER_DAY));

        if (effortCanAllocate.compareTo(zero()) > 0) {
            DayAssignment dayAssignment = createDayAssignment(resourceAllocation, resource, date,
                    effortCanAllocate);
            totalIntended = totalIntended.minus(addDayAssignment(result, dayAssignment));
        }

        // Generate rest of day assignments
        for (date = date.minusDays(1); totalIntended.compareTo(zero()) > 0; date = date.minusDays(1)) {

            EffortDuration duration = min(totalIntended,
                    calendar.asDurationOn(PartialDay.wholeDay(date), ONE_RESOURCE_PER_DAY));

            DayAssignment dayAssignment = createDayAssignment(resourceAllocation, resource, date, duration);
            totalIntended = totalIntended.minus(addDayAssignment(result, dayAssignment));
        }

        return result;
    }

    private static DayAssignment createDayAssignment(ResourceAllocation<?> resourceAllocation, Resource resource,
            LocalDate date, EffortDuration toAllocate) {

        if (resourceAllocation instanceof SpecificResourceAllocation) {
            return SpecificDayAssignment.create(date, toAllocate, resource);
        } else if (resourceAllocation instanceof GenericResourceAllocation) {
            return GenericDayAssignment.create(date, toAllocate, resource);
        }
        return null;
    }

    private static EffortDuration addDayAssignment(List<DayAssignment> list, DayAssignment dayAssignment) {
        if (dayAssignment != null) {
            list.add(dayAssignment);
            return dayAssignment.getDuration();
        }

        return zero();
    }

    public static DateAndHour startTimeToAllocateStartingFromEnd(ResourceAllocation<?> resourceAllocation,
            Resource resource, Gap gap) {

        // Last element, time is end of last element (gap.starttime)
        if (gap.getEndTime() == null) {
            return gap.getStartTime();
        }

        final List<DayAssignment> dayAssignments = LimitingResourceAllocator
                .generateDayAssignmentsStartingFromEnd(resourceAllocation, resource, gap.getEndTime());

        return LimitingResourceAllocator.getLastElementTime(dayAssignments);
    }

}