ItEr59S08CUAsignacionRecursosLimitantesItEr58S10: Add new mechanism for checking if a gap is suitable and getting the resulting allocation

This commit is contained in:
Óscar González Fernández 2010-06-06 12:50:03 +02:00
parent 2b5c5edc26
commit c6da0b2081
4 changed files with 449 additions and 0 deletions

View file

@ -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;
}
}

View file

@ -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();
}
};
}
};
}
}

View file

@ -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);

View file

@ -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;
}
}