diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/planner/limiting/entities/AllocationOnGap.java b/navalplanner-business/src/main/java/org/navalplanner/business/planner/limiting/entities/AllocationOnGap.java
new file mode 100644
index 000000000..63154a82f
--- /dev/null
+++ b/navalplanner-business/src/main/java/org/navalplanner/business/planner/limiting/entities/AllocationOnGap.java
@@ -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 .
+ */
+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 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 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 toFiniteList(Iterable daysUntil) {
+ List result = new ArrayList();
+ for (LocalDate each : daysUntil) {
+ result.add(each);
+ }
+ return result;
+ }
+
+ @Override
+ public List getAssignmentsFor(
+ ResourceAllocation> allocation, Resource resource)
+ throws IllegalStateException {
+ List 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 createSpecific(List days,
+ SpecificResourceAllocation allocation, Resource resource) {
+ List result = new ArrayList();
+ int i = 0;
+ for (LocalDate each : days) {
+ result.add(SpecificDayAssignment.create(each, assignableHours[i],
+ resource));
+ i++;
+ }
+ return result;
+ }
+
+ private List createGeneric(List days,
+ GenericResourceAllocation allocation, Resource resource) {
+ List result = new ArrayList();
+ 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;
+ }
+
+}
\ 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 663c0ed0f..51c77dbaa 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
@@ -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 {
return isAfter(DateAndHour.from(date));
}
+ /**
+ * Creates an {@link Iterable} that returns a lazy iterator. If
+ * end is null it will not stop and will keep on
+ * producing days forever
+ */
+ public Iterable daysUntil(final DateAndHour end) {
+ Validate.isTrue(end == null || end.isAfter(this));
+ return new Iterable() {
+ @Override
+ public Iterator iterator() {
+ return new Iterator() {
+
+ 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();
+ }
+ };
+ }
+ };
+ }
+
}
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 d0b3798e7..5a5fea82a 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
@@ -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 {
}
}
+ public List 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 result = new ArrayList();
+ Iterator 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);
diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/planner/limiting/entities/GapRequirements.java b/navalplanner-business/src/main/java/org/navalplanner/business/planner/limiting/entities/GapRequirements.java
new file mode 100644
index 000000000..cb1207795
--- /dev/null
+++ b/navalplanner-business/src/main/java/org/navalplanner/business/planner/limiting/entities/GapRequirements.java
@@ -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 .
+ */
+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
+ */
+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 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 hours) {
+ return AllocationOnGap.validOn(gap, realStart, calculateEnd(realStart,
+ hours), asArray(hours));
+ }
+
+ private DateAndHour calculateEnd(DateAndHour realStart, List 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 hours) {
+ return hours.get(hours.size() - 1);
+ }
+
+ private static class StartRemoval {
+
+ /**
+ * removes the initial assignments so the resulting list has
+ * hoursSurplus less hours
+ */
+ static StartRemoval removeStartSurplus(DateAndHour start,
+ List 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 hours,
+ int hoursSurplus) {
+ ListIterator 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 hours;
+
+ private StartRemoval(DateAndHour newStart, List hours) {
+ this.newStart = newStart;
+ this.hours = hours;
+ }
+ }
+
+ private static int[] asArray(Collection integers) {
+ int[] result = new int[integers.size()];
+ int i = 0;
+ for (Integer each : integers) {
+ result[i++] = each;
+ }
+ return result;
+ }
+
+ private static int sum(List hours) {
+ int result = 0;
+ for (int each : hours) {
+ result += each;
+ }
+ return result;
+ }
+
+}