ItEr49S04ValidacionEProbasFuncionaisItEr48S04: Adding ValidityTimeLine and associated class.
This commit is contained in:
parent
3fe819dafa
commit
b681df269f
2 changed files with 576 additions and 0 deletions
|
|
@ -0,0 +1,393 @@
|
|||
/*
|
||||
* This file is part of ###PROJECT_NAME###
|
||||
*
|
||||
* 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.calendars.entities;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
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 AvailabilityTimeLine {
|
||||
|
||||
private static abstract class DatePoint implements Comparable<DatePoint> {
|
||||
|
||||
protected abstract int compareTo(FixedPoint fixedPoint);
|
||||
|
||||
protected abstract int compareTo(EndOfTime endOfTime);
|
||||
|
||||
protected abstract int compareTo(StartOfTime startOfTime);
|
||||
|
||||
protected abstract boolean equalTo(FixedPoint fixedPoint);
|
||||
|
||||
protected abstract boolean equalTo(EndOfTime endOfTime);
|
||||
|
||||
protected abstract boolean equalTo(StartOfTime startOfTime);
|
||||
|
||||
@Override
|
||||
public final int compareTo(DatePoint obj) {
|
||||
Validate.notNull(obj);
|
||||
if (obj instanceof FixedPoint) {
|
||||
return compareTo((FixedPoint) obj);
|
||||
} else if (obj instanceof EndOfTime) {
|
||||
return compareTo((EndOfTime) obj);
|
||||
} else if (obj instanceof StartOfTime) {
|
||||
return compareTo((StartOfTime) obj);
|
||||
} else {
|
||||
throw new RuntimeException("unknown subclass for " + obj);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract int hashCode();
|
||||
|
||||
@Override
|
||||
public final boolean equals(Object obj) {
|
||||
if (!(obj instanceof DatePoint)) {
|
||||
return false;
|
||||
}
|
||||
if (obj instanceof FixedPoint) {
|
||||
return equalTo((FixedPoint) obj);
|
||||
} else if (obj instanceof EndOfTime) {
|
||||
return equalTo((EndOfTime) obj);
|
||||
} else if (obj instanceof StartOfTime) {
|
||||
return equalTo((StartOfTime) obj);
|
||||
} else {
|
||||
throw new RuntimeException("unknown subclass for " + obj);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class FixedPoint extends DatePoint {
|
||||
private final LocalDate date;
|
||||
|
||||
private FixedPoint(LocalDate date) {
|
||||
Validate.notNull(date);
|
||||
this.date = date;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int compareTo(FixedPoint fixedPoint) {
|
||||
return this.date.compareTo(fixedPoint.date);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int compareTo(EndOfTime endOfTime) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int compareTo(StartOfTime startOfTime) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean equalTo(FixedPoint fixedPoint) {
|
||||
return date.equals(fixedPoint.date);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean equalTo(EndOfTime endOfTime) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean equalTo(StartOfTime startOfTime) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return date.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
private static class EndOfTime extends DatePoint {
|
||||
private static final EndOfTime INSTANCE = new EndOfTime();
|
||||
|
||||
public static EndOfTime create() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int compareTo(FixedPoint fixedPoint) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int compareTo(EndOfTime endOfTime) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int compareTo(StartOfTime startOfTime) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean equalTo(FixedPoint fixedPoint) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean equalTo(EndOfTime endOfTime) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean equalTo(StartOfTime startOfTime) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return EndOfTime.class.hashCode();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class StartOfTime extends DatePoint {
|
||||
private static final StartOfTime INSTANCE = new StartOfTime();
|
||||
|
||||
public static StartOfTime create() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int compareTo(FixedPoint fixedPoint) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int compareTo(EndOfTime endOfTime) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int compareTo(StartOfTime startOfTime) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean equalTo(FixedPoint fixedPoint) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean equalTo(EndOfTime endOfTime) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean equalTo(StartOfTime startOfTime) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return StartOfTime.class.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
private static class Interval implements
|
||||
Comparable<Interval> {
|
||||
|
||||
static Interval create(LocalDate start, LocalDate end) {
|
||||
return new Interval(new FixedPoint(start), new FixedPoint(
|
||||
end));
|
||||
}
|
||||
|
||||
static Interval from(LocalDate date) {
|
||||
return new Interval(new FixedPoint(date), EndOfTime.create());
|
||||
}
|
||||
|
||||
public static Interval to(LocalDate date) {
|
||||
return new Interval(StartOfTime.create(), new FixedPoint(
|
||||
date));
|
||||
}
|
||||
|
||||
static Interval point(LocalDate start) {
|
||||
return new Interval(new FixedPoint(start), new FixedPoint(start
|
||||
.plusDays(1)));
|
||||
}
|
||||
|
||||
private final DatePoint start;
|
||||
|
||||
private final DatePoint end;
|
||||
|
||||
private Interval(DatePoint start, DatePoint end) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Interval other) {
|
||||
return this.start.compareTo(other.start) * 2
|
||||
- this.end.compareTo(other.end);
|
||||
}
|
||||
|
||||
public boolean includes(LocalDate date) {
|
||||
return includes(new FixedPoint(date));
|
||||
}
|
||||
|
||||
private boolean includes(FixedPoint point) {
|
||||
return start.equals(point) || start.compareTo(point) <= 0
|
||||
&& point.compareTo(end) < 0;
|
||||
}
|
||||
|
||||
public boolean overlaps(Interval other) {
|
||||
return start.compareTo(other.end) <= 0
|
||||
&& end.compareTo(other.start) >= 0;
|
||||
}
|
||||
|
||||
public Interval coalesce(Interval other) {
|
||||
if (!overlaps(other)) {
|
||||
throw new IllegalArgumentException(
|
||||
"in order to coalesce two intervals must overlap");
|
||||
}
|
||||
return new Interval(min(start, other.start), max(end,
|
||||
other.end));
|
||||
}
|
||||
|
||||
private DatePoint min(DatePoint... values) {
|
||||
return (DatePoint) Collections.min(Arrays.asList(values));
|
||||
}
|
||||
|
||||
private DatePoint max(DatePoint... values) {
|
||||
return (DatePoint) Collections.max(Arrays.asList(values));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static AvailabilityTimeLine allValid() {
|
||||
return new AvailabilityTimeLine();
|
||||
}
|
||||
|
||||
private List<Interval> invalids = new ArrayList<Interval>();
|
||||
|
||||
private AvailabilityTimeLine() {
|
||||
}
|
||||
|
||||
public boolean isValid(LocalDate date) {
|
||||
if (invalids.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
Interval point = Interval.point(date);
|
||||
int binarySearch = Collections.binarySearch(invalids, point);
|
||||
if (binarySearch >= 0) {
|
||||
Interval interval = invalids.get(binarySearch);
|
||||
return !interval.includes(date);
|
||||
} else {
|
||||
int insertionPoint = insertionPoint(binarySearch);
|
||||
if (insertionPoint == 0) {
|
||||
return true;
|
||||
}
|
||||
Interval interval = invalids
|
||||
.get(insertionPoint - 1);
|
||||
return !interval.includes(date);
|
||||
}
|
||||
}
|
||||
|
||||
public void invalidAt(LocalDate date) {
|
||||
Interval point = Interval.point(date);
|
||||
insert(point);
|
||||
}
|
||||
|
||||
private void insert(Interval toBeInserted) {
|
||||
int binarySearch = Collections.binarySearch(invalids, toBeInserted);
|
||||
if (invalids.isEmpty()) {
|
||||
invalids.add(toBeInserted);
|
||||
return;
|
||||
}
|
||||
toBeInserted = coalesceWithAdjacent(insertionPoint(binarySearch),
|
||||
toBeInserted);
|
||||
int insertionPoint = insertBeforeAllAdjacent(toBeInserted);
|
||||
removeAdjacent(insertionPoint, toBeInserted);
|
||||
}
|
||||
|
||||
private int insertBeforeAllAdjacent(Interval toBeInserted) {
|
||||
int n = Collections.binarySearch(invalids, toBeInserted);
|
||||
int insertionPoint = insertionPoint(n);
|
||||
invalids.add(insertionPoint, toBeInserted);
|
||||
return insertionPoint;
|
||||
}
|
||||
|
||||
public Interval coalesceWithAdjacent(int insertionPoint,
|
||||
Interval toBeInserted) {
|
||||
Interval result = toBeInserted;
|
||||
for (int i = insertionPoint; i >= 0
|
||||
&& (i == invalids.size() || at(i).overlaps(
|
||||
toBeInserted)); i--) {
|
||||
if (i < invalids.size()) {
|
||||
result = result.coalesce(at(i));
|
||||
}
|
||||
}
|
||||
for (int i = insertionPoint; i < invalids.size()
|
||||
&& at(i).overlaps(toBeInserted); i++) {
|
||||
result = result.coalesce(at(i));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void removeAdjacent(int insertionPoint, Interval inserted) {
|
||||
ListIterator<Interval> listIterator = invalids
|
||||
.listIterator(insertionPoint + 1);
|
||||
while (listIterator.hasNext()) {
|
||||
Interval next = listIterator.next();
|
||||
if (!next.overlaps(inserted)) {
|
||||
break;
|
||||
}
|
||||
listIterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
private Interval at(int i) {
|
||||
return i >= 0 && i < invalids.size() ? invalids.get(i) : null;
|
||||
}
|
||||
|
||||
private int insertionPoint(int binarySearchResult) {
|
||||
return binarySearchResult < 0 ? (-binarySearchResult) - 1
|
||||
: binarySearchResult;
|
||||
}
|
||||
|
||||
public void invalidAt(LocalDate intervalStart, LocalDate intervalEnd) {
|
||||
if (intervalStart.isAfter(intervalEnd)) {
|
||||
throw new IllegalArgumentException(
|
||||
"end must be equal or after start");
|
||||
}
|
||||
insert(Interval.create(intervalStart, intervalEnd));
|
||||
}
|
||||
|
||||
public void invalidFrom(LocalDate date) {
|
||||
insert(Interval.from(date));
|
||||
}
|
||||
|
||||
public void invalidUntil(LocalDate date) {
|
||||
insert(Interval.to(date));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
/*
|
||||
* This file is part of ###PROJECT_NAME###
|
||||
*
|
||||
* 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.test.calendars.entities;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.junit.Test;
|
||||
import org.navalplanner.business.calendars.entities.AvailabilityTimeLine;
|
||||
|
||||
/**
|
||||
* @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(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 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));
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue