org.libreplan.business.planner.limiting.entities.Gap.java Source code

Java tutorial

Introduction

Here is the source code for org.libreplan.business.planner.limiting.entities.Gap.java

Source

/*
 * This file is part of LibrePlan
 *
 * Copyright (C) 2009-2010 Fundacin para o Fomento da Calidade Industrial e
 *                         Desenvolvemento Tecnolxico 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.business.planner.limiting.entities;

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

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang3.Validate;
import org.joda.time.LocalDate;
import org.libreplan.business.calendars.entities.AvailabilityTimeLine;
import org.libreplan.business.calendars.entities.AvailabilityTimeLine.DatePoint;
import org.libreplan.business.calendars.entities.AvailabilityTimeLine.EndOfTime;
import org.libreplan.business.calendars.entities.AvailabilityTimeLine.FixedPoint;
import org.libreplan.business.calendars.entities.AvailabilityTimeLine.Interval;
import org.libreplan.business.calendars.entities.AvailabilityTimeLine.StartOfTime;
import org.libreplan.business.calendars.entities.BaseCalendar;
import org.libreplan.business.calendars.entities.ResourceCalendar;
import org.libreplan.business.planner.entities.AvailabilityCalculator;
import org.libreplan.business.resources.entities.Criterion;
import org.libreplan.business.resources.entities.LimitingResourceQueue;
import org.libreplan.business.resources.entities.Resource;
import org.libreplan.business.workingday.EffortDuration;
import org.libreplan.business.workingday.IntraDayDate;
import org.libreplan.business.workingday.IntraDayDate.PartialDay;

/**
 * Note: this class has a natural ordering that is inconsistent with equals.
 *
 * @author Diego Pino Garcia <dpino@igalia.com>
 */
public class Gap implements Comparable<Gap> {

    private DateAndHour startTime;

    private DateAndHour endTime;

    private Integer hoursInGap;

    public Gap(Resource resource, DateAndHour startTime, DateAndHour endTime) {
        this.startTime = startTime;
        this.endTime = endTime;
        hoursInGap = calculateHoursInGap(resource, startTime, endTime);
    }

    public static class GapOnQueue {

        private final LimitingResourceQueue originQueue;

        private final Gap gap;

        GapOnQueue(LimitingResourceQueue originQueue, Gap gap) {
            this.originQueue = originQueue;
            this.gap = gap;
        }

        public static List<GapOnQueue> onQueue(LimitingResourceQueue queue, Collection<? extends Gap> gaps) {
            List<GapOnQueue> result = new ArrayList<>();
            for (Gap each : gaps) {
                result.add(each.onQueue(queue));
            }
            return result;
        }

        public static List<GapOnQueue> onQueue(LimitingResourceQueue queue, DateAndHour startTime,
                DateAndHour endTime) {

            Gap gap = (endTime == null || endTime.compareTo(startTime) <= 0)
                    ? Gap.untilEnd(queue.getResource(), startTime)
                    : Gap.create(queue.getResource(), startTime, endTime);

            return GapOnQueue.onQueue(queue, Collections.singleton(gap));
        }

        public LimitingResourceQueue getOriginQueue() {
            return originQueue;
        }

        public Gap getGap() {
            return gap;
        }

        public List<GapOnQueue> splitIntoGapsSatisfyingCriteria(Set<Criterion> criteria) {
            return GapOnQueue.onQueue(originQueue,
                    gap.splitIntoGapsSatisfyingCriteria(originQueue.getResource(), criteria));
        }

        @Override
        public String toString() {
            return "queue: " + originQueue + "; gap: " + gap;
        }

    }

    /**
     * Stores a {@link GapOnQueue} plus its adjacent {@link LimitingResourceQueueElement}.
     *
     * @author Diego Pino Garca <dpino@igalia.com>
     */
    public static class GapOnQueueWithQueueElement {

        private final LimitingResourceQueueElement queueElement;

        private final GapOnQueue gapOnQueue;

        GapOnQueueWithQueueElement(GapOnQueue gapOnQueue, LimitingResourceQueueElement queueElement) {
            this.gapOnQueue = gapOnQueue;
            this.queueElement = queueElement;
        }

        public static GapOnQueueWithQueueElement create(GapOnQueue gapOnQueue,
                LimitingResourceQueueElement queueElement) {
            return new GapOnQueueWithQueueElement(gapOnQueue, queueElement);
        }

        public LimitingResourceQueueElement getQueueElement() {
            return queueElement;
        }

        public GapOnQueue getGapOnQueue() {
            return gapOnQueue;
        }

        /**
         * Joins first.gap + second.gap and keeps second.queueElement as {@link LimitingResourceQueueElement}.
         *
         * @param first
         * @param second
         * @return {@link GapOnQueueWithQueueElement}
         */
        public static GapOnQueueWithQueueElement coalesce(GapOnQueueWithQueueElement first,
                GapOnQueueWithQueueElement second) {

            LimitingResourceQueue queue = first.getGapOnQueue().getOriginQueue();
            DateAndHour startTime = first.getGapOnQueue().getGap().getStartTime();
            DateAndHour endTime = second.getGapOnQueue().getGap().getEndTime();
            Gap coalescedGap = Gap.create(queue.getResource(), startTime, endTime);

            return create(coalescedGap.onQueue(queue), second.getQueueElement());
        }

        @Override
        public String toString() {
            return "gapOnQueue: " + gapOnQueue + "; queueElement: " + queueElement;
        }

    }

    public static Gap untilEnd(LimitingResourceQueueElement current, DateAndHour startInclusive) {
        return untilEnd(current.getResource(), startInclusive);
    }

    private static Gap untilEnd(Resource resource, DateAndHour startInclusive) {
        return new Gap(resource, startInclusive, null);
    }

    public GapOnQueue onQueue(LimitingResourceQueue queue) {
        return new GapOnQueue(queue, this);
    }

    private Integer calculateHoursInGap(Resource resource, DateAndHour startTime, DateAndHour endTime) {
        if (endTime == null || startTime == null) {
            // startTime is never null when hours in gap is really use
            return Integer.MAX_VALUE;
        } else {
            return calculateHoursInGap(resource, startTime.getDate(), startTime.getHour(), endTime.getDate(),
                    endTime.getHour());
        }
    }

    private Integer calculateHoursInGap(Resource resource, LocalDate startDate, int startHour, LocalDate endDate,
            int endHour) {
        IntraDayDate intraStart = IntraDayDate.create(startDate, hours(startHour));
        IntraDayDate intraEnd = IntraDayDate.create(endDate, hours(endHour));
        return calculateHoursInGap(resource, intraStart, intraEnd);
    }

    private Integer calculateHoursInGap(Resource resource, IntraDayDate start, IntraDayDate end) {
        final ResourceCalendar calendar = resource.getCalendar();
        Iterable<PartialDay> days = start.daysUntil(end);
        EffortDuration result = zero();
        for (PartialDay each : days) {
            result = result.plus(calendar.getCapacityOn(each));
        }
        return result.roundToHours();
    }

    public List<Integer> getHoursInGapUntilAllocatingAndGoingToTheEnd(BaseCalendar calendar, DateAndHour realStart,
            DateAndHour allocationEnd, int total) {

        Validate.isTrue(endTime == null || allocationEnd.compareTo(endTime) <= 0);
        Validate.isTrue(startTime == null || realStart.compareTo(startTime) >= 0);
        Validate.isTrue(total >= 0);
        List<Integer> result = new ArrayList<>();

        // If endTime is null (last tasks) assume the end is in 10 years from now
        DateAndHour endDate = getEndTime();
        if (endDate == null) {
            endDate = DateAndHour.TEN_YEARS_FROM(realStart);
        }

        for (PartialDay each : realStart.toIntraDayDate().daysUntil(endDate.toIntraDayDate())) {
            int hoursAtDay = calendar.getCapacityOn(each).roundToHours();
            int hours = Math.min(hoursAtDay, total);
            total -= hours;

            // Don't add hours when total and hours are zero (it'd be like adding an extra 0 hour day when total is completed)
            if (total != 0 || hours != 0) {
                result.add(hours);
            }

            if (total == 0 && DateAndHour.from(each.getDate()).compareTo(allocationEnd) >= 0) {
                break;
            }
        }
        return result;
    }

    public static Gap create(Resource resource, DateAndHour startTime, DateAndHour endTime) {
        return new Gap(resource, startTime, endTime);
    }

    public DateAndHour getStartTime() {
        return startTime;
    }

    public DateAndHour getEndTime() {
        return endTime;
    }

    /**
     * Returns true if the gap starts after earlierStartDateBecauseOfGantt and if it's big enough for fitting candidate.
     *
     * @param candidate
     * @return boolean
     */
    public boolean canFit(LimitingResourceQueueElement candidate) {
        LocalDate startAfter = LocalDate.fromDateFields(candidate.getEarliestStartDateBecauseOfGantt());
        LocalDate endsAfter = LocalDate.fromDateFields(candidate.getEarliestEndDateBecauseOfGantt());

        return canSatisfyStartConstraint(startAfter) && canSatisfyEndConstraint(endsAfter)
                && hoursInGap >= candidate.getIntentedTotalHours();
    }

    private boolean canSatisfyStartConstraint(final LocalDate startsAfter) {
        return startsAfter.compareTo(startTime.getDate()) <= 0;
    }

    private boolean canSatisfyEndConstraint(LocalDate endsAfter) {
        return endTime == null || endsAfter.compareTo(endTime.getDate()) <= 0;
    }

    @Override
    public String toString() {
        String result = "";

        if (startTime != null) {
            result = startTime.getDate() + " - " + startTime.getHour();
        }
        if (endTime != null) {
            result += "; " + endTime.getDate() + " - " + endTime.getHour();
        }
        return result;
    }

    @Override
    public int compareTo(Gap other) {
        if (other == null) {
            return 1;
        }

        if (this.getStartTime() == null && other.getStartTime() == null) {
            return 0;

        } else if (this.getStartTime() == null) {
            return -1;

        } else if (other.getStartTime() == null) {
            return 1;
        }
        return this.getStartTime().compareTo(other.getStartTime());
    }

    public boolean isBefore(Gap gap) {
        return compareTo(gap) < 0;
    }

    public List<Gap> splitIntoGapsSatisfyingCriteria(Resource resource, Set<Criterion> criteria) {
        return splitIntoGapsSatisfyingCriteria(resource, criteria, getStartTime(), getEndTime());
    }

    /**
     * Returns a set of {@link Gap} composed by those gaps which satisfy <em>criteria</em> within the period:
     * <em>gapStartTime</em> till <em>gapEndTime</em>.
     *
     * @param resource
     * @param criteria
     *            criteria to be satisfied by resource
     * @param gapStartTime
     *            start time of gap
     * @param gapEndTime
     *            end time of gap
     * @return {@link List<Gap>}
     */
    private static List<Gap> splitIntoGapsSatisfyingCriteria(Resource resource, Set<Criterion> criteria,
            DateAndHour gapStartTime, DateAndHour gapEndTime) {

        AvailabilityTimeLine criterionsAvailability = AvailabilityCalculator.getCriterionsAvailabilityFor(criteria,
                resource);

        if (gapStartTime != null) {
            criterionsAvailability.invalidUntil(gapStartTime.getDate());
        }

        if (gapEndTime != null) {
            criterionsAvailability.invalidFrom(gapEndTime.getDate());
        }

        List<Interval> validPeriods = criterionsAvailability.getValidPeriods();
        List<Gap> result = new ArrayList<>();
        for (Interval each : validPeriods) {
            result.add(createGap(resource, each, gapStartTime, gapEndTime));
        }
        return result;
    }

    private static Gap createGap(Resource resource, Interval interval, DateAndHour originalGapStartTime,
            DateAndHour originalGapEndTime) {

        DateAndHour start = convert(originalGapStartTime, interval.getStart());
        DateAndHour end = convert(originalGapEndTime, interval.getEnd());
        return Gap.create(resource, start, end);
    }

    private static DateAndHour convert(DateAndHour possibleMatch, DatePoint datePoint) {
        if (datePoint instanceof StartOfTime || datePoint instanceof EndOfTime) {
            return null;
        }

        FixedPoint p = (FixedPoint) datePoint;
        if (possibleMatch != null && possibleMatch.getDate().equals(p.getDate())) {
            return possibleMatch;
        }
        return DateAndHour.from(p.getDate());
    }

}