/* * 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.zkoss.ganttz.timetracker.zoom; import static java.util.Arrays.asList; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; import org.joda.time.DateTime; import org.joda.time.Days; import org.joda.time.LocalDate; import org.joda.time.Months; import org.joda.time.ReadablePeriod; import org.joda.time.Weeks; import org.joda.time.Years; import org.joda.time.base.BaseSingleFieldPeriod; import org.zkoss.ganttz.util.Interval; /** * @author Francisco Javier Moran Rúa <jmoran@igalia.com> * @author Lorenzo Tilve Álvaro <ltilve@igalia.com> */ public abstract class TimeTrackerState { private final IDetailItemModifier firstLevelModifier; private final IDetailItemModifier secondLevelModifier; protected TimeTrackerState(IDetailItemModifier firstLevelModifier, IDetailItemModifier secondLevelModifier) { this.firstLevelModifier = firstLevelModifier; this.secondLevelModifier = secondLevelModifier; } public static Date year(int year) { Calendar calendar = Calendar.getInstance(); calendar.clear(); calendar.set(Calendar.YEAR, year); return calendar.getTime(); } public abstract static class LazyGenerator<T> implements Iterator<T> { private T current; protected LazyGenerator(T first) { this.current = first; } @Override public boolean hasNext() { return true; } @Override public T next() { this.current = next(this.current); return this.current; } protected abstract T next(T last); @Override public void remove() { throw new UnsupportedOperationException(); } } /** * When applied after setting current day, removes extra data as current day or bank holidays, * and must process the array twice. * * May be refactorized. */ private static List<DetailItem> markEvens(Collection<? extends DetailItem> items) { boolean even = false; ArrayList<DetailItem> result = new ArrayList<>(); for (DetailItem detailItem : items) { detailItem.setEven(even); result.add(detailItem); even = !even; } return result; } protected static LocalDate asLocalDate(Date date) { return new LocalDate(date); } public interface IDetailItemCreator { DetailItem create(DateTime dateTime); } public Collection<DetailItem> getSecondLevelDetails(Interval interval) { if (getZoomLevel() == ZoomLevel.DETAIL_FIVE) { // Events are not highlighted in day view return applyConfiguredModifications( secondLevelModifier, createDetailsForSecondLevel(interval), getZoomLevel()); } else { return markEvens(applyConfiguredModifications( secondLevelModifier, createDetailsForSecondLevel(interval), getZoomLevel())); } } public Collection<DetailItem> getFirstLevelDetails(Interval interval) { return applyConfiguredModifications( firstLevelModifier, createDetailsForFirstLevel(interval), getZoomLevel()); } private static List<DetailItem> applyConfiguredModifications(IDetailItemModifier modifier, Collection<? extends DetailItem> detailsItems, ZoomLevel zoomlevel) { List<DetailItem> result = new ArrayList<>(detailsItems.size()); for (DetailItem each : detailsItems) { result.add(modifier.applyModificationsTo(each, zoomlevel)); } return result; } private Collection<DetailItem> createDetails(Interval interval, Iterator<LocalDate> datesGenerator, IDetailItemCreator detailItemCreator) { List<DetailItem> result = new ArrayList<>(); LocalDate current = interval.getStart(); LocalDate end = interval.getFinish(); while (current.isBefore(end)) { result.add(detailItemCreator.create(current.toDateTimeAtStartOfDay())); assert datesGenerator.hasNext(); current = datesGenerator.next(); } return result; } private final Collection<DetailItem> createDetailsForFirstLevel(Interval interval) { Interval realInterval = getRealIntervalFor(interval); return createDetails( realInterval, getPeriodsFirstLevelGenerator(realInterval.getStart()), getDetailItemCreatorFirstLevel()); } protected abstract Iterator<LocalDate> getPeriodsFirstLevelGenerator(LocalDate start); private final Collection<DetailItem> createDetailsForSecondLevel(Interval interval) { Interval realInterval = getRealIntervalFor(interval); return createDetails( realInterval, getPeriodsSecondLevelGenerator(realInterval.getStart()), getDetailItemCreatorSecondLevel()); } protected abstract Iterator<LocalDate> getPeriodsSecondLevelGenerator(LocalDate start); protected abstract IDetailItemCreator getDetailItemCreatorFirstLevel(); protected abstract IDetailItemCreator getDetailItemCreatorSecondLevel(); protected abstract LocalDate round(LocalDate date, boolean down); public enum PeriodType { YEARS { @Override public ReadablePeriod toPeriod(int amount) { return Years.years(amount); } @Override public Years differenceBetween(LocalDate start, LocalDate end) { return Years.yearsBetween(start, end); } }, MONTHS { @Override public ReadablePeriod toPeriod(int amount) { return Months.months(amount); } @Override public Months differenceBetween(LocalDate start, LocalDate end) { return Months.monthsBetween(start, end); } }, WEEKS { @Override public ReadablePeriod toPeriod(int amount) { return Weeks.weeks(amount); } @Override public Weeks differenceBetween(LocalDate start, LocalDate end) { return Weeks.weeksBetween(start, end); } }, DAYS { @Override public ReadablePeriod toPeriod(int amount) { return Days.days(amount); } @Override public Days differenceBetween(LocalDate start, LocalDate end) { return Days.daysBetween(start, end); } }; public abstract ReadablePeriod toPeriod(int amount); public abstract BaseSingleFieldPeriod differenceBetween(LocalDate start, LocalDate end); public Period amount(int amount) { return new Period(this, amount); } } static class Period { private final PeriodType type; private final int amount; private Period(PeriodType type, int amount) { this.type = type; this.amount = amount; } ReadablePeriod toPeriod() { return this.type.toPeriod(amount); } BaseSingleFieldPeriod asPeriod(Interval interval) { return type.differenceBetween(interval.getStart(), interval.getFinish()); } } protected abstract Period getMinimumPeriod(); private Interval ensureMinimumInterval(Interval interval) { LocalDate newEnd = interval.getStart().plus(getMinimumPeriod().toPeriod()); return new Interval(interval.getStart(), Collections.max(asList(newEnd, interval.getFinish()))); } public Interval getRealIntervalFor(Interval testInterval) { return calculateForAtLeastMinimum(ensureMinimumInterval(testInterval)); } private Interval calculateForAtLeastMinimum(Interval atLeastMinimum) { LocalDate start = round(atLeastMinimum.getStart(), true); LocalDate finish = round(atLeastMinimum.getFinish(), false); return new Interval(start.toDateTimeAtStartOfDay().toDate(), finish.toDateTimeAtStartOfDay().toDate()); } public abstract double daysPerPixel(); protected abstract ZoomLevel getZoomLevel(); public abstract int getSecondLevelSize(); }