diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/planner/chart/ContiguousDaysLine.java b/navalplanner-business/src/main/java/org/navalplanner/business/planner/chart/ContiguousDaysLine.java index 7940cd3ab..9b606916f 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/planner/chart/ContiguousDaysLine.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/planner/chart/ContiguousDaysLine.java @@ -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 implements Iterable> { return new ContiguousDaysLine(fromInclusive, daysBetween.getDays()); } + public static ContiguousDaysLine> byDay( + Collection assignments) { + if (assignments.isEmpty()) { + return invalid(); + } + DayAssignment min = Collections.min(assignments, + DayAssignment.byDayComparator()); + DayAssignment max = Collections.max(assignments, + DayAssignment.byDayComparator()); + ContiguousDaysLine> result = create(min.getDay(), + max.getDay().plusDays(1)); + result.transformInSitu(new IValueTransformer, List>() { + + @Override + public List transform(LocalDate day, + List previousValue) { + return new LinkedList(); + } + }); + for (DayAssignment each : assignments) { + result.get(each.getDay()).add(each); + } + return result; + } + + public static ContiguousDaysLine invalid() { + return new ContiguousDaysLine(null, 0); + } + + @SuppressWarnings("unchecked") + public static ContiguousDaysLine min( + ContiguousDaysLine a, + ContiguousDaysLine b) { + return join(EffortDuration.class, minTransformer(), a, b); + } + + public static IValueTransformer minTransformer() { + return new IValueTransformer() { + + @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 substract( + ContiguousDaysLine minuend, + ContiguousDaysLine subtrahend) { + return join(EffortDuration.class, substractTransformer(), minuend, + subtrahend); + } + + private static IValueTransformer substractTransformer() { + return new IValueTransformer() { + + @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 sum( + ContiguousDaysLine summandA, + ContiguousDaysLine summandB) { + return join(EffortDuration.class, additionTransformer(), summandA, + summandB); + } + + public static SortedMap toSortedMap( + ContiguousDaysLine line) { + SortedMap result = new TreeMap(); + for (ONDay each : line) { + result.put(each.getDay(), each.getValue()); + } + return result; + } + + private static IValueTransformer additionTransformer() { + return new IValueTransformer() { + + @Override + public EffortDuration transform(LocalDate day, + EffortDuration[] previousValue) { + return EffortDuration.sum(previousValue); + } + }; + } + + public static ContiguousDaysLine join(final Class klass, + final IValueTransformer transformer, + final ContiguousDaysLine... lines) { + if (lines[0].isNotValid()) { + return invalid(); + } + LocalDate start = lines[0].getStart(); + LocalDate endExclusive = lines[0].getEndExclusive(); + for (ContiguousDaysLine 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 result = ContiguousDaysLine.create(start, + endExclusive); + result.transformInSitu(new IValueTransformer() { + + @Override + public R transform(LocalDate day, R previousValue) { + return transformer.transform(day, getValues(day, lines)); + } + + private T[] getValues(LocalDate day, ContiguousDaysLine... 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 values; private ContiguousDaysLine(LocalDate start, int size) { - Validate.notNull(start); this.startInclusive = start; this.values = new ArrayList(size); for (int i = 0; i < size; i++) { @@ -88,12 +238,23 @@ public class ContiguousDaysLine implements Iterable> { } } + 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 implements Iterable> { public ContiguousDaysLine transform( IValueTransformer doubleTransformer) { + if (isNotValid()) { + return invalid(); + } ContiguousDaysLine result = ContiguousDaysLine.create( startInclusive, getEndExclusive()); for (ONDay onDay : this) { @@ -146,6 +310,34 @@ public class ContiguousDaysLine implements Iterable> { return result; } + public ContiguousDaysLine copy() { + return transform(ContiguousDaysLine. identity()); + } + + private static IValueTransformer identity() { + return new IValueTransformer() { + + @Override + public T transform(LocalDate day, T previousValue) { + return previousValue; + } + }; + } + + public static IValueTransformer compound( + final IValueTransformer first, + final IValueTransformer last) { + + return new IValueTransformer() { + + @Override + public R transform(LocalDate day, T previousValue) { + S intermediateValue = first.transform(day, previousValue); + return last.transform(day, intermediateValue); + } + }; + } + @Override public Iterator> iterator() { final Iterator iterator = values.iterator(); diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/planner/chart/ResourceLoadChartData.java b/navalplanner-business/src/main/java/org/navalplanner/business/planner/chart/ResourceLoadChartData.java index 9696a006c..943dfa13b 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/planner/chart/ResourceLoadChartData.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/planner/chart/ResourceLoadChartData.java @@ -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 availability; public ResourceLoadChartData(List dayAssignments, List resources) { - SortedMap> map = - groupDurationsByDayAndResource(dayAssignments); - this.load = calculateResourceLoadPerDate(map); - this.overload = calculateResourceOverloadPerDate(map); - if(load.keySet().isEmpty()) { - this.availability = new TreeMap(); - } - 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> assignments = ContiguousDaysLine + .byDay(dayAssignments); + + ContiguousDaysLine load = assignments + .transform(extractLoad()); + + ContiguousDaysLine overload = assignments + .transform(extractOverload()); + + ContiguousDaysLine availabilityOnAllResources = assignments + .transform(extractAvailabilityOnAllResources(resources)); + + this.load = toSortedMap(ContiguousDaysLine.min(load, + availabilityOnAllResources)); + this.overload = toSortedMap(sum(overload, availabilityOnAllResources)); + this.availability = toSortedMap(availabilityOnAllResources); + } + + private IValueTransformer, EffortDuration> extractOverload() { + return compound(effortByResource(), calculateOverload()); + } + + private IValueTransformer, Map> effortByResource() { + return new IValueTransformer, Map>() { + + @Override + public Map transform(LocalDate day, + List previousValue) { + Map> byResource = DayAssignment + .byResource(previousValue); + Map result = new HashMap(); + for (Entry> each : byResource + .entrySet()) { + result.put(each.getKey(), + DayAssignment.sum(each.getValue())); + } + return result; + } + }; + } + + private IValueTransformer, EffortDuration> calculateOverload() { + return new IValueTransformer, EffortDuration>() { + + @Override + public EffortDuration transform(LocalDate day, + Map previousValue) { + + final PartialDay wholeDay = PartialDay.wholeDay(day); + return EffortDuration.sum(previousValue.entrySet(), + new IEffortFrom>() { + + @Override + public EffortDuration from( + Entry each) { + EffortDuration capacity = calendarCapacityFor( + each.getKey(), wholeDay); + EffortDuration assigned = each.getValue(); + return assigned.minus(min(capacity, assigned)); + } + }); + } + }; + } + + private IValueTransformer, EffortDuration> extractLoad() { + return new IValueTransformer, EffortDuration>() { + + @Override + public EffortDuration transform(LocalDate day, + List previousValue) { + return DayAssignment.sum(previousValue); + } + }; + } + + private IValueTransformer, EffortDuration> extractAvailabilityOnAssignedResources() { + return new IValueTransformer, EffortDuration>() { + + @Override + public EffortDuration transform(LocalDate day, + List previousValue) { + Set resources = getResources(previousValue); + return sumCalendarCapacitiesForDay(resources, day); + } + + private Set getResources(List assignments) { + Set resources = new HashSet(); + for (DayAssignment dayAssignment : assignments) { + resources.add(dayAssignment.getResource()); + } + return resources; + } + }; + } + + private IValueTransformer, EffortDuration> extractAvailabilityOnAllResources( + final List resources) { + return new IValueTransformer, EffortDuration>() { + + @Override + public EffortDuration transform(LocalDate day, + List previousValue) { + return sumCalendarCapacitiesForDay(resources, day); + } + }; } public SortedMap getLoad() { @@ -130,140 +220,7 @@ public class ResourceLoadChartData implements ILoadChartData { }; } - private SortedMap> groupDurationsByDayAndResource( - List dayAssignments) { - SortedMap> map = - new TreeMap>(); - - 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()); - } - Map 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 calculateResourceLoadPerDate( - SortedMap> durationsGrouped) { - SortedMap map = new TreeMap(); - - 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 { - public SortedMap calculate( - Collection elements) { - SortedMap result = new TreeMap(); - 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 calculateResourceOverloadPerDate( - SortedMap> dayAssignmentGrouped) { - return new EffortByDayCalculator>>() { - - @Override - protected LocalDate getDayFor( - Entry> element) { - return element.getKey(); - } - - @Override - protected EffortDuration getDurationFor( - Entry> element) { - - final PartialDay day = PartialDay.wholeDay(element.getKey()); - - return EffortDuration.sum(element.getValue().entrySet(), - new IEffortFrom>() { - - @Override - public EffortDuration from( - Entry 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 calculateAvailabilityDurationByDay( - final List resources, LocalDate start, LocalDate finish) { - return new EffortByDayCalculator>>() { - - @Override - protected LocalDate getDayFor( - Entry> element) { - return element.getKey(); - } - - @Override - protected EffortDuration getDurationFor( - Entry> element) { - LocalDate day = element.getKey(); - return sumCalendarCapacitiesForDay(resources, day); - } - - }.calculate(getResourcesByDateBetween(resources, start, finish)); - } - - protected static EffortDuration sumCalendarCapacitiesForDay( + private static EffortDuration sumCalendarCapacitiesForDay( Collection 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>> getResourcesByDateBetween( - List resources, LocalDate start, LocalDate finish) { - Map> result = new HashMap>(); - for (LocalDate date = new LocalDate(start); date.compareTo(finish) <= 0; date = date - .plusDays(1)) { - result.put(date, resources); - } - return result.entrySet(); - } } diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/workingday/EffortDuration.java b/navalplanner-business/src/main/java/org/navalplanner/business/workingday/EffortDuration.java index 987e13089..254aa71e5 100644 --- a/navalplanner-business/src/main/java/org/navalplanner/business/workingday/EffortDuration.java +++ b/navalplanner-business/src/main/java/org/navalplanner/business/workingday/EffortDuration.java @@ -130,6 +130,16 @@ public class EffortDuration implements Comparable { return result; } + public static EffortDuration sum(EffortDuration... summands) { + return sum(Arrays.asList(summands), new IEffortFrom() { + + @Override + public EffortDuration from(EffortDuration each) { + return each; + } + }); + } + public static EffortDuration zero() { return elapsing(0, Granularity.SECONDS); }