/*
* This file is part of LibrePlan
*
* Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e
* Desenvolvemento Tecnolóxico de Galicia
* Copyright (C) 2010-2011 Igalia, S.L.
*
* 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.libreplan.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;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.Validate;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.joda.time.LocalDate;
import org.junit.Test;
import org.libreplan.business.calendars.entities.AvailabilityTimeLine;
import org.libreplan.business.calendars.entities.AvailabilityTimeLine.DatePoint;
import org.libreplan.business.calendars.entities.AvailabilityTimeLine.EndOfTime;
import org.libreplan.business.calendars.entities.AvailabilityTimeLine.FixedPoint;
import org.libreplan.business.calendars.entities.AvailabilityTimeLine.IVetoer;
import org.libreplan.business.calendars.entities.AvailabilityTimeLine.Interval;
import org.libreplan.business.calendars.entities.AvailabilityTimeLine.StartOfTime;
/**
* @author Óscar González Fernández <ogonzalez@igalia.com>
*/
public class AvailabilityTimeLineTest {
private LocalDate earlyExample = new LocalDate(1000, 10, 6);
private LocalDate contemporaryExample = new LocalDate(2010, 10, 6);
private LocalDate lateExample = new LocalDate(3000, 10, 6);
@Test
public void anAllValidTimeLineIsValidForAllDates() {
AvailabilityTimeLine timeLine = AvailabilityTimeLine.allValid();
assertTrue(timeLine.isValid(earlyExample));
assertTrue(timeLine.isValid(contemporaryExample));
assertTrue(timeLine.isValid(lateExample));
}
@Test
public void canBeAddedInvalidDates() {
AvailabilityTimeLine timeLine = AvailabilityTimeLine.allValid();
timeLine.invalidAt(earlyExample);
assertFalse(timeLine.isValid(earlyExample));
assertTrue(timeLine.isValid(contemporaryExample));
assertTrue(timeLine.isValid(lateExample));
}
@Test
public void canBeAddedInvalidIntervals() {
AvailabilityTimeLine timeLine = AvailabilityTimeLine.allValid();
LocalDate intervalStart = contemporaryExample.minusDays(10);
LocalDate intervalEnd = contemporaryExample.plusDays(5);
timeLine.invalidAt(intervalStart, intervalEnd);
assertFalse("the start is inclusive", timeLine.isValid(intervalStart));
assertFalse(timeLine.isValid(contemporaryExample.minusDays(1)));
assertFalse(timeLine.isValid(contemporaryExample));
assertFalse(timeLine.isValid(contemporaryExample.plusDays(1)));
assertTrue("the end is exclusive", timeLine.isValid(intervalEnd));
}
@Test
public void addingAnIntervalThatIsCompletelyInvalidIsIgnored() {
AvailabilityTimeLine timeLine = AvailabilityTimeLine.allValid();
LocalDate intervalStart = contemporaryExample.minusDays(10);
LocalDate intervalEnd = contemporaryExample.plusDays(5);
timeLine.invalidAt(intervalStart, intervalEnd);
timeLine.invalidAt(intervalStart.plusDays(2), intervalEnd.minusDays(2));
assertFalse(timeLine.isValid(intervalEnd.minusDays(1)));
}
@Test
public void addingAnIntervalThatItsNotCompletelyInvalid() {
AvailabilityTimeLine timeLine = AvailabilityTimeLine.allValid();
LocalDate intervalStart = contemporaryExample.minusDays(10);
LocalDate intervalEnd = contemporaryExample.plusDays(5);
timeLine.invalidAt(intervalStart, intervalEnd);
timeLine.invalidAt(intervalStart.minusDays(3), intervalEnd.plusDays(4));
assertFalse(timeLine.isValid(intervalStart.minusDays(3)));
assertFalse(timeLine.isValid(intervalEnd));
assertFalse(timeLine.isValid(intervalEnd.plusDays(3)));
assertTrue(timeLine.isValid(intervalEnd.plusDays(4)));
}
@Test
public void addingAnIntervalThatJoinsTwoInvalidIntervals() {
AvailabilityTimeLine timeLine = AvailabilityTimeLine.allValid();
LocalDate intervalStart = contemporaryExample.minusDays(10);
LocalDate intervalEnd = contemporaryExample.plusDays(5);
timeLine.invalidAt(intervalStart, intervalEnd);
timeLine.invalidAt(intervalStart.minusDays(20), intervalStart
.minusDays(10));
timeLine.invalidAt(intervalStart.minusDays(10), intervalStart);
LocalDate current = intervalStart.minusDays(20);
while (current.isBefore(intervalEnd)) {
assertFalse(timeLine.isValid(current));
current = current.plusDays(1);
}
}
@Test
public void pointsAreMergedCorrectly() {
AvailabilityTimeLine timeLine = AvailabilityTimeLine.allValid();
timeLine.invalidAt(new LocalDate(2010, 4, 7));
timeLine.invalidAt(new LocalDate(2010, 4, 11));
timeLine.invalidAt(new LocalDate(2010, 4, 8));
timeLine.invalidAt(new LocalDate(2010, 4, 6));
assertFalse(timeLine.isValid(new LocalDate(2010, 4, 8)));
}
@Test(expected = IllegalArgumentException.class)
public void endMustBeAfterStart() {
AvailabilityTimeLine timeLine = AvailabilityTimeLine.allValid();
LocalDate intervalStart = contemporaryExample.minusDays(10);
LocalDate intervalEnd = contemporaryExample.plusDays(5);
timeLine.invalidAt(intervalEnd, intervalStart);
}
@Test
public void addingFromInterval() {
AvailabilityTimeLine timeLine = AvailabilityTimeLine.allValid();
timeLine.invalidFrom(contemporaryExample);
assertFalse(timeLine.isValid(contemporaryExample));
assertFalse(timeLine.isValid(contemporaryExample.plusDays(10)));
assertFalse(timeLine.isValid(lateExample));
assertTrue(timeLine.isValid(contemporaryExample.minusDays(1)));
}
@Test
public void addingToInterval() {
AvailabilityTimeLine timeLine = AvailabilityTimeLine.allValid();
timeLine.invalidUntil(contemporaryExample);
assertTrue(timeLine.isValid(contemporaryExample));
assertTrue(timeLine.isValid(contemporaryExample.plusDays(1)));
assertFalse(timeLine.isValid(contemporaryExample.minusDays(1)));
assertFalse(timeLine.isValid(earlyExample));
}
@Test
public void addingAndAlreadyIncludedIntervalToAFromIntervalDoesNothing() {
AvailabilityTimeLine timeLine = AvailabilityTimeLine.allValid();
timeLine.invalidFrom(contemporaryExample);
timeLine.invalidAt(contemporaryExample.plusDays(30),
contemporaryExample.plusDays(100));
assertFalse(timeLine.isValid(contemporaryExample));
assertFalse(timeLine.isValid(contemporaryExample.plusDays(10)));
assertFalse(timeLine.isValid(lateExample));
assertTrue(timeLine.isValid(contemporaryExample.minusDays(1)));
}
@Test
public void addingAllInvalidMakesAllInvalid() {
AvailabilityTimeLine timeLine = AvailabilityTimeLine.allValid();
timeLine.invalidFrom(contemporaryExample);
timeLine.invalidUntil(contemporaryExample.minusDays(10));
assertFalse(timeLine.isValid(earlyExample));
assertTrue(timeLine.isValid(contemporaryExample.minusDays(10)));
assertFalse(timeLine.isValid(contemporaryExample));
assertFalse(timeLine.isValid(lateExample));
timeLine.allInvalid();
assertFalse(timeLine.isValid(earlyExample));
assertFalse(timeLine.isValid(contemporaryExample.minusDays(10)));
assertFalse(timeLine.isValid(contemporaryExample));
assertFalse(timeLine.isValid(lateExample));
}
@Test
public void addingSeveralTypesOfIntervals() {
AvailabilityTimeLine timeLine = AvailabilityTimeLine.allValid();
timeLine.invalidFrom(contemporaryExample);
timeLine.invalidUntil(contemporaryExample.minusDays(10));
assertFalse(timeLine.isValid(earlyExample));
assertTrue(timeLine.isValid(contemporaryExample.minusDays(10)));
assertFalse(timeLine.isValid(contemporaryExample));
assertFalse(timeLine.isValid(lateExample));
timeLine.invalidAt(contemporaryExample.minusDays(10),
contemporaryExample);
assertFalse(timeLine.isValid(contemporaryExample));
assertFalse(timeLine.isValid(contemporaryExample.plusDays(1)));
assertFalse(timeLine.isValid(contemporaryExample.minusDays(1)));
assertFalse(timeLine.isValid(earlyExample));
}
@Test
public void addingTwoAvailabilityTimeLines() {
AvailabilityTimeLine timeLine = AvailabilityTimeLine.allValid();
timeLine.invalidAt(contemporaryExample.minusDays(10),
contemporaryExample.plusDays(10));
AvailabilityTimeLine another = AvailabilityTimeLine.allValid();
another.invalidAt(contemporaryExample.minusDays(40),
contemporaryExample.minusDays(20));
AvailabilityTimeLine result = timeLine.and(another);
LocalDate current = contemporaryExample.minusDays(40);
LocalDate end = contemporaryExample.plusDays(20);
while (current.compareTo(end) < 0) {
assertEquals("must give the same value for: " + current, timeLine
.isValid(current)
&& another.isValid(current), result.isValid(current));
current = current.plusDays(1);
}
}
@Test
public void doingOROnTwoTimeLinesResultingOnAnAllValidTimeLine() {
AvailabilityTimeLine one = AvailabilityTimeLine.allValid();
one.invalidAt(contemporaryExample.minusDays(40), contemporaryExample
.minusDays(20));
AvailabilityTimeLine another = AvailabilityTimeLine.allValid();
another.invalidAt(contemporaryExample.minusDays(10),
contemporaryExample.plusDays(10));
AvailabilityTimeLine result = one.or(another);
assertThat(result.getValidPeriods(), definedBy(StartOfTime.create(),
EndOfTime.create()));
}
@Test
public void doingORonTwoTimeLinesWithSeveralIntersectingInvalidPeriods() {
AvailabilityTimeLine one = AvailabilityTimeLine.allValid();
one.invalidAt(contemporaryExample.minusDays(40), contemporaryExample
.minusDays(20));
one.invalidAt(contemporaryExample.plusDays(35), contemporaryExample
.plusDays(50));
AvailabilityTimeLine another = AvailabilityTimeLine.allValid();
another.invalidAt(contemporaryExample.minusDays(25),
contemporaryExample.plusDays(10));
another.invalidAt(contemporaryExample.plusDays(30), contemporaryExample
.plusDays(40));
AvailabilityTimeLine result = one.or(another);
assertThat(result.getValidPeriods(), definedBy(StartOfTime.create(),
point(contemporaryExample.minusDays(25)),
point(contemporaryExample.minusDays(20)),
point(contemporaryExample.plusDays(35)),
point(contemporaryExample.plusDays(40)), EndOfTime.create()));
}
@Test
public void doingOROnTheSameTimeLineResultsInTheSameTimeLine() {
AvailabilityTimeLine timeLine = AvailabilityTimeLine.allValid();
timeLine.invalidAt(earlyExample, contemporaryExample);
timeLine.invalidAt(lateExample, lateExample.plusDays(20));
AvailabilityTimeLine result = timeLine.or(timeLine);
assertThat(result.getValidPeriods(), definedBy(StartOfTime.create(),
point(earlyExample), point(contemporaryExample),
point(lateExample), point(lateExample
.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();
timeLine.invalidAt(earlyExample, contemporaryExample);
timeLine.invalidAt(lateExample, lateExample.plusDays(20));
AvailabilityTimeLine result = timeLine.and(AvailabilityTimeLine
.allValid());
assertThat(result.getValidPeriods(), definedBy(StartOfTime.create(),
point(earlyExample), point(contemporaryExample),
point(lateExample), point(lateExample
.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();
timeLine.invalidAt(earlyExample, contemporaryExample);
timeLine.invalidAt(lateExample, lateExample.plusDays(20));
AvailabilityTimeLine another = AvailabilityTimeLine.allValid();
another.allInvalid();
AvailabilityTimeLine result = timeLine.and(another);
assertThat(result.getValidPeriods(), definedBy(StartOfTime.create(),
point(earlyExample), point(contemporaryExample),
point(lateExample), point(lateExample.plusDays(20)), EndOfTime
.create()));
}
@Test
public void anAllValidPeriodsGeneratesAnAllEncompassingInterval() {
AvailabilityTimeLine timeLine = AvailabilityTimeLine.allValid();
List<Interval> validPeriods = timeLine.getValidPeriods();
assertThat(validPeriods, definedBy(StartOfTime.create(), EndOfTime
.create()));
}
@Test
public void anInvalidPeriodUntilGeneratesAValidIntervalAfterwards() {
AvailabilityTimeLine timeLine = AvailabilityTimeLine.allValid();
timeLine.invalidUntil(contemporaryExample);
List<Interval> validPeriods = timeLine.getValidPeriods();
assertThat(validPeriods, definedBy(point(contemporaryExample),
EndOfTime.create()));
}
@Test
public void anInvalidFromPeriodGeneratesAValidIntervalBefore() {
AvailabilityTimeLine timeLine = AvailabilityTimeLine.allValid();
timeLine.invalidFrom(contemporaryExample);
List<Interval> validPeriods = timeLine.getValidPeriods();
assertThat(validPeriods, definedBy(StartOfTime.create(),
new FixedPoint(contemporaryExample)));
}
@Test
public void anInvalidityPeriodGeneratesTwoValidIntervals() {
AvailabilityTimeLine timeLine = AvailabilityTimeLine.allValid();
timeLine.invalidAt(contemporaryExample, lateExample);
List<Interval> validPeriods = timeLine.getValidPeriods();
assertThat(validPeriods, definedBy(StartOfTime.create(),
point(contemporaryExample), point(lateExample), EndOfTime
.create()));
}
@Test
public void anAllInvalidTimelineGeneratesZeroValidIntervals() {
AvailabilityTimeLine timeLine = AvailabilityTimeLine.allValid();
timeLine.allInvalid();
assertTrue(timeLine.getValidPeriods().isEmpty());
}
@Test
public void anInvalidPointGeneratesTwoValidPeriods() {
AvailabilityTimeLine timeLine = AvailabilityTimeLine.allValid();
timeLine.invalidAt(contemporaryExample);
assertThat(timeLine.getValidPeriods(), definedBy(StartOfTime.create(),
point(contemporaryExample), point(contemporaryExample
.plusDays(1)), EndOfTime.create()));
}
private static FixedPoint point(LocalDate param) {
return new FixedPoint(param);
}
@Test
public void aCombinationOfSeveralInvalidPeriods() {
AvailabilityTimeLine timeLine = AvailabilityTimeLine.allValid();
timeLine.invalidUntil(earlyExample);
timeLine.invalidAt(contemporaryExample, lateExample);
timeLine.invalidFrom(lateExample.plusDays(10));
assertThat(timeLine.getValidPeriods(), definedBy(point(earlyExample),
point(contemporaryExample), point(lateExample),
point(lateExample.plusDays(10))));
}
private static Matcher<List<Interval>> definedBy(final DatePoint... points) {
Validate.isTrue(points.length % 2 == 0,
"number of points provided must be even");
return new BaseMatcher<List<Interval>>() {
@SuppressWarnings("unchecked")
@Override
public boolean matches(Object object) {
if (object instanceof List) {
List<Interval> intervals = (List<Interval>) object;
List<DatePoint[]> pairsOfPoints = pointsAsPairs();
for (int i = 0; i < intervals.size(); i++) {
Interval interval = intervals.get(i);
DatePoint[] pair = pairsOfPoints.get(i);
if (!(pair[0].equals(interval.getStart()) && pair[1]
.equals(interval.getEnd()))) {
return false;
}
}
return true;
}
return false;
}
private List<DatePoint[]> pointsAsPairs() {
List<DatePoint[]> result = new ArrayList<DatePoint[]>();
for (int i = 0; i < points.length / 2; i++) {
DatePoint[] pair = { points[i * 2], points[i * 2 + 1] };
result.add(pair);
}
return result;
}
@Override
public void describeTo(Description description) {
StringBuilder text = new StringBuilder();
boolean first = true;
for (DatePoint[] each : pointsAsPairs()) {
DatePoint start = each[0];
DatePoint end = each[1];
if (!first) {
text.append(", ");
}
text.append(String.format("[%s, %s]", start, end));
first = false;
}
description.appendText(text.toString());
}
};
}
}