Package org.libreplan.web.limitingresources

Source Code of org.libreplan.web.limitingresources.LimitingResourceQueueModel$IDayAssignmentBehaviour

/*
* 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.web.limitingresources;

import static org.libreplan.business.planner.limiting.entities.LimitingResourceQueueElement.isAfter;
import static org.libreplan.business.planner.limiting.entities.LimitingResourceQueueElement.isInTheMiddle;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedSet;

import org.apache.commons.lang.Validate;
import org.hibernate.Hibernate;
import org.hibernate.proxy.HibernateProxy;
import org.jgrapht.DirectedGraph;
import org.jgrapht.traverse.TopologicalOrderIterator;
import org.joda.time.LocalDate;
import org.libreplan.business.calendars.entities.BaseCalendar;
import org.libreplan.business.calendars.entities.CalendarAvailability;
import org.libreplan.business.calendars.entities.CalendarData;
import org.libreplan.business.calendars.entities.CalendarException;
import org.libreplan.business.common.exceptions.InstanceNotFoundException;
import org.libreplan.business.orders.daos.IOrderDAO;
import org.libreplan.business.orders.entities.HoursGroup;
import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.orders.entities.TaskSource;
import org.libreplan.business.planner.daos.IDependencyDAO;
import org.libreplan.business.planner.daos.ITaskElementDAO;
import org.libreplan.business.planner.entities.DayAssignment;
import org.libreplan.business.planner.entities.Dependency;
import org.libreplan.business.planner.entities.GenericResourceAllocation;
import org.libreplan.business.planner.entities.ResourceAllocation;
import org.libreplan.business.planner.entities.Task;
import org.libreplan.business.planner.entities.TaskElement;
import org.libreplan.business.planner.limiting.daos.ILimitingResourceQueueDAO;
import org.libreplan.business.planner.limiting.daos.ILimitingResourceQueueDependencyDAO;
import org.libreplan.business.planner.limiting.daos.ILimitingResourceQueueElementDAO;
import org.libreplan.business.planner.limiting.entities.AllocationSpec;
import org.libreplan.business.planner.limiting.entities.DateAndHour;
import org.libreplan.business.planner.limiting.entities.Gap;
import org.libreplan.business.planner.limiting.entities.Gap.GapOnQueue;
import org.libreplan.business.planner.limiting.entities.Gap.GapOnQueueWithQueueElement;
import org.libreplan.business.planner.limiting.entities.InsertionRequirements;
import org.libreplan.business.planner.limiting.entities.LimitingResourceAllocator;
import org.libreplan.business.planner.limiting.entities.LimitingResourceQueueDependency;
import org.libreplan.business.planner.limiting.entities.LimitingResourceQueueDependency.QueueDependencyType;
import org.libreplan.business.planner.limiting.entities.LimitingResourceQueueElement;
import org.libreplan.business.resources.entities.Criterion;
import org.libreplan.business.resources.entities.CriterionSatisfaction;
import org.libreplan.business.resources.entities.LimitingResourceQueue;
import org.libreplan.business.resources.entities.Resource;
import org.libreplan.business.scenarios.bootstrap.PredefinedScenarios;
import org.libreplan.business.scenarios.entities.Scenario;
import org.libreplan.business.users.daos.IOrderAuthorizationDAO;
import org.libreplan.business.users.daos.IUserDAO;
import org.libreplan.business.users.entities.OrderAuthorization;
import org.libreplan.business.users.entities.OrderAuthorizationType;
import org.libreplan.business.users.entities.User;
import org.libreplan.business.users.entities.UserRole;
import org.libreplan.business.workingday.EffortDuration;
import org.libreplan.business.workingday.IntraDayDate;
import org.libreplan.web.common.concurrentdetection.OnConcurrentModification;
import org.libreplan.web.limitingresources.QueuesState.Edge;
import org.libreplan.web.planner.order.SaveCommandBuilder;
import org.libreplan.web.security.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.zkoss.ganttz.timetracker.zoom.ZoomLevel;
import org.zkoss.ganttz.util.Interval;

@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
@OnConcurrentModification(goToPage = "/planner/index.zul;limiting_resources")
public class LimitingResourceQueueModel implements ILimitingResourceQueueModel {

    @Autowired
    private IOrderDAO orderDAO;

    @Autowired
    private IUserDAO userDAO;

    @Autowired
    private IOrderAuthorizationDAO orderAuthorizationDAO;

    @Autowired
    private ILimitingResourceQueueElementDAO limitingResourceQueueElementDAO;

    @Autowired
    private ILimitingResourceQueueDAO limitingResourceQueueDAO;

    @Autowired
    private ITaskElementDAO taskDAO;

    @Autowired
    private ILimitingResourceQueueDependencyDAO limitingResourceQueueDependencyDAO;

    private QueuesState queuesState;

    private Interval viewInterval;

    private LimitingResourceQueueElement beingEdited;

    private Set<LimitingResourceQueueElement> toBeRemoved = new HashSet<LimitingResourceQueueElement>();

    private Set<LimitingResourceQueueElement> toBeSaved = new HashSet<LimitingResourceQueueElement>();

    private Set<TaskElement> parentElementsToBeUpdated = new HashSet<TaskElement>();

    private Scenario master;

    private Map<LimitingResourceQueueElement, HashSet<LimitingResourceQueueDependency>> toBeSavedDependencies =
        new HashMap<LimitingResourceQueueElement, HashSet<LimitingResourceQueueDependency>>();

    @Override
    @Transactional(readOnly = true)
    public void initGlobalView() {
        doGlobalView();
    }


    private void doGlobalView() {
        master = PredefinedScenarios.MASTER.getScenario();
        List<LimitingResourceQueueElement> unassigned = findUnassignedLimitingResourceQueueElements();
        List<LimitingResourceQueue> queues = loadLimitingResourceQueues();
        queuesState = new QueuesState(queues, unassigned);
        final Date startingDate = getEarliestDate();
        Date endDate = (new LocalDate(startingDate)).plusYears(2)
                .toDateTimeAtCurrentTime().toDate();
        viewInterval = new Interval(startingDate, endDate);

        Date currentDate = new Date();
        viewInterval = new Interval(
                startingDate.after(currentDate) ? currentDate : startingDate,
                endDate);

    }

    private Date getEarliestDate() {
        final LimitingResourceQueueElement element = getEarliestQueueElement();
        return (element != null) ? element.getStartDate()
                .toDateTimeAtCurrentTime().toDate() : new Date();
    }

    private LimitingResourceQueueElement getEarliestQueueElement() {
        LimitingResourceQueueElement earliestQueueElement = null;

        for (LimitingResourceQueue each : queuesState.getQueues()) {
            LimitingResourceQueueElement element = getFirstLimitingResourceQueueElement(each);
            if (element == null) {
                continue;
            }
            if (earliestQueueElement == null
                    || isEarlier(element, earliestQueueElement)) {
                earliestQueueElement = element;
            }
        }
        return earliestQueueElement;
    }

    private boolean isEarlier(LimitingResourceQueueElement arg1,
            LimitingResourceQueueElement arg2) {
        return (arg1.getStartDate().isBefore(arg2.getStartDate()));
    }

    private LimitingResourceQueueElement getFirstLimitingResourceQueueElement(
            LimitingResourceQueue queue) {
        return getFirstChild(queue.getLimitingResourceQueueElements());
    }

    private LimitingResourceQueueElement getFirstChild(
            SortedSet<LimitingResourceQueueElement> elements) {
        return (elements.isEmpty()) ? null : elements.iterator().next();
    }

    /**
     * Loads unassigned {@link LimitingResourceQueueElement} from DB
     *
     * @return
     */
    private List<LimitingResourceQueueElement> findUnassignedLimitingResourceQueueElements() {
        return initializeLimitingResourceQueueElements(limitingResourceQueueElementDAO
                .getUnassigned());
    }

    private List<LimitingResourceQueueElement> initializeLimitingResourceQueueElements(
            List<LimitingResourceQueueElement> elements) {
        for (LimitingResourceQueueElement each : elements) {
            initializeLimitingResourceQueueElement(each);
        }
        return elements;
    }

    private void initializeLimitingResourceQueueElement(
            LimitingResourceQueueElement element) {
        ResourceAllocation<?> resourceAllocation = element
                .getResourceAllocation();
        resourceAllocation.switchToScenario(master);
        resourceAllocation = initializeResourceAllocationIfNecessary(resourceAllocation);
        element.setResourceAllocation(resourceAllocation);
        initializeTask(resourceAllocation.getTask());
        initializeResourceIfAny(element.getResource());
    }

    private void initializeTask(Task task) {
        if (hasResourceAllocation(task)) {
            ResourceAllocation<?> resourceAllocation = initializeResourceAllocationIfNecessary(getResourceAllocation(task));
            task.setResourceAllocation(resourceAllocation);
        }

        Hibernate.initialize(task);
        for (ResourceAllocation<?> each: task.getAllResourceAllocations()) {
            Hibernate.initialize(each);
        }
        initializeDependencies(task);
        initializeTaskSource(task.getTaskSource());
        initializeRootOrder(task);
    }

    private void initializeDependencies(Task task) {
        for (Dependency each: task.getDependenciesWithThisOrigin()) {
            Hibernate.initialize(each.getDestination());
        }
        for (Dependency each: task.getDependenciesWithThisDestination()) {
            Hibernate.initialize(each.getOrigin());
        }
    }

    private boolean hasResourceAllocation(Task task) {
        return !task.getLimitingResourceAllocations().isEmpty();
    }

    private ResourceAllocation<?> getResourceAllocation(Task task) {
        return task.getLimitingResourceAllocations().iterator().next();
    }

    private void initializeTaskSource(TaskSource taskSource) {
        Hibernate.initialize(taskSource);
        for (HoursGroup each: taskSource.getHoursGroups()) {
            Hibernate.initialize(each);
        }
    }

    // FIXME: Needed to fetch order.name in QueueComponent.composeTooltiptext.
    // Try to replace it with a HQL query instead of iterating all the way up
    // through order
    private void initializeRootOrder(Task task) {
        Hibernate.initialize(task.getOrderElement());
        OrderElement order = task.getOrderElement();
        do {
            Hibernate.initialize(order.getParent());
            order = order.getParent();
        } while (order.getParent() != null);
    }

    private void initializeCalendarIfAny(BaseCalendar calendar) {
        if (calendar != null) {
            Hibernate.initialize(calendar);
            initializeCalendarAvailabilities(calendar);
            initializeCalendarExceptions(calendar);
            initializeCalendarDataVersions(calendar);
        }
    }

    private void initializeCalendarAvailabilities(BaseCalendar calendar) {
        for (CalendarAvailability each : calendar.getCalendarAvailabilities()) {
            Hibernate.initialize(each);
        }
    }

    private void initializeCalendarExceptions(BaseCalendar calendar) {
        for (CalendarException each : calendar.getExceptions()) {
            Hibernate.initialize(each);
            Hibernate.initialize(each.getType());
        }
    }

    private void initializeCalendarDataVersions(BaseCalendar calendar) {
        for (CalendarData each : calendar.getCalendarDataVersions()) {
            Hibernate.initialize(each);
            Hibernate.initialize(each.getHoursPerDay());
            initializeCalendarIfAny(each.getParent());
        }
    }

    private ResourceAllocation<?> initializeResourceAllocationIfNecessary(
            ResourceAllocation<?> resourceAllocation) {
        if (resourceAllocation instanceof HibernateProxy) {
            resourceAllocation = (ResourceAllocation<?>) ((HibernateProxy) resourceAllocation)
                    .getHibernateLazyInitializer().getImplementation();
            if (resourceAllocation instanceof GenericResourceAllocation) {
                GenericResourceAllocation generic = (GenericResourceAllocation) resourceAllocation;
                initializeCriteria(generic.getCriterions());
            }
            Hibernate.initialize(resourceAllocation.getAssignments());
            Hibernate.initialize(resourceAllocation.getLimitingResourceQueueElement());
        }
        return resourceAllocation;
    }

    private void initializeCriteria(Set<Criterion> criteria) {
        for (Criterion each: criteria) {
            initializeCriterion(each);
        }
    }

    private void initializeCriterion(Criterion criterion) {
        Hibernate.initialize(criterion);
        Hibernate.initialize(criterion.getType());
    }

    private List<LimitingResourceQueue> loadLimitingResourceQueues() {
        return initializeLimitingResourceQueues(limitingResourceQueueDAO
                .getAll());
    }

    private List<LimitingResourceQueue> initializeLimitingResourceQueues(
            List<LimitingResourceQueue> queues) {
        for (LimitingResourceQueue each : queues) {
            initializeLimitingResourceQueue(each);
        }
        return queues;
    }

    private void initializeLimitingResourceQueue(LimitingResourceQueue queue) {
        initializeResourceIfAny(queue.getResource());
        for (LimitingResourceQueueElement each : queue
                .getLimitingResourceQueueElements()) {
            initializeLimitingResourceQueueElement(each);
        }
    }

    private void initializeResourceIfAny(Resource resource) {
        if (resource != null) {
            Hibernate.initialize(resource);
            initializeCalendarIfAny(resource.getCalendar());
            resource.getAssignments();
            for (CriterionSatisfaction each : resource
                    .getCriterionSatisfactions()) {
                Hibernate.initialize(each);
                initializeCriterion(each.getCriterion());
            }
        }
    }

    @Override
    @Transactional(readOnly = true)
    public Order getOrderByTask(TaskElement task) {
        return orderDAO.loadOrderAvoidingProxyFor(task.getOrderElement());
    }

    @Override
    public Interval getViewInterval() {
        return viewInterval;
    }

    @Override
    @Transactional(readOnly = true)
    public boolean userCanRead(Order order, String loginName) {
        if (SecurityUtils.isSuperuserOrUserInRoles(
                UserRole.ROLE_READ_ALL_PROJECTS,
                UserRole.ROLE_EDIT_ALL_PROJECTS)) {
            return true;
        }
        try {
            User user = userDAO.findByLoginName(loginName);
            for (OrderAuthorization authorization : orderAuthorizationDAO
                    .listByOrderUserAndItsProfiles(order, user)) {
                if (authorization.getAuthorizationType() == OrderAuthorizationType.READ_AUTHORIZATION
                        || authorization.getAuthorizationType() == OrderAuthorizationType.WRITE_AUTHORIZATION) {
                    return true;
                }
            }
        } catch (InstanceNotFoundException e) {
            // this case shouldn't happen, because it would mean that there
            // isn't a logged user
            // anyway, if it happenned we don't allow the user to pass
        }
        return false;
    }

    @Override
    public List<LimitingResourceQueue> getLimitingResourceQueues() {
        return queuesState.getQueuesOrderedByResourceName();
    }

    @Override
    public List<LimitingResourceQueueElement> getUnassignedLimitingResourceQueueElements() {
        return queuesState.getUnassigned();
    }

    @Override
    public ZoomLevel calculateInitialZoomLevel() {
        Interval interval = getViewInterval();
        return ZoomLevel.getDefaultZoomByDates(new LocalDate(interval
                .getStart()), new LocalDate(interval.getFinish()));
    }

    @Override
    public List<LimitingResourceQueueElement> assignLimitingResourceQueueElement(
            LimitingResourceQueueElement externalQueueElement) {

        InsertionRequirements requirements = queuesState
                .getRequirementsFor(externalQueueElement);
        AllocationSpec allocation = insertAtGap(requirements);
        if (allocation == null) {
            return Collections.emptyList();
        }
        applyAllocation(allocation);

        assert allocation.isValid();
        List<LimitingResourceQueueElement> result = new ArrayList<LimitingResourceQueueElement>();
        result.add(requirements.getElement());

        List<LimitingResourceQueueElement> moved = shift(
                queuesState
                        .getPotentiallyAffectedByInsertion(externalQueueElement),
                requirements.getElement(), allocation);
        result.addAll(rescheduleAffectedElementsToSatisfyDependencies(allocation, moved));

        return result;
    }

    /**
     * After an allocation dependencies might be broken, this method unschedules
     * elements affected by an allocation and reschedule them again in
     * topological order, so dependencies are satisfied
     *
     * If the allocation was appropriative it also allocates those elements that
     * might be unscheduled before due to the appropriative allocation
     *
     * @param allocation
     * @param moved
     * @return
     */
    private Collection<? extends LimitingResourceQueueElement> rescheduleAffectedElementsToSatisfyDependencies(
            AllocationSpec allocation, List<LimitingResourceQueueElement> moved) {

        List<LimitingResourceQueueElement> result = new ArrayList<LimitingResourceQueueElement>();
        List<LimitingResourceQueueElement> toReschedule = new ArrayList<LimitingResourceQueueElement>();

        checkAllocationIsAppropriative(false);
        for (LimitingResourceQueueElement each: moved) {
            toReschedule.add(unschedule(each));
        }
        if (allocation.isAppropriative()) {
            toReschedule.addAll(allocation.getUnscheduledElements());
        }
        for (LimitingResourceQueueElement each: queuesState.inTopologicalOrder(toReschedule)) {
            result.addAll(assignLimitingResourceQueueElement(each));
        }
        checkAllocationIsAppropriative(true);

        return result;
    }

    /**
     * Moves elements in order to satisfy dependencies
     *
     * @param potentiallyAffectedByInsertion
     * @param elementInserted
     * @param allocationAlreadyDone
     * @return the elements that have been moved
     */
    private List<LimitingResourceQueueElement> shift(
            DirectedGraph<LimitingResourceQueueElement, Edge> potentiallyAffectedByInsertion,
            LimitingResourceQueueElement elementInserted,
            AllocationSpec allocationAlreadyDone) {

        List<AllocationSpec> allocationsToBeDone = getAllocationsToBeDone(
                        potentiallyAffectedByInsertion, elementInserted,
                        allocationAlreadyDone);
        List<LimitingResourceQueueElement> result = new ArrayList<LimitingResourceQueueElement>();
        for (AllocationSpec each : allocationsToBeDone) {
            applyAllocation(each);

            LimitingResourceQueueElement element = each.getElement();
            result.add(element);
        }

        return result;
    }

    private List<AllocationSpec> getAllocationsToBeDone(
            DirectedGraph<LimitingResourceQueueElement, Edge> potentiallyAffectedByInsertion,
            LimitingResourceQueueElement elementInserted,
            AllocationSpec allocationAlreadyDone) {
        List<AllocationSpec> result = new ArrayList<AllocationSpec>();
        Map<LimitingResourceQueueElement, AllocationSpec> allocationsToBeDoneByElement = new HashMap<LimitingResourceQueueElement, AllocationSpec>();
        allocationsToBeDoneByElement.put(elementInserted, allocationAlreadyDone);

        List<LimitingResourceQueueElement> mightNeedShift = withoutElementInserted(
                elementInserted,
                QueuesState.topologicalIterator(potentiallyAffectedByInsertion));
        for (LimitingResourceQueueElement each : mightNeedShift) {
            AllocationSpec futureAllocation = getAllocationToBeDoneFor(
                    potentiallyAffectedByInsertion,
                    allocationsToBeDoneByElement, each);
            if (futureAllocation != null) {
                result.add(futureAllocation);
                allocationsToBeDoneByElement.put(each, futureAllocation);
            }
        }
        return result;
    }

    private List<LimitingResourceQueueElement> withoutElementInserted(
            LimitingResourceQueueElement elementInserted,
            final TopologicalOrderIterator<LimitingResourceQueueElement, Edge> topologicalIterator) {
        List<LimitingResourceQueueElement> result = QueuesState
                .toList(topologicalIterator);
        result.remove(elementInserted);
        return result;
    }

    private AllocationSpec getAllocationToBeDoneFor(
            DirectedGraph<LimitingResourceQueueElement, Edge> potentiallyAffectedByInsertion,
            Map<LimitingResourceQueueElement, AllocationSpec> allocationsToBeDoneByElement,
            LimitingResourceQueueElement current) {
        Validate.isTrue(!current.isDetached());
        DateAndHour newStart = current.getStartTime();
        DateAndHour newEnd = current.getEndTime();
        Map<LimitingResourceQueueElement, List<Edge>> incoming = bySource(potentiallyAffectedByInsertion
                .incomingEdgesOf(current));
        for (Entry<LimitingResourceQueueElement, List<Edge>> each : incoming
                .entrySet()) {
            AllocationSpec previous = allocationsToBeDoneByElement.get(each
                    .getKey());
            if (previous != null) {
                newStart = DateAndHour.max(newStart, getStartFrom(previous,
                        each.getValue()));
                newEnd = DateAndHour.max(newEnd, getEndFrom(previous, each
                        .getValue()));
            }
        }
        if (current.getStartTime().compareTo(newStart) == 0
                && current.getEndTime().compareTo(newEnd) == 0) {
            return null;
        }
        InsertionRequirements requirements = InsertionRequirements.create(
                current, newStart, newEnd);
        GapOnQueue gap = Gap.untilEnd(current, newStart).onQueue(
                current.getLimitingResourceQueue());
        AllocationSpec result = requirements.guessValidity(gap);
        assert result.isValid();
        return result;
    }

    private DateAndHour getStartFrom(AllocationSpec previous,
            List<Edge> edges) {
        DateAndHour result = null;
        for (Edge each : edges) {
            result = DateAndHour.max(result,
                    calculateStart(previous, each.type));
        }
        return result;
    }


    private DateAndHour calculateStart(AllocationSpec previous,
            QueueDependencyType type) {
        if (!type.modifiesDestinationStart()) {
            return null;
        }
        return type.calculateDateTargetFrom(previous.getStartInclusive(), previous
                .getEndExclusive());
    }

    private DateAndHour getEndFrom(AllocationSpec previous, List<Edge> edges) {
        DateAndHour result = null;
        for (Edge each : edges) {
            result = DateAndHour.max(result, calculateEnd(previous, each.type));
        }
        return result;
    }

    private DateAndHour calculateEnd(AllocationSpec previous,
            QueueDependencyType type) {
        if (!type.modifiesDestinationEnd()) {
            return null;
        }
        return type.calculateDateTargetFrom(previous.getStartInclusive(),
                previous.getEndExclusive());
    }

    private Map<LimitingResourceQueueElement, List<Edge>> bySource(
            Collection<? extends Edge> incomingEdgesOf) {
        Map<LimitingResourceQueueElement, List<Edge>> result = new HashMap<LimitingResourceQueueElement, List<Edge>>();
        for (Edge each : incomingEdgesOf) {
            if (result.get(each.source) == null) {
                result.put(each.source, new ArrayList<Edge>());
            }
            result.get(each.source).add(each);
        }
        return result;
    }

    /**
     * @return <code>null</code> if no suitable gap found; the allocation found
     *         otherwise
     */
    private AllocationSpec insertAtGap(InsertionRequirements requirements) {
        AllocationSpec allocationStillNotDone = findAllocationSpecFor(requirements);
        return doAppropriativeIfNecessary(allocationStillNotDone, requirements);
    }

    /**
     * Find valid {@link AllocationSpec} taking into account requirements
     *
     * @param requirements
     * @return
     */
    private AllocationSpec findAllocationSpecFor(InsertionRequirements requirements) {
        List<GapOnQueue> potentiallyValidGapsFor = queuesState
                .getPotentiallyValidGapsFor(requirements);
        return findAllocationSpecFor(potentiallyValidGapsFor, requirements);
    }

    private AllocationSpec findAllocationSpecFor(List<GapOnQueue> gapsOnQueue, InsertionRequirements requirements) {
        boolean generic = requirements.getElement().isGeneric();
        for (GapOnQueue each : gapsOnQueue) {
            for (GapOnQueue eachSubGap : getSubGaps(each,
                    requirements.getElement(), generic)) {
                AllocationSpec allocation = requirements
                        .guessValidity(eachSubGap);
                if (allocation.isValid()) {
                    return allocation;
                }
            }
        }
        return null;
    }

    private AllocationSpec doAppropriativeIfNecessary(AllocationSpec allocation,
            InsertionRequirements requirements) {
        if (allocation != null) {
            if (checkAllocationIsAppropriative()
                    && requirements.isAppropiativeAllocation(allocation)) {
                return doAppropriativeAllocation(requirements, allocation);
            }
            return allocation;
        }
        return null;
    }

    private AllocationSpec insertAtGap(InsertionRequirements requirements, LimitingResourceQueue queue) {
        AllocationSpec allocationStillNotDone = findAllocationSpecForInQueue(requirements, queue);
        return doAppropriativeIfNecessary(allocationStillNotDone, requirements);
    }

    private AllocationSpec findAllocationSpecForInQueue(
            InsertionRequirements requirements, LimitingResourceQueue queue) {

        List<GapOnQueue> potentiallyValidGapsFor = new ArrayList<GapOnQueue>();

        for (GapOnQueue each : queuesState
                .getPotentiallyValidGapsFor(requirements)) {
            if (each.getOriginQueue().equals(queue)) {
                potentiallyValidGapsFor.add(each);
            }
        }
        return findAllocationSpecFor(potentiallyValidGapsFor, requirements);
    }

    private boolean checkAllocationIsAppropriative = true;

    private AllocationSpec doAppropriativeAllocation(
            InsertionRequirements requirements, AllocationSpec allocation) {

        LimitingResourceQueueElement element = requirements.getElement();
        List<LimitingResourceQueue> potentiallyValidQueues = getAssignableQueues(element);
        LimitingResourceQueue queue = earliestQueue(potentiallyValidQueues);

        List<LimitingResourceQueueElement> unscheduled = new ArrayList<LimitingResourceQueueElement>();
        allocation = unscheduleElementsFor(queue, requirements, unscheduled);
        allocation.setUnscheduledElements(queuesState.inTopologicalOrder(unscheduled));
        return allocation;
    }

    /**
     * Returns queue which last element is at a earliest date
     *
     * @param potentiallyValidQueues
     * @return
     */
    private LimitingResourceQueue earliestQueue(
            List<LimitingResourceQueue> potentiallyValidQueues) {

        LimitingResourceQueue result = null;
        LocalDate latestDate = null;

        for (LimitingResourceQueue each : potentiallyValidQueues) {
            SortedSet<LimitingResourceQueueElement> elements = each
                    .getLimitingResourceQueueElements();
            if (!elements.isEmpty()) {
                LocalDate date = elements.last().getEndDate();
                if (latestDate == null || date.isAfter(latestDate)) {
                    latestDate = date;
                    result = each;
                }
            }
        }
        if(result == null && !potentiallyValidQueues.isEmpty()) {
            result = potentiallyValidQueues.get(0);
        }
        return result;
    }

    private void checkAllocationIsAppropriative(boolean value) {
        checkAllocationIsAppropriative = value;
    }

    private boolean checkAllocationIsAppropriative() {
        return checkAllocationIsAppropriative;
    }

    private List<GapOnQueue> getSubGaps(GapOnQueue each,
            LimitingResourceQueueElement element, boolean generic) {
        if (generic) {
            return each.splitIntoGapsSatisfyingCriteria(element.getCriteria());
        }
        return Collections.singletonList(each);
    }

    private AllocationSpec applyAllocation(final AllocationSpec allocationStillNotDone) {
        applyAllocation(allocationStillNotDone, new IDayAssignmentBehaviour() {

            @Override
            public void allocateDayAssigments(IntraDayDate start,
                    IntraDayDate end) {
                ResourceAllocation<?> resourceAllocation = getResourceAllocation(allocationStillNotDone);
                Resource resource = getResource(allocationStillNotDone);

                List<DayAssignment> assignments = allocationStillNotDone.getAssignmentsFor(
                        resourceAllocation, resource);
                resourceAllocation.allocateLimitingDayAssignments(assignments,
                        start, end);
            }

            private ResourceAllocation<?> getResourceAllocation(AllocationSpec allocation) {
                return allocation.getElement().getResourceAllocation();
            }

            private Resource getResource(AllocationSpec allocation) {
                return allocation.getQueue().getResource();
            }

        });
        return allocationStillNotDone;
    }

    private void applyAllocation(AllocationSpec allocationStillNotDone,
            IDayAssignmentBehaviour allocationBehaviour) {

        // Do day allocation
        allocationBehaviour.allocateDayAssigments(
                convert(allocationStillNotDone.getStartInclusive()),
                convert(allocationStillNotDone.getEndExclusive()));

        LimitingResourceQueueElement element = allocationStillNotDone
            .getElement();
        LimitingResourceQueue queue = allocationStillNotDone.getQueue();

        // Update start and end time of task
        updateStartAndEndTimes(element, allocationStillNotDone
                .getStartInclusive(), allocationStillNotDone
                        .getEndExclusive());

        // Add to queue and mark as modified
        addLimitingResourceQueueElementIfNeeded(queue, element);
        markAsModified(element);
    }

    private DateAndHour getEndsAfterBecauseOfGantt(
            LimitingResourceQueueElement queueElement) {
        return DateAndHour.from(LocalDate.fromDateFields(queueElement
                        .getEarliestEndDateBecauseOfGantt()));
    }

    private List<LimitingResourceQueueElement> assignLimitingResourceQueueElementToQueueAt(
            final LimitingResourceQueueElement element,
            final LimitingResourceQueue queue,
            final DateAndHour startAt,
            final DateAndHour endsAfter) {

        // Check if allocation is possible
        InsertionRequirements requirements = queuesState.getRequirementsFor(
                element, startAt);
        AllocationSpec allocation = insertAtGap(requirements, queue);
        if (!allocation.isValid()) {
            return Collections.emptyList();
        }

        // Do allocation
        applyAllocation(allocation, new IDayAssignmentBehaviour() {

            @Override
            public void allocateDayAssigments(IntraDayDate start,
                    IntraDayDate end) {

                List<DayAssignment> assignments = LimitingResourceAllocator
                        .generateDayAssignments(
                                element.getResourceAllocation(),
                                queue.getResource(), startAt, endsAfter);
                element.getResourceAllocation().allocateLimitingDayAssignments(
                        assignments, start, end);
            }

        });

        assert allocation.isValid();

        // Move other tasks to respect dependency constraints
        List<LimitingResourceQueueElement> result = new ArrayList<LimitingResourceQueueElement>();
        result.add(requirements.getElement());

        List<LimitingResourceQueueElement> moved = shift(
                queuesState.getPotentiallyAffectedByInsertion(element),
                requirements.getElement(), allocation);
        result.addAll(rescheduleAffectedElementsToSatisfyDependencies(allocation, moved));

        return result;
    }

    /**
     *
     * Describes how day assignments are going to be generated for an allocation
     *
     * @author Diego Pino García<dpino@igalia.com>
     *
     */
    private interface IDayAssignmentBehaviour {

        void allocateDayAssigments(IntraDayDate start, IntraDayDate end);

    }

    private void markAsModified(LimitingResourceQueueElement element) {
        if (!toBeSaved.contains(element)) {
            toBeSaved.add(element);
        }
    }

    public Gap createGap(Resource resource, DateAndHour startTime,
            DateAndHour endTime) {
        return Gap.create(resource, startTime, endTime);
    }

    private void updateStartAndEndTimes(LimitingResourceQueueElement element,
            DateAndHour startTime, DateAndHour endTime) {

        element.setStartDate(startTime.getDate());
        element.setStartHour(startTime.getHour());
        element.setEndDate(endTime.getDate());
        element.setEndHour(endTime.getHour());

        // Update starting and ending dates for associated Task
        Task task = element.getResourceAllocation().getTask();
        updateStartingAndEndingDate(task, convert(startTime), convert(endTime));
    }

    private IntraDayDate convert(DateAndHour dateAndHour) {
        return IntraDayDate.create(dateAndHour.getDate(),
                EffortDuration.hours(dateAndHour.getHour()));
    }

    private void updateStartingAndEndingDate(Task task, IntraDayDate startDate,
            IntraDayDate endDate) {
        task.setIntraDayStartDate(startDate);
        task.setIntraDayEndDate(endDate);
        task.explicityMoved(startDate, endDate);
    }

    private void addLimitingResourceQueueElementIfNeeded(LimitingResourceQueue queue,
            LimitingResourceQueueElement element) {
        if (element.getLimitingResourceQueue() == null) {
            queuesState.assignedToQueue(element, queue);
        }
    }

    @Override
    @Transactional
    public void confirm() {
        applyChanges();
    }

    private void applyChanges() {
        removeQueueElements();
        saveQueueElements();
    }

    private void saveQueueElements() {
        for (LimitingResourceQueueElement each: toBeSaved) {
            if (each != null) {
                saveQueueElement(each);
            }
        }
        updateEndDateForParentTasks();
        SaveCommandBuilder.dontPoseAsTransientAndChildrenObjects(getAllocations(toBeSaved));
        toBeSaved.clear();
        parentElementsToBeUpdated.clear();
    }

    private List<ResourceAllocation<?>> getAllocations(
            Collection<? extends LimitingResourceQueueElement> elements) {
        List<ResourceAllocation<?>> result = new ArrayList<ResourceAllocation<?>>();
        for (LimitingResourceQueueElement each : elements) {
            if (each.getResourceAllocation() != null) {
                result.add(each.getResourceAllocation());
            }
        }
        return result;
    }

    private void saveQueueElement(LimitingResourceQueueElement element) {
        Long previousId = element.getId();
        limitingResourceQueueElementDAO.save(element);
        limitingResourceQueueDAO.flush();
        if (element.isNewObject()) {
            queuesState.idChangedFor(previousId, element);
        }
        element.dontPoseAsTransientObjectAnymore();
        element.getResourceAllocation().dontPoseAsTransientObjectAnymore();
        for (DayAssignment each: element.getDayAssignments()) {
            each.dontPoseAsTransientObjectAnymore();
        }
        if (toBeSavedDependencies.get(element) != null) {
            saveDependencies(toBeSavedDependencies.get(element));
            toBeSavedDependencies.remove(element);
        }
        taskDAO.save(getAssociatedTask(element));
    }

    private void updateEndDateForParentTasks() {
        for(TaskElement task : parentElementsToBeUpdated) {
            TaskElement parent = task;
            while(parent != null) {
                parent.setIntraDayEndDate(null);
                parent.initializeDatesIfNeeded();
                taskDAO.save(parent);
                parent = parent.getParent();
            }
        }
    }

    private void saveDependencies(HashSet<LimitingResourceQueueDependency> dependencies) {
        for (LimitingResourceQueueDependency each: dependencies) {
            limitingResourceQueueDependencyDAO.save(each);
            each.dontPoseAsTransientObjectAnymore();
        }
    }

    private Task getAssociatedTask(LimitingResourceQueueElement element) {
        return element.getResourceAllocation().getTask();
    }

    private void removeQueueElements() {
        for (LimitingResourceQueueElement each: toBeRemoved) {
            removeQueueElement(each);
        }
        toBeRemoved.clear();
    }

    private void removeQueueElement(LimitingResourceQueueElement element) {
        Task task = getAssociatedTask(element);
        removeQueueDependenciesIfAny(task);
        removeQueueElementById(element.getId());
    }

    private void removeQueueElementById(Long id) {
        try {
            limitingResourceQueueElementDAO.remove(id);
        } catch (InstanceNotFoundException e) {
            e.printStackTrace();
            throw new RuntimeException("Trying to remove non-existing entity");
        }
    }

    private void removeQueueDependenciesIfAny(Task task) {
        for (Dependency each: task.getDependenciesWithThisOrigin()) {
            removeQueueDependencyIfAny(each);
        }
        for (Dependency each: task.getDependenciesWithThisDestination()) {
            removeQueueDependencyIfAny(each);
        }
    }

    private void removeQueueDependencyIfAny(Dependency dependency) {
        LimitingResourceQueueDependency queueDependency = dependency.getQueueDependency();
        if (queueDependency != null) {
            queueDependency.getHasAsOrigin().remove(queueDependency);
            queueDependency.getHasAsDestiny().remove(queueDependency);
            dependency.setQueueDependency(null);
            dependencyDAO.save(dependency);
            removeQueueDependencyById(queueDependency.getId());
        }
    }

    @Autowired
    private IDependencyDAO dependencyDAO;

    private void removeQueueDependencyById(Long id) {
        try {
            limitingResourceQueueDependencyDAO.remove(id);
        } catch (InstanceNotFoundException e) {
            e.printStackTrace();
            throw new RuntimeException("Trying to remove non-existing entity");
        }
    }

    /**
     * Unschedules an element from the list of queue elements. The element is
     * later added to the list of unassigned elements
     */
    @Override
    public LimitingResourceQueueElement unschedule(LimitingResourceQueueElement queueElement) {
        queuesState.unassingFromQueue(queueElement);
        markAsModified(queueElement);
        return queueElement;
    }

    /**
     * Removes an {@link LimitingResourceQueueElement} from the list of
     * unassigned elements
     *
     */
    @Override
    public void removeUnassignedLimitingResourceQueueElement(
            LimitingResourceQueueElement element) {
        LimitingResourceQueueElement queueElement = queuesState.getEquivalent(element);

        queueElement.getResourceAllocation().setLimitingResourceQueueElement(null);
        queueElement.getResourceAllocation().getTask()
                .removeAllResourceAllocations();
        queuesState.removeUnassigned(queueElement);
        markAsRemoved(queueElement);
    }

    private void markAsRemoved(LimitingResourceQueueElement element) {
        if (toBeSaved.contains(element)) {
            toBeSaved.remove(element);
        }
        if (!toBeRemoved.contains(element)) {
            toBeRemoved.add(element);
        }
    }

    @Override
    public List<LimitingResourceQueue> getAssignableQueues(
            LimitingResourceQueueElement element) {
        return queuesState.getAssignableQueues(element);
    }

    @Override
    public List<LimitingResourceQueueElement> nonAppropriativeAllocation(
            LimitingResourceQueueElement element,
            LimitingResourceQueue queue,
            DateAndHour startTime) {

        Validate.notNull(element);
        Validate.notNull(queue);
        Validate.notNull(startTime);

        if (element.getLimitingResourceQueue() != null) {
            unschedule(element);
        }
        return assignLimitingResourceQueueElementToQueueAt(element, queue,
                startTime, getEndsAfterBecauseOfGantt(element));
    }

    @Override
    public void init(LimitingResourceQueueElement element) {
        beingEdited = queuesState.getEquivalent(element);
    }

    @Override
    public LimitingResourceQueueElement getLimitingResourceQueueElement() {
        return beingEdited;
    }

    @Override
    public Set<LimitingResourceQueueElement> appropriativeAllocation(
            LimitingResourceQueueElement _element,
            LimitingResourceQueue _queue, DateAndHour allocationTime) {

        Set<LimitingResourceQueueElement> result = new HashSet<LimitingResourceQueueElement>();

        LimitingResourceQueue queue = queuesState.getEquivalent(_queue);
        LimitingResourceQueueElement element = queuesState.getEquivalent(_element);

        InsertionRequirements requirements = queuesState
                .getRequirementsFor(element, allocationTime);

        if (element.getLimitingResourceQueue() != null) {
            unschedule(element);
        }

        // Unschedule elements in queue since allocationTime and put them in
        // toSchedule
        List<LimitingResourceQueueElement> toSchedule = new ArrayList<LimitingResourceQueueElement>();
        unscheduleElementsFor(queue, requirements, toSchedule);

        result.addAll(assignLimitingResourceQueueElementToQueueAt(element,
                queue, allocationTime, getEndsAfterBecauseOfGantt(element)));

        for (LimitingResourceQueueElement each: queuesState
                .inTopologicalOrder(toSchedule)) {
            result.addAll(assignLimitingResourceQueueElement(each));
        }
        return result;
    }

    /**
     * Creates room enough in a queue for fitting requirements
     *
     * Starts unscheduling elements in queue since
     * requirements.earliestPossibleStart() When there's room enough for
     * allocating requirements, the method stops unscheduling more elements
     *
     * Returns the list of elements that were unscheduled in the process
     *
     * @param queue
     * @param requirements
     * @return
     */
    private AllocationSpec unscheduleElementsFor(
            LimitingResourceQueue queue, InsertionRequirements requirements,
            List<LimitingResourceQueueElement> result) {
        DateAndHour allocationTime = requirements.getEarliestPossibleStart();
        List<GapOnQueueWithQueueElement> gapsWithQueueElements = queuesState
                .getGapsWithQueueElementsOnQueueSince(queue, allocationTime);

        return unscheduleElementsFor(gapsWithQueueElements, requirements, result);
    }

    private AllocationSpec unscheduleElementsFor(List<GapOnQueueWithQueueElement> gaps,
            InsertionRequirements requirements,
            List<LimitingResourceQueueElement> result) {

        if (gaps.isEmpty()) {
            return null;
        }
        GapOnQueueWithQueueElement first = gaps.get(0);
        GapOnQueue gapOnQueue = first.getGapOnQueue();
        if (gapOnQueue != null) {
            AllocationSpec allocation = requirements.guessValidity(gapOnQueue);
            if (allocation.isValid()) {
                return allocation;
            }
        }
        result.add(unschedule(first.getQueueElement()));
        if (gaps.size() > 1) {
            gaps.set(1, GapOnQueueWithQueueElement.coalesce(first, gaps.get(1)));
        }
        gaps.remove(0);
        return unscheduleElementsFor(gaps, requirements, result);
    }

    @SuppressWarnings("unchecked")
    public LimitingResourceQueueElement getFirstElementFrom(LimitingResourceQueue queue, DateAndHour allocationTime) {
        final List<LimitingResourceQueueElement> elements = new ArrayList(queue.getLimitingResourceQueueElements());

        // First element
        final LimitingResourceQueueElement first = elements.get(0);
        if (isAfter(first, allocationTime)) {
            return first;
        }

        // Rest of elements
        for (int i = 0; i < elements.size(); i++) {
            final LimitingResourceQueueElement each = elements.get(i);
            if (isInTheMiddle(each, allocationTime) ||
                    isAfter(each, allocationTime)) {
                return each;
            }
        }
        return null;
    }

    @Override
    @Transactional(readOnly=true)
    public List<LimitingResourceQueueElement> replaceLimitingResourceQueueElement(
            LimitingResourceQueueElement oldElement,
            LimitingResourceQueueElement newElement) {

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

        boolean needToReassign = oldElement.hasDayAssignments();

        limitingResourceQueueElementDAO.save(oldElement);
        limitingResourceQueueElementDAO.save(newElement);
        toBeSaved.remove(oldElement);
        queuesState.replaceLimitingResourceQueueElement(oldElement, newElement);

        if (needToReassign) {
            result.addAll(assignLimitingResourceQueueElement(newElement));
        }
        HashSet<LimitingResourceQueueDependency> dependencies = new HashSet<LimitingResourceQueueDependency>();
        dependencies.addAll(newElement.getDependenciesAsOrigin());
        dependencies.addAll(newElement.getDependenciesAsDestiny());
        toBeSavedDependencies.put(newElement, dependencies);

        markAsModified(newElement);

        return result;
    }


    @Override
    public Set<LimitingResourceQueueElement> assignLimitingResourceQueueElements(
            List<LimitingResourceQueueElement> queueElements) {
        Set<LimitingResourceQueueElement> result = new HashSet<LimitingResourceQueueElement>();
        for (LimitingResourceQueueElement each: queuesState.inTopologicalOrder(queueElements)) {
            result.addAll(assignLimitingResourceQueueElement(each));
        }
        return result;
    }

}
TOP

Related Classes of org.libreplan.web.limitingresources.LimitingResourceQueueModel$IDayAssignmentBehaviour

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.