com.outerspacecat.icalendar.RecurrencePredicates.java Source code

Java tutorial

Introduction

Here is the source code for com.outerspacecat.icalendar.RecurrencePredicates.java

Source

/**
 * Copyright 2011 Caleb Richardson
 * 
 * Licensed 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 com.outerspacecat.icalendar;

import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import java.time.DayOfWeek;
import java.time.temporal.ChronoField;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalField;
import java.time.temporal.ValueRange;

/**
 * Defines utility methods for working with recurrences.
 * 
 * @author Caleb Richardson
 */
final class RecurrencePredicates {
    private RecurrencePredicates() {
    }

    /**
     * Returns a predicate for determining whether or not to keep instances of
     * {@link FixedSchedule}. The returned predicate will return {@code true} for
     * a given {@link FixedSchedule} if the value of the field specified by
     * {@code field} is in {@code values}, {@code false} otherwise. If the
     * specified field is not applicable or if a value is out of range then the
     * predicate will return {@code false}.
     * 
     * @param field the field to check using each value in {@code values} . Must
     *        be non {@code null}.
     * @param values the values to check supplied fixed schedules against. Must be
     *        non {@code null}, must contain one or more elements, and each
     *        element must be >= 0.
     * @return a predicate for determining whether or not to keep instances of
     *         {@link FixedSchedule}. Never {@code null}.
     */
    static Predicate<ChronoFixedSchedule> limiter(final TemporalField field, final ImmutableSet<Integer> values) {
        Preconditions.checkNotNull(field, "field required");
        Preconditions.checkNotNull(values, "values required");
        Preconditions.checkArgument(!values.isEmpty(), "values must be non empty");
        for (Integer value : values)
            Preconditions.checkArgument(value >= 0, "each values element must be non negative");

        return new Predicate<ChronoFixedSchedule>() {
            @Override
            public boolean apply(final ChronoFixedSchedule input) {
                Preconditions.checkNotNull(input, "input required");

                Temporal partial = null;
                ValueRange range = null;

                if (input.getStartDate() != null) {
                    partial = input.getStartDate();

                    if (!partial.isSupported(field))
                        return false;

                    range = input.getStartDate().range(field);
                } else if (input.getZonedStartDateTime() != null) {
                    partial = input.getZonedStartDateTime().toLocalDateTime();

                    if (!partial.isSupported(field))
                        return false;

                    range = input.getZonedStartDateTime().range(field);
                } else {
                    throw new IllegalStateException("no start date or zoned start date-time");
                }

                for (Long value : Iterables.transform(values, (value) -> value.longValue())) {
                    if (!range.isValidValue(value))
                        continue;

                    if (partial.get(field) == value)
                        return true;
                }

                return false;
            }
        };
    }

    /**
     * Returns a predicate for determining whether or not to keep instances of
     * {@link FixedSchedule}. The returned predicate will return {@code true} for
     * a given {@link FixedSchedule} if the value of the field specified by
     * {@code field} is in {@code values}, {@code false} otherwise. If the
     * specified field is not applicable or if a value is out of range then the
     * predicate will return {@code false}.
     * 
     * @param field the field to check using each value in {@code values} . Must
     *        be non {@code null}.
     * @param values the values to check supplied fixed schedules against. Must be
     *        non {@code null}, must contain one or more elements. Elements may be
     *        be negative, but must be non zero.
     * @return a predicate for determining whether or not to keep instances of
     *         {@link FixedSchedule}. Never {@code null}.
     */
    static Predicate<ChronoFixedSchedule> signedLimiter(final TemporalField field,
            final ImmutableSet<Integer> values) {
        Preconditions.checkNotNull(field, "field required");
        Preconditions.checkNotNull(values, "values required");
        Preconditions.checkArgument(!values.isEmpty(), "values must be non empty");
        for (Integer value : values)
            Preconditions.checkArgument(value != 0, "each values element must be non zero");

        return new Predicate<ChronoFixedSchedule>() {
            @Override
            public boolean apply(final ChronoFixedSchedule input) {
                Preconditions.checkNotNull(input, "input required");

                Temporal partial = null;
                ValueRange range = null;

                if (input.getStartDate() != null) {
                    partial = input.getStartDate();

                    if (!partial.isSupported(field))
                        return true;

                    range = input.getStartDate().range(field);
                } else if (input.getZonedStartDateTime() != null) {
                    partial = input.getZonedStartDateTime().toLocalDateTime();

                    if (!partial.isSupported(field))
                        return true;

                    range = input.getZonedStartDateTime().range(field);
                } else {
                    throw new IllegalStateException("no start date or zoned start date-time");
                }

                for (Long value : Iterables.transform(values, (value) -> value.longValue())) {
                    if (value < 0)
                        value = range.getMaximum() + value + 1;

                    if (!range.isValidValue(value))
                        continue;

                    if (partial.get(field) == value)
                        return true;
                }

                return false;
            }
        };
    }

    /**
     * Returns a predicate for determining whether or not to keep instances of
     * {@link FixedSchedule}. Used to apply BYDAY values to a MONTHLY recurrence.
     * <p>
     * May not work for all chronologies.
     * 
     * @param byDay the values to check supplied fixed schedules against. Must be
     *        non {@code null} and must contain one or more elements. The second
     *        element of each tuple must be non {@code null}. If the first element
     *        is non {@code null}, then it must be >= 1 and <= 31.
     * @return a predicate for determining whether or not to keep instances of
     *         {@link FixedSchedule}. Never {@code null}.
     */
    static Predicate<ChronoFixedSchedule> monthlyByDayLimiter(final ImmutableSet<DayOfWeekOccurrence> byDay) {
        Preconditions.checkNotNull(byDay, "byDay required");
        Preconditions.checkArgument(!byDay.isEmpty(), "byDay must be non empty");

        return new Predicate<ChronoFixedSchedule>() {
            @Override
            public boolean apply(final ChronoFixedSchedule input) {
                Preconditions.checkNotNull(input, "input required");

                DayOfWeek dayOfWeek = DayOfWeek.of(input.getDateOfStart().get(ChronoField.DAY_OF_WEEK));

                int dayOfMonth = input.getDateOfStart().get(ChronoField.DAY_OF_MONTH);

                int numericPrefix = dayOfMonth / 7 + (dayOfMonth % 7 == 0 ? 0 : 1);

                long daysInMonth = input.getDateOfStart().range(ChronoField.DAY_OF_MONTH).getMaximum();

                int dayOfWeekOccurrencesInMonth = (int) (numericPrefix + ((daysInMonth - dayOfMonth) / 7));

                return byDay.contains(new DayOfWeekOccurrence(Optional.absent(), dayOfWeek))
                        || byDay.contains(new DayOfWeekOccurrence(Optional.fromNullable(numericPrefix), dayOfWeek))
                        || byDay.contains(new DayOfWeekOccurrence(
                                Optional.fromNullable(numericPrefix - dayOfWeekOccurrencesInMonth - 1), dayOfWeek));
            }
        };
    }

    /**
     * Returns a predicate for determining whether or not to keep instances of
     * {@link FixedSchedule}. Used to apply BYDAY values to a YEARLY recurrence.
     * <p>
     * May not work for all chronologies.
     * 
     * @param byDay the values to check supplied fixed schedules against. Must be
     *        non {@code null} and must contain one or more elements.
     * @return a predicate for determining whether or not to keep instances of
     *         {@link FixedSchedule}. Never {@code null}.
     */
    static Predicate<ChronoFixedSchedule> yearlyByDayLimiter(final ImmutableSet<DayOfWeekOccurrence> byDay) {
        Preconditions.checkNotNull(byDay, "byDay required");
        Preconditions.checkArgument(!byDay.isEmpty(), "byDay must be non empty");

        return new Predicate<ChronoFixedSchedule>() {
            @Override
            public boolean apply(final ChronoFixedSchedule input) {
                Preconditions.checkNotNull(input, "input required");

                DayOfWeek dayOfWeek = DayOfWeek.of(input.getDateOfStart().get(ChronoField.DAY_OF_WEEK));

                int dayOfYear = input.getDateOfStart().get(ChronoField.DAY_OF_YEAR);

                int numericPrefix = dayOfYear / 7 + (dayOfYear % 7 == 0 ? 0 : 1);

                int daysInYear = (int) (input.getDateOfStart().range(ChronoField.DAY_OF_YEAR).getMaximum());

                int dayOfWeekOccurrencesInYear = (int) (numericPrefix + ((daysInYear - dayOfYear) / 7));

                return byDay.contains(new DayOfWeekOccurrence(Optional.absent(), dayOfWeek))
                        || byDay.contains(new DayOfWeekOccurrence(Optional.fromNullable(numericPrefix), dayOfWeek))
                        || byDay.contains(new DayOfWeekOccurrence(
                                Optional.fromNullable(numericPrefix - dayOfWeekOccurrencesInYear - 1), dayOfWeek));
            }
        };
    }
}