ItEr59S08CUAsignacionRecursosLimitantesItEr58S10: Add new mechanism for checking if a gap is suitable and getting the resulting allocation
This commit is contained in:
parent
2b5c5edc26
commit
c6da0b2081
4 changed files with 449 additions and 0 deletions
|
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
* This file is part of NavalPlan
|
||||
*
|
||||
* Copyright (C) 2009 Fundación para o Fomento da Calidade Industrial e
|
||||
* Desenvolvemento Tecnolóxico de Galicia
|
||||
*
|
||||
* 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.navalplanner.business.planner.limiting.entities;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang.Validate;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.navalplanner.business.planner.entities.DayAssignment;
|
||||
import org.navalplanner.business.planner.entities.GenericDayAssignment;
|
||||
import org.navalplanner.business.planner.entities.GenericResourceAllocation;
|
||||
import org.navalplanner.business.planner.entities.ResourceAllocation;
|
||||
import org.navalplanner.business.planner.entities.SpecificDayAssignment;
|
||||
import org.navalplanner.business.planner.entities.SpecificResourceAllocation;
|
||||
import org.navalplanner.business.resources.entities.Resource;
|
||||
|
||||
public abstract class AllocationOnGap {
|
||||
|
||||
public static AllocationOnGap invalidOn(Gap gap) {
|
||||
return new InvalidAllocationOnGap(gap);
|
||||
}
|
||||
|
||||
public static AllocationOnGap validOn(Gap gap, DateAndHour start,
|
||||
DateAndHour endExclusive, int[] assignableHours) {
|
||||
return new ValidAllocationOnGap(gap, start, endExclusive,
|
||||
assignableHours);
|
||||
}
|
||||
|
||||
private final Gap originalGap;
|
||||
|
||||
protected AllocationOnGap(Gap originalGap) {
|
||||
Validate.notNull(originalGap);
|
||||
this.originalGap = originalGap;
|
||||
}
|
||||
|
||||
public abstract boolean isValid();
|
||||
|
||||
public abstract List<DayAssignment> getAssignmentsFor(
|
||||
ResourceAllocation<?> allocation, Resource resource)
|
||||
throws IllegalStateException;
|
||||
|
||||
public abstract DateAndHour getStartInclusive()
|
||||
throws IllegalStateException;
|
||||
|
||||
public abstract DateAndHour getEndExclusive() throws IllegalStateException;
|
||||
|
||||
public Gap getOriginalGap() {
|
||||
return originalGap;
|
||||
}
|
||||
}
|
||||
|
||||
class InvalidAllocationOnGap extends AllocationOnGap {
|
||||
|
||||
private static final String INVALID_ALLOCATION_ON_GAP = "invalid allocation on gap";
|
||||
|
||||
InvalidAllocationOnGap(Gap originalGap) {
|
||||
super(originalGap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DayAssignment> getAssignmentsFor(
|
||||
ResourceAllocation<?> allocation, Resource resource) {
|
||||
throw new IllegalStateException(INVALID_ALLOCATION_ON_GAP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DateAndHour getEndExclusive() throws IllegalStateException {
|
||||
throw new IllegalStateException(INVALID_ALLOCATION_ON_GAP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DateAndHour getStartInclusive() throws IllegalStateException {
|
||||
throw new IllegalStateException(INVALID_ALLOCATION_ON_GAP);
|
||||
}
|
||||
}
|
||||
|
||||
class ValidAllocationOnGap extends AllocationOnGap {
|
||||
|
||||
private final DateAndHour start;
|
||||
private final DateAndHour end;
|
||||
private final int[] assignableHours;
|
||||
|
||||
public ValidAllocationOnGap(Gap gap, DateAndHour startInclusive,
|
||||
DateAndHour endExclusive, int[] assignableHours) {
|
||||
super(gap);
|
||||
Validate.notNull(startInclusive);
|
||||
Validate.notNull(endExclusive);
|
||||
Validate.notNull(assignableHours);
|
||||
Validate.isTrue(endExclusive.isAfter(startInclusive));
|
||||
this.start = startInclusive;
|
||||
this.end = endExclusive;
|
||||
Validate.isTrue(assignableHours.length == toFiniteList(
|
||||
start.daysUntil(end)).size());
|
||||
this.assignableHours = assignableHours.clone();
|
||||
}
|
||||
|
||||
private List<LocalDate> toFiniteList(Iterable<LocalDate> daysUntil) {
|
||||
List<LocalDate> result = new ArrayList<LocalDate>();
|
||||
for (LocalDate each : daysUntil) {
|
||||
result.add(each);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DayAssignment> getAssignmentsFor(
|
||||
ResourceAllocation<?> allocation, Resource resource)
|
||||
throws IllegalStateException {
|
||||
List<LocalDate> days = toFiniteList(start.daysUntil(end));
|
||||
assert assignableHours.length == days.size();
|
||||
if (allocation instanceof SpecificResourceAllocation) {
|
||||
return createSpecific(days,
|
||||
(SpecificResourceAllocation) allocation, resource);
|
||||
} else {
|
||||
return createGeneric(days, (GenericResourceAllocation) allocation,
|
||||
resource);
|
||||
}
|
||||
}
|
||||
|
||||
private List<DayAssignment> createSpecific(List<LocalDate> days,
|
||||
SpecificResourceAllocation allocation, Resource resource) {
|
||||
List<DayAssignment> result = new ArrayList<DayAssignment>();
|
||||
int i = 0;
|
||||
for (LocalDate each : days) {
|
||||
result.add(SpecificDayAssignment.create(each, assignableHours[i],
|
||||
resource));
|
||||
i++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<DayAssignment> createGeneric(List<LocalDate> days,
|
||||
GenericResourceAllocation allocation, Resource resource) {
|
||||
List<DayAssignment> result = new ArrayList<DayAssignment>();
|
||||
int i = 0;
|
||||
for (LocalDate each : days) {
|
||||
result.add(GenericDayAssignment.create(each, assignableHours[i],
|
||||
resource));
|
||||
i++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DateAndHour getEndExclusive() throws IllegalStateException {
|
||||
return end;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DateAndHour getStartInclusive() throws IllegalStateException {
|
||||
return start;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -20,6 +20,8 @@
|
|||
|
||||
package org.navalplanner.business.planner.limiting.entities;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.apache.commons.lang.Validate;
|
||||
import org.joda.time.LocalDate;
|
||||
|
||||
|
|
@ -99,4 +101,39 @@ public class DateAndHour implements Comparable<DateAndHour> {
|
|||
return isAfter(DateAndHour.from(date));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link Iterable} that returns a lazy iterator. If
|
||||
* <code>end</code> is <code>null</code> it will not stop and will keep on
|
||||
* producing days forever
|
||||
*/
|
||||
public Iterable<LocalDate> daysUntil(final DateAndHour end) {
|
||||
Validate.isTrue(end == null || end.isAfter(this));
|
||||
return new Iterable<LocalDate>() {
|
||||
@Override
|
||||
public Iterator<LocalDate> iterator() {
|
||||
return new Iterator<LocalDate>() {
|
||||
|
||||
private LocalDate current = getDate();
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return end == null || end.isAfter(current);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDate next() {
|
||||
LocalDate result = current;
|
||||
current = current.plusDays(1);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,13 @@
|
|||
|
||||
package org.navalplanner.business.planner.limiting.entities;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang.Validate;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.navalplanner.business.calendars.entities.BaseCalendar;
|
||||
import org.navalplanner.business.calendars.entities.ResourceCalendar;
|
||||
import org.navalplanner.business.resources.entities.Resource;
|
||||
|
||||
|
|
@ -69,6 +75,56 @@ public class Gap implements Comparable<Gap> {
|
|||
}
|
||||
}
|
||||
|
||||
public List<Integer> getHoursInGapUntilAllocatingAndGoingToTheEnd(
|
||||
BaseCalendar calendar,
|
||||
DateAndHour realStart, DateAndHour allocationEnd, int total) {
|
||||
DateAndHour gapEnd = getEndTime();
|
||||
Validate.isTrue(gapEnd == null || allocationEnd.compareTo(gapEnd) <= 0);
|
||||
Validate.isTrue(startTime == null
|
||||
|| realStart.compareTo(startTime) >= 0);
|
||||
List<Integer> result = new ArrayList<Integer>();
|
||||
Iterator<LocalDate> daysUntilEnd = realStart.daysUntil(gapEnd)
|
||||
.iterator();
|
||||
boolean isFirst = true;
|
||||
while (daysUntilEnd.hasNext()) {
|
||||
LocalDate each = daysUntilEnd.next();
|
||||
final boolean isLast = !daysUntilEnd.hasNext();
|
||||
int hoursAtDay = getHoursAtDay(each, calendar, realStart, isFirst,
|
||||
isLast);
|
||||
final int hours;
|
||||
if (total > 0) {
|
||||
hours = Math.min(hoursAtDay, total);
|
||||
total -= hours;
|
||||
} else {
|
||||
hours = hoursAtDay;
|
||||
}
|
||||
if (isFirst) {
|
||||
isFirst = false;
|
||||
}
|
||||
result.add(hours);
|
||||
if (total == 0
|
||||
&& DateAndHour.from(each).compareTo(allocationEnd) >= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private int getHoursAtDay(LocalDate day, BaseCalendar calendar,
|
||||
DateAndHour realStart, boolean isFirst, final boolean isLast) {
|
||||
final int capacity = calendar.getCapacityAt(day);
|
||||
if (isLast && isFirst) {
|
||||
return Math.min(endTime.getHour() - realStart.getHour(),
|
||||
capacity);
|
||||
} else if (isFirst) {
|
||||
return capacity - realStart.getHour();
|
||||
} else if (isLast) {
|
||||
return Math.min(endTime.getHour(), capacity);
|
||||
} else {
|
||||
return capacity;
|
||||
}
|
||||
}
|
||||
|
||||
public static Gap create(Resource resource, DateAndHour startTime,
|
||||
DateAndHour endTime) {
|
||||
return new Gap(resource, startTime, endTime);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
* This file is part of NavalPlan
|
||||
*
|
||||
* Copyright (C) 2009 Fundación para o Fomento da Calidade Industrial e
|
||||
* Desenvolvemento Tecnolóxico de Galicia
|
||||
*
|
||||
* 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.navalplanner.business.planner.limiting.entities;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
import org.apache.commons.lang.Validate;
|
||||
import org.joda.time.LocalDate;
|
||||
|
||||
|
||||
/**
|
||||
* @author Óscar González Fernández <ogonzalez@igalia.com>
|
||||
*/
|
||||
public class GapRequirements {
|
||||
|
||||
private final LimitingResourceQueueElement element;
|
||||
|
||||
private final DateAndHour earliestPossibleStart;
|
||||
|
||||
private final DateAndHour earliestPossibleEnd;
|
||||
|
||||
public static GapRequirements forElement(
|
||||
LimitingResourceQueueElement element) {
|
||||
return new GapRequirements(element, asDateAndHour(element
|
||||
.getEarlierStartDateBecauseOfGantt()), asDateAndHour(element
|
||||
.getEarliestEndDateBecauseOfGantt()));
|
||||
}
|
||||
|
||||
private static DateAndHour asDateAndHour(Date date) {
|
||||
return DateAndHour.from(LocalDate.fromDateFields(date));
|
||||
}
|
||||
|
||||
private GapRequirements(LimitingResourceQueueElement element,
|
||||
DateAndHour earliestPossibleStart, DateAndHour earliestPossibleEnd) {
|
||||
Validate.notNull(element);
|
||||
Validate.notNull(earliestPossibleStart);
|
||||
Validate.notNull(earliestPossibleEnd);
|
||||
this.element = element;
|
||||
this.earliestPossibleStart = earliestPossibleStart;
|
||||
this.earliestPossibleEnd = earliestPossibleEnd;
|
||||
}
|
||||
|
||||
public AllocationOnGap guessValidity(Gap gap) {
|
||||
DateAndHour gapEnd = gap.getEndTime();
|
||||
if (gapEnd != null
|
||||
&& (earliestPossibleStart.compareTo(gapEnd) >= 0 || earliestPossibleEnd
|
||||
.isAfter(gapEnd))) {
|
||||
return AllocationOnGap.invalidOn(gap);
|
||||
}
|
||||
DateAndHour realStart = DateAndHour.Max(earliestPossibleStart, gap
|
||||
.getStartTime());
|
||||
List<Integer> hours = gap.getHoursInGapUntilAllocatingAndGoingToTheEnd(
|
||||
element.getResource().getCalendar(), realStart,
|
||||
earliestPossibleEnd, element.getIntentedTotalHours());
|
||||
int total = sum(hours);
|
||||
if (total < element.getIntentedTotalHours()) {
|
||||
return AllocationOnGap.invalidOn(gap);
|
||||
} else if (total == element.getIntentedTotalHours()) {
|
||||
return validAllocation(gap, realStart, hours);
|
||||
} else {
|
||||
assert total > element.getIntentedTotalHours();
|
||||
int hoursSurplus = total - element.getIntentedTotalHours();
|
||||
StartRemoval result = StartRemoval.removeStartSurplus(realStart,
|
||||
hours, hoursSurplus);
|
||||
return validAllocation(gap, result.newStart, result.hours);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private AllocationOnGap validAllocation(Gap gap, DateAndHour realStart,
|
||||
List<Integer> hours) {
|
||||
return AllocationOnGap.validOn(gap, realStart, calculateEnd(realStart,
|
||||
hours), asArray(hours));
|
||||
}
|
||||
|
||||
private DateAndHour calculateEnd(DateAndHour realStart, List<Integer> hours) {
|
||||
if (hours.size() == 1) {
|
||||
return new DateAndHour(realStart.getDate(), hours.get(0)
|
||||
+ realStart.getHour());
|
||||
}
|
||||
return new DateAndHour(realStart.getDate().plusDays(hours.size() - 1),
|
||||
getLast(hours));
|
||||
}
|
||||
|
||||
private int getLast(List<Integer> hours) {
|
||||
return hours.get(hours.size() - 1);
|
||||
}
|
||||
|
||||
private static class StartRemoval {
|
||||
|
||||
/**
|
||||
* removes the initial assignments so the resulting list has
|
||||
* <code>hoursSurplus</code> less hours
|
||||
*/
|
||||
static StartRemoval removeStartSurplus(DateAndHour start,
|
||||
List<Integer> hours, int hoursSurplus) {
|
||||
int previousSize = hours.size();
|
||||
int hoursRemovedAtFirstDayOfNewHours = stripStartAssignments(hours,
|
||||
hoursSurplus);
|
||||
int currentSize = hours.size();
|
||||
int daysRemoved = previousSize - currentSize;
|
||||
LocalDate newStartDay = start.getDate().plusDays(daysRemoved);
|
||||
return new StartRemoval(new DateAndHour(newStartDay,
|
||||
hoursRemovedAtFirstDayOfNewHours), hours);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the hours reduced in the resulting first assignment
|
||||
*/
|
||||
private static int stripStartAssignments(List<Integer> hours,
|
||||
int hoursSurplus) {
|
||||
ListIterator<Integer> listIterator = hours.listIterator();
|
||||
while (listIterator.hasNext() && hoursSurplus > 0) {
|
||||
Integer current = listIterator.next();
|
||||
int hoursTaken = Math.min(hoursSurplus, current);
|
||||
hoursSurplus -= hoursTaken;
|
||||
if (hoursTaken == current) {
|
||||
listIterator.remove();
|
||||
} else {
|
||||
listIterator.set(hoursTaken);
|
||||
return current - hoursTaken;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private final DateAndHour newStart;
|
||||
|
||||
private final List<Integer> hours;
|
||||
|
||||
private StartRemoval(DateAndHour newStart, List<Integer> hours) {
|
||||
this.newStart = newStart;
|
||||
this.hours = hours;
|
||||
}
|
||||
}
|
||||
|
||||
private static int[] asArray(Collection<Integer> integers) {
|
||||
int[] result = new int[integers.size()];
|
||||
int i = 0;
|
||||
for (Integer each : integers) {
|
||||
result[i++] = each;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int sum(List<Integer> hours) {
|
||||
int result = 0;
|
||||
for (int each : hours) {
|
||||
result += each;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue