/*
* 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;
import static org.libreplan.business.workingday.EffortDuration.hours;
import static org.libreplan.business.workingday.EffortDuration.zero;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Objects;
import org.apache.commons.lang3.Validate;
import javax.validation.constraints.NotNull;
import org.joda.time.LocalDate;
import org.libreplan.business.calendars.entities.AvailabilityTimeLine;
import org.libreplan.business.calendars.entities.BaseCalendar;
import org.libreplan.business.calendars.entities.Capacity;
import org.libreplan.business.calendars.entities.CombinedWorkHours;
import org.libreplan.business.calendars.entities.ICalendar;
import org.libreplan.business.calendars.entities.SameWorkHoursEveryDay;
import org.libreplan.business.calendars.entities.ThereAreHoursOnWorkHoursCalculator;
import org.libreplan.business.calendars.entities.ThereAreHoursOnWorkHoursCalculator.CapacityResult;
import org.libreplan.business.common.BaseEntity;
import org.libreplan.business.common.Registry;
import org.libreplan.business.planner.entities.AssignedEffortForResource.IAssignedEffortForResource;
import org.libreplan.business.planner.entities.DerivedAllocationGenerator.IWorkerFinder;
import org.libreplan.business.planner.entities.allocationalgorithms.AllocationModification;
import org.libreplan.business.planner.entities.allocationalgorithms.AllocatorForTaskDurationAndSpecifiedResourcesPerDay;
import org.libreplan.business.planner.entities.allocationalgorithms.Distributor;
import org.libreplan.business.planner.entities.allocationalgorithms.EffortModification;
import org.libreplan.business.planner.entities.allocationalgorithms.ResourcesPerDayModification;
import org.libreplan.business.planner.entities.allocationalgorithms.UntilFillingHoursAllocator;
import org.libreplan.business.planner.entities.consolidations.Consolidation;
import org.libreplan.business.planner.limiting.entities.LimitingResourceQueueElement;
import org.libreplan.business.resources.daos.IResourcesSearcher;
import org.libreplan.business.resources.entities.Criterion;
import org.libreplan.business.resources.entities.Machine;
import org.libreplan.business.resources.entities.MachineWorkersConfigurationUnit;
import org.libreplan.business.resources.entities.Resource;
import org.libreplan.business.scenarios.IScenarioManager;
import org.libreplan.business.scenarios.entities.Scenario;
import org.libreplan.business.util.deepcopy.OnCopy;
import org.libreplan.business.util.deepcopy.Strategy;
import org.libreplan.business.workingday.EffortDuration;
import org.libreplan.business.workingday.EffortDuration.IEffortFrom;
import org.libreplan.business.workingday.IntraDayDate;
import org.libreplan.business.workingday.IntraDayDate.PartialDay;
import org.libreplan.business.workingday.ResourcesPerDay;
/**
* Resources are allocated to planner tasks.
*
* @author Diego Pino GarcÃa <dpino@igalia.com>
* @author Manuel Rego Casasnovas <mrego@igalia.com>
*/
public abstract class ResourceAllocation<T extends DayAssignment>
extends BaseEntity
implements IAssignedEffortForResource {
private Task task;
private AssignmentFunction assignmentFunction;
@OnCopy(Strategy.SHARE)
private ResourcesPerDay resourcesPerDay;
@OnCopy(Strategy.SHARE)
private ResourcesPerDay intendedResourcesPerDay;
private Integer intendedTotalHours;
private Set<DerivedAllocation> derivedAllocations = new HashSet<>();
@OnCopy(Strategy.SHARE_COLLECTION_ELEMENTS)
private Set<LimitingResourceQueueElement> limitingResourceQueueElements = new HashSet<>();
@OnCopy(Strategy.SHARE)
private EffortDuration intendedTotalAssignment = zero();
@OnCopy(Strategy.SHARE)
private EffortDuration intendedNonConsolidatedEffort = zero();
@OnCopy(Strategy.IGNORE)
private DayAssignmentsState assignmentsState;
private IOnDayAssignmentRemoval dayAssignmenteRemoval = new DoNothing();
/**
* Constructor for hibernate. Do not use!
*/
public ResourceAllocation() {
this.assignmentsState = buildFromDBState();
}
public static <T extends ResourceAllocation<?>> List<T> getSatisfied(Collection<T> resourceAllocations) {
Validate.notNull(resourceAllocations);
Validate.noNullElements(resourceAllocations);
List<T> result = new ArrayList<>();
for (T each : resourceAllocations) {
if ( each.isSatisfied() ) {
result.add(each);
}
}
return result;
}
public static <T extends ResourceAllocation<?>> List<T> getOfType(
Class<T> type, Collection<? extends ResourceAllocation<?>> resourceAllocations) {
List<T> result = new ArrayList<>();
for (ResourceAllocation<?> allocation : resourceAllocations) {
if ( type.isInstance(allocation) ) {
result.add(type.cast(allocation));
}
}
return result;
}
public static <R extends ResourceAllocation<?>> Map<Resource, List<R>> byResource(
Collection<? extends R> allocations) {
Map<Resource, List<R>> result = new HashMap<>();
for (R resourceAllocation : allocations) {
for (Resource resource : resourceAllocation.getAssociatedResources()) {
if ( !result.containsKey(resource) ) {
result.put(resource, new ArrayList<>());
}
result.get(resource).add(resourceAllocation);
}
}
return result;
}
public static <R extends ResourceAllocation<?>> List<R> sortedByStartDate(Collection<? extends R> allocations) {
List<R> result = new ArrayList<>(allocations);
Collections.sort(result, byStartDateComparator());
return result;
}
public static <R extends ResourceAllocation<?>> Map<Task, List<R>> byTask(List<? extends R> allocations) {
Map<Task, List<R>> result = new LinkedHashMap<>();
for (R resourceAllocation : allocations) {
if ( resourceAllocation.getTask() != null ) {
Task task = resourceAllocation.getTask();
initializeIfNeeded(result, task);
result.get(task).add(resourceAllocation);
}
}
return result;
}
private static <E extends ResourceAllocation<?>> void initializeIfNeeded(Map<Task, List<E>> result, Task task) {
if ( !result.containsKey(task) ) {
result.put(task, new ArrayList<>());
}
}
private static Comparator<ResourceAllocation<?>> byStartDateComparator() {
return (o1, o2) -> {
if ( o1.getIntraDayStartDate() == null ) {
return -1;
}
if ( o2.getIntraDayStartDate() == null ) {
return 1;
}
return o1.getIntraDayStartDate().compareTo(o2.getIntraDayStartDate());
};
}
public enum Direction {
FORWARD {
@Override
public IntraDayDate getDateFromWhichToAllocate(Task task) {
return IntraDayDate.max(task.getFirstDayNotConsolidated(), task.getIntraDayStartDate());
}
@Override
void limitAvailabilityOn(AvailabilityTimeLine availability, IntraDayDate dateFromWhichToAllocate) {
availability.invalidUntil(dateFromWhichToAllocate.asExclusiveEnd());
}
},
BACKWARD {
@Override
public IntraDayDate getDateFromWhichToAllocate(Task task) {
return task.getIntraDayEndDate();
}
@Override
void limitAvailabilityOn(AvailabilityTimeLine availability, IntraDayDate dateFromWhichToAllocate) {
availability.invalidFrom(dateFromWhichToAllocate.getDate());
}
};
public abstract IntraDayDate getDateFromWhichToAllocate(Task task);
abstract void limitAvailabilityOn(AvailabilityTimeLine availability, IntraDayDate dateFromWhichToAllocate);
}
public static AllocationsSpecified allocating(List<ResourcesPerDayModification> resourceAllocations) {
resourceAllocations = new ArrayList<>(resourceAllocations);
sortBySpecificFirst(resourceAllocations);
return new AllocationsSpecified(resourceAllocations);
}
/**
* Specific allocations should be done first in order to generic allocations
* selects the less charged resources if there are several allocations in the same task.
*
* @param resourceAllocations
* Sorted with specific allocations before generic ones
*/
private static <T extends AllocationModification> void sortBySpecificFirst(List<T> resourceAllocations) {
Collections.sort(resourceAllocations, new Comparator<AllocationModification>() {
@Override
public int compare(AllocationModification o1, AllocationModification o2) {
if ( o1.isSpecific() && o2.isSpecific() ) {
return 0;
}
if ( o1.isSpecific() ) {
return -1;
}
if ( o2.isSpecific() ) {
return 1;
}
return 0;
}
});
}
private static void checkStartLessOrEqualToEnd(IntraDayDate startInclusive, IntraDayDate endExclusive) {
Validate.isTrue(startInclusive.compareTo(endExclusive) <= 0, "the end must be equal or posterior to the start");
}
private static void checkStartLessOrEqualToEnd(LocalDate start, LocalDate end) {
Validate.isTrue(start.compareTo(end) <= 0, "the end must be equal or posterior to the start");
}
/**
* Needed for doing fluent interface calls:
* <ul>
* <li>
* {@link ResourceAllocation#allocating(List)}.
* {@link AllocationsSpecified#untilAllocating(int) untiAllocating(int)}
* </li>
* <li>
* {@link ResourceAllocation#allocating(List)}.
* {@link AllocationsSpecified#allocateOnTaskLength() allocateOnTaskLength}
* </li>
* <li>
* {@link ResourceAllocation#allocating(List)}.
* {@link AllocationsSpecified#allocateUntil(LocalDate) allocateUntil(LocalDate)}
* </li>
* </ul>
*/
public static class AllocationsSpecified {
private final List<ResourcesPerDayModification> allocations;
private final Task task;
public AllocationsSpecified(List<ResourcesPerDayModification> resourceAllocations) {
Validate.notNull(resourceAllocations);
Validate.notEmpty(resourceAllocations);
Validate.noNullElements(resourceAllocations);
checkNoOneHasNullTask(resourceAllocations);
checkAllHaveSameTask(resourceAllocations);
checkNoAllocationWithZeroResourcesPerDay(resourceAllocations);
this.allocations = resourceAllocations;
this.task = resourceAllocations.get(0).getBeingModified().getTask();
}
private static void checkNoAllocationWithZeroResourcesPerDay(List<ResourcesPerDayModification> allocations) {
for (ResourcesPerDayModification r : allocations) {
if (isZero(r.getGoal().getAmount())) {
throw new IllegalArgumentException("all resources per day must be no zero");
}
}
}
public static boolean isZero(BigDecimal amount) {
return amount.movePointRight(amount.scale()).intValue() == 0;
}
private static void checkNoOneHasNullTask(List<ResourcesPerDayModification> allocations) {
for (ResourcesPerDayModification resourcesPerDayModification : allocations) {
if (resourcesPerDayModification.getBeingModified().getTask() == null) {
throw new IllegalArgumentException("all allocations must have task");
}
}
}
private static void checkAllHaveSameTask(List<ResourcesPerDayModification> resourceAllocations) {
Task task = null;
for (ResourcesPerDayModification r : resourceAllocations) {
if (task == null) {
task = r.getBeingModified().getTask();
}
if (!task.equals(r.getBeingModified().getTask())) {
throw new IllegalArgumentException("all allocations must belong to the same task");
}
}
}
public interface INotFulfilledReceiver {
void cantFulfill(ResourcesPerDayModification allocationAttempt, CapacityResult capacityResult);
}
public IntraDayDate untilAllocating(EffortDuration effort) {
return untilAllocating(Direction.FORWARD, effort);
}
public IntraDayDate untilAllocating(Direction direction, EffortDuration effort) {
return untilAllocating(direction, effort, doNothing());
}
private static INotFulfilledReceiver doNothing() {
return (allocationAttempt, capacityResult) -> {};
}
public IntraDayDate untilAllocating(EffortDuration effort, final INotFulfilledReceiver receiver) {
return untilAllocating(Direction.FORWARD, effort, receiver);
}
public IntraDayDate untilAllocating(
Direction direction,
EffortDuration toAllocate,
final INotFulfilledReceiver receiver) {
UntilFillingHoursAllocator allocator = new UntilFillingHoursAllocator(direction, task, allocations) {
@Override
protected <T extends DayAssignment> void setNewDataForAllocation(
ResourceAllocation<T> allocation,
IntraDayDate resultDate,
ResourcesPerDay resourcesPerDay,
List<T> dayAssignments) {
Task task = AllocationsSpecified.this.task;
allocation.setIntendedResourcesPerDay(resourcesPerDay);
if ( isForwardScheduling() ) {
allocation.resetAllAllocationAssignmentsTo(
dayAssignments, task.getIntraDayStartDate(), resultDate);
} else {
allocation.resetAllAllocationAssignmentsTo(
dayAssignments, resultDate, task.getIntraDayEndDate());
}
allocation.updateResourcesPerDay();
}
@Override
protected CapacityResult thereAreAvailableHoursFrom(
IntraDayDate dateFromWhichToAllocate,
ResourcesPerDayModification resourcesPerDayModification,
EffortDuration effortToAllocate) {
ICalendar calendar = getCalendar(resourcesPerDayModification);
ResourcesPerDay resourcesPerDay = resourcesPerDayModification.getGoal();
AvailabilityTimeLine availability = resourcesPerDayModification.getAvailability();
getDirection().limitAvailabilityOn(availability, dateFromWhichToAllocate);
return ThereAreHoursOnWorkHoursCalculator.thereIsAvailableCapacityFor(
calendar,
availability,
resourcesPerDay,
effortToAllocate);
}
private CombinedWorkHours getCalendar(ResourcesPerDayModification resourcesPerDayModification) {
return CombinedWorkHours.minOf(
resourcesPerDayModification.getBeingModified().getTaskCalendar(),
resourcesPerDayModification.getResourcesCalendar());
}
@Override
protected void markUnsatisfied(
ResourcesPerDayModification allocationAttempt, CapacityResult capacityResult) {
allocationAttempt.getBeingModified().markAsUnsatisfied();
receiver.cantFulfill(allocationAttempt, capacityResult);
}
};
IntraDayDate result = allocator.untilAllocating(toAllocate);
if (result == null) {
// Allocation could not be done
return direction == Direction.FORWARD ? task.getIntraDayEndDate() : task.getIntraDayStartDate();
}
return result;
}
public void allocateOnTaskLength() {
AllocatorForTaskDurationAndSpecifiedResourcesPerDay allocator =
new AllocatorForTaskDurationAndSpecifiedResourcesPerDay(allocations);
allocator.allocateOnTaskLength();
}
public void allocateUntil(IntraDayDate endExclusive) {
AllocatorForTaskDurationAndSpecifiedResourcesPerDay allocator =
new AllocatorForTaskDurationAndSpecifiedResourcesPerDay(allocations);
allocator.allocateUntil(endExclusive);
}
public void allocateFromEndUntil(IntraDayDate start) {
AllocatorForTaskDurationAndSpecifiedResourcesPerDay allocator =
new AllocatorForTaskDurationAndSpecifiedResourcesPerDay(allocations);
allocator.allocateFromEndUntil(start);
}
}
public static HoursAllocationSpecified allocatingHours(List<EffortModification> effortsModifications) {
effortsModifications = new ArrayList<>(effortsModifications);
sortBySpecificFirst(effortsModifications);
return new HoursAllocationSpecified(effortsModifications);
}
/**
* Needed for doing fluent interface calls:
* <ul>
* <li>
* {@link ResourceAllocation#allocatingHours(List)}
* .{@link HoursAllocationSpecified#allocateUntil(LocalDate) allocateUntil(LocalDate)}
* </li>
* <li>
* {@link ResourceAllocation#allocatingHours(List)}.{@link HoursAllocationSpecified#allocate() allocate()}
* </li>
* </ul>
*/
public static class HoursAllocationSpecified {
private final List<EffortModification> hoursModifications;
private Task task;
public HoursAllocationSpecified(List<EffortModification> hoursModifications) {
Validate.noNullElements(hoursModifications);
Validate.isTrue(!hoursModifications.isEmpty());
this.hoursModifications = hoursModifications;
this.task = hoursModifications.get(0).getBeingModified().getTask();
Validate.notNull(task);
}
public void allocate() {
allocateUntil(task.getIntraDayEndDate());
}
public void allocateUntil(IntraDayDate end) {
Validate.notNull(end);
checkStartLessOrEqualToEnd(task.getIntraDayStartDate(), end);
for (EffortModification each : hoursModifications) {
each.allocateUntil(end);
}
}
public void allocateFromEndUntil(IntraDayDate start) {
Validate.notNull(start);
checkStartLessOrEqualToEnd(start, task.getIntraDayEndDate());
for (EffortModification each : hoursModifications) {
each.allocateFromEndUntil(start);
}
}
}
public interface IOnDayAssignmentRemoval {
void onRemoval(ResourceAllocation<?> allocation, DayAssignment assignment);
}
public static class DoNothing implements IOnDayAssignmentRemoval {
@Override
public void onRemoval(ResourceAllocation<?> allocation, DayAssignment assignment) {}
}
public static class DetachDayAssignmentOnRemoval implements IOnDayAssignmentRemoval {
@Override
public void onRemoval(ResourceAllocation<?> allocation, DayAssignment assignment) {
assignment.detach();
}
}
public void setOnDayAssignmentRemoval(IOnDayAssignmentRemoval dayAssignmentRemoval) {
Validate.notNull(dayAssignmentRemoval);
this.dayAssignmenteRemoval = dayAssignmentRemoval;
}
/**
* Returns the associated resources from the day assignments of this {@link ResourceAllocation}.
*
* @return the associated resources with no repeated elements
*/
public abstract List<Resource> getAssociatedResources();
public void switchToScenario(Scenario scenario) {
Validate.notNull(scenario);
assignmentsState = assignmentsState.switchTo(scenario);
switchDerivedAllocationsTo(scenario);
}
private void switchDerivedAllocationsTo(Scenario scenario) {
for (DerivedAllocation each : derivedAllocations) {
each.useScenario(scenario);
}
}
protected void updateResourcesPerDay() {
if ( !isSatisfied() ) {
return;
}
ResourcesPerDay resourcesPerDay = calculateResourcesPerDayFromAssignments(getAssignments());
assert resourcesPerDay != null;
this.resourcesPerDay = resourcesPerDay;
}
protected void setResourcesPerDayToAmount(int amount) {
this.resourcesPerDay = ResourcesPerDay.amount(amount);
this.intendedResourcesPerDay = this.resourcesPerDay;
}
private void setIntendedResourcesPerDay(ResourcesPerDay resourcesPerDay) {
Validate.notNull(resourcesPerDay);
Validate.isTrue(!resourcesPerDay.isZero());
this.intendedResourcesPerDay = resourcesPerDay;
}
/**
* Returns the last specified resources per day.
*/
public ResourcesPerDay getIntendedResourcesPerDay() {
return intendedResourcesPerDay;
}
private ResourcesPerDay getReassignationResourcesPerDay() {
ResourcesPerDay intended = getIntendedResourcesPerDay();
return intended != null ? intended : getResourcesPerDay();
}
public boolean areIntendedResourcesPerDaySatisfied() {
return getTask().getCalculatedValue() == CalculatedValue.RESOURCES_PER_DAY ||
Objects.equals(getNonConsolidatedResourcePerDay(), getIntendedResourcesPerDay());
}
public ResourceAllocation(Task task) {
this(task, null);
}
public ResourceAllocation(Task task, AssignmentFunction assignmentFunction) {
Validate.notNull(task);
this.task = task;
this.assignmentFunction = assignmentFunction;
this.assignmentsState = buildInitialTransientState();
}
protected ResourceAllocation(ResourcesPerDay resourcesPerDay, Task task) {
this(task);
Validate.notNull(resourcesPerDay);
this.resourcesPerDay = resourcesPerDay;
}
@NotNull
public Task getTask() {
return task;
}
private void updateOriginalTotalAssignment() {
if ( !isSatisfied() ) {
return;
}
intendedNonConsolidatedEffort = getNonConsolidatedEffort();
Consolidation consolidation = task.getConsolidation();
if ( consolidation == null ) {
intendedTotalAssignment = intendedNonConsolidatedEffort;
} else if ( consolidation.isCompletelyConsolidated() ) {
intendedTotalAssignment = getConsolidatedEffort();
} else {
intendedTotalAssignment = consolidation.getTotalFromNotConsolidated(getNonConsolidatedEffort());
}
}
@NotNull
public EffortDuration getIntendedTotalAssignment() {
return intendedTotalAssignment;
}
public interface IVisitor<T> {
T on(SpecificResourceAllocation specificAllocation);
T on(GenericResourceAllocation genericAllocation);
}
public static <T> T visit(ResourceAllocation<?> allocation, IVisitor<T> visitor) {
Validate.notNull(allocation);
Validate.notNull(visitor);
if (allocation instanceof GenericResourceAllocation) {
GenericResourceAllocation generic = (GenericResourceAllocation) allocation;
return visitor.on(generic);
} else if (allocation instanceof SpecificResourceAllocation) {
SpecificResourceAllocation specific = (SpecificResourceAllocation) allocation;
return visitor.on(specific);
}
throw new RuntimeException("can't handle: " + allocation.getClass());
}
/**
* This method is in use.
*/
public abstract ResourcesPerDayModification withDesiredResourcesPerDay(ResourcesPerDay resourcesPerDay);
public final ResourcesPerDayModification asResourcesPerDayModification() {
if ( getReassignationResourcesPerDay().isZero() ) {
return null;
}
return visit(this, new IVisitor<ResourcesPerDayModification>() {
@Override
public ResourcesPerDayModification on(SpecificResourceAllocation specificAllocation) {
return ResourcesPerDayModification.create(specificAllocation, getReassignationResourcesPerDay());
}
@Override
public ResourcesPerDayModification on(GenericResourceAllocation genericAllocation) {
return ResourcesPerDayModification.create(
genericAllocation,
getReassignationResourcesPerDay(),
getAssociatedResources());
}
});
}
public final EffortModification asHoursModification() {
return visit(this, new IVisitor<EffortModification>() {
@Override
public EffortModification on(GenericResourceAllocation genericAllocation) {
return EffortModification.create(
genericAllocation, getEffortForReassignation(), getAssociatedResources());
}
@Override
public EffortModification on(SpecificResourceAllocation specificAllocation) {
return EffortModification.create(specificAllocation, getEffortForReassignation());
}
});
}
public abstract IAllocatable withPreviousAssociatedResources();
public interface IEffortDistributor<T extends DayAssignment> {
/**
* It does not add the created assignments to the underlying allocation.
* It just distributes them.
*/
List<T> distributeForDay(PartialDay day, EffortDuration effort);
}
protected abstract class AssignmentsAllocator implements IAllocatable, IEffortDistributor<T> {
@Override
public final void allocate(ResourcesPerDay resourcesPerDay) {
Task currentTask = getTask();
AllocateResourcesPerDayOnInterval allocator = new AllocateResourcesPerDayOnInterval(
currentTask.getIntraDayStartDate(),
currentTask.getIntraDayEndDate());
allocator.allocate(resourcesPerDay);
}
private List<T> createAssignments(
ResourcesPerDay resourcesPerDay,
IntraDayDate startInclusive,
IntraDayDate endExclusive) {
List<T> assignmentsCreated = new ArrayList<>();
for (PartialDay day : getDays(startInclusive, endExclusive)) {
EffortDuration durationForDay = calculateTotalToDistribute(day, resourcesPerDay);
assignmentsCreated.addAll(distributeForDay(day, durationForDay));
}
return onlyNonZeroHours(assignmentsCreated);
}
@Override
public IAllocateResourcesPerDay resourcesPerDayUntil(IntraDayDate end) {
IntraDayDate startInclusive = getStartSpecifiedByTask();
return new AllocateResourcesPerDayOnInterval(startInclusive, end);
}
@Override
public IAllocateResourcesPerDay resourcesPerDayFromEndUntil(IntraDayDate start) {
IntraDayDate startInclusive = IntraDayDate.max(start, getStartSpecifiedByTask());
IntraDayDate endDate = task.getIntraDayEndDate();
return new AllocateResourcesPerDayOnInterval(startInclusive, endDate);
}
private Iterable<PartialDay> getDays(IntraDayDate startInclusive, IntraDayDate endExclusive) {
checkStartLessOrEqualToEnd(startInclusive, endExclusive);
return startInclusive.daysUntil(endExclusive);
}
private final class AllocateResourcesPerDayOnInterval implements IAllocateResourcesPerDay {
private final IntraDayDate startInclusive;
private final IntraDayDate endExclusive;
private AllocateResourcesPerDayOnInterval(IntraDayDate startInclusive, IntraDayDate endExclusive) {
this.startInclusive = startInclusive;
this.endExclusive = IntraDayDate.max(startInclusive, endExclusive);
}
@Override
public void allocate(ResourcesPerDay resourcesPerDay) {
setIntendedResourcesPerDay(resourcesPerDay);
List<T> assignmentsCreated = createAssignments(resourcesPerDay, startInclusive, endExclusive);
resetAllAllocationAssignmentsTo(assignmentsCreated, startInclusive, endExclusive);
updateResourcesPerDay();
}
}
@Override
public IAllocateEffortOnInterval onIntervalWithinTask(final LocalDate start, final LocalDate end) {
checkStartLessOrEqualToEnd(start, end);
return new OnSubIntervalAllocator(new AllocationIntervalInsideTask(start, end));
}
@Override
public IAllocateEffortOnInterval onIntervalWithinTask(IntraDayDate start, IntraDayDate end) {
checkStartLessOrEqualToEnd(start, end);
return new OnSubIntervalAllocator(new AllocationIntervalInsideTask(start, end));
}
@Override
public IAllocateEffortOnInterval onInterval(final LocalDate startInclusive, final LocalDate endExclusive) {
checkStartLessOrEqualToEnd(startInclusive, endExclusive);
return new OnSubIntervalAllocator(new AllocationInterval(startInclusive, endExclusive));
}
@Override
public IAllocateEffortOnInterval onInterval(IntraDayDate start, IntraDayDate end) {
checkStartLessOrEqualToEnd(start, end);
return new OnSubIntervalAllocator(new AllocationInterval(start, end));
}
private class OnSubIntervalAllocator implements IAllocateEffortOnInterval {
private final AllocationInterval allocationInterval;
private OnSubIntervalAllocator(AllocationInterval allocationInterval) {
this.allocationInterval = allocationInterval;
}
@Override
public void allocateHours(int hours) {
allocate(hours(hours));
}
@Override
public void allocate(EffortDuration duration) {
List<T> assignmentsCreated = createAssignments(allocationInterval, duration);
allocationInterval.resetAssignments(assignmentsCreated);
}
@Override
public void allocate(List<EffortDuration> durationsByDay) {
allocateDurationsByDay(allocationInterval, durationsByDay);
}
}
private void allocateDurationsByDay(AllocationInterval interval, List<EffortDuration> durationsByDay) {
List<EffortDuration> rightSlice = interval.getRightSlice(durationsByDay);
AvailabilityTimeLine availability = getAvailability();
List<T> assignments = createAssignments(
interval, availability, rightSlice.toArray(new EffortDuration[rightSlice.size()]));
interval.resetAssignments(assignments);
}
@Override
public IAllocateEffortOnInterval fromStartUntil(final IntraDayDate end) {
return getIAllocateEffortOnInterval(new AllocationInterval(getStartSpecifiedByTask(), end));
}
private IAllocateEffortOnInterval getIAllocateEffortOnInterval(final AllocationInterval interval) {
return new IAllocateEffortOnInterval() {
@Override
public void allocateHours(int hours) {
allocate(hours(hours));
}
@Override
public void allocate(EffortDuration effortDuration) {
allocateTheWholeAllocation(interval, effortDuration);
}
@Override
public void allocate(List<EffortDuration> durationsByDay) {
List<EffortDuration> rightSlice = interval.getRightSlice(durationsByDay);
AvailabilityTimeLine availability = getAvailability();
createAssignments(interval, availability, rightSlice.toArray(new EffortDuration[rightSlice.size()]));
}
};
}
@Override
public IAllocateEffortOnInterval fromEndUntil(IntraDayDate start) {
final AllocationInterval interval = new AllocationInterval(start, task.getIntraDayEndDate());
return new IAllocateEffortOnInterval() {
@Override
public void allocateHours(int hours) {
allocate(hours(hours));
}
@Override
public void allocate(EffortDuration effortDuration) {
allocateTheWholeAllocation(interval, effortDuration);
}
@Override
public void allocate(List<EffortDuration> durationsByDay) {
allocateDurationsByDay(interval, durationsByDay);
}
};
}
private void allocateTheWholeAllocation(AllocationInterval interval, EffortDuration durationToAssign) {
List<T> assignmentsCreated = createAssignments(interval, durationToAssign);
ResourceAllocation.this.allocateTheWholeAllocation(interval, assignmentsCreated);
}
protected abstract AvailabilityTimeLine getResourcesAvailability();
private List<T> createAssignments(AllocationInterval interval, EffortDuration durationToAssign) {
AvailabilityTimeLine availability = getAvailability();
Iterable<PartialDay> days = getDays(interval.getStartInclusive(), interval.getEndExclusive());
EffortDuration[] durationsEachDay = secondsDistribution(availability, days, durationToAssign);
return createAssignments(interval, availability, durationsEachDay);
}
private List<T> createAssignments(
AllocationInterval interval,
AvailabilityTimeLine availability,
EffortDuration[] durationsEachDay) {
List<T> result = new ArrayList<>();
int i = 0;
for (PartialDay day : getDays(interval.getStartInclusive(), interval.getEndExclusive())) {
// If all days are not available, it would try to assign them anyway, preventing it with a check
if ( availability.isValid(day.getDate()) ) {
result.addAll(distributeForDay(day, durationsEachDay[i]));
}
i++;
}
return onlyNonZeroHours(result);
}
private AvailabilityTimeLine getAvailability() {
AvailabilityTimeLine resourcesAvailability = getResourcesAvailability();
BaseCalendar taskCalendar = getTask().getCalendar();
if ( taskCalendar != null ) {
return taskCalendar.getAvailability().and(resourcesAvailability);
} else {
return resourcesAvailability;
}
}
private List<T> onlyNonZeroHours(List<T> assignmentsCreated) {
List<T> result = new ArrayList<>();
for (T each : assignmentsCreated) {
if ( !each.getDuration().isZero() ) {
result.add(each);
}
}
return result;
}
private EffortDuration[] secondsDistribution(
AvailabilityTimeLine availability,
Iterable<PartialDay> days,
EffortDuration duration) {
List<Capacity> capacities = new ArrayList<>();
for (PartialDay each : days) {
capacities.add(getCapacity(availability, each));
}
Distributor distributor = Distributor.among(capacities);
return distributor.distribute(duration).toArray(new EffortDuration[0]);
}
private Capacity getCapacity(AvailabilityTimeLine availability, PartialDay day) {
return availability.isValid(day.getDate())
? getCapacityAt(day)
: Capacity.create(zero()).notOverAssignableWithoutLimit();
}
protected abstract Capacity getCapacityAt(PartialDay each);
}
public void markAsUnsatisfied() {
removingAssignments(getAssignments());
assert isUnsatisfied();
}
public boolean isLimiting() {
return getLimitingResourceQueueElement() != null;
}
public boolean isLimitingAndHasDayAssignments() {
return isLimiting() && hasAssignments();
}
public boolean isSatisfied() {
return isCompletelyConsolidated() ? hasAssignments() : !getNonConsolidatedAssignments().isEmpty();
}
private boolean isCompletelyConsolidated() {
return task.getConsolidation() != null && task.getConsolidation().isCompletelyConsolidated();
}
public boolean isUnsatisfied() {
return !isSatisfied();
}
public void copyAssignmentsFromOneScenarioToAnother(Scenario from, Scenario to){
copyAssignments(from, to);
for (DerivedAllocation each : derivedAllocations) {
each.copyAssignments(from, to);
}
}
protected abstract void copyAssignments(Scenario from, Scenario to);
protected void resetAssignmentsTo(List<T> assignments) {
resetAllAllocationAssignmentsTo(assignments, task.getIntraDayStartDate(), task.getIntraDayEndDate());
}
protected void allocateTheWholeAllocation(AllocationInterval interval, List<T> assignments) {
resetAllAllocationAssignmentsTo(assignments, interval.getStartInclusive(), interval.getEndExclusive());
updateResourcesPerDay();
}
protected void resetAllAllocationAssignmentsTo(
List<T> assignments,
IntraDayDate intraDayStart,
IntraDayDate intraDayEnd) {
removingAssignments(withoutConsolidated(getAssignments()));
addingAssignments(assignments);
updateOriginalTotalAssignment();
getDayAssignmentsState().setIntraDayStart(intraDayStart);
getDayAssignmentsState().setIntraDayEnd(intraDayEnd);
}
class AllocationInterval {
private IntraDayDate originalStart;
private IntraDayDate originalEnd;
private final IntraDayDate start;
private final IntraDayDate end;
AllocationInterval(IntraDayDate originalStart, IntraDayDate originalEnd, IntraDayDate start, IntraDayDate end) {
this.originalStart = originalStart;
this.originalEnd = originalEnd;
IntraDayDate startConsideringConsolidated =
task.hasConsolidations() ? IntraDayDate.max(task.getFirstDayNotConsolidated(), start) : start;
this.start = IntraDayDate.min(startConsideringConsolidated, end);
this.end = IntraDayDate.max(this.start, end);
}
AllocationInterval(IntraDayDate start, IntraDayDate end) {
this(start, end, start, end);
}
AllocationInterval(LocalDate startInclusive, LocalDate endExclusive) {
this(IntraDayDate.startOfDay(startInclusive), IntraDayDate.startOfDay(endExclusive));
}
public List<EffortDuration> getRightSlice(List<EffortDuration> original) {
List<EffortDuration> result = new ArrayList<>(original);
final int numberOfDaysToFill = originalStart.numberOfDaysUntil(originalEnd);
for (int i = 0; i < numberOfDaysToFill - original.size(); i++) {
result.add(zero());
}
return result.subList(
originalStart.numberOfDaysUntil(start),
result.size() - end.numberOfDaysUntil(originalEnd));
}
public void resetAssignments(List<T> assignmentsCreated) {
resetAssignmentsFittingAllocationDatesToResultingAssignments(this, assignmentsCreated);
}
public IntraDayDate getStartInclusive() {
return this.start;
}
public IntraDayDate getEndExclusive() {
return this.end;
}
public List<DayAssignment> getAssignmentsOnInterval() {
return getAssignments(this.start.getDate(), this.end.asExclusiveEnd());
}
public List<DayAssignment> getNoConsolidatedAssignmentsOnInterval() {
return DayAssignment.withConsolidatedValue(getAssignmentsOnInterval(), false);
}
public List<DayAssignment> getConsolidatedAssignmentsOnInterval() {
return DayAssignment.withConsolidatedValue(getAssignmentsOnInterval(), true);
}
}
class AllocationIntervalInsideTask extends AllocationInterval {
AllocationIntervalInsideTask(LocalDate startInclusive, LocalDate endExclusive) {
this(IntraDayDate.startOfDay(startInclusive), IntraDayDate.startOfDay(endExclusive));
}
AllocationIntervalInsideTask(IntraDayDate startInclusive, IntraDayDate endExclusive) {
super(
startInclusive,
endExclusive,
IntraDayDate.max(startInclusive, getTask().getFirstDayNotConsolidated()),
IntraDayDate.min(endExclusive, task.getIntraDayEndDate()));
}
@Override
public void resetAssignments(List<T> assignmentsCreated) {
resetAssignmentsForInterval(this, assignmentsCreated);
}
}
protected void resetAssignmentsForInterval(AllocationIntervalInsideTask interval, List<T> assignmentsCreated) {
IntraDayDate originalStart = getIntraDayStartDate();
IntraDayDate originalEnd = getIntraDayEndDate();
updateAssignments(interval, assignmentsCreated);
// The resource allocation cannot grow beyond the start of the task.
// This is guaranteed by IntervalInsideTask.
// It also cannot shrink from the original size, this is guaranteed by originalStart.
getDayAssignmentsState().setIntraDayStart(IntraDayDate.min(originalStart, interval.getStartInclusive()));
// The resource allocation cannot grow beyond the end of the task.
// This is guaranteed by IntervalInsideTask.
// It also cannot shrink from the original size, this is guaranteed by originalEnd.
getDayAssignmentsState().setIntraDayEnd(IntraDayDate.max(originalEnd, interval.getEndExclusive()));
}
private void updateAssignments(AllocationInterval interval, List<T> assignmentsCreated) {
removingAssignments(withoutConsolidated(interval.getAssignmentsOnInterval()));
addingAssignments(assignmentsCreated);
updateOriginalTotalAssignment();
updateResourcesPerDay();
}
void updateAssignmentsConsolidatedValues() {
LocalDate firstNotConsolidated = task.getFirstDayNotConsolidated().getDate();
for (T each : getAssignments()) {
each.setConsolidated(each.getDay().isBefore(firstNotConsolidated));
}
}
private void resetAssignmentsFittingAllocationDatesToResultingAssignments(
AllocationInterval interval, List<T> assignmentsCreated) {
updateAssignments(interval, assignmentsCreated);
LocalDate startConsideringAssignments = getStartConsideringAssignments();
IntraDayDate start = IntraDayDate.startOfDay(startConsideringAssignments);
if ( interval.getStartInclusive().areSameDay(startConsideringAssignments) ) {
start = interval.getStartInclusive();
}
getDayAssignmentsState().setIntraDayStart(start);
LocalDate endConsideringAssignments = getEndDateGiven(getAssignments());
IntraDayDate end = IntraDayDate.startOfDay(endConsideringAssignments);
if ( interval.getEndExclusive().areSameDay(endConsideringAssignments) ) {
end = interval.getEndExclusive();
}
getDayAssignmentsState().setIntraDayEnd(end);
}
private static <T extends DayAssignment> List<T> withoutConsolidated(List<? extends T> assignments) {
List<T> result = new ArrayList<>();
for (T each : assignments) {
if ( !each.isConsolidated() ) {
result.add(each);
}
}
return result;
}
protected final void addingAssignments(Collection<? extends T> assignments) {
getDayAssignmentsState().addingAssignments(withoutAlreadyPresent(assignments));
}
private List<? extends T> withoutAlreadyPresent(Collection<? extends T> assignments) {
if ( assignments.isEmpty() ) {
return Collections.emptyList();
}
LocalDate min = Collections.min(assignments, DayAssignment.byDayComparator()).getDay();
LocalDate max = Collections.max(assignments, DayAssignment.byDayComparator()).getDay();
Set<LocalDate> daysPresent = DayAssignment.byDay(getAssignments(min, max.plusDays(1))).keySet();
List<T> result = new ArrayList<>();
for (T each : assignments) {
if ( !daysPresent.contains(each.getDay()) ) {
result.add(each);
}
}
return result;
}
public void removeLimitingDayAssignments() {
resetAssignmentsTo(Collections.emptyList());
}
@SuppressWarnings("unchecked")
public void allocateLimitingDayAssignments(
List<? extends DayAssignment> assignments, IntraDayDate start, IntraDayDate end) {
assert isLimiting();
resetAllAllocationAssignmentsTo((List<T>) assignments, start, end);
}
private void removingAssignments(List<? extends DayAssignment> assignments) {
getDayAssignmentsState().removingAssignments(assignments);
}
public final EffortDuration calculateTotalToDistribute(PartialDay day, ResourcesPerDay resourcesPerDay) {
return getAllocationCalendar().asDurationOn(day, resourcesPerDay);
}
public ResourcesPerDay calculateResourcesPerDayFromAssignments() {
return calculateResourcesPerDayFromAssignments(getAssignments());
}
private ResourcesPerDay calculateResourcesPerDayFromAssignments(Collection<? extends T> assignments) {
if ( assignments.isEmpty() ) {
return ResourcesPerDay.amount(0);
}
Map<LocalDate, List<T>> byDay = DayAssignment.byDay(assignments);
LocalDate min = Collections.min(byDay.keySet());
LocalDate max = Collections.max(byDay.keySet());
Iterable<PartialDay> daysToIterate = startFor(min).daysUntil(endFor(max));
EffortDuration sumTotalEffort = zero();
EffortDuration sumWorkableEffort = zero();
final ResourcesPerDay ONE_RESOURCE_PER_DAY = ResourcesPerDay.amount(1);
for (PartialDay day : daysToIterate) {
List<T> assignmentsAtDay = avoidNull(byDay.get(day.getDate()), Collections.<T> emptyList());
EffortDuration incrementWorkable = getAllocationCalendar().asDurationOn(day, ONE_RESOURCE_PER_DAY);
sumWorkableEffort = sumWorkableEffort.plus(incrementWorkable);
sumTotalEffort = sumTotalEffort.plus(sumDuration(assignmentsAtDay));
}
if ( sumWorkableEffort.equals(zero()) ) {
return ResourcesPerDay.amount(0);
}
return ResourcesPerDay.calculateFrom(sumTotalEffort, sumWorkableEffort);
}
private IntraDayDate startFor(LocalDate dayDate) {
IntraDayDate start = getIntraDayStartDate();
return start.getDate().equals(dayDate) ? start : IntraDayDate.startOfDay(dayDate);
}
private IntraDayDate endFor(LocalDate assignmentDate) {
IntraDayDate end = getIntraDayEndDate();
return end.getDate().equals(assignmentDate) ? end : IntraDayDate.startOfDay(assignmentDate).nextDayAtStart();
}
private static <T> T avoidNull(T value, T defaultValue) {
return value != null ? value : defaultValue;
}
public ICalendar getAllocationCalendar() {
return getCalendarGivenTaskCalendar(getTaskCalendar());
}
private ICalendar getTaskCalendar() {
return getTask().getCalendar() == null ? SameWorkHoursEveryDay.getDefaultWorkingDay() : getTask().getCalendar();
}
protected abstract ICalendar getCalendarGivenTaskCalendar(ICalendar taskCalendar);
/**
* This method is in use.
*/
protected abstract Class<T> getDayAssignmentType();
public ResourceAllocation<T> copy(Scenario scenario) {
Validate.notNull(scenario);
ResourceAllocation<T> copy = createCopy(scenario);
copy.assignmentsState = copy.toTransientStateWithInitial(
getUnorderedFor(scenario),
getIntraDayStartDateFor(scenario),
getIntraDayEndFor(scenario));
copy.resourcesPerDay = resourcesPerDay;
copy.intendedTotalAssignment = intendedTotalAssignment;
copy.task = task;
copy.assignmentFunction = assignmentFunction;
copy.intendedResourcesPerDay = intendedResourcesPerDay;
return copy;
}
private DayAssignmentsState toTransientStateWithInitial(
Collection<? extends T> initialAssignments,
IntraDayDate start,
IntraDayDate end) {
TransientState result = new TransientState(initialAssignments);
result.setIntraDayStart(start);
result.setIntraDayEnd(end);
return result;
}
private Set<T> getUnorderedFor(Scenario scenario) {
IDayAssignmentsContainer<T> container = retrieveContainerFor(scenario);
return container == null ? new HashSet<>() : container.getDayAssignments();
}
private IntraDayDate getIntraDayStartDateFor(Scenario scenario) {
IDayAssignmentsContainer<T> container = retrieveContainerFor(scenario);
return container == null ? null : container.getIntraDayStart();
}
private IntraDayDate getIntraDayEndFor(Scenario scenario) {
IDayAssignmentsContainer<T> container = retrieveContainerFor(scenario);
return container == null ? null : container.getIntraDayEnd();
}
abstract ResourceAllocation<T> createCopy(Scenario scenario);
public AssignmentFunction getAssignmentFunction() {
return assignmentFunction;
}
/**
* If {@link AssignmentFunction} is null, it's just set and nothing is applied.
*
* @param assignmentFunction
*/
public void setAssignmentFunctionAndApplyIfNotFlat(AssignmentFunction assignmentFunction) {
this.assignmentFunction = assignmentFunction;
if ( this.assignmentFunction != null ) {
this.assignmentFunction.applyTo(this);
}
}
public void setAssignmentFunctionWithoutApply(AssignmentFunction assignmentFunction) {
this.assignmentFunction = assignmentFunction;
}
public int getAssignedHours() {
return getAssignedEffort().roundToHours();
}
public EffortDuration getAssignedEffort() {
return DayAssignment.sum(getAssignments());
}
protected EffortDuration getIntendedNonConsolidatedEffort() {
return intendedNonConsolidatedEffort;
}
protected DayAssignmentsState getDayAssignmentsState() {
return assignmentsState;
}
private TransientState buildInitialTransientState() {
return new TransientState(new HashSet<T>());
}
private DayAssignmentsState buildFromDBState() {
return new NoExplicitlySpecifiedScenario();
}
abstract class DayAssignmentsState {
private List<T> dayAssignmentsOrdered = null;
protected List<T> getOrderedDayAssignments() {
if ( dayAssignmentsOrdered == null ) {
dayAssignmentsOrdered = DayAssignment.orderedByDay(getUnorderedAssignments());
}
return dayAssignmentsOrdered;
}
/**
* It can be null.
* It allows to mark that the allocation is started in a point within a day instead of the start of the day.
*/
abstract IntraDayDate getIntraDayStart();
/**
* Set a new intraDayStart.
*
* @param intraDayStart
* it can be <code>null</code>
* @see getIntraDayStart
*/
public abstract void setIntraDayStart(IntraDayDate intraDayStart);
/**
* It can be null.
* It allows to mark that the allocation is finished in a point within a day instead of taking the whole day.
*/
abstract IntraDayDate getIntraDayEnd();
/**
* Set a new intraDayEnd.
*
* @param intraDayEnd
* it can be <code>null</code>
* @see getIntraDayEnd
*/
public abstract void setIntraDayEnd(IntraDayDate intraDayEnd);
protected abstract Collection<T> getUnorderedAssignments();
protected void addingAssignments(Collection<? extends T> assignments) {
setParentFor(assignments);
addAssignments(assignments);
clearCachedData();
}
protected void clearCachedData() {
dayAssignmentsOrdered = null;
}
private void setParentFor(Collection<? extends T> assignments) {
for (T each : assignments) {
setItselfAsParentFor(each);
}
}
protected void removingAssignments(List<? extends DayAssignment> assignments){
removeAssignments(assignments);
clearCachedData();
for (DayAssignment each : assignments) {
dayAssignmenteRemoval.onRemoval(ResourceAllocation.this, each);
}
}
protected abstract void removeAssignments(List<? extends DayAssignment> assignments);
protected abstract void addAssignments(Collection<? extends T> assignments);
@SuppressWarnings("unchecked")
public void mergeAssignments(ResourceAllocation<?> modification) {
detachAssignments();
resetTo(((ResourceAllocation<T>) modification).getAssignments());
clearCachedData();
}
protected abstract void resetTo(Collection<T> assignmentsCopied);
void detachAssignments() {
for (DayAssignment each : getUnorderedAssignments()) {
each.detach();
}
}
final protected DayAssignmentsState switchTo(Scenario scenario) {
DayAssignmentsState result = explicitlySpecifiedState(scenario);
copyTransientPropertiesIfAppropiateTo(result);
return result;
}
/**
* Override if necessary to do extra actions.
*/
protected void copyTransientPropertiesIfAppropiateTo(DayAssignmentsState newStateForScenario) {}
}
protected abstract void setItselfAsParentFor(T dayAssignment);
private class TransientState extends DayAssignmentsState {
private final Set<T> assignments;
private IntraDayDate intraDayStart;
private IntraDayDate intraDayEnd;
TransientState(Collection<? extends T> assignments) {
this.assignments = new HashSet<>(assignments);
}
@Override
final protected Collection<T> getUnorderedAssignments() {
return assignments;
}
@Override
final protected void removeAssignments(List<? extends DayAssignment> assignments) {
this.assignments.removeAll(assignments);
}
@Override
final protected void addAssignments(Collection<? extends T> assignments) {
this.assignments.addAll(assignments);
}
@Override
final protected void resetTo(Collection<T> assignments) {
this.assignments.clear();
this.assignments.addAll(assignments);
}
@Override
public IntraDayDate getIntraDayStart() {
return intraDayStart;
}
@Override
public void setIntraDayStart(IntraDayDate intraDayStart) {
this.intraDayStart = intraDayStart;
}
@Override
final IntraDayDate getIntraDayEnd() {
return intraDayEnd;
}
@Override
public final void setIntraDayEnd(IntraDayDate intraDayEnd) {
this.intraDayEnd = intraDayEnd;
}
protected void copyTransientPropertiesIfAppropiateTo(DayAssignmentsState newStateForScenario) {
newStateForScenario.resetTo(getUnorderedAssignments());
newStateForScenario.setIntraDayStart(getIntraDayStart());
newStateForScenario.setIntraDayEnd(getIntraDayEnd());
}
}
private DayAssignmentsState explicitlySpecifiedState(Scenario scenario) {
return new ExplicitlySpecifiedScenarioState(retrieveOrCreateContainerFor(scenario));
}
protected abstract IDayAssignmentsContainer<T> retrieveContainerFor(Scenario scenario);
protected abstract IDayAssignmentsContainer<T> retrieveOrCreateContainerFor(Scenario scenario);
/**
* It uses the current scenario retrieved from {@link IScenarioManager} in
* order to return the assignments for that scenario.
* This state doesn't allow to update the current assignments for that scenario.
* <br />
* Note that this implementation doesn't work well if the current scenario
* is changed since the assignments are cached and the assignments for the
* previous one would be returned.
* <br />
*/
private class NoExplicitlySpecifiedScenario extends DayAssignmentsState {
@Override
protected final void removeAssignments(List<? extends DayAssignment> assignments) {
modificationsNotAllowed();
}
@Override
protected final void addAssignments(Collection<? extends T> assignments) {
modificationsNotAllowed();
}
@Override
final void detachAssignments() {
modificationsNotAllowed();
}
@Override
protected final void resetTo(Collection<T> assignmentsCopied) {
modificationsNotAllowed();
}
private void modificationsNotAllowed() {
throw new IllegalStateException("modifications to assignments can't be done " +
"if the scenario on which to work on is not explicitly specified");
}
@Override
protected Collection<T> getUnorderedAssignments() {
return retrieveOrCreateContainerFor(currentScenario()).getDayAssignments();
}
private Scenario currentScenario() {
return Registry.getScenarioManager().getCurrent();
}
@Override
IntraDayDate getIntraDayStart() {
return retrieveContainerFor(currentScenario()).getIntraDayStart();
}
@Override
IntraDayDate getIntraDayEnd() {
return retrieveOrCreateContainerFor(currentScenario()).getIntraDayEnd();
}
@Override
public void setIntraDayEnd(IntraDayDate intraDayEnd) {
modificationsNotAllowed();
}
@Override
public void setIntraDayStart(IntraDayDate intraDayStart) {
modificationsNotAllowed();
}
}
private class ExplicitlySpecifiedScenarioState extends DayAssignmentsState {
private final IDayAssignmentsContainer<T> container;
ExplicitlySpecifiedScenarioState(IDayAssignmentsContainer<T> container) {
Validate.notNull(container);
this.container = container;
}
@Override
protected void addAssignments(Collection<? extends T> assignments) {
container.addAll(assignments);
}
@Override
protected Collection<T> getUnorderedAssignments() {
return container.getDayAssignments();
}
@Override
protected void removeAssignments(List<? extends DayAssignment> assignments) {
container.removeAll(assignments);
}
@Override
protected void resetTo(Collection<T> assignmentsCopied) {
container.resetTo(assignmentsCopied);
}
@Override
IntraDayDate getIntraDayStart() {
return container.getIntraDayStart();
}
@Override
public void setIntraDayStart(IntraDayDate intraDayStart) {
container.setIntraDayStart(intraDayStart);
}
@Override
IntraDayDate getIntraDayEnd() {
return container.getIntraDayEnd();
}
@Override
public void setIntraDayEnd(IntraDayDate intraDayEnd) {
container.setIntraDayEnd(intraDayEnd);
}
}
public EffortDuration getConsolidatedEffort() {
return DayAssignment.sum(getConsolidatedAssignments());
}
public int getNonConsolidatedHours() {
return getNonConsolidatedEffort().roundToHours();
}
public EffortDuration getEffortForReassignation() {
return isSatisfied() ? getNonConsolidatedEffort() : getIntendedNonConsolidatedEffort();
}
public EffortDuration getNonConsolidatedEffort() {
return DayAssignment.sum(getNonConsolidatedAssignments());
}
/**
* @return a list of {@link DayAssignment} ordered by date
*/
public final List<T> getAssignments() {
return getDayAssignmentsState().getOrderedDayAssignments();
}
public List<T> getNonConsolidatedAssignments() {
return DayAssignment.withConsolidatedValue(getAssignments(), false);
}
public List<T> getConsolidatedAssignments() {
return DayAssignment.withConsolidatedValue(getAssignments(), true);
}
public ResourcesPerDay getNonConsolidatedResourcePerDay() {
return calculateResourcesPerDayFromAssignments(getNonConsolidatedAssignments());
}
public ResourcesPerDay getConsolidatedResourcePerDay() {
return calculateResourcesPerDayFromAssignments(getConsolidatedAssignments());
}
/**
* Just called for validation purposes.
* It must be public, otherwise if it's a proxy the call is not intercepted.
*/
@NotNull
public ResourcesPerDay getRawResourcesPerDay() {
return resourcesPerDay;
}
public ResourcesPerDay getResourcesPerDay() {
return resourcesPerDay == null ? ResourcesPerDay.amount(0) : resourcesPerDay;
}
public void createDerived(IWorkerFinder finder) {
final List<? extends DayAssignment> assignments = getAssignments();
List<DerivedAllocation> result = new ArrayList<>();
List<Machine> machines = Resource.machines(getAssociatedResources());
for (Machine machine : machines) {
for (MachineWorkersConfigurationUnit each : machine.getConfigurationUnits()) {
result.add(DerivedAllocationGenerator.generate(this, finder, each, assignments));
}
}
resetDerivedAllocationsTo(result);
}
/**
* Resets the derived allocations.
*/
private void resetDerivedAllocationsTo(Collection<DerivedAllocation> derivedAllocations) {
// Avoiding error:
// A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance.
this.derivedAllocations.clear();
this.derivedAllocations.addAll(derivedAllocations);
}
public Set<DerivedAllocation> getDerivedAllocations() {
return Collections.unmodifiableSet(derivedAllocations);
}
public LocalDate getStartConsideringAssignments() {
List<? extends DayAssignment> assignments = getAssignments();
return assignments.isEmpty() ? getStartDate() : assignments.get(0).getDay();
}
public LocalDate getStartDate() {
IntraDayDate start = getIntraDayStartDate();
return start != null ? start.getDate() : null;
}
private IntraDayDate getStartSpecifiedByTask() {
IntraDayDate taskStart = task.getIntraDayStartDate();
IntraDayDate firstDayNotConsolidated = getTask().getFirstDayNotConsolidated();
return IntraDayDate.max(taskStart, firstDayNotConsolidated);
}
public IntraDayDate getIntraDayStartDate() {
IntraDayDate intraDayStart = getDayAssignmentsState().getIntraDayStart();
return intraDayStart != null ? intraDayStart : task.getIntraDayStartDate();
}
public LocalDate getEndDate() {
IntraDayDate intraDayEndDate = getIntraDayEndDate();
return intraDayEndDate != null ? intraDayEndDate.asExclusiveEnd() : null;
}
public IntraDayDate getIntraDayEndDate() {
IntraDayDate intraDayEnd = getDayAssignmentsState().getIntraDayEnd();
if (intraDayEnd != null) {
return intraDayEnd;
}
LocalDate l = getEndDateGiven(getAssignments());
return l == null ? task.getIntraDayEndDate() : IntraDayDate.startOfDay(l);
}
private LocalDate getEndDateGiven(List<? extends DayAssignment> assignments) {
if (assignments.isEmpty()) {
return null;
}
DayAssignment lastAssignment = assignments.get(assignments.size() - 1);
return IntraDayDate.create(lastAssignment.getDay(), lastAssignment.getDuration()).asExclusiveEnd();
}
public boolean isAlreadyFinishedBy(LocalDate date) {
return getEndDate() != null && getEndDate().compareTo(date) <= 0;
}
private interface PredicateOnDayAssignment {
boolean satisfiedBy(DayAssignment dayAssignment);
}
public int getAssignedHours(final Resource resource, LocalDate start, LocalDate endExclusive) {
return getAssignedEffort(
resource, IntraDayDate.create(start, zero()), IntraDayDate.create(endExclusive, zero())).roundToHours();
}
public EffortDuration getAssignedEffort(final Resource resource, IntraDayDate start, IntraDayDate endExclusive) {
return getAssignedDuration(
getAssingments(resource, start.getDate(), endExclusive.asExclusiveEnd()), start, endExclusive);
}
@Override
public EffortDuration getAssignedDurationAt(Resource resource, LocalDate day) {
IntraDayDate start = IntraDayDate.startOfDay(day);
return getAssignedEffort(resource, start, start.nextDayAtStart());
}
private List<DayAssignment> getAssingments(
final Resource resource, LocalDate startInclusive, LocalDate endExclusive) {
return filter(
getAssignments(startInclusive, endExclusive), dayAssignment -> dayAssignment.isAssignedTo(resource));
}
public List<DayAssignment> getAssignments(IntraDayDate start, IntraDayDate endExclusive) {
return getAssignments(start.getDate(), endExclusive.asExclusiveEnd());
}
public List<DayAssignment> getAssignments(LocalDate start, LocalDate endExclusive) {
return new ArrayList<>(DayAssignment.getAtInterval(getAssignments(), start, endExclusive));
}
public int getAssignedHours(LocalDate start, LocalDate endExclusive) {
return getAssignedDuration(
IntraDayDate.create(start, zero()), IntraDayDate.create(endExclusive, zero())).roundToHours();
}
public abstract EffortDuration getAssignedEffort(
Criterion criterion, IntraDayDate startInclusive, IntraDayDate endExclusive);
private List<DayAssignment> filter(List<DayAssignment> assignments, PredicateOnDayAssignment predicate) {
List<DayAssignment> result = new ArrayList<>();
for (DayAssignment dayAssignment : assignments) {
if (predicate.satisfiedBy(dayAssignment)) {
result.add(dayAssignment);
}
}
return result;
}
protected EffortDuration getAssignedDuration(IntraDayDate startInclusive, IntraDayDate endExclusive) {
return getAssignedDuration(getAssignments(startInclusive, endExclusive), startInclusive, endExclusive);
}
private EffortDuration sumDuration(Collection<? extends DayAssignment> assignments) {
return EffortDuration.sum(assignments, new IEffortFrom<DayAssignment>() {
@Override
public EffortDuration from(DayAssignment each) {
return each.getDuration();
}
});
}
private EffortDuration getAssignedDuration(
List<? extends DayAssignment> assignments,
final IntraDayDate startInclusive,
final IntraDayDate endExclusive) {
final IntraDayDate allocationStart = getIntraDayStartDate();
return EffortDuration.sum(assignments, new IEffortFrom<DayAssignment>() {
@Override
public EffortDuration from(DayAssignment value) {
return getPartialDay(value, startInclusive, endExclusive).limitWorkingDay(value.getDuration());
}
private PartialDay getPartialDay(DayAssignment assignment,
IntraDayDate startInclusive,
IntraDayDate endExclusive) {
LocalDate assignmentDay = assignment.getDay();
LocalDate startDate = startInclusive.getDate();
LocalDate endDate = endExclusive.getDate();
PartialDay result = PartialDay.wholeDay(assignment.getDay());
if (assignmentDay.equals(startDate)) {
result = new PartialDay(startInclusive, result.getEnd());
}
if (assignmentDay.equals(endDate)) {
result = new PartialDay(result.getStart(), endExclusive);
}
return adjustPartialDayToAllocationStart(result);
}
// If the start of the allocation is in the middle of a day, its work also starts later,
// so the PartialDay must be moved to earlier so it doesn't limit the duration more that it should
private PartialDay adjustPartialDayToAllocationStart(PartialDay day) {
PartialDay result = day;
if (allocationStart.areSameDay(day.getDate())) {
EffortDuration substractingAtStart = day.getStart().getEffortDuration();
EffortDuration newSubstractionAtStart = substractingAtStart.minus(
EffortDuration.min(substractingAtStart, allocationStart.getEffortDuration()));
IntraDayDate newStart = IntraDayDate.create(day.getDate(), newSubstractionAtStart);
result = new PartialDay(newStart, day.getEnd());
}
return result;
}
});
}
public void mergeAssignmentsAndResourcesPerDay(Scenario scenario, ResourceAllocation<?> modifications) {
if (modifications == this) {
return;
}
switchToScenario(scenario);
mergeAssignments(modifications);
this.intendedResourcesPerDay = modifications.intendedResourcesPerDay;
if (modifications.isSatisfied()) {
updateOriginalTotalAssignment();
updateResourcesPerDay();
}
setAssignmentFunctionWithoutApply(modifications.getAssignmentFunction());
mergeDerivedAllocations(scenario, modifications.getDerivedAllocations());
}
private void mergeDerivedAllocations(Scenario scenario, Set<DerivedAllocation> derivedAllocations) {
Map<MachineWorkersConfigurationUnit, DerivedAllocation> newMap =
DerivedAllocation.byConfigurationUnit(derivedAllocations);
Map<MachineWorkersConfigurationUnit, DerivedAllocation> currentMap =
DerivedAllocation.byConfigurationUnit(getDerivedAllocations());
for (Entry<MachineWorkersConfigurationUnit, DerivedAllocation> entry : newMap.entrySet()) {
final MachineWorkersConfigurationUnit key = entry.getKey();
final DerivedAllocation modification = entry.getValue();
DerivedAllocation current = currentMap.get(key);
if (current == null) {
DerivedAllocation derived = modification.asDerivedFrom(this);
derived.useScenario(scenario);
currentMap.put(key, derived);
} else {
current.useScenario(scenario);
current.resetAssignmentsTo(modification.getAssignments());
}
}
resetDerivedAllocationsTo(currentMap.values());
}
final void mergeAssignments(ResourceAllocation<?> modifications) {
getDayAssignmentsState().mergeAssignments(modifications);
getDayAssignmentsState().setIntraDayStart(modifications.getDayAssignmentsState().getIntraDayStart());
getDayAssignmentsState().setIntraDayEnd(modifications.getDayAssignmentsState().getIntraDayEnd());
}
public void detach() {
getDayAssignmentsState().detachAssignments();
}
void associateAssignmentsToResource() {
for (DayAssignment dayAssignment : getAssignments()) {
dayAssignment.associateToResource();
}
}
public boolean hasAssignments() {
return !getAssignments().isEmpty();
}
public LimitingResourceQueueElement getLimitingResourceQueueElement() {
return (!limitingResourceQueueElements.isEmpty()) ? limitingResourceQueueElements.iterator().next() : null;
}
public void setLimitingResourceQueueElement(LimitingResourceQueueElement element) {
limitingResourceQueueElements.clear();
if (element != null) {
element.setResourceAllocation(this);
limitingResourceQueueElements.add(element);
}
}
public Integer getIntendedTotalHours() {
return intendedTotalHours;
}
public void setIntendedTotalHours(Integer intendedTotalHours) {
this.intendedTotalHours = intendedTotalHours;
}
/**
* Do a query to recover a list of resources that are suitable for this allocation.
* For a {@link SpecificResourceAllocation} returns the current resource.
* For a {@link GenericResourceAllocation} returns the resources that currently match this allocation criterions.
*
* @return a list of resources that are proper for this allocation
*/
public abstract List<Resource> querySuitableResources(IResourcesSearcher resourceSearcher);
public abstract void makeAssignmentsContainersDontPoseAsTransientAnyMore();
public void removePredecessorsDayAssignmentsFor(Scenario scenario) {
for (DerivedAllocation each : getDerivedAllocations()) {
each.removePredecessorContainersFor(scenario);
}
removePredecessorContainersFor(scenario);
}
protected abstract void removePredecessorContainersFor(Scenario scenario);
public void removeDayAssignmentsFor(Scenario scenario) {
for (DerivedAllocation each : getDerivedAllocations()) {
each.removeContainersFor(scenario);
}
removeContainersFor(scenario);
}
protected abstract void removeContainersFor(Scenario scenario);
/**
* Returns first non consolidated day.
*/
public LocalDate getFirstNonConsolidatedDate() {
List<T> nonConsolidated = getNonConsolidatedAssignments();
return (!nonConsolidated.isEmpty()) ? nonConsolidated.get(0).getDay() : null;
}
public boolean isManualAssignmentFunction() {
return assignmentFunction != null && assignmentFunction.isManual();
}
public void resetIntendedIntendedResourcesPerDayWithNonConsolidated() {
intendedResourcesPerDay = getNonConsolidatedResourcePerDay();
}
public void removeDayAssignmentsBeyondDate(LocalDate date) {
List<T> toRemove = new ArrayList<>();
for (T t : getAssignments()) {
if (t.getDay().compareTo(date) >= 0) {
toRemove.add(t);
}
}
setOnDayAssignmentRemoval(new DetachDayAssignmentOnRemoval());
getDayAssignmentsState().removingAssignments(toRemove);
}
}