diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/calendars/entities/AvailabilityTimeLine.java b/navalplanner-business/src/main/java/org/navalplanner/business/calendars/entities/AvailabilityTimeLine.java index 53d36a19f..a26c4b13d 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/calendars/entities/AvailabilityTimeLine.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/calendars/entities/AvailabilityTimeLine.java @@ -333,6 +333,10 @@ public class AvailabilityTimeLine { } } + public interface IVetoer { + public boolean isValid(LocalDate date); + } + public static AvailabilityTimeLine allValid() { return new AvailabilityTimeLine(); } @@ -343,18 +347,31 @@ public class AvailabilityTimeLine { return result; } + private static IVetoer NO_VETOER = new IVetoer() { + + @Override + public boolean isValid(LocalDate date) { + return true; + } + }; + + private IVetoer vetoer = NO_VETOER; + private List invalids = new ArrayList(); private AvailabilityTimeLine() { } public boolean isValid(LocalDate date) { + return isValidBasedOnInvaidIntervals(date) && vetoer.isValid(date); + } + + private boolean isValidBasedOnInvaidIntervals(LocalDate date) { if (invalids.isEmpty()) { return true; } Interval possibleInterval = findPossibleIntervalFor(date); - return (possibleInterval == null || !possibleInterval.includes(date)) - && additionalRestriction.isValid(date); + return possibleInterval == null || !possibleInterval.includes(date); } private Interval findPossibleIntervalFor(LocalDate date) { @@ -380,6 +397,19 @@ public class AvailabilityTimeLine { insert(point); } + /** + * There are some invalid dates that cannot or are not suitable to be + * represented as belonging to invalid intervals. For example if the invalid + * dates are an infinite set. + * + * @param vetoer + * the vetoer to use + */ + public void setVetoer(IVetoer vetoer) { + Validate.notNull(vetoer); + this.vetoer = vetoer; + } + private void insert(Interval toBeInserted) { if (invalids.isEmpty()) { invalids.add(toBeInserted); @@ -481,9 +511,20 @@ public class AvailabilityTimeLine { AvailabilityTimeLine result = AvailabilityTimeLine.allValid(); inserting(result, invalids); inserting(result, another.invalids); + result.setVetoer(and(this.vetoer, another.vetoer)); return result; } + private static IVetoer and(final IVetoer a, + final IVetoer b) { + return new IVetoer() { + @Override + public boolean isValid(LocalDate date) { + return a.isValid(date) && b.isValid(date); + } + }; + } + public AvailabilityTimeLine or(AvailabilityTimeLine another) { List intersections = doIntersections(this, another); AvailabilityTimeLine result = AvailabilityTimeLine.allValid(); @@ -502,9 +543,20 @@ public class AvailabilityTimeLine { FixedPoint.tryExtract(each.getEnd())); } } + result.setVetoer(or(this.vetoer, another.vetoer)); return result; } + private static IVetoer or(final IVetoer a, + final IVetoer b) { + return new IVetoer() { + @Override + public boolean isValid(LocalDate date) { + return a.isValid(date) || b.isValid(date); + } + }; + } + private static List doIntersections(AvailabilityTimeLine one, AvailabilityTimeLine another) { List result = new ArrayList(); diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/calendars/entities/BaseCalendar.java b/navalplanner-business/src/main/java/org/navalplanner/business/calendars/entities/BaseCalendar.java index 9f151363e..f12eedcf9 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/calendars/entities/BaseCalendar.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/calendars/entities/BaseCalendar.java @@ -36,6 +36,7 @@ import org.hibernate.validator.NotNull; import org.hibernate.validator.Valid; import org.joda.time.LocalDate; import org.navalplanner.business.calendars.daos.IBaseCalendarDAO; +import org.navalplanner.business.calendars.entities.AvailabilityTimeLine.IVetoer; import org.navalplanner.business.calendars.entities.CalendarData.Days; import org.navalplanner.business.common.IntegrationEntity; import org.navalplanner.business.common.entities.EntitySequence; @@ -789,10 +790,23 @@ public class BaseCalendar extends IntegrationEntity implements ICalendar { private void addInvaliditiesDerivedFromCalendar(AvailabilityTimeLine result) { addInvaliditiesFromAvailabilities(result); addInvaliditiesFromExceptions(result); - addInvaliditiesFromCalendarDatas(result); + addInvaliditiesFromEmptyCalendarDatas(result); + addInvaliditiesFromEmptyDaysInCalendarDatas(result); } - private void addInvaliditiesFromCalendarDatas(AvailabilityTimeLine result) { + private void addInvaliditiesFromEmptyDaysInCalendarDatas( + AvailabilityTimeLine result) { + result.setVetoer(new IVetoer() { + + @Override + public boolean isValid(LocalDate date) { + return canWorkOn(date); + } + }); + } + + private void addInvaliditiesFromEmptyCalendarDatas( + AvailabilityTimeLine result) { LocalDate previous = null; for (CalendarData each : calendarDataVersions) { addInvalidityIfDataEmpty(result, previous, each); diff --git a/navalplanner-business/src/test/java/org/navalplanner/business/test/calendars/entities/AvailabilityTimeLineTest.java b/navalplanner-business/src/test/java/org/navalplanner/business/test/calendars/entities/AvailabilityTimeLineTest.java index 8e38f6fff..290a1af8e 100644 --- a/navalplanner-business/src/test/java/org/navalplanner/business/test/calendars/entities/AvailabilityTimeLineTest.java +++ b/navalplanner-business/src/test/java/org/navalplanner/business/test/calendars/entities/AvailabilityTimeLineTest.java @@ -20,6 +20,7 @@ */ package org.navalplanner.business.test.calendars.entities; +import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; @@ -38,6 +39,7 @@ import org.navalplanner.business.calendars.entities.AvailabilityTimeLine; import org.navalplanner.business.calendars.entities.AvailabilityTimeLine.DatePoint; import org.navalplanner.business.calendars.entities.AvailabilityTimeLine.EndOfTime; import org.navalplanner.business.calendars.entities.AvailabilityTimeLine.FixedPoint; +import org.navalplanner.business.calendars.entities.AvailabilityTimeLine.IVetoer; import org.navalplanner.business.calendars.entities.AvailabilityTimeLine.Interval; import org.navalplanner.business.calendars.entities.AvailabilityTimeLine.StartOfTime; @@ -300,6 +302,36 @@ public class AvailabilityTimeLineTest { .plusDays(20)), EndOfTime.create())); } + @Test + public void doingAnORDoesAnOrForAdditionalConstraints() { + boolean[][] validities = { { true, true }, { true, false }, + { false, true }, { false, false } }; + for (final boolean[] pairs : validities) { + AvailabilityTimeLine a = AvailabilityTimeLine.allValid(); + a.setVetoer(new IVetoer() { + + @Override + public boolean isValid(LocalDate date) { + return pairs[0]; + } + }); + AvailabilityTimeLine b = AvailabilityTimeLine.allValid(); + b.setVetoer(new IVetoer() { + + @Override + public boolean isValid(LocalDate date) { + return pairs[1]; + } + }); + AvailabilityTimeLine result = a.or(b); + boolean expected = pairs[0] || pairs[1]; + + assertThat(result.isValid(earlyExample), equalTo(expected)); + assertThat(result.isValid(contemporaryExample), equalTo(expected)); + assertThat(result.isValid(lateExample), equalTo(expected)); + } + } + @Test public void doingAnAndWithAnAllValidTimeLineProducesTheSameTimeLine() { AvailabilityTimeLine timeLine = AvailabilityTimeLine.allValid(); @@ -315,6 +347,36 @@ public class AvailabilityTimeLineTest { .plusDays(20)), EndOfTime.create())); } + @Test + public void doingAnAndIntersectsTheAdditionalConstraints() { + boolean[][] validities = { { true, true }, { true, false }, + { false, true }, { false, false } }; + for (final boolean[] pairs : validities) { + AvailabilityTimeLine a = AvailabilityTimeLine.allValid(); + a.setVetoer(new IVetoer() { + + @Override + public boolean isValid(LocalDate date) { + return pairs[0]; + } + }); + AvailabilityTimeLine b = AvailabilityTimeLine.allValid(); + b.setVetoer(new IVetoer() { + + @Override + public boolean isValid(LocalDate date) { + return pairs[1]; + } + }); + AvailabilityTimeLine result = a.and(b); + boolean expected = pairs[0] && pairs[1]; + + assertThat(result.isValid(earlyExample), equalTo(expected)); + assertThat(result.isValid(contemporaryExample), equalTo(expected)); + assertThat(result.isValid(lateExample), equalTo(expected)); + } + } + @Test public void doingAnOrWithANeverValidTimeLineProducesTheSameTimeLine() { AvailabilityTimeLine timeLine = AvailabilityTimeLine.allValid(); diff --git a/navalplanner-business/src/test/java/org/navalplanner/business/test/calendars/entities/BaseCalendarTest.java b/navalplanner-business/src/test/java/org/navalplanner/business/test/calendars/entities/BaseCalendarTest.java index 07afd2202..18033a90e 100644 --- a/navalplanner-business/src/test/java/org/navalplanner/business/test/calendars/entities/BaseCalendarTest.java +++ b/navalplanner-business/src/test/java/org/navalplanner/business/test/calendars/entities/BaseCalendarTest.java @@ -35,6 +35,7 @@ import java.util.Set; import org.joda.time.LocalDate; import org.junit.Test; +import org.navalplanner.business.calendars.entities.AvailabilityTimeLine; import org.navalplanner.business.calendars.entities.BaseCalendar; import org.navalplanner.business.calendars.entities.BaseCalendar.DayType; import org.navalplanner.business.calendars.entities.CalendarData.Days; @@ -909,4 +910,13 @@ public class BaseCalendarTest { assertFalse(calendar.canWorkOn(MONDAY_LOCAL_DATE)); } + @Test + public void theAvailabilityTimeLineTakesIntoAccountTheDaysItCannotWorkDueToCalendarData() { + BaseCalendar calendar = createBasicCalendar(); + calendar.setCapacityAt(Days.MONDAY, Capacity.create(hours(0)) + .overAssignableWithoutLimit(false)); + + AvailabilityTimeLine availability = calendar.getAvailability(); + assertFalse(availability.isValid(MONDAY_LOCAL_DATE)); + } }