/* * This file is part of LibrePlan * * Copyright (C) 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.planner.chart; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; 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.lang3.Validate; import org.joda.time.Days; import org.joda.time.LocalDate; import org.libreplan.business.planner.chart.ContiguousDaysLine.OnDay; import org.libreplan.business.planner.entities.DayAssignment; import org.libreplan.business.workingday.EffortDuration; /** * It represents some contiguous days from a start date to a not included end * date. Each of these {@link LocalDate} has an associated value that can be * <code>null</code>. * * @author Óscar González Fernández <ogonzalez@igalia.com> * */ public class ContiguousDaysLine<T> implements Iterable<OnDay<T>> { public static class OnDay<T> { private final LocalDate day; private final T value; private OnDay(LocalDate day, T value) { Validate.notNull(day); this.day = day; this.value = value; } public LocalDate getDay() { return day; } public T getValue() { return value; } } public static <T> ContiguousDaysLine<T> create(LocalDate fromInclusive, LocalDate endExclusive, Class<T> klass) { return create(fromInclusive, endExclusive); } public static <T> ContiguousDaysLine<T> create(LocalDate fromInclusive, LocalDate endExclusive) { if (fromInclusive.isAfter(endExclusive)) { throw new IllegalArgumentException("fromInclusive (" + fromInclusive + ") is after endExclusive (" + endExclusive + ")"); } Days daysBetween = Days.daysBetween(fromInclusive, endExclusive); 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) { this.startInclusive = start; this.values = new ArrayList<T>(size); for (int i = 0; i < size; i++) { values.add(null); } } 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 ContiguousDaysLine<T> subInterval(LocalDate startInclusive, LocalDate endExclusive) { if (isNotValid() || startInclusive.compareTo(endExclusive) >= 0 || startInclusive.compareTo(getEndExclusive()) >= 0 || endExclusive.compareTo(getStart()) <= 0) { return invalid(); } LocalDate newStart = max(this.startInclusive, startInclusive); Days days = Days.daysBetween(newStart, min(getEndExclusive(), endExclusive)); ContiguousDaysLine<T> result = new ContiguousDaysLine<T>(newStart, days.getDays()); for (OnDay<T> each : result) { result.set(each.getDay(), this.get(each.getDay())); } return result; } @SuppressWarnings("unchecked") LocalDate min(LocalDate... dates) { return Collections.min(Arrays.asList(dates)); } @SuppressWarnings("unchecked") private static LocalDate max(LocalDate... dates) { return Collections.max(Arrays.asList(dates)); } public LocalDate getEndExclusive() { return getStart().plusDays(values.size()); } public T get(LocalDate day) throws IndexOutOfBoundsException { Validate.notNull(day); Days days = Days.daysBetween(startInclusive, day); return values.get(days.getDays()); } public void set(LocalDate day, T value) throws IndexOutOfBoundsException { Validate.notNull(day); Days days = Days.daysBetween(startInclusive, day); values.set(days.getDays(), value); } public boolean isEmpty() { return values.isEmpty(); } public void setValueForAll(T value) { ListIterator<T> listIterator = values.listIterator(); while (listIterator.hasNext()) { listIterator.next(); listIterator.set(value); } } public interface IValueTransformer<T, R> { R transform(LocalDate day, T previousValue); } public void transformInSitu(IValueTransformer<T, T> transformer) { LocalDate current = startInclusive; ListIterator<T> listIterator = values.listIterator(); while (listIterator.hasNext()) { T previousValue = listIterator.next(); listIterator.set(transformer.transform(current, previousValue)); current = current.plusDays(1); } } 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) { LocalDate day = onDay.getDay(); result.set(day, doubleTransformer.transform(day, onDay.getValue())); } 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(); return new Iterator<OnDay<T>>() { private LocalDate current = startInclusive; @Override public boolean hasNext() { return iterator.hasNext(); } @Override public OnDay<T> next() { T next = iterator.next(); OnDay<T> result = new OnDay<T>(current, next); current = current.plusDays(1); return result; } @Override public void remove() throws UnsupportedOperationException { throw new UnsupportedOperationException(); } }; } public int size() { return values.size(); } }