From d7d552c07c2b8ce83040bc4811aa7d6f518e0b65 Mon Sep 17 00:00:00 2001 From: Diego Pino Garcia Date: Sun, 19 Dec 2010 22:09:54 +0100 Subject: [PATCH] Change behaviour for appropriative and non-appropriative allocations Now, when a queue element is allocated at a specific date: * Unschedule all elements in target queue from that date on. If there's already an element at that date, unschedule it too. * Schedule those elements back in the queue in topological order. This guarantees that dependencies are satisfied. As a result, the new element is allocated at specified date and all the elements after that day are shifted in the queue. If there were dependencies between those shifted elements and other elements in the queue, they will be shifted in their own queue too. Take into account scheduling time when moving a task to a specific date. Distinguish between how to generate day assigments between appropriate and non-appropriative allocations. FEA: ItEr66OTS03AlgoritmosLimitantesItEr65OTS05 --- .../limiting/entities/AllocationSpec.java | 18 +- .../limiting/entities/DateAndHour.java | 2 + .../planner/limiting/entities/Gap.java | 6 +- .../entities/InsertionRequirements.java | 22 ++ .../entities/LimitingResourceQueue.java | 13 ++ .../ILimitingResourceQueueModel.java | 3 +- .../LimitingResourceQueueModel.java | 188 ++++++++++++------ .../web/limitingresources/QueuesState.java | 10 + 8 files changed, 193 insertions(+), 69 deletions(-) diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/planner/limiting/entities/AllocationSpec.java b/navalplanner-business/src/main/java/org/navalplanner/business/planner/limiting/entities/AllocationSpec.java index 3abc27edd..0ed978b75 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/planner/limiting/entities/AllocationSpec.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/planner/limiting/entities/AllocationSpec.java @@ -51,6 +51,8 @@ public abstract class AllocationSpec { private final GapOnQueue originalGap; + private List unscheduled = new ArrayList(); + protected AllocationSpec(GapOnQueue originalGap) { Validate.notNull(originalGap); this.originalGap = originalGap; @@ -77,6 +79,20 @@ public abstract class AllocationSpec { public LimitingResourceQueue getQueue() { return originalGap.getOriginQueue(); } + + public boolean isAppropriative() { + return !unscheduled.isEmpty(); + } + + public void setUnscheduledElements( + List queueElements) { + unscheduled.addAll(queueElements); + } + + public List getUnscheduledElements() { + return unscheduled; + } + } class InvalidAllocationAttempt extends AllocationSpec { @@ -206,4 +222,4 @@ class ValidAllocationAttempt extends AllocationSpec { return true; } -} \ No newline at end of file +} diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/planner/limiting/entities/DateAndHour.java b/navalplanner-business/src/main/java/org/navalplanner/business/planner/limiting/entities/DateAndHour.java index e7c6e66cb..a216efef0 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/planner/limiting/entities/DateAndHour.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/planner/limiting/entities/DateAndHour.java @@ -51,11 +51,13 @@ public class DateAndHour implements Comparable { private Integer hour; public DateAndHour(LocalDate date, Integer hour) { + Validate.notNull(date); this.date = date; this.hour = hour; } public DateAndHour(DateAndHour dateAndHour) { + Validate.notNull(date); this.date = dateAndHour.getDate(); this.hour = dateAndHour.getHour(); } diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/planner/limiting/entities/Gap.java b/navalplanner-business/src/main/java/org/navalplanner/business/planner/limiting/entities/Gap.java index 6f5aae921..e8776be42 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/planner/limiting/entities/Gap.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/planner/limiting/entities/Gap.java @@ -242,7 +242,11 @@ public class Gap implements Comparable { } public String toString() { - String result = startTime.getDate() + " - " + startTime.getHour(); + String result = ""; + + if (startTime != null) { + result = startTime.getDate() + " - " + startTime.getHour(); + } if (endTime != null) { result += "; " + endTime.getDate() + " - " + endTime.getHour(); } diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/planner/limiting/entities/InsertionRequirements.java b/navalplanner-business/src/main/java/org/navalplanner/business/planner/limiting/entities/InsertionRequirements.java index 9410ec02c..dde0a31cd 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/planner/limiting/entities/InsertionRequirements.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/planner/limiting/entities/InsertionRequirements.java @@ -57,6 +57,28 @@ public class InsertionRequirements { calculateEarliestPossibleEnd(element, dependenciesAffectingEnd)); } + /** + * Specifies a minimum startTime, earliestStart should be lower than this value + * + * @param element + * @param dependenciesAffectingStart + * @param dependenciesAffectingEnd + * @param startAt + * @return + */ + public static InsertionRequirements forElement( + LimitingResourceQueueElement element, + List dependenciesAffectingStart, + List dependenciesAffectingEnd, + DateAndHour startAt) { + + DateAndHour earliesPossibleStart = calculateEarliestPossibleStart( + element, dependenciesAffectingStart); + return new InsertionRequirements(element, DateAndHour.max( + earliesPossibleStart, startAt), calculateEarliestPossibleEnd( + element, dependenciesAffectingEnd)); + } + private static DateAndHour calculateEarliestPossibleEnd( LimitingResourceQueueElement element, List dependenciesAffectingEnd) { diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/LimitingResourceQueue.java b/navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/LimitingResourceQueue.java index f591b569d..5fceae53e 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/LimitingResourceQueue.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/resources/entities/LimitingResourceQueue.java @@ -141,6 +141,19 @@ public class LimitingResourceQueue extends BaseEntity { return queueElements.subList(position + 1, queueElements.size()); } + public List getElementsSince(DateAndHour time) { + List result = new ArrayList(); + + for (LimitingResourceQueueElement each: getLimitingResourceQueueElements()) { + if (each.getStartTime().isEquals(time) + || each.getStartTime().isAfter(time)) { + result.add(each); + } + } + return result; + } + + public void queueElementMoved( LimitingResourceQueueElement limitingResourceQueueElement) { invalidCachedGaps(); diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/limitingresources/ILimitingResourceQueueModel.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/limitingresources/ILimitingResourceQueueModel.java index d50f37351..bb326c28b 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/limitingresources/ILimitingResourceQueueModel.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/limitingresources/ILimitingResourceQueueModel.java @@ -133,8 +133,7 @@ public interface ILimitingResourceQueueModel { LimitingResourceQueueElement element, LimitingResourceQueue queue, DateAndHour allocationTime); - void unschedule(LimitingResourceQueueElement element); - + LimitingResourceQueueElement unschedule(LimitingResourceQueueElement element); void removeUnassignedLimitingResourceQueueElement( LimitingResourceQueueElement element); diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/limitingresources/LimitingResourceQueueModel.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/limitingresources/LimitingResourceQueueModel.java index adc13e23a..87aa01364 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/limitingresources/LimitingResourceQueueModel.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/limitingresources/LimitingResourceQueueModel.java @@ -418,22 +418,26 @@ public class LimitingResourceQueueModel implements ILimitingResourceQueueModel { InsertionRequirements requirements = queuesState .getRequirementsFor(externalQueueElement); - AllocationSpec allocationDone = insertAtGap(requirements); - if (allocationDone == null) { + AllocationSpec allocation = insertAtGap(requirements); + if (allocation == null) { return Collections.emptyList(); } + applyAllocation(allocation); - assert allocationDone.isValid(); + assert allocation.isValid(); List result = new ArrayList(); result.add(requirements.getElement()); List moved = shift( queuesState - .getPotentiallyAffectedByInsertion(externalQueueElement), - requirements.getElement(), - allocationDone); - + .getPotentiallyAffectedByInsertion(externalQueueElement), + requirements.getElement(), allocation); result.addAll(moved); + + // Return all moved tasks (including the allocated one) + if (allocation.isAppropriative()) { + result.addAll(scheduleElements(allocation.getUnscheduledElements())); + } return result; } @@ -589,22 +593,30 @@ public class LimitingResourceQueueModel implements ILimitingResourceQueueModel { * otherwise */ private AllocationSpec insertAtGap(InsertionRequirements requirements) { + AllocationSpec allocationStillNotDone = findAllocationSpecFor(requirements); + return doAppropriativeIfNecessary(allocationStillNotDone, requirements); + } + + /** + * Find valid {@link AllocationSpec} taking into account requirements + * + * @param requirements + * @return + */ + private AllocationSpec findAllocationSpecFor(InsertionRequirements requirements) { List potentiallyValidGapsFor = queuesState .getPotentiallyValidGapsFor(requirements); + return findAllocationSpecFor(potentiallyValidGapsFor, requirements); + } + + private AllocationSpec findAllocationSpecFor(List gapsOnQueue, InsertionRequirements requirements) { boolean generic = requirements.getElement().isGeneric(); - for (GapOnQueue each : potentiallyValidGapsFor) { - for (GapOnQueue eachSubGap : getSubGaps(each, requirements - .getElement(), generic)) { + for (GapOnQueue each : gapsOnQueue) { + for (GapOnQueue eachSubGap : getSubGaps(each, + requirements.getElement(), generic)) { AllocationSpec allocation = requirements .guessValidity(eachSubGap); if (allocation.isValid()) { - if (checkAllocationIsAppropriative() - && requirements - .isAppropiativeAllocation(allocation)) { - doAppropriativeAllocation(requirements, allocation); - } else { - applyAllocation(allocation); - } return allocation; } } @@ -612,17 +624,53 @@ public class LimitingResourceQueueModel implements ILimitingResourceQueueModel { return null; } + private AllocationSpec doAppropriativeIfNecessary(AllocationSpec allocation, + InsertionRequirements requirements) { + if (allocation != null) { + if (checkAllocationIsAppropriative() + && requirements.isAppropiativeAllocation(allocation)) { + doAppropriativeAllocation(requirements, allocation); + } + return allocation; + } + return null; + } + + private AllocationSpec insertAtGap(InsertionRequirements requirements, LimitingResourceQueue queue) { + AllocationSpec allocationStillNotDone = findAllocationSpecForInQueue(requirements, queue); + return doAppropriativeIfNecessary(allocationStillNotDone, requirements); + } + + private AllocationSpec findAllocationSpecForInQueue( + InsertionRequirements requirements, LimitingResourceQueue queue) { + + List potentiallyValidGapsFor = new ArrayList(); + + for (GapOnQueue each : queuesState + .getPotentiallyValidGapsFor(requirements)) { + if (each.getOriginQueue().equals(queue)) { + potentiallyValidGapsFor.add(each); + } + } + return findAllocationSpecFor(potentiallyValidGapsFor, requirements); + } + private boolean checkAllocationIsAppropriative = true; - private void doAppropriativeAllocation(InsertionRequirements requirements, - AllocationSpec allocation) { - LimitingResourceQueueElement element = requirements.getElement(); - LimitingResourceQueue queue = queuesState.getQueueFor(element.getResource()); - DateAndHour allocationTime = requirements.getEarliestPossibleStart(allocation); + private AllocationSpec doAppropriativeAllocation( + InsertionRequirements requirements, AllocationSpec allocation) { - checkAllocationIsAppropriative(false); - appropriativeAllocation(element, queue, allocationTime); - checkAllocationIsAppropriative(true); + LimitingResourceQueueElement element = requirements.getElement(); + LimitingResourceQueue queue = queuesState.getQueueFor(element + .getResource()); + DateAndHour earliestPossibleStart = requirements + .getEarliestPossibleStart(allocation); + + List unscheduled = unscheduleElementsSince( + queue, earliestPossibleStart); + allocation.setUnscheduledElements(queuesState.inTopologicalOrder(unscheduled)); + + return allocation; } private void checkAllocationIsAppropriative(boolean value) { @@ -641,7 +689,7 @@ public class LimitingResourceQueueModel implements ILimitingResourceQueueModel { return Collections.singletonList(each); } - private void applyAllocation(final AllocationSpec allocationStillNotDone) { + private AllocationSpec applyAllocation(final AllocationSpec allocationStillNotDone) { applyAllocation(allocationStillNotDone, new IDayAssignmentBehaviour() { @Override @@ -663,6 +711,7 @@ public class LimitingResourceQueueModel implements ILimitingResourceQueueModel { } }); + return allocationStillNotDone; } private void applyAllocation(AllocationSpec allocationStillNotDone, @@ -693,16 +742,14 @@ public class LimitingResourceQueueModel implements ILimitingResourceQueueModel { private List assignLimitingResourceQueueElementToQueueAt( final LimitingResourceQueueElement element, - final LimitingResourceQueue queue, final DateAndHour startTime, + final LimitingResourceQueue queue, + final DateAndHour startAt, final DateAndHour endsAfter) { // Check if allocation is possible - InsertionRequirements requirements = queuesState - .getRequirementsFor(element); - List gapOnQueue = GapOnQueue.onQueue(queue, startTime, - endsAfter); - AllocationSpec allocation = requirements.guessValidity(gapOnQueue - .iterator().next()); + InsertionRequirements requirements = queuesState.getRequirementsFor( + element, startAt); + AllocationSpec allocation = insertAtGap(requirements, queue); if (!allocation.isValid()) { return Collections.emptyList(); } @@ -716,7 +763,7 @@ public class LimitingResourceQueueModel implements ILimitingResourceQueueModel { List assignments = LimitingResourceAllocator .generateDayAssignments( element.getResourceAllocation(), - queue.getResource(), startTime, endsAfter); + queue.getResource(), startAt, endsAfter); element.getResourceAllocation().allocateLimitingDayAssignments( assignments); } @@ -732,9 +779,25 @@ public class LimitingResourceQueueModel implements ILimitingResourceQueueModel { List moved = shift( queuesState.getPotentiallyAffectedByInsertion(element), requirements.getElement(), allocation); + result.addAll(moved); // Return all moved tasks (including the allocated one) - result.addAll(moved); + if (allocation.isAppropriative()) { + result.addAll(scheduleElements(allocation.getUnscheduledElements())); + } + return result; + } + + private List scheduleElements( + List unscheduled) { + + List result = new ArrayList(); + + checkAllocationIsAppropriative(false); + for (LimitingResourceQueueElement each: unscheduled) { + result.addAll(assignLimitingResourceQueueElement(each)); + } + checkAllocationIsAppropriative(true); return result; } @@ -929,9 +992,10 @@ public class LimitingResourceQueueModel implements ILimitingResourceQueueModel { * later added to the list of unassigned elements */ @Override - public void unschedule(LimitingResourceQueueElement queueElement) { + public LimitingResourceQueueElement unschedule(LimitingResourceQueueElement queueElement) { queuesState.unassingFromQueue(queueElement); markAsModified(queueElement); + return queueElement; } /** @@ -993,10 +1057,10 @@ public class LimitingResourceQueueModel implements ILimitingResourceQueueModel { return beingEdited; } - @Override - public Set appropriativeAllocation(LimitingResourceQueueElement _element, LimitingResourceQueue _queue, - DateAndHour allocationTime) { + public Set appropriativeAllocation( + LimitingResourceQueueElement _element, + LimitingResourceQueue _queue, DateAndHour allocationTime) { Set result = new HashSet(); @@ -1007,38 +1071,32 @@ public class LimitingResourceQueueModel implements ILimitingResourceQueueModel { unschedule(element); } - List unscheduledElements = new ArrayList(); + // Unschedule elements in queue since allocationTime + List toSchedule = new ArrayList( + unscheduleElementsSince(queue, allocationTime)); - Gap gap; - do { - gap = LimitingResourceAllocator.getFirstValidGapSince(element, queue, allocationTime); + result.addAll(assignLimitingResourceQueueElementToQueueAt(element, + queue, allocationTime, getEndsAfterBecauseOfGantt(element))); - if (gap != null) { - final LocalDate startDate = gap.getStartTime().getDate(); - - if (startDate.equals(allocationTime.getDate())) { - result.addAll(assignLimitingResourceQueueElementToQueueAt( - element, queue, allocationTime, - getEndsAfterBecauseOfGantt(element))); - break; - } else { - LimitingResourceQueueElement elementAtTime = getFirstElementFrom( - queue, allocationTime); - if (elementAtTime != null) { - unschedule(elementAtTime); - unscheduledElements.add(elementAtTime); - } - } - } - } while (gap != null); - - for (LimitingResourceQueueElement each: unscheduledElements) { - assignLimitingResourceQueueElement(each); + for (LimitingResourceQueueElement each: queuesState + .inTopologicalOrder(toSchedule)) { + result.addAll(assignLimitingResourceQueueElement(each)); } - return result; } + private List unscheduleElementsSince( + LimitingResourceQueue queue, DateAndHour allocationTime) { + + List result = new ArrayList(); + + for (LimitingResourceQueueElement each: queue.getElementsSince(allocationTime)) { + result.add(unschedule(each)); + } + return result; + } + + @SuppressWarnings("unchecked") public LimitingResourceQueueElement getFirstElementFrom(LimitingResourceQueue queue, DateAndHour allocationTime) { final List elements = new ArrayList(queue.getLimitingResourceQueueElements()); diff --git a/navalplanner-webapp/src/main/java/org/navalplanner/web/limitingresources/QueuesState.java b/navalplanner-webapp/src/main/java/org/navalplanner/web/limitingresources/QueuesState.java index 4d025c1ad..e9450f158 100644 --- a/navalplanner-webapp/src/main/java/org/navalplanner/web/limitingresources/QueuesState.java +++ b/navalplanner-webapp/src/main/java/org/navalplanner/web/limitingresources/QueuesState.java @@ -41,6 +41,7 @@ import org.navalplanner.business.common.BaseEntity; import org.navalplanner.business.planner.entities.GenericResourceAllocation; import org.navalplanner.business.planner.entities.ResourceAllocation; import org.navalplanner.business.planner.entities.SpecificResourceAllocation; +import org.navalplanner.business.planner.limiting.entities.DateAndHour; import org.navalplanner.business.planner.limiting.entities.Gap.GapOnQueue; import org.navalplanner.business.planner.limiting.entities.InsertionRequirements; import org.navalplanner.business.planner.limiting.entities.LimitingResourceQueueDependency; @@ -221,6 +222,15 @@ public class QueuesState { dependenciesStart, dependenciesEnd); } + public InsertionRequirements getRequirementsFor( + LimitingResourceQueueElement element, DateAndHour startAt) { + List dependenciesStart = new ArrayList(); + List dependenciesEnd = new ArrayList(); + fillIncoming(element, dependenciesStart, dependenciesEnd); + return InsertionRequirements.forElement(getEquivalent(element), + dependenciesStart, dependenciesEnd, startAt); + } + private void fillIncoming(LimitingResourceQueueElement element, List dependenciesStart, List dependenciesEnd) {