Package org.libreplan.business.planner.limiting.entities

Source Code of org.libreplan.business.planner.limiting.entities.LimitingResourceAllocator

/*
* 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.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:
* <em>getFirstValidGap</em>, <em>calculateStartAndEndTime</em> or
* <em>generateDayAssignments</em>, needed to do the allocation of
* {@link LimitingResourceQueueElement}
*
* @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
     */
    public static Gap getFirstValidGap(
            LimitingResourceQueue queue, LimitingResourceQueueElement element) {

        final Resource resource = queue.getResource();
        final List<LimitingResourceQueueElement> elements = new LinkedList<LimitingResourceQueueElement>(
                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<Gap>();

        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<Gap>();

        final Resource resource = queue.getResource();
        final List<LimitingResourceQueueElement> elements = new LinkedList<LimitingResourceQueueElement>(
                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.getHours());
    }

    public static DateAndHour getLastElementTime(List<DayAssignment> dayAssignments) {
        final DayAssignment end = dayAssignments.get(dayAssignments.size() - 1);
        return new DateAndHour(end.getDay(), end.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 resouceAllocation.
     *
     * 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
     */
    public static List<DayAssignment> generateDayAssignments(
            ResourceAllocation<?> resourceAllocation,
            Resource resource,
            DateAndHour startTime, DateAndHour endsAfter) {

        List<DayAssignment> assignments = new LinkedList<DayAssignment>();
        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<DayAssignment>(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<DayAssignment>();

        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 dayAssigment = createDayAssignment(
                    resourceAllocation, resource, date, duration);
            totalIntended = totalIntended.minus(addDayAssignment(result,
                    dayAssigment));
        }
        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);
    }

}
TOP

Related Classes of org.libreplan.business.planner.limiting.entities.LimitingResourceAllocator

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.