org.jasig.schedassist.model.VisibleScheduleBuilder.java Source code

Java tutorial

Introduction

Here is the source code for org.jasig.schedassist.model.VisibleScheduleBuilder.java

Source

/**
 * Licensed to Jasig under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Jasig licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a
 * copy of the License at:
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.jasig.schedassist.model;

import java.util.Date;
import java.util.SortedSet;

import net.fortuna.ical4j.model.Calendar;
import net.fortuna.ical4j.model.Component;
import net.fortuna.ical4j.model.ComponentList;
import net.fortuna.ical4j.model.Period;
import net.fortuna.ical4j.model.PeriodList;
import net.fortuna.ical4j.model.Property;
import net.fortuna.ical4j.model.component.VEvent;

import org.apache.commons.lang.Validate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jasig.schedassist.NullAffiliationSourceImpl;

/**
 * This class implements the mechanism of merging the {@link IScheduleOwner}'s {@link AvailableSchedule}
 * and the {@link IScheduleOwner}'s {@link Calendar} for an {@link IScheduleVisitor}.
 * 
 * @author Nicholas Blair, nblair@doit.wisc.edu
 * @version $Id: VisibleScheduleBuilder.java 2530 2010-09-10 20:21:16Z npblair $
 */
public class VisibleScheduleBuilder implements IVisibleScheduleBuilder {

    private static Log LOG = LogFactory.getLog(VisibleScheduleBuilder.class);

    public static final String FREE = "free";
    public static final String BUSY = "busy";
    public static final String ATTENDING = "attending";

    private IEventUtils eventUtils = new DefaultEventUtilsImpl(new NullAffiliationSourceImpl());

    /**
     * Default Constructor, will set the eventUtils field to {@link DefaultEventUtilsImpl}.
     */
    public VisibleScheduleBuilder() {
    }

    /**
     * @param eventUtils
     */
    public VisibleScheduleBuilder(IEventUtils eventUtils) {
        this.eventUtils = eventUtils;
    }

    /**
     * @param eventUtils the eventUtils to set
     */
    public void setEventUtils(IEventUtils eventUtils) {
        this.eventUtils = eventUtils;
    }

    /*
     * (non-Javadoc)
     * @see org.jasig.schedassist.model.IVisibleScheduleBuilder#calculateVisibleSchedule(java.util.Date, java.util.Date, net.fortuna.ical4j.model.Calendar, org.jasig.schedassist.model.AvailableSchedule, org.jasig.schedassist.model.IScheduleOwner)
     */
    @Override
    public VisibleSchedule calculateVisibleSchedule(final Date startTime, final Date endTime,
            final Calendar calendar, final AvailableSchedule schedule, final IScheduleOwner owner) {
        return calculateVisibleScheduleNoAttendingCheck(startTime, endTime, calendar, schedule,
                owner.getPreferredMeetingDurations(), owner.getCalendarAccount());
    }

    /*
     * (non-Javadoc)
     * @see org.jasig.schedassist.model.IVisibleScheduleBuilder#calculateVisitorConflicts(java.util.Date, java.util.Date, net.fortuna.ical4j.model.Calendar, org.jasig.schedassist.model.AvailableSchedule, org.jasig.schedassist.model.MeetingDurations, org.jasig.schedassist.model.IScheduleVisitor)
     */
    @Override
    public VisibleSchedule calculateVisitorConflicts(Date startTime, Date endTime, Calendar calendar,
            AvailableSchedule schedule, MeetingDurations meetingDurations, IScheduleVisitor visitor) {
        return calculateVisibleScheduleNoAttendingCheck(startTime, endTime, calendar, schedule, meetingDurations,
                visitor.getCalendarAccount());
    }

    /*
     * (non-Javadoc)
     * @see org.jasig.schedassist.model.IVisibleScheduleBuilder#calculateVisibleSchedule(java.util.Date, java.util.Date, net.fortuna.ical4j.model.Calendar, org.jasig.schedassist.model.AvailableSchedule, org.jasig.schedassist.model.IScheduleOwner, org.jasig.schedassist.model.IScheduleVisitor)
     */
    @Override
    public VisibleSchedule calculateVisibleSchedule(final Date startTime, final Date endTime,
            final Calendar calendar, final AvailableSchedule schedule, final IScheduleOwner owner,
            final IScheduleVisitor visitor) {
        Validate.notNull(startTime, "startTime cannot be null");
        Validate.notNull(endTime, "endTime cannot be null");
        Validate.notNull(calendar, "calendar cannot be null");
        Validate.notNull(schedule, "available schedule cannot be null");
        Validate.notNull(owner, "owner cannot be null");

        ICalendarAccount visitorCalendarAccount = null;
        if (visitor != null) {
            visitorCalendarAccount = visitor.getCalendarAccount();
        }
        if (endTime.before(startTime)) {
            throw new IllegalArgumentException(
                    "cannot pass end time (" + endTime + ") that is before start time (" + startTime + ")");
        }
        LOG.debug("startTime: " + startTime + "; endTime: " + endTime);

        final MeetingDurations durations = owner.getPreferredMeetingDurations();

        // expand the passed in schedule's availableBlocks
        SortedSet<AvailableBlock> availableBlocks = AvailableBlockBuilder.expand(schedule.getAvailableBlocks(),
                durations.getMinLength());

        // create endpoints for the subset of availableBlocks
        AvailableBlock availabilityStartBlock = AvailableBlockBuilder.createPreferredMinimumDurationBlock(startTime,
                durations);
        AvailableBlock availabilityEndBlock = AvailableBlockBuilder.createPreferredMinimumDurationBlock(endTime,
                durations);

        // trim the availableBlocks set to within startTime/endTime
        availableBlocks = availableBlocks.subSet(availabilityStartBlock, availabilityEndBlock);

        // construct our return value
        VisibleSchedule visibleSchedule = new VisibleSchedule(durations);
        // add the trimmed availableSchedule to the visibleSchedule as "FREE" blocks
        visibleSchedule.addFreeBlocks(availableBlocks);

        // now iterate through the schedule and construct blocks to overwrite in the visibleSchedul
        ComponentList events = calendar.getComponents(Component.VEVENT);
        for (Object component : events) {
            VEvent event = (VEvent) component;

            boolean causesConflict = this.eventUtils.willEventCauseConflict(owner.getCalendarAccount(), event);
            if (!causesConflict) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("event will not cause conflict, skipping: " + event);
                }
                continue;
            }

            // if we reach this point, this event is not skippable,
            // it's going to be either BUSY, FREE with visitors, or ATTENDING
            if (eventUtils.isEventRecurring(event)) {
                // expand the recurrence rules
                PeriodList recurrenceList = this.eventUtils.calculateRecurrence(event, startTime, endTime);
                for (Object o : recurrenceList) {
                    Period period = (Period) o;
                    mutateAppropriateBlockInVisibleSchedule(visibleSchedule, event, owner.getCalendarAccount(),
                            visitorCalendarAccount, period.getStart(), period.getEnd(), true);
                }
            } else {
                // event is not recurring, just check block on start/end
                Date startDate = event.getStartDate().getDate();
                Date endDate = event.getEndDate(true).getDate();
                mutateAppropriateBlockInVisibleSchedule(visibleSchedule, event, owner.getCalendarAccount(),
                        visitorCalendarAccount, startDate, endDate, true);
            }
        }

        return visibleSchedule;
    }

    /**
     * 
     * @param startTime
     * @param endTime
     * @param calendar
     * @param schedule
     * @param meetingDurations
     * @param calendarAccount
     * @return an appropriate {@link VisibleSchedule}
     */
    protected VisibleSchedule calculateVisibleScheduleNoAttendingCheck(Date startTime, Date endTime,
            Calendar calendar, AvailableSchedule schedule, MeetingDurations meetingDurations,
            ICalendarAccount calendarAccount) {

        Validate.notNull(startTime, "startTime cannot be null");
        Validate.notNull(endTime, "endTime cannot be null");
        Validate.notNull(calendar, "calendar cannot be null");
        Validate.notNull(meetingDurations, "MeetingDurations argument cannot be null");
        Validate.notNull(schedule, "AvailableSchedule argument cannot be null");
        Validate.notNull(calendarAccount, "calendarAccount cannot be null");

        if (endTime.before(startTime)) {
            throw new IllegalArgumentException(
                    "cannot pass end time (" + endTime + ") that is before start time (" + startTime + ")");
        }
        LOG.debug("startTime: " + startTime + "; endTime: " + endTime);

        // expand the passed in schedule's availableBlocks
        SortedSet<AvailableBlock> availableBlocks = AvailableBlockBuilder.expand(schedule.getAvailableBlocks(),
                meetingDurations.getMinLength());

        // create endpoints for the subset of availableBlocks
        AvailableBlock availabilityStartBlock = AvailableBlockBuilder.createPreferredMinimumDurationBlock(startTime,
                meetingDurations);
        AvailableBlock availabilityEndBlock = AvailableBlockBuilder.createPreferredMinimumDurationBlock(endTime,
                meetingDurations);

        // trim the availableBlocks set to within startTime/endTime
        availableBlocks = availableBlocks.subSet(availabilityStartBlock, availabilityEndBlock);

        // construct our return value
        VisibleSchedule visibleSchedule = new VisibleSchedule(meetingDurations);
        // add the trimmed availableSchedule to the visibleSchedule as "FREE" blocks
        visibleSchedule.addFreeBlocks(availableBlocks);

        // now iterate through the schedule and construct blocks to overwrite in the visibleSchedul
        ComponentList events = calendar.getComponents(Component.VEVENT);
        for (Object component : events) {
            VEvent event = (VEvent) component;

            boolean causesConflict = this.eventUtils.willEventCauseConflict(calendarAccount, event);
            if (!causesConflict) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("event will not cause conflict, skipping: " + event);
                }
                continue;
            }

            // if we reach this point, this event is not skippable,
            // it's going to be either BUSY, FREE with visitors, or ATTENDING
            // whether event is recurring or not, check block on start/end
            Date startDate = event.getStartDate().getDate();
            Date endDate = event.getEndDate(true).getDate();
            mutateAppropriateBlockInVisibleSchedule(visibleSchedule, event, calendarAccount, null, startDate,
                    endDate, false);

            if (eventUtils.isEventRecurring(event)) {
                // expand the recurrence rules
                PeriodList recurrenceList = this.eventUtils.calculateRecurrence(event, startTime, endTime);
                for (Object o : recurrenceList) {
                    Period period = (Period) o;
                    mutateAppropriateBlockInVisibleSchedule(visibleSchedule, event, calendarAccount, null,
                            period.getStart(), period.getEnd(), false);
                }
            }
        }

        return visibleSchedule;

    }

    /**
     * Mutative method to alter the {@link VisibleSchedule} in an appropriate fashion according to the {@link VEvent}'s properties.
     * 
     * @param visibleSchedule
     * @param event
     * @param owner
     * @param visitor
     * @param eventInstanceStartDate
     * @param eventInstanceEndDate
     * @param performAttendingCheck
     */
    void mutateAppropriateBlockInVisibleSchedule(VisibleSchedule visibleSchedule, VEvent event,
            ICalendarAccount owner, ICalendarAccount visitor, Date eventInstanceStartDate,
            Date eventInstanceEndDate, boolean performAttendingCheck) {
        int visitorLimit = safeVisitorLimit(event);
        final AvailableBlock eventBlock = AvailableBlockBuilder.createBlock(eventInstanceStartDate,
                eventInstanceEndDate, visitorLimit);
        // test to see if this appointment is an available appointment
        Property availableEventMarker = event.getProperty(SchedulingAssistantAppointment.AVAILABLE_APPOINTMENT);
        if (null == availableEventMarker || !SchedulingAssistantAppointment.TRUE.equals(availableEventMarker)) {
            // non available appointments will ALWAYS simply be busy
            visibleSchedule.setBusyBlock(eventBlock);
        } else {
            // the event is an available appointment
            // first test if it's an ATTENDING match
            if (performAttendingCheck && null != visitor && this.eventUtils.isAttendingAsOwner(event, owner)
                    && this.eventUtils.isAttendingAsVisitor(event, visitor)) {
                visibleSchedule.setAttendingBlock(eventBlock);
            } else if (this.eventUtils.isAttendingAsOwner(event, owner)) {
                // not an attending match, check visitorLimit exceeded
                int availableVisitorCount = this.eventUtils.getScheduleVisitorCount(event);
                if (availableVisitorCount >= visitorLimit) {
                    // busy
                    visibleSchedule.setBusyBlock(eventBlock);
                } else {
                    // visitor count is less than limit - this is still free
                    // amend the block to represent current visitor count 
                    eventBlock.setVisitorsAttending(availableVisitorCount);
                    visibleSchedule.overwriteFreeBlockOnlyIfPresent(eventBlock);
                }
            } else {
                // the event is an available appointment, but does not match attending criteria and should
                // be considered busy
                visibleSchedule.setBusyBlock(eventBlock);
            }
        }
    }

    /**
     * Safely return the value of the {@link VisitorLimit} of the event.
     * If it's not set, this returns 1.
     * 
     * @param event
     * @return the value of the {@link VisitorLimit}, or 1 if not set.
     */
    int safeVisitorLimit(VEvent event) {
        Integer visitorLimit = eventUtils.getEventVisitorLimit(event);
        if (visitorLimit == null) {
            return 1;
        }

        return visitorLimit;
    }
}