All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.ical.iter.InstanceGenerators Maven / Gradle / Ivy

The newest version!
// Copyright (C) 2006 Google Inc.
//
// 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.google.ical.iter;

import com.google.ical.util.DTBuilder;
import com.google.ical.util.Predicate;
import com.google.ical.util.TimeUtils;
import com.google.ical.values.Frequency;
import com.google.ical.values.TimeValue;
import com.google.ical.values.Weekday;
import com.google.ical.values.DateValue;

import java.util.ArrayList;
import java.util.List;

/**
 * factory for generators that operate on groups of generators to generate full
 * dates.
 *
 * @author [email protected] (Mike Samuel)
 */
class InstanceGenerators {

  /**
   * a collector that yields each date in the period without doing any set
   * collecting.
   */
  static Generator serialInstanceGenerator(
      final Predicate filter,
      final Generator yearGenerator, final Generator monthGenerator,
      final Generator dayGenerator, final Generator hourGenerator,
      final Generator minuteGenerator, final Generator secondGenerator) {
    if (skipSubDayGenerators(hourGenerator, minuteGenerator, secondGenerator)) {
      // Fast case for generators that are not more frequent than daily.
      return new Generator() {
        @Override
        public boolean generate(DTBuilder builder)
            throws IteratorShortCircuitingException {
          // cascade through periods to compute the next date
          do {
            // until we run out of days in the current month
            while (!dayGenerator.generate(builder)) {
              // until we run out of months in the current year
              while (!monthGenerator.generate(builder)) {
                // if there are more years available fetch one
                if (!yearGenerator.generate(builder)) {
                  // otherwise the recurrence is exhausted
                  return false;
                }
              }
            }
            // apply filters to generated dates
          } while (!filter.apply(builder.toDateTime()));

          return true;
        }
      };
    } else {
      return new Generator() {
        @Override
        public boolean generate(DTBuilder builder)
            throws IteratorShortCircuitingException {
          // cascade through periods to compute the next date
          do {
            // until we run out of seconds in the current minute
            while (!secondGenerator.generate(builder)) {
              // until we run out of minutes in the current hour
              while (!minuteGenerator.generate(builder)) {
                // until we run out of hours in the current day
                while (!hourGenerator.generate(builder)) {
                  // until we run out of days in the current month
                  while (!dayGenerator.generate(builder)) {
                    // until we run out of months in the current year
                    while (!monthGenerator.generate(builder)) {
                      // if there are more years available fetch one
                      if (!yearGenerator.generate(builder)) {
                        // otherwise the recurrence is exhausted
                        return false;
                      }
                    }
                  }
                }
              }
            }
            // apply filters to generated dates
          } while (!filter.apply(builder.toDateTime()));
          // TODO: maybe group the filters into different kinds so we don't
          // apply filters that only affect days to every second.

          return true;
        }
      };
    }
  }

  static Generator bySetPosInstanceGenerator(
      int[] setPos, final Frequency freq, final Weekday wkst,
      final Predicate filter,
      final Generator yearGenerator, final Generator monthGenerator,
      final Generator dayGenerator, final Generator hourGenerator,
      final Generator minuteGenerator, final Generator secondGenerator) {
    final int[] uSetPos = Util.uniquify(setPos);

    final Generator serialInstanceGenerator = serialInstanceGenerator(
          filter, yearGenerator, monthGenerator, dayGenerator,
          hourGenerator, minuteGenerator, secondGenerator);

    final boolean allPositive;
    final int maxPos;
    if (false) {
      int mp = 0;
      boolean ap = true;
      for (int i = setPos.length; --i >= 0;) {
        if (setPos[i] < 0) {
          ap = false;
          break;
        }
        mp = Math.max(setPos[i], mp);
      }
      maxPos = mp;
      allPositive = ap;
    } else {
      // TODO(msamuel): does this work?
      maxPos = uSetPos[uSetPos.length - 1];
      allPositive = uSetPos[0] > 0;
    }

    return new Generator() {
        DateValue pushback = null;
        /**
         * Is this the first instance we generate?
         * We need to know so that we don't clobber dtStart.
         */
        boolean first = true;
        /** Do we need to halt iteration once the current set has been used? */
        boolean done = false;

        /** The elements in the current set, filtered by set pos */
        List candidates;
        /**
         * index into candidates.  The number of elements in candidates already
         * consumed.
         */
        int i;

        @Override
        public boolean generate(DTBuilder builder)
            throws IteratorShortCircuitingException {
          while (null == candidates || i >= candidates.size()) {
            if (done) { return false; }

            // (1) Make sure that builder is appropriately initialized so that
            // we only generate instances in the next set

            DateValue d0 = null;
            if (null != pushback) {
              d0 = pushback;
              builder.year = d0.year();
              builder.month = d0.month();
              builder.day = d0.day();
              pushback = null;
            } else if (!first) {
              // we need to skip ahead to the next item since we didn't exhaust
              // the last period
              switch (freq) {
                case YEARLY:
                  if (!yearGenerator.generate(builder)) { return false; }
                  // $FALL-THROUGH$
                case MONTHLY:
                  while (!monthGenerator.generate(builder)) {
                    if (!yearGenerator.generate(builder)) { return false; }
                  }
                  break;
                case WEEKLY:
                  // consume because just incrementing date doesn't do anything
                  DateValue nextWeek =
                    Util.nextWeekStart(builder.toDateTime(), wkst);
                  do {
                    if (!serialInstanceGenerator.generate(builder)) {
                      return false;
                    }
                  } while (builder.compareTo(nextWeek) < 0);
                  d0 = builder.toDateTime();
                  break;
                default:
                  break;
              }
            } else {
              first = false;
            }

            // (2) Build a set of the dates in the year/month/week that match
            // the other rule.
            List dates = new ArrayList();
            if (null != d0) { dates.add(d0); }

            // Optimization: if min(bySetPos) > 0 then we already have absolute
            // positions, so we don't need to generate all of the instances for
            // the period.
            // This speeds up things like the first weekday of the year:
            //     RRULE:FREQ=YEARLY;BYDAY=MO,TU,WE,TH,FR,BYSETPOS=1
            // that would otherwise generate 260+ instances per one emitted
            // TODO(msamuel): this may be premature.  If needed, We could
            // improve more generally by inferring a BYMONTH generator based on
            // distribution of set positions within the year.
            int limit = allPositive ? maxPos : Integer.MAX_VALUE;

            while (limit > dates.size()) {
              if (!serialInstanceGenerator.generate(builder)) {
                // If we can't generate any, then make sure we return false
                // once the instances we have generated are exhausted.
                // If this is returning false due to some artificial limit, such
                // as the 100 year limit in serialYearGenerator, then we exit
                // via an exception because otherwise we would pick the wrong
                // elements for some uSetPoses that contain negative elements.
                done = true;
                break;
              }
              DateValue d = builder.toDateTime();
              boolean contained = false;
              if (null == d0) {
                d0 = d;
                contained = true;
              } else {
                switch (freq) {
                  case WEEKLY:
                    int nb = TimeUtils.daysBetween(d, d0);
                    // Two dates (d, d0) are in the same week
                    // if there isn't a whole week in between them and the
                    // later day is later in the week than the earlier day.
                    contained =
                      nb < 7
                      && ((7 + Weekday.valueOf(d).javaDayNum
                           - wkst.javaDayNum) % 7)
                      > ((7 + Weekday.valueOf(d0).javaDayNum
                          - wkst.javaDayNum) % 7);
                    break;
                  case MONTHLY:
                    contained =
                      d0.month() == d.month() && d0.year() == d.year();
                    break;
                  case YEARLY:
                    contained = d0.year() == d.year();
                    break;
                  default:
                    done = true;
                    return false;
                }
              }
              if (contained) {
                dates.add(d);
              } else {
                // reached end of the set
                pushback = d;  // save d so we can use it later
                break;
              }
            }

            // (3) Resolve the positions to absolute positions and order them
            int[] absSetPos;
            if (allPositive) {
              absSetPos = uSetPos;
            } else {
              IntSet uAbsSetPos = new IntSet();
              for (int j = 0; j < uSetPos.length; ++j) {
                int p = uSetPos[j];
                if (p < 0) { p = dates.size() + p + 1; }
                uAbsSetPos.add(p);
              }
              absSetPos = uAbsSetPos.toIntArray();
            }

            candidates = new ArrayList();
            for (int p : absSetPos) {
              if (p >= 1 && p <= dates.size()) {  // p is 1-indexed
                candidates.add(dates.get(p - 1));
              }
            }
            i = 0;
            if (candidates.isEmpty()) {
              // none in this region, so keep looking
              candidates = null;
              continue;
            }
          }
          // (5) Emit a date.  It will be checked against the end condition and
          // dtStart elsewhere
          DateValue d = candidates.get(i++);
          builder.year = d.year();
          builder.month = d.month();
          builder.day = d.day();
          if (d instanceof TimeValue) {
            TimeValue t = (TimeValue) d;
            builder.hour = t.hour();
            builder.minute = t.minute();
            builder.second = t.second();
          }
          return true;
        }
      };
  }

  static boolean skipSubDayGenerators(
      Generator hourGenerator, Generator minuteGenerator,
      Generator secondGenerator) {
    return secondGenerator instanceof SingleValueGenerator
        && minuteGenerator instanceof SingleValueGenerator
        && hourGenerator instanceof SingleValueGenerator;
  }

  private InstanceGenerators() {
    // uninstantiable
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy