Package org.libreplan.web.planner.chart

Source Code of org.libreplan.web.planner.chart.ChartFiller$DefaultGraphicSpecificationCreator

/*
* 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.libreplan.web.planner.chart;

import static org.libreplan.business.workingday.EffortDuration.zero;

import java.io.IOException;
import java.io.PrintWriter;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
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 javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.joda.time.DateTime;
import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.libreplan.business.planner.entities.DayAssignment;
import org.libreplan.business.resources.entities.Resource;
import org.libreplan.business.workingday.EffortDuration;
import org.libreplan.business.workingday.IntraDayDate.PartialDay;
import org.zkforge.timeplot.Plotinfo;
import org.zkforge.timeplot.Timeplot;
import org.zkforge.timeplot.data.PlotDataSource;
import org.zkforge.timeplot.geometry.DefaultTimeGeometry;
import org.zkforge.timeplot.geometry.DefaultValueGeometry;
import org.zkforge.timeplot.geometry.TimeGeometry;
import org.zkforge.timeplot.geometry.ValueGeometry;
import org.zkoss.ganttz.servlets.CallbackServlet;
import org.zkoss.ganttz.servlets.CallbackServlet.DisposalMode;
import org.zkoss.ganttz.servlets.CallbackServlet.IServletRequestHandler;
import org.zkoss.ganttz.timetracker.zoom.ZoomLevel;
import org.zkoss.ganttz.util.Interval;
import org.zkoss.zk.ui.Executions;

/**
* Abstract class with the basic functionality to fill the chart.
* @author Manuel Rego Casasnovas <mrego@igalia.com>
*/
public abstract class ChartFiller implements IChartFiller {

    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 groupAsNeededByZoom(result);
        }

        protected abstract LocalDate getDayFor(T element);

        protected abstract EffortDuration getDurationFor(T element);

        protected boolean included(T each) {
            return true;
        }
    }

    protected static EffortDuration sumCalendarCapacitiesForDay(
            Collection<? extends Resource> resources, LocalDate day) {
        PartialDay wholeDay = PartialDay.wholeDay(day);
        EffortDuration sum = zero();
        for (Resource resource : resources) {
            sum = sum.plus(calendarCapacityFor(resource,
                    wholeDay));
        }
        return sum;
    }

    protected static EffortDuration calendarCapacityFor(Resource resource,
            PartialDay day) {
        return resource.getCalendarOrDefault().getCapacityOn(day);
    }

    protected abstract class GraphicSpecificationCreator implements
            IServletRequestHandler {

        private final LocalDate finish;
        private final SortedMap<LocalDate, BigDecimal> map;
        private final LocalDate start;

        protected GraphicSpecificationCreator(LocalDate finish,
                SortedMap<LocalDate, BigDecimal> map, LocalDate start) {
            this.finish = new LocalDate(finish);
            this.map = map;
            this.start = new LocalDate(start);
        }

        protected Set<LocalDate> getDays() {
            return map.keySet();
        }

        @Override
        public void handle(HttpServletRequest request,
                HttpServletResponse response) throws ServletException,
                IOException {
            PrintWriter writer = response.getWriter();
            fillValues(writer);
            writer.close();
        }

        private void fillValues(PrintWriter writer) {
            fillZeroValueFromStart(writer);
            fillInnerValues(writer, firstDay(), lastDay());
            fillZeroValueToFinish(writer);
        }

        protected abstract void fillInnerValues(PrintWriter writer,
                LocalDate firstDay, LocalDate lastDay);

        protected LocalDate nextDay(LocalDate date) {
            if (isZoomByDayOrWeek()) {
                return date.plusDays(1);
            } else {
                return date.plusWeeks(1);
            }
        }

        private LocalDate firstDay() {
            LocalDate date = map.firstKey();
            return convertAsNeededByZoom(date);
        }

        private LocalDate lastDay() {
            LocalDate date = map.lastKey();
            return convertAsNeededByZoom(date);
        }

        private LocalDate convertAsNeededByZoom(LocalDate date) {
            if (isZoomByDayOrWeek()) {
                return date;
            } else {
                return getThursdayOfThisWeek(date);
            }
        }

        protected BigDecimal getHoursForDay(LocalDate day) {
            return map.get(day) != null ? map.get(day) : BigDecimal.ZERO;
        }

        protected void printLine(PrintWriter writer, DateTime day,
                BigDecimal hours) {
            // using ISO 8601 format [YYYY][MM][DD]T[hh][mm][ss]Z.
            String position = day.toString("yyyyMMdd") + "T"
                    + day.toString("HHmmss") + "Z";
            writer.println(position + " " + hours);
        }

        protected void printIntervalLine(PrintWriter writer, LocalDate day,
                BigDecimal hours, boolean isZoomByDay) {
            // using ISO 8601 format [YYYY][MM][DD]T[hh][mm][ss]Z.
            DateTime initOfInterval = getInitOfInterval(day, isZoomByDay);
            DateTime finishOfInterval = getFinishOfInterval(day, isZoomByDay);

            printLine(writer, initOfInterval, hours);
            printLine(writer, finishOfInterval, hours);
        }

        protected DateTime getInitOfInterval(LocalDate day,
                boolean isZoomByDayOrWeek) {
            if (isZoomByDayOrWeek) {
                return day.toDateTimeAtStartOfDay();
            } else {
                return day.minusDays(day.getDayOfWeek() - 1)
                        .toDateTimeAtStartOfDay();
            }
        }

        protected DateTime getFinishOfInterval(LocalDate day,
                boolean isZoomByDayOrWeek) {
            if (isZoomByDayOrWeek) {
                return day.plusDays(1).toDateTimeAtStartOfDay().minusSeconds(1);
            } else {
                return day.plusDays(8 - day.getDayOfWeek())
                        .toDateTimeAtStartOfDay().minusSeconds(1);
            }
        }

        private void fillZeroValueFromStart(PrintWriter writer) {
            if (!startIsDayOfFirstAssignment()) {
                printLine(writer, start.toDateTimeAtStartOfDay(),
                        BigDecimal.ZERO);
                if (startIsPreviousToPreviousDayToFirstAssignment()) {
                    printLine(writer, previousDayToFirstAssignment(),
                            BigDecimal.ZERO);
                }
            }
        }

        private boolean startIsDayOfFirstAssignment() {
            return !map.isEmpty() && start.compareTo(map.firstKey()) == 0;
        }

        private boolean startIsPreviousToPreviousDayToFirstAssignment() {
            return !map.isEmpty()
                    && start.compareTo(previousDayToFirstAssignment()
                            .toLocalDate()) < 0;
        }

        private DateTime previousDayToFirstAssignment() {
            return getInitOfInterval(map.firstKey(), isZoomByDayOrWeek())
                    .minusSeconds(1);
        }

        private void fillZeroValueToFinish(PrintWriter writer) {
            if (!finishIsDayOfLastAssignment()) {
                if (finishIsPosteriorToNextDayToLastAssignment()) {
                    printLine(writer, nextDayToLastAssignment(),
                            BigDecimal.ZERO);
                }
                DateTime finishMidNight = finish.plusDays(1)
                        .toDateTimeAtStartOfDay().minusSeconds(1);
                printLine(writer, finishMidNight, BigDecimal.ZERO);
            }
        }

        private boolean finishIsDayOfLastAssignment() {
            return !map.isEmpty() && start.compareTo(map.lastKey()) == 0;
        }

        private boolean finishIsPosteriorToNextDayToLastAssignment() {
            return !map.isEmpty()
                    && finish
                            .compareTo(nextDayToLastAssignment().toLocalDate()) > 0;
        }

        private DateTime nextDayToLastAssignment() {
            return this.getFinishOfInterval(map.lastKey(), isZoomByDayOrWeek())
                    .plusSeconds(1);
        }
    }

    protected class DefaultGraphicSpecificationCreator extends
            GraphicSpecificationCreator {

        private DefaultGraphicSpecificationCreator(LocalDate finish,
                SortedMap<LocalDate, BigDecimal> map, LocalDate start) {
            super(finish, map, start);
        }

        @Override
        protected void fillInnerValues(PrintWriter writer, LocalDate firstDay,
                LocalDate lastDay) {
            for (LocalDate day = firstDay; day.compareTo(lastDay) <= 0; day = nextDay(day)) {
                BigDecimal hours = getHoursForDay(day);
                printIntervalLine(writer, day, hours, isZoomByDayOrWeek());
            }
        }

    }

    protected class JustDaysWithInformationGraphicSpecificationCreator extends
            GraphicSpecificationCreator {

        public JustDaysWithInformationGraphicSpecificationCreator(
                LocalDate finish, SortedMap<LocalDate, BigDecimal> map,
                LocalDate start) {
            super(finish, map, start);
        }

        @Override
        protected void fillInnerValues(PrintWriter writer, LocalDate firstDay,
                LocalDate lastDay) {
            for (LocalDate day : getDays()) {
                BigDecimal hours = getHoursForDay(day);
                printLine(writer, day.toDateTimeAtStartOfDay(), hours);
            }
        }

    }

    /**
     * Number of days to Thursday since the beginning of the week. In order to
     * calculate the middle of a week.
     */
    private final static int DAYS_TO_THURSDAY = 3;

    private ZoomLevel zoomLevel = ZoomLevel.DETAIL_ONE;

    private BigDecimal minimumValueForChart = BigDecimal.ZERO;
    private BigDecimal maximumValueForChart = BigDecimal.ZERO;

    @Override
    public abstract void fillChart(Timeplot chart, Interval interval,
            Integer size);

    private void setMinimumValueForChartIfLess(BigDecimal min) {
        if (minimumValueForChart.compareTo(min) > 0) {
            minimumValueForChart = min;
        }
    }

    private void setMaximumValueForChartIfGreater(BigDecimal max) {
        if (maximumValueForChart.compareTo(max) < 0) {
            maximumValueForChart = max;
        }
    }

    private static LocalDate getThursdayOfThisWeek(LocalDate date) {
        return date.dayOfWeek().withMinimumValue().plusDays(DAYS_TO_THURSDAY);
    }

    private boolean isZoomByDayOrWeek() {
        return (zoomLevel.equals(ZoomLevel.DETAIL_FIVE) || zoomLevel
                .equals(ZoomLevel.DETAIL_FOUR));
    }

    protected void resetMinimumAndMaximumValueForChart() {
        this.minimumValueForChart = BigDecimal.ZERO;
        this.maximumValueForChart = BigDecimal.ZERO;
    }

    protected BigDecimal getMinimumValueForChart() {
        return minimumValueForChart;
    }

    protected BigDecimal getMaximumValueForChart() {
        return maximumValueForChart;
    }

    protected SortedMap<LocalDate, BigDecimal> groupByWeek(
            SortedMap<LocalDate, BigDecimal> map) {
        SortedMap<LocalDate, BigDecimal> result = new TreeMap<LocalDate, BigDecimal>();
        for (Entry<LocalDate, BigDecimal> entry : map.entrySet()) {
            LocalDate day = entry.getKey();
            LocalDate key = getThursdayOfThisWeek(day);
            BigDecimal hours = entry.getValue() == null ? BigDecimal.ZERO
                    : entry.getValue();
            if (result.get(key) == null) {
                result.put(key, hours);
            } else {
                result.put(key, result.get(key).add(hours));
            }
        }
        for (Entry<LocalDate, BigDecimal> entry : result.entrySet()) {
            LocalDate day = entry.getKey();
            result.put(entry.getKey(), result.get(day).setScale(2).divide(
                    new BigDecimal(7), RoundingMode.DOWN));
        }
        return result;
    }

    protected SortedMap<LocalDate, EffortDuration> groupAsNeededByZoom(
            SortedMap<LocalDate, EffortDuration> map) {
        if (isZoomByDayOrWeek()) {
            return map;
        }
        return groupByWeekDurations(map);
    }

    protected SortedMap<LocalDate, EffortDuration> groupByWeekDurations(
            SortedMap<LocalDate, EffortDuration> map) {
        return average(accumulatePerWeek(map));
    }

    private static SortedMap<LocalDate, EffortDuration> accumulatePerWeek(
            SortedMap<LocalDate, EffortDuration> map) {
        SortedMap<LocalDate, EffortDuration> result = new TreeMap<LocalDate, EffortDuration>();
        for (Entry<LocalDate, EffortDuration> each : map.entrySet()) {
            LocalDate centerOfWeek = getThursdayOfThisWeek(each.getKey());
            EffortDuration accumulated = result.get(centerOfWeek);
            accumulated = accumulated == null ? zero() : accumulated;
            result.put(centerOfWeek, accumulated.plus(each.getValue()));
        }
        return result;
    }

    private static SortedMap<LocalDate, EffortDuration> average(
            SortedMap<LocalDate, EffortDuration> accumulatedPerWeek) {
        SortedMap<LocalDate, EffortDuration> result = new TreeMap<LocalDate, EffortDuration>();
        for (Entry<LocalDate, EffortDuration> each : accumulatedPerWeek
                .entrySet()) {
            result.put(each.getKey(), each.getValue().divideBy(7));
        }
        return result;
    }

    protected TimeGeometry getTimeGeometry(Interval interval) {
        LocalDate start = new LocalDate(interval.getStart());
        LocalDate finish = new LocalDate(interval.getFinish());

        TimeGeometry timeGeometry = new DefaultTimeGeometry();

        if (!isZoomByDayOrWeek()) {
            start = getThursdayOfThisWeek(start);
            finish = getThursdayOfThisWeek(finish);
        }

        timeGeometry.setMin(start.toDateTimeAtStartOfDay().toDate());
        timeGeometry.setMax(finish.toDateTimeAtStartOfDay().toDate());
        timeGeometry.setAxisLabelsPlacement("bottom");
        // Remove year separators
        timeGeometry.setGridColor("#FFFFFF");

        return timeGeometry;
    }

    protected ValueGeometry getValueGeometry() {
        DefaultValueGeometry valueGeometry = new DefaultValueGeometry();
        valueGeometry.setMin(getMinimumValueForChart().intValue());
        valueGeometry.setMax(getMaximumValueForChart().intValue());
        valueGeometry.setGridColor("#000000");
        valueGeometry.setAxisLabelsPlacement("left");

        return valueGeometry;
    }

    protected 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;
    }

    protected void addCost(SortedMap<LocalDate, BigDecimal> currentCost,
            SortedMap<LocalDate, BigDecimal> additionalCost) {
        for (LocalDate day : additionalCost.keySet()) {
            if (!currentCost.containsKey(day)) {
                currentCost.put(day, BigDecimal.ZERO);
            }
            currentCost.put(day, currentCost.get(day).add(
                    additionalCost.get(day)));
        }
    }

    protected SortedMap<LocalDate, BigDecimal> accumulateResult(
            SortedMap<LocalDate, BigDecimal> map) {
        SortedMap<LocalDate, BigDecimal> result = new TreeMap<LocalDate, BigDecimal>();
        if (map.isEmpty()) {
            return result;
        }

        BigDecimal accumulatedResult = BigDecimal.ZERO;
        for (LocalDate day : map.keySet()) {
            BigDecimal value = map.get(day);
            accumulatedResult = accumulatedResult.add(value);
            result.put(day, accumulatedResult);
        }

        return result;
    }

    protected SortedMap<LocalDate, BigDecimal> convertToBigDecimal(
            SortedMap<LocalDate, Integer> map) {
        SortedMap<LocalDate, BigDecimal> result = new TreeMap<LocalDate, BigDecimal>();

        for (LocalDate day : map.keySet()) {
            BigDecimal value = new BigDecimal(map.get(day));
            result.put(day, value);
        }

        return result;
    }

    protected SortedMap<LocalDate, BigDecimal> calculatedValueForEveryDay(
            SortedMap<LocalDate, BigDecimal> values, Interval interval) {
        return calculatedValueForEveryDay(values, interval.getStart(),
                interval.getFinish());
    }

    protected SortedMap<LocalDate, BigDecimal> calculatedValueForEveryDay(
            SortedMap<LocalDate, BigDecimal> map, Date start, Date finish) {
        return calculatedValueForEveryDay(map, new LocalDate(start),
                new LocalDate(finish));
    }

    protected SortedMap<LocalDate, BigDecimal> calculatedValueForEveryDay(
            SortedMap<LocalDate, BigDecimal> map, LocalDate start,
            LocalDate finish) {
        SortedMap<LocalDate, BigDecimal> result = new TreeMap<LocalDate, BigDecimal>();

        LocalDate previousDay = start;
        BigDecimal previousValue = BigDecimal.ZERO;

        for (LocalDate day : map.keySet()) {
            BigDecimal value = map.get(day);
            fillValues(result, previousDay, day, previousValue, value);

            previousDay = day;
            previousValue = value;
        }

        if (previousDay.compareTo(finish) < 0) {
            fillValues(result, previousDay, finish, previousValue,
                    previousValue);
        }

        return result;
    }

    private void fillValues(SortedMap<LocalDate, BigDecimal> map,
            LocalDate firstDay, LocalDate lastDay, BigDecimal firstValue,
            BigDecimal lastValue) {

        Integer days = Days.daysBetween(firstDay, lastDay).getDays();
        if (days > 0) {
            BigDecimal ammount = lastValue.subtract(firstValue);
            BigDecimal ammountPerDay = ammount.setScale(2, RoundingMode.DOWN).divide(
                    new BigDecimal(days), RoundingMode.DOWN);

            BigDecimal value = firstValue.setScale(2, RoundingMode.DOWN);
            for (LocalDate day = firstDay; day.compareTo(lastDay) <= 0; day = day
                    .plusDays(1)) {
                map.put(day, value);
                value = value.add(ammountPerDay);
            }
        }
    }

    protected Plotinfo createPlotinfoFromDurations(SortedMap<LocalDate, EffortDuration> map,
            Interval interval) {
        return createPlotinfo(toHoursDecimal(map), interval);
    }

    public static <K> SortedMap<K, BigDecimal> toHoursDecimal(
            Map<K, EffortDuration> map) {
        SortedMap<K, BigDecimal> result = new TreeMap<K, BigDecimal>();
        for (Entry<K, EffortDuration> each : map.entrySet()) {
            result.put(each.getKey(), each.getValue()
                    .toHoursAsDecimalWithScale(2));
        }
        return result;
    }

    protected Plotinfo createPlotinfo(SortedMap<LocalDate, BigDecimal> map,
            Interval interval) {
        return createPlotinfo(map, interval, false);
    }

    protected Plotinfo createPlotinfo(SortedMap<LocalDate, BigDecimal> map,
            Interval interval, boolean justDaysWithInformation) {
        if (!map.isEmpty()) {
            setMinimumValueForChartIfLess(Collections.min(map.values()));
            setMaximumValueForChartIfGreater(Collections.max(map.values()));
        }
        return createPlotInfoFrom(createGraphicSpecification(map,
                interval, justDaysWithInformation));
    }

    private GraphicSpecificationCreator createGraphicSpecification(
            SortedMap<LocalDate, BigDecimal> map, Interval interval,
            boolean justDaysWithInformation) {
        if (map.isEmpty()) {
            return null;
        }
        if (justDaysWithInformation) {
            return new JustDaysWithInformationGraphicSpecificationCreator(
                    interval.getFinish(), map, interval.getStart());
        } else {
            return new DefaultGraphicSpecificationCreator(interval.getFinish(),
                    map, interval.getStart());
        }
    }

    private String getServletUri(
            final GraphicSpecificationCreator graphicSpecificationCreator) {
        if (graphicSpecificationCreator == null) {
            return "";
        }
        HttpServletRequest request = (HttpServletRequest) Executions
                .getCurrent().getNativeRequest();
        return CallbackServlet.registerAndCreateURLFor(request,
                graphicSpecificationCreator, false,
                DisposalMode.WHEN_NO_LONGER_REFERENCED);
    }

    private Plotinfo createPlotInfoFrom(
            GraphicSpecificationCreator graphicSpecificationCreator) {
        PlotDataSource pds = new PlotDataSource();
        pds.setDataSourceUri(getServletUri(graphicSpecificationCreator));
        pds.setSeparator(" ");

        Plotinfo plotinfo = new Plotinfo();
        plotinfo.setAttribute("keep-chart-specification-creator-referenced",
                graphicSpecificationCreator);
        plotinfo.setPlotDataSource(pds);
        return plotinfo;
    }

    protected void appendPlotinfo(Timeplot chart, Plotinfo plotinfo,
            ValueGeometry valueGeometry, TimeGeometry timeGeometry) {
        plotinfo.setValueGeometry(valueGeometry);
        plotinfo.setTimeGeometry(timeGeometry);
        plotinfo.setShowValues(true);
        chart.appendChild(plotinfo);
    }

    @Override
    public void setZoomLevel(ZoomLevel zoomLevel) {
        this.zoomLevel = zoomLevel;
    }

}
TOP

Related Classes of org.libreplan.web.planner.chart.ChartFiller$DefaultGraphicSpecificationCreator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.