/*
* 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 org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.Validate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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;
/**
* @author Diego Pino García <dpino@igalia.com>
* @author Manuel Rego Casasnovas <mrego@igalia.com>
*
* Resources are allocated to planner tasks.
*/
public abstract class ResourceAllocation<T extends DayAssignment> extends
BaseEntity implements IAssignedEffortForResource {
private static final Log LOG = LogFactory.getLog(ResourceAllocation.class);
public static <T extends ResourceAllocation<?>> List<T> getSatisfied(
Collection<T> resourceAllocations) {
Validate.notNull(resourceAllocations);
Validate.noNullElements(resourceAllocations);
List<T> result = new ArrayList<T>();
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<T>();
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<Resource, List<R>>();
for (R resourceAllocation : allocations) {
for (Resource resource : resourceAllocation
.getAssociatedResources()) {
if (!result.containsKey(resource)) {
result.put(resource, new ArrayList<R>());
}
result.get(resource).add(resourceAllocation);
}
}
return result;
}
public static <R extends ResourceAllocation<?>> List<R> sortedByStartDate(
Collection<? extends R> allocations) {
List<R> result = new ArrayList<R>(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<Task, List<R>>();
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<E>());
}
}
private static Comparator<ResourceAllocation<?>> byStartDateComparator() {
return new Comparator<ResourceAllocation<?>>() {
@Override
public int compare(ResourceAllocation<?> o1,
ResourceAllocation<?> 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<ResourcesPerDayModification>(
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 {
public 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 new INotFulfilledReceiver() {
@Override
public void cantFulfill(
ResourcesPerDayModification allocationAttempt,
CapacityResult 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<EffortModification>(
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>
* </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);
}
}
}
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<DerivedAllocation>();
@OnCopy(Strategy.SHARE_COLLECTION_ELEMENTS)
private Set<LimitingResourceQueueElement> limitingResourceQueueElements = new HashSet<LimitingResourceQueueElement>();
@OnCopy(Strategy.SHARE)
private EffortDuration intendedTotalAssignment = zero();
@OnCopy(Strategy.SHARE)
private EffortDuration intendedNonConsolidatedEffort = zero();
private IOnDayAssignmentRemoval dayAssignmenteRemoval = new DoNothing();
public interface IOnDayAssignmentRemoval {
public 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;
}
/**
* Constructor for hibernate. Do not use!
*/
public ResourceAllocation() {
this.assignmentsState = buildFromDBState();
}
/**
* 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();
if (intended != null) {
return intended;
}
return getResourcesPerDay();
}
public boolean areIntendedResourcesPerDaySatisfied() {
CalculatedValue calculatedValue = getTask().getCalculatedValue();
return calculatedValue == CalculatedValue.RESOURCES_PER_DAY
|| ObjectUtils.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 updateOriginalTotalAssigment() {
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 getIntendedTotalAssigment() {
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());
}
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 assigments 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<T>();
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);
Iterable<PartialDay> daysUntil = startInclusive
.daysUntil(endExclusive);
return daysUntil;
}
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) {
final AllocationInterval interval = new AllocationInterval(
getStartSpecifiedByTask(), end);
return getIAllocateEffortOnInterval(interval);
}
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<T>();
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<T>();
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<Capacity>();
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) {
if (availability.isValid(day.getDate())) {
return getCapacityAt(day);
} else {
return Capacity.create(zero())
.notOverAssignableWithoutLimit();
}
}
protected abstract Capacity getCapacityAt(PartialDay each);
private Share getShareAt(PartialDay day,
AvailabilityTimeLine availability) {
if (availability.isValid(day.getDate())) {
EffortDuration capacityAtDay = getAllocationCalendar()
.getCapacityOn(day);
return new Share(-capacityAtDay.getSeconds());
} else {
return new Share(Integer.MAX_VALUE);
}
}
}
public void markAsUnsatisfied() {
removingAssignments(getAssignments());
assert isUnsatisfied();
}
public boolean isLimiting() {
return getLimitingResourceQueueElement() != null;
}
public boolean isLimitingAndHasDayAssignments() {
return isLimiting() && hasAssignments();
}
public boolean isSatisfied() {
if (isCompletelyConsolidated()) {
return hasAssignments();
} else {
return !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);
updateOriginalTotalAssigment();
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<EffortDuration>(
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) {
resetAssigmentsFittingAllocationDatesToResultingAssignments(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) {
resetAssigmentsForInterval(this, assignmentsCreated);
}
}
protected void resetAssigmentsForInterval(
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);
updateOriginalTotalAssigment();
updateResourcesPerDay();
}
void updateAssignmentsConsolidatedValues() {
LocalDate firstNotConsolidated = task.getFirstDayNotConsolidated()
.getDate();
for (T each : getAssignments()) {
each.setConsolidated(each.getDay().isBefore(firstNotConsolidated));
}
}
private void resetAssigmentsFittingAllocationDatesToResultingAssignments(
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<T>();
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<T>();
for (T each : assignments) {
if (!daysPresent.contains(each.getDay())) {
result.add(each);
}
}
return result;
}
public void removeLimitingDayAssignments() {
resetAssignmentsTo(Collections.<T> 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();
if (start.getDate().equals(dayDate)) {
return start;
} else {
return IntraDayDate.startOfDay(dayDate);
}
}
private IntraDayDate endFor(LocalDate assignmentDate) {
IntraDayDate end = getIntraDayEndDate();
if (end.getDate().equals(assignmentDate)) {
return end;
} else {
return IntraDayDate.startOfDay(assignmentDate).nextDayAtStart();
}
}
private static <T> T avoidNull(T value, T defaultValue) {
if (value != null) {
return value;
} else {
return defaultValue;
}
}
public ICalendar getAllocationCalendar() {
return getCalendarGivenTaskCalendar(getTaskCalendar());
}
private ICalendar getTaskCalendar() {
if (getTask().getCalendar() == null) {
return SameWorkHoursEveryDay.getDefaultWorkingDay();
} else {
return getTask().getCalendar();
}
}
protected abstract ICalendar getCalendarGivenTaskCalendar(
ICalendar taskCalendar);
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);
if (container == null) {
return new HashSet<T>();
}
return container.getDayAssignments();
}
private IntraDayDate getIntraDayStartDateFor(Scenario scenario) {
IDayAssignmentsContainer<T> container = retrieveContainerFor(scenario);
if (container == null) {
return null;
}
return container.getIntraDayStart();
}
private IntraDayDate getIntraDayEndFor(Scenario scenario) {
IDayAssignmentsContainer<T> container = retrieveContainerFor(scenario);
if (container == null) {
return null;
}
return 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;
}
@OnCopy(Strategy.IGNORE)
private DayAssignmentsState assignmentsState;
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<T>(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) {
IDayAssignmentsContainer<T> container;
container = retrieveOrCreateContainerFor(scenario);
return new ExplicitlySpecifiedScenarioState(container);
}
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() {
Scenario scenario = currentScenario();
return retrieveOrCreateContainerFor(scenario).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 int getConsolidatedHours() {
return getConsolidatedEffort().roundToHours();
}
public EffortDuration getConsolidatedEffort() {
return DayAssignment.sum(getConsolidatedAssignments());
}
public int getNonConsolidatedHours() {
return getNonConsolidatedEffort().roundToHours();
}
public EffortDuration getEffortForReassignation() {
if (isSatisfied()) {
return getNonConsolidatedEffort();
} else {
return 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() {
if (resourcesPerDay == null) {
return ResourcesPerDay.amount(0);
}
return resourcesPerDay;
}
public void createDerived(IWorkerFinder finder) {
final List<? extends DayAssignment> assignments = getAssignments();
List<DerivedAllocation> result = new ArrayList<DerivedAllocation>();
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();
if (assignments.isEmpty()) {
return getStartDate();
}
return 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();
if (intraDayStart != null) {
return intraDayStart;
}
return 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());
if (l == null) {
return task.getIntraDayEndDate();
}
return 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) {
if (getEndDate() == null) {
return false;
}
return 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) {
List<DayAssignment> assignments = getAssingments(resource,
start.getDate(), endExclusive.asExclusiveEnd());
return getAssignedDuration(assignments, 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),
new PredicateOnDayAssignment() {
@Override
public boolean satisfiedBy(
DayAssignment dayAssignment) {
return 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>(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<DayAssignment>();
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) {
PartialDay partial = getPartialDay(value,
startInclusive, endExclusive);
return partial.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()) {
updateOriginalTotalAssigment();
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()) ? (LimitingResourceQueueElement) 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 removeDayAssigmentsFor(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() {
if (assignmentFunction != null) {
return assignmentFunction.isManual();
}
return false;
}
public void resetIntendedIntendedResourcesPerDayWithNonConsolidated() {
intendedResourcesPerDay = getNonConsolidatedResourcePerDay();
}
public void removeDayAssignmentsBeyondDate(LocalDate date) {
List<T> toRemove = new ArrayList<T>();
for (T t : getAssignments()) {
if (t.getDay().compareTo(date) >= 0) {
toRemove.add(t);
}
}
setOnDayAssignmentRemoval(new DetachDayAssignmentOnRemoval());
getDayAssignmentsState().removingAssignments(toRemove);
}
}