Implement ResourceLoadChartData using ContiguousDaysLine

FEA: ItEr75S11PreventLooseChanges
This commit is contained in:
Óscar González Fernández 2011-08-03 17:01:46 +02:00
parent 0aa872e1cb
commit d083daecba
3 changed files with 316 additions and 166 deletions

View file

@ -18,15 +18,23 @@
*/
package org.navalplanner.business.planner.chart;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.SortedMap;
import java.util.TreeMap;
import org.apache.commons.lang.Validate;
import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.navalplanner.business.planner.chart.ContiguousDaysLine.ONDay;
import org.navalplanner.business.planner.entities.DayAssignment;
import org.navalplanner.business.workingday.EffortDuration;
/**
* It represents some contiguous days from a start date to a not included end
@ -75,12 +83,154 @@ public class ContiguousDaysLine<T> implements Iterable<ONDay<T>> {
return new ContiguousDaysLine<T>(fromInclusive, daysBetween.getDays());
}
public static ContiguousDaysLine<List<DayAssignment>> byDay(
Collection<? extends DayAssignment> assignments) {
if (assignments.isEmpty()) {
return invalid();
}
DayAssignment min = Collections.min(assignments,
DayAssignment.byDayComparator());
DayAssignment max = Collections.max(assignments,
DayAssignment.byDayComparator());
ContiguousDaysLine<List<DayAssignment>> result = create(min.getDay(),
max.getDay().plusDays(1));
result.transformInSitu(new IValueTransformer<List<DayAssignment>, List<DayAssignment>>() {
@Override
public List<DayAssignment> transform(LocalDate day,
List<DayAssignment> previousValue) {
return new LinkedList<DayAssignment>();
}
});
for (DayAssignment each : assignments) {
result.get(each.getDay()).add(each);
}
return result;
}
public static <T> ContiguousDaysLine<T> invalid() {
return new ContiguousDaysLine<T>(null, 0);
}
@SuppressWarnings("unchecked")
public static ContiguousDaysLine<EffortDuration> min(
ContiguousDaysLine<EffortDuration> a,
ContiguousDaysLine<EffortDuration> b) {
return join(EffortDuration.class, minTransformer(), a, b);
}
public static IValueTransformer<EffortDuration[], EffortDuration> minTransformer() {
return new IValueTransformer<EffortDuration[], EffortDuration>() {
@Override
public EffortDuration transform(LocalDate day,
EffortDuration[] previousValue) {
return EffortDuration.min(previousValue);
}
};
}
/**
* Joins both {@link ContiguousDaysLine} substracting from minuend line. An
* effortDuration can't be negative so, if subtrahend line is at some point
* bigger than minuend, zero is returned at that point.
*
* @param minuend
* @param subtrahend
* @return
*/
@SuppressWarnings("unchecked")
public static ContiguousDaysLine<EffortDuration> substract(
ContiguousDaysLine<EffortDuration> minuend,
ContiguousDaysLine<EffortDuration> subtrahend) {
return join(EffortDuration.class, substractTransformer(), minuend,
subtrahend);
}
private static IValueTransformer<EffortDuration[], EffortDuration> substractTransformer() {
return new IValueTransformer<EffortDuration[], EffortDuration>() {
@Override
public EffortDuration transform(LocalDate day,
EffortDuration[] previousValue) {
EffortDuration result = previousValue[0];
for (int i = 1; i < previousValue.length; i++) {
result = result.minus(EffortDuration.min(previousValue[i],
result));
}
return result;
}
};
}
@SuppressWarnings("unchecked")
public static ContiguousDaysLine<EffortDuration> sum(
ContiguousDaysLine<EffortDuration> summandA,
ContiguousDaysLine<EffortDuration> summandB) {
return join(EffortDuration.class, additionTransformer(), summandA,
summandB);
}
public static SortedMap<LocalDate, EffortDuration> toSortedMap(
ContiguousDaysLine<EffortDuration> line) {
SortedMap<LocalDate, EffortDuration> result = new TreeMap<LocalDate, EffortDuration>();
for (ONDay<EffortDuration> each : line) {
result.put(each.getDay(), each.getValue());
}
return result;
}
private static IValueTransformer<EffortDuration[], EffortDuration> additionTransformer() {
return new IValueTransformer<EffortDuration[], EffortDuration>() {
@Override
public EffortDuration transform(LocalDate day,
EffortDuration[] previousValue) {
return EffortDuration.sum(previousValue);
}
};
}
public static <T, R> ContiguousDaysLine<R> join(final Class<T> klass,
final IValueTransformer<T[], R> transformer,
final ContiguousDaysLine<T>... lines) {
if (lines[0].isNotValid()) {
return invalid();
}
LocalDate start = lines[0].getStart();
LocalDate endExclusive = lines[0].getEndExclusive();
for (ContiguousDaysLine<T> each : lines) {
Validate.isTrue(each.getStart().equals(start),
"the start of all lines must be same date");
Validate.isTrue(each.getEndExclusive().equals(endExclusive),
"the start of all lines must be same date");
}
ContiguousDaysLine<R> result = ContiguousDaysLine.create(start,
endExclusive);
result.transformInSitu(new IValueTransformer<R, R>() {
@Override
public R transform(LocalDate day, R previousValue) {
return transformer.transform(day, getValues(day, lines));
}
private T[] getValues(LocalDate day, ContiguousDaysLine<T>... lines) {
@SuppressWarnings("unchecked")
T[] result = (T[]) Array.newInstance(klass, lines.length);
for (int i = 0; i < result.length; i++) {
result[i] = lines[i].get(day);
}
return result;
}
});
return result;
}
private final LocalDate startInclusive;
private final List<T> values;
private ContiguousDaysLine(LocalDate start, int size) {
Validate.notNull(start);
this.startInclusive = start;
this.values = new ArrayList<T>(size);
for (int i = 0; i < size; i++) {
@ -88,12 +238,23 @@ public class ContiguousDaysLine<T> implements Iterable<ONDay<T>> {
}
}
public boolean isNotValid() {
return startInclusive == null;
}
public LocalDate getStart() {
mustBeValid();
return startInclusive;
}
private void mustBeValid() {
if (isNotValid()) {
throw new IllegalStateException("this line is invalid");
}
}
public LocalDate getEndExclusive() {
return startInclusive.plusDays(values.size());
return getStart().plusDays(values.size());
}
public T get(LocalDate day) throws IndexOutOfBoundsException {
@ -137,6 +298,9 @@ public class ContiguousDaysLine<T> implements Iterable<ONDay<T>> {
public <R> ContiguousDaysLine<R> transform(
IValueTransformer<T, R> doubleTransformer) {
if (isNotValid()) {
return invalid();
}
ContiguousDaysLine<R> result = ContiguousDaysLine.create(
startInclusive, getEndExclusive());
for (ONDay<T> onDay : this) {
@ -146,6 +310,34 @@ public class ContiguousDaysLine<T> implements Iterable<ONDay<T>> {
return result;
}
public ContiguousDaysLine<T> copy() {
return transform(ContiguousDaysLine.<T> identity());
}
private static <T> IValueTransformer<T, T> identity() {
return new IValueTransformer<T, T>() {
@Override
public T transform(LocalDate day, T previousValue) {
return previousValue;
}
};
}
public static <T, S, R> IValueTransformer<T, R> compound(
final IValueTransformer<T, S> first,
final IValueTransformer<S, R> last) {
return new IValueTransformer<T, R>() {
@Override
public R transform(LocalDate day, T previousValue) {
S intermediateValue = first.transform(day, previousValue);
return last.transform(day, intermediateValue);
}
};
}
@Override
public Iterator<ONDay<T>> iterator() {
final Iterator<T> iterator = values.iterator();

View file

@ -21,21 +21,23 @@
package org.navalplanner.business.planner.chart;
import static org.navalplanner.business.planner.chart.ContiguousDaysLine.compound;
import static org.navalplanner.business.planner.chart.ContiguousDaysLine.sum;
import static org.navalplanner.business.planner.chart.ContiguousDaysLine.toSortedMap;
import static org.navalplanner.business.workingday.EffortDuration.min;
import static org.navalplanner.business.workingday.EffortDuration.zero;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import org.joda.time.LocalDate;
import org.navalplanner.business.calendars.entities.ICalendar;
import org.navalplanner.business.hibernate.notification.PredefinedDatabaseSnapshots;
import org.navalplanner.business.planner.chart.ContiguousDaysLine.IValueTransformer;
import org.navalplanner.business.planner.entities.DayAssignment;
import org.navalplanner.business.resources.entities.Resource;
import org.navalplanner.business.workingday.EffortDuration;
@ -60,25 +62,113 @@ public class ResourceLoadChartData implements ILoadChartData {
private SortedMap<LocalDate, EffortDuration> availability;
public ResourceLoadChartData(List<DayAssignment> dayAssignments, List<Resource> resources) {
SortedMap<LocalDate, Map<Resource, EffortDuration>> map =
groupDurationsByDayAndResource(dayAssignments);
this.load = calculateResourceLoadPerDate(map);
this.overload = calculateResourceOverloadPerDate(map);
if(load.keySet().isEmpty()) {
this.availability = new TreeMap<LocalDate, EffortDuration>();
}
else {
this.availability = calculateAvailabilityDurationByDay(
resources, load.firstKey(), load.lastKey());
}
for (LocalDate day : this.overload.keySet()) {
EffortDuration overloadDuration = this.overload
.get(day);
EffortDuration maxDuration = this.availability.get(day);
this.overload.put(day,
overloadDuration.plus(maxDuration));
}
ContiguousDaysLine<List<DayAssignment>> assignments = ContiguousDaysLine
.byDay(dayAssignments);
ContiguousDaysLine<EffortDuration> load = assignments
.transform(extractLoad());
ContiguousDaysLine<EffortDuration> overload = assignments
.transform(extractOverload());
ContiguousDaysLine<EffortDuration> availabilityOnAllResources = assignments
.transform(extractAvailabilityOnAllResources(resources));
this.load = toSortedMap(ContiguousDaysLine.min(load,
availabilityOnAllResources));
this.overload = toSortedMap(sum(overload, availabilityOnAllResources));
this.availability = toSortedMap(availabilityOnAllResources);
}
private IValueTransformer<List<DayAssignment>, EffortDuration> extractOverload() {
return compound(effortByResource(), calculateOverload());
}
private IValueTransformer<List<DayAssignment>, Map<Resource, EffortDuration>> effortByResource() {
return new IValueTransformer<List<DayAssignment>, Map<Resource, EffortDuration>>() {
@Override
public Map<Resource, EffortDuration> transform(LocalDate day,
List<DayAssignment> previousValue) {
Map<Resource, List<DayAssignment>> byResource = DayAssignment
.byResource(previousValue);
Map<Resource, EffortDuration> result = new HashMap<Resource, EffortDuration>();
for (Entry<Resource, List<DayAssignment>> each : byResource
.entrySet()) {
result.put(each.getKey(),
DayAssignment.sum(each.getValue()));
}
return result;
}
};
}
private IValueTransformer<Map<Resource, EffortDuration>, EffortDuration> calculateOverload() {
return new IValueTransformer<Map<Resource, EffortDuration>, EffortDuration>() {
@Override
public EffortDuration transform(LocalDate day,
Map<Resource, EffortDuration> previousValue) {
final PartialDay wholeDay = PartialDay.wholeDay(day);
return EffortDuration.sum(previousValue.entrySet(),
new IEffortFrom<Entry<Resource, EffortDuration>>() {
@Override
public EffortDuration from(
Entry<Resource, EffortDuration> each) {
EffortDuration capacity = calendarCapacityFor(
each.getKey(), wholeDay);
EffortDuration assigned = each.getValue();
return assigned.minus(min(capacity, assigned));
}
});
}
};
}
private IValueTransformer<List<DayAssignment>, EffortDuration> extractLoad() {
return new IValueTransformer<List<DayAssignment>, EffortDuration>() {
@Override
public EffortDuration transform(LocalDate day,
List<DayAssignment> previousValue) {
return DayAssignment.sum(previousValue);
}
};
}
private IValueTransformer<List<DayAssignment>, EffortDuration> extractAvailabilityOnAssignedResources() {
return new IValueTransformer<List<DayAssignment>, EffortDuration>() {
@Override
public EffortDuration transform(LocalDate day,
List<DayAssignment> previousValue) {
Set<Resource> resources = getResources(previousValue);
return sumCalendarCapacitiesForDay(resources, day);
}
private Set<Resource> getResources(List<DayAssignment> assignments) {
Set<Resource> resources = new HashSet<Resource>();
for (DayAssignment dayAssignment : assignments) {
resources.add(dayAssignment.getResource());
}
return resources;
}
};
}
private IValueTransformer<List<DayAssignment>, EffortDuration> extractAvailabilityOnAllResources(
final List<Resource> resources) {
return new IValueTransformer<List<DayAssignment>, EffortDuration>() {
@Override
public EffortDuration transform(LocalDate day,
List<DayAssignment> previousValue) {
return sumCalendarCapacitiesForDay(resources, day);
}
};
}
public SortedMap<LocalDate, EffortDuration> getLoad() {
@ -130,140 +220,7 @@ public class ResourceLoadChartData implements ILoadChartData {
};
}
private SortedMap<LocalDate, Map<Resource, EffortDuration>> groupDurationsByDayAndResource(
List<DayAssignment> dayAssignments) {
SortedMap<LocalDate, Map<Resource, EffortDuration>> map =
new TreeMap<LocalDate, Map<Resource, EffortDuration>>();
for (DayAssignment dayAssignment : dayAssignments) {
final LocalDate day = dayAssignment.getDay();
final EffortDuration dayAssignmentDuration = dayAssignment
.getDuration();
Resource resource = dayAssignment.getResource();
if (map.get(day) == null) {
map.put(day, new HashMap<Resource, EffortDuration>());
}
Map<Resource, EffortDuration> forDay = map.get(day);
EffortDuration previousDuration = forDay.get(resource);
previousDuration = previousDuration != null ? previousDuration
: EffortDuration.zero();
forDay.put(dayAssignment.getResource(),
previousDuration.plus(dayAssignmentDuration));
}
return map;
}
private SortedMap<LocalDate, EffortDuration> calculateResourceLoadPerDate(
SortedMap<LocalDate, Map<Resource, EffortDuration>> durationsGrouped) {
SortedMap<LocalDate, EffortDuration> map = new TreeMap<LocalDate, EffortDuration>();
for (LocalDate date : durationsGrouped.keySet()) {
EffortDuration result = zero();
PartialDay day = PartialDay.wholeDay(date);
for (Resource resource : durationsGrouped.get(date).keySet()) {
ICalendar calendar = resource.getCalendarOrDefault();
EffortDuration workableTime = calendar.getCapacityOn(day);
EffortDuration assignedDuration = durationsGrouped.get(
day.getDate()).get(resource);
result = result.plus(min(assignedDuration, workableTime));
}
map.put(date, result);
}
return map;
}
protected abstract class EffortByDayCalculator<T> {
public SortedMap<LocalDate, EffortDuration> calculate(
Collection<? extends T> elements) {
SortedMap<LocalDate, EffortDuration> result = new TreeMap<LocalDate, EffortDuration>();
if (elements.isEmpty()) {
return result;
}
for (T element : elements) {
if (included(element)) {
EffortDuration duration = getDurationFor(element);
LocalDate day = getDayFor(element);
EffortDuration previous = result.get(day);
previous = previous == null ? zero() : previous;
result.put(day, previous.plus(duration));
}
}
return result;
}
protected abstract LocalDate getDayFor(T element);
protected abstract EffortDuration getDurationFor(T element);
protected boolean included(T each) {
return true;
}
}
private SortedMap<LocalDate, EffortDuration> calculateResourceOverloadPerDate(
SortedMap<LocalDate, Map<Resource, EffortDuration>> dayAssignmentGrouped) {
return new EffortByDayCalculator<Entry<LocalDate, Map<Resource, EffortDuration>>>() {
@Override
protected LocalDate getDayFor(
Entry<LocalDate, Map<Resource, EffortDuration>> element) {
return element.getKey();
}
@Override
protected EffortDuration getDurationFor(
Entry<LocalDate, Map<Resource, EffortDuration>> element) {
final PartialDay day = PartialDay.wholeDay(element.getKey());
return EffortDuration.sum(element.getValue().entrySet(),
new IEffortFrom<Entry<Resource, EffortDuration>>() {
@Override
public EffortDuration from(
Entry<Resource, EffortDuration> each) {
EffortDuration overload = getOverloadAt(day,
each.getKey(), each.getValue());
return overload;
}
});
}
private EffortDuration getOverloadAt(PartialDay day,
Resource resource, EffortDuration assignedDuration) {
ICalendar calendar = resource.getCalendarOrDefault();
EffortDuration workableDuration = calendar
.getCapacityOn(day);
if (assignedDuration.compareTo(workableDuration) > 0) {
return assignedDuration.minus(workableDuration);
}
return zero();
}
}.calculate(dayAssignmentGrouped.entrySet());
}
private SortedMap<LocalDate, EffortDuration> calculateAvailabilityDurationByDay(
final List<Resource> resources, LocalDate start, LocalDate finish) {
return new EffortByDayCalculator<Entry<LocalDate, List<Resource>>>() {
@Override
protected LocalDate getDayFor(
Entry<LocalDate, List<Resource>> element) {
return element.getKey();
}
@Override
protected EffortDuration getDurationFor(
Entry<LocalDate, List<Resource>> element) {
LocalDate day = element.getKey();
return sumCalendarCapacitiesForDay(resources, day);
}
}.calculate(getResourcesByDateBetween(resources, start, finish));
}
protected static EffortDuration sumCalendarCapacitiesForDay(
private static EffortDuration sumCalendarCapacitiesForDay(
Collection<? extends Resource> resources, LocalDate day) {
final PartialDay wholeDay = PartialDay.wholeDay(day);
@ -281,13 +238,4 @@ public class ResourceLoadChartData implements ILoadChartData {
return resource.getCalendarOrDefault().getCapacityOn(day);
}
private Set<Entry<LocalDate, List<Resource>>> getResourcesByDateBetween(
List<Resource> resources, LocalDate start, LocalDate finish) {
Map<LocalDate, List<Resource>> result = new HashMap<LocalDate, List<Resource>>();
for (LocalDate date = new LocalDate(start); date.compareTo(finish) <= 0; date = date
.plusDays(1)) {
result.put(date, resources);
}
return result.entrySet();
}
}

View file

@ -130,6 +130,16 @@ public class EffortDuration implements Comparable<EffortDuration> {
return result;
}
public static EffortDuration sum(EffortDuration... summands) {
return sum(Arrays.asList(summands), new IEffortFrom<EffortDuration>() {
@Override
public EffortDuration from(EffortDuration each) {
return each;
}
});
}
public static EffortDuration zero() {
return elapsing(0, Granularity.SECONDS);
}