001    /**
002     * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    /*
016     * Copyright (c) 2000, Columbia University.  All rights reserved.
017     *
018     * Redistribution and use in source and binary forms, with or without
019     * modification, are permitted provided that the following conditions are met:
020     *
021     * 1. Redistributions of source code must retain the above copyright
022     *        notice, this list of conditions and the following disclaimer.
023     *
024     * 2. Redistributions in binary form must reproduce the above copyright
025     *        notice, this list of conditions and the following disclaimer in the
026     *        documentation and/or other materials provided with the distribution.
027     *
028     * 3. Neither the name of the University nor the names of its contributors
029     *        may be used to endorse or promote products derived from this software
030     *        without specific prior written permission.
031     *
032     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS
033     * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
034     * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
035     * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
036     * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
037     * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
038     * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
039     * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
040     * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
041     * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
042     * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
043     */
044    
045    package com.liferay.util.cal;
046    
047    import com.liferay.portal.kernel.util.ArrayUtil;
048    import com.liferay.portal.kernel.util.CalendarFactoryUtil;
049    import com.liferay.portal.kernel.util.StringBundler;
050    import com.liferay.portal.kernel.util.StringPool;
051    import com.liferay.portal.kernel.util.TimeZoneUtil;
052    
053    import java.io.Serializable;
054    
055    import java.util.Calendar;
056    import java.util.Date;
057    
058    /**
059     * @author     Jonathan Lennox
060     * @deprecated As of 6.2.0, moved to {@link
061     *             com.liferay.portal.kernel.cal.Recurrence}
062     */
063    @Deprecated
064    public class Recurrence implements Serializable {
065    
066            /**
067             * Field DAILY
068             */
069            public static final int DAILY = 3;
070    
071            /**
072             * Field MONTHLY
073             */
074            public static final int MONTHLY = 5;
075    
076            /**
077             * Field NO_RECURRENCE
078             */
079            public static final int NO_RECURRENCE = 7;
080    
081            /**
082             * Field WEEKLY
083             */
084            public static final int WEEKLY = 4;
085    
086            /**
087             * Field YEARLY
088             */
089            public static final int YEARLY = 6;
090    
091            /**
092             * Constructor Recurrence
093             */
094            public Recurrence() {
095                    this(null, new Duration(), NO_RECURRENCE);
096            }
097    
098            /**
099             * Constructor Recurrence
100             */
101            public Recurrence(Calendar start, Duration dur) {
102                    this(start, dur, NO_RECURRENCE);
103            }
104    
105            /**
106             * Constructor Recurrence
107             */
108            public Recurrence(Calendar start, Duration dur, int freq) {
109                    setDtStart(start);
110    
111                    duration = (Duration)dur.clone();
112                    frequency = freq;
113                    interval = 1;
114            }
115    
116            /* Accessors */
117    
118            /**
119             * Method getByDay
120             *
121             * @return DayAndPosition[]
122             */
123            public DayAndPosition[] getByDay() {
124                    if (byDay == null) {
125                            return null;
126                    }
127    
128                    DayAndPosition[] b = new DayAndPosition[byDay.length];
129    
130                    /*
131                     * System.arraycopy isn't good enough -- we want to clone each
132                     * individual element.
133                     */
134                    for (int i = 0; i < byDay.length; i++) {
135                            b[i] = (DayAndPosition)byDay[i].clone();
136                    }
137    
138                    return b;
139            }
140    
141            /**
142             * Method getByMonth
143             *
144             * @return int[]
145             */
146            public int[] getByMonth() {
147                    if (byMonth == null) {
148                            return null;
149                    }
150    
151                    int[] b = new int[byMonth.length];
152    
153                    System.arraycopy(byMonth, 0, b, 0, byMonth.length);
154    
155                    return b;
156            }
157    
158            /**
159             * Method getByMonthDay
160             *
161             * @return int[]
162             */
163            public int[] getByMonthDay() {
164                    if (byMonthDay == null) {
165                            return null;
166                    }
167    
168                    int[] b = new int[byMonthDay.length];
169    
170                    System.arraycopy(byMonthDay, 0, b, 0, byMonthDay.length);
171    
172                    return b;
173            }
174    
175            /**
176             * Method getByWeekNo
177             *
178             * @return int[]
179             */
180            public int[] getByWeekNo() {
181                    if (byWeekNo == null) {
182                            return null;
183                    }
184    
185                    int[] b = new int[byWeekNo.length];
186    
187                    System.arraycopy(byWeekNo, 0, b, 0, byWeekNo.length);
188    
189                    return b;
190            }
191    
192            /**
193             * Method getByYearDay
194             *
195             * @return int[]
196             */
197            public int[] getByYearDay() {
198                    if (byYearDay == null) {
199                            return null;
200                    }
201    
202                    int[] b = new int[byYearDay.length];
203    
204                    System.arraycopy(byYearDay, 0, b, 0, byYearDay.length);
205    
206                    return b;
207            }
208    
209            /**
210             * Method getCandidateStartTime
211             *
212             * @param  current the current time
213             * @return Calendar
214             */
215            public Calendar getCandidateStartTime(Calendar current) {
216                    if (dtStart.getTime().getTime() > current.getTime().getTime()) {
217                            throw new IllegalArgumentException("Current time before DtStart");
218                    }
219    
220                    int minInterval = getMinimumInterval();
221                    Calendar candidate = (Calendar)current.clone();
222    
223                    if (true) {
224    
225                            // This block is only needed while this function is public...
226    
227                            candidate.clear(Calendar.ZONE_OFFSET);
228                            candidate.clear(Calendar.DST_OFFSET);
229                            candidate.setTimeZone(TimeZoneUtil.getTimeZone(StringPool.UTC));
230                            candidate.setMinimalDaysInFirstWeek(4);
231                            candidate.setFirstDayOfWeek(dtStart.getFirstDayOfWeek());
232                    }
233    
234                    if (frequency == NO_RECURRENCE) {
235                            candidate.setTime(dtStart.getTime());
236    
237                            return candidate;
238                    }
239    
240                    reduce_constant_length_field(Calendar.SECOND, dtStart, candidate);
241                    reduce_constant_length_field(Calendar.MINUTE, dtStart, candidate);
242                    reduce_constant_length_field(Calendar.HOUR_OF_DAY, dtStart, candidate);
243    
244                    switch (minInterval) {
245    
246                            case DAILY :
247    
248                                    /* No more adjustments needed */
249    
250                                    break;
251    
252                            case WEEKLY :
253                                    reduce_constant_length_field(
254                                            Calendar.DAY_OF_WEEK, dtStart, candidate);
255                                    break;
256    
257                            case MONTHLY :
258                                    reduce_day_of_month(dtStart, candidate);
259                                    break;
260    
261                            case YEARLY :
262                                    reduce_day_of_year(dtStart, candidate);
263                                    break;
264                    }
265    
266                    return candidate;
267            }
268    
269            /**
270             * Method getDtEnd
271             *
272             * @return Calendar
273             */
274            public Calendar getDtEnd() {
275    
276                    /*
277                     * Make dtEnd a cloned dtStart, so non-time fields of the Calendar
278                     * are accurate.
279                     */
280                    Calendar tempEnd = (Calendar)dtStart.clone();
281    
282                    tempEnd.setTime(
283                            new Date(dtStart.getTime().getTime() + duration.getInterval()));
284    
285                    return tempEnd;
286            }
287    
288            /**
289             * Method getDtStart
290             *
291             * @return Calendar
292             */
293            public Calendar getDtStart() {
294                    return (Calendar)dtStart.clone();
295            }
296    
297            /**
298             * Method getDuration
299             *
300             * @return Duration
301             */
302            public Duration getDuration() {
303                    return (Duration)duration.clone();
304            }
305    
306            /**
307             * Method getFrequency
308             *
309             * @return int
310             */
311            public int getFrequency() {
312                    return frequency;
313            }
314    
315            /**
316             * Method getInterval
317             *
318             * @return int
319             */
320            public int getInterval() {
321                    return interval;
322            }
323    
324            /**
325             * Method getOccurrence
326             *
327             * @return int
328             */
329            public int getOccurrence() {
330                    return occurrence;
331            }
332    
333            /**
334             * Method getUntil
335             *
336             * @return Calendar
337             */
338            public Calendar getUntil() {
339                    return ((until != null) ? (Calendar)until.clone() : null);
340            }
341    
342            /**
343             * Method getWeekStart
344             *
345             * @return int
346             */
347            public int getWeekStart() {
348                    return dtStart.getFirstDayOfWeek();
349            }
350    
351            /**
352             * Method isInRecurrence
353             *
354             * @param  current the current time
355             * @return boolean
356             */
357            public boolean isInRecurrence(Calendar current) {
358                    return isInRecurrence(current, false);
359            }
360    
361            /**
362             * Method isInRecurrence
363             *
364             * @param  current the current time
365             * @param  debug whether to print debug messages
366             * @return boolean
367             */
368            public boolean isInRecurrence(Calendar current, boolean debug) {
369                    Calendar myCurrent = (Calendar)current.clone();
370    
371                    // Do all calculations in GMT.  Keep other parameters consistent.
372    
373                    myCurrent.clear(Calendar.ZONE_OFFSET);
374                    myCurrent.clear(Calendar.DST_OFFSET);
375                    myCurrent.setTimeZone(TimeZoneUtil.getTimeZone(StringPool.UTC));
376                    myCurrent.setMinimalDaysInFirstWeek(4);
377                    myCurrent.setFirstDayOfWeek(dtStart.getFirstDayOfWeek());
378                    myCurrent.set(Calendar.SECOND, 0);
379                    myCurrent.set(Calendar.MILLISECOND, 0);
380    
381                    if (myCurrent.getTime().getTime() < dtStart.getTime().getTime()) {
382    
383                            // The current time is earlier than the start time.
384    
385                            if (debug) {
386                                    System.err.println("current < start");
387                            }
388    
389                            return false;
390                    }
391    
392                    Calendar candidate = getCandidateStartTime(myCurrent);
393    
394                    /* Loop over ranges for the duration. */
395    
396                    while ((candidate.getTime().getTime() + duration.getInterval()) >
397                                            myCurrent.getTime().getTime()) {
398    
399                            if (candidateIsInRecurrence(candidate, debug)) {
400                                    return true;
401                            }
402    
403                            /* Roll back to one second previous, and try again. */
404    
405                            candidate.add(Calendar.SECOND, -1);
406    
407                            /* Make sure we haven't rolled back to before dtStart. */
408    
409                            if (candidate.getTime().getTime() < dtStart.getTime().getTime()) {
410                                    if (debug) {
411                                            System.err.println("No candidates after dtStart");
412                                    }
413    
414                                    return false;
415                            }
416    
417                            candidate = getCandidateStartTime(candidate);
418                    }
419    
420                    if (debug) {
421                            System.err.println("No matching candidates");
422                    }
423    
424                    return false;
425            }
426    
427            /**
428             * Method setByDay
429             */
430            public void setByDay(DayAndPosition[] b) {
431                    if (b == null) {
432                            byDay = null;
433    
434                            return;
435                    }
436    
437                    byDay = new DayAndPosition[b.length];
438    
439                    /*
440                     * System.arraycopy isn't good enough -- we want to clone each
441                     * individual element.
442                     */
443                    for (int i = 0; i < b.length; i++) {
444                            byDay[i] = (DayAndPosition)b[i].clone();
445                    }
446            }
447    
448            /**
449             * Method setByMonth
450             */
451            public void setByMonth(int[] b) {
452                    if (b == null) {
453                            byMonth = null;
454    
455                            return;
456                    }
457    
458                    byMonth = new int[b.length];
459    
460                    System.arraycopy(b, 0, byMonth, 0, b.length);
461            }
462    
463            /**
464             * Method setByMonthDay
465             */
466            public void setByMonthDay(int[] b) {
467                    if (b == null) {
468                            byMonthDay = null;
469    
470                            return;
471                    }
472    
473                    byMonthDay = new int[b.length];
474    
475                    System.arraycopy(b, 0, byMonthDay, 0, b.length);
476            }
477    
478            /**
479             * Method setByWeekNo
480             */
481            public void setByWeekNo(int[] b) {
482                    if (b == null) {
483                            byWeekNo = null;
484    
485                            return;
486                    }
487    
488                    byWeekNo = new int[b.length];
489    
490                    System.arraycopy(b, 0, byWeekNo, 0, b.length);
491            }
492    
493            /**
494             * Method setByYearDay
495             */
496            public void setByYearDay(int[] b) {
497                    if (b == null) {
498                            byYearDay = null;
499    
500                            return;
501                    }
502    
503                    byYearDay = new int[b.length];
504    
505                    System.arraycopy(b, 0, byYearDay, 0, b.length);
506            }
507    
508            /**
509             * Method setDtEnd
510             */
511            public void setDtEnd(Calendar end) {
512                    Calendar tempEnd = (Calendar)end.clone();
513    
514                    tempEnd.clear(Calendar.ZONE_OFFSET);
515                    tempEnd.clear(Calendar.DST_OFFSET);
516                    tempEnd.setTimeZone(TimeZoneUtil.getTimeZone(StringPool.UTC));
517                    duration.setInterval(
518                            tempEnd.getTime().getTime() - dtStart.getTime().getTime());
519            }
520    
521            /**
522             * Method setDtStart
523             */
524            public void setDtStart(Calendar start) {
525                    int oldStart;
526    
527                    if (dtStart != null) {
528                            oldStart = dtStart.getFirstDayOfWeek();
529                    }
530                    else {
531                            oldStart = Calendar.MONDAY;
532                    }
533    
534                    if (start == null) {
535                            dtStart = CalendarFactoryUtil.getCalendar(
536                                    TimeZoneUtil.getTimeZone(StringPool.UTC));
537    
538                            dtStart.setTime(new Date(0L));
539                    }
540                    else {
541                            dtStart = (Calendar)start.clone();
542    
543                            dtStart.clear(Calendar.ZONE_OFFSET);
544                            dtStart.clear(Calendar.DST_OFFSET);
545                            dtStart.setTimeZone(TimeZoneUtil.getTimeZone(StringPool.UTC));
546                    }
547    
548                    dtStart.setMinimalDaysInFirstWeek(4);
549                    dtStart.setFirstDayOfWeek(oldStart);
550                    dtStart.set(Calendar.SECOND, 0);
551                    dtStart.set(Calendar.MILLISECOND, 0);
552            }
553    
554            /**
555             * Method setDuration
556             */
557            public void setDuration(Duration d) {
558                    duration = (Duration)d.clone();
559            }
560    
561            /**
562             * Method setFrequency
563             */
564            public void setFrequency(int freq) {
565                    if ((frequency != DAILY) && (frequency != WEEKLY) &&
566                            (frequency != MONTHLY) && (frequency != YEARLY) &&
567                            (frequency != NO_RECURRENCE)) {
568    
569                            throw new IllegalArgumentException("Invalid frequency");
570                    }
571    
572                    frequency = freq;
573            }
574    
575            /**
576             * Method setInterval
577             */
578            public void setInterval(int intr) {
579                    interval = (intr > 0) ? intr : 1;
580            }
581    
582            /**
583             * Method setOccurrence
584             */
585            public void setOccurrence(int occur) {
586                    occurrence = occur;
587            }
588    
589            /**
590             * Method setUntil
591             */
592            public void setUntil(Calendar u) {
593                    if (u == null) {
594                            until = null;
595    
596                            return;
597                    }
598    
599                    until = (Calendar)u.clone();
600    
601                    until.clear(Calendar.ZONE_OFFSET);
602                    until.clear(Calendar.DST_OFFSET);
603                    until.setTimeZone(TimeZoneUtil.getTimeZone(StringPool.UTC));
604            }
605    
606            /**
607             * Method setWeekStart
608             */
609            public void setWeekStart(int weekstart) {
610                    dtStart.setFirstDayOfWeek(weekstart);
611            }
612    
613            /**
614             * Method toString
615             *
616             * @return String
617             */
618            @Override
619            public String toString() {
620                    StringBundler sb = new StringBundler();
621    
622                    sb.append(getClass().getName());
623                    sb.append("[dtStart=");
624                    sb.append((dtStart != null) ? dtStart.toString() : "null");
625                    sb.append(",duration=");
626                    sb.append((duration != null) ? duration.toString() : "null");
627                    sb.append(",frequency=");
628                    sb.append(frequency);
629                    sb.append(",interval=");
630                    sb.append(interval);
631                    sb.append(",until=");
632                    sb.append((until != null) ? until.toString() : "null");
633                    sb.append(",byDay=");
634    
635                    if (byDay == null) {
636                            sb.append("null");
637                    }
638                    else {
639                            sb.append("[");
640    
641                            for (int i = 0; i < byDay.length; i++) {
642                                    if (i != 0) {
643                                            sb.append(",");
644                                    }
645    
646                                    if (byDay[i] != null) {
647                                            sb.append(byDay[i].toString());
648                                    }
649                                    else {
650                                            sb.append("null");
651                                    }
652                            }
653    
654                            sb.append("]");
655                    }
656    
657                    sb.append(",byMonthDay=");
658                    sb.append(stringizeIntArray(byMonthDay));
659                    sb.append(",byYearDay=");
660                    sb.append(stringizeIntArray(byYearDay));
661                    sb.append(",byWeekNo=");
662                    sb.append(stringizeIntArray(byWeekNo));
663                    sb.append(",byMonth=");
664                    sb.append(stringizeIntArray(byMonth));
665                    sb.append(']');
666    
667                    return sb.toString();
668            }
669    
670            /**
671             * Method getDayNumber
672             *
673             * @return long
674             */
675            protected static long getDayNumber(Calendar cal) {
676                    Calendar tempCal = (Calendar)cal.clone();
677    
678                    // Set to midnight, GMT
679    
680                    tempCal.set(Calendar.MILLISECOND, 0);
681                    tempCal.set(Calendar.SECOND, 0);
682                    tempCal.set(Calendar.MINUTE, 0);
683                    tempCal.set(Calendar.HOUR_OF_DAY, 0);
684    
685                    return tempCal.getTime().getTime() / (24 * 60 * 60 * 1000);
686            }
687    
688            /**
689             * Method getMonthNumber
690             *
691             * @return long
692             */
693            protected static long getMonthNumber(Calendar cal) {
694                    return
695                            ((cal.get(Calendar.YEAR) - 1970) * 12L) +
696                                    ((cal.get(Calendar.MONTH) - Calendar.JANUARY));
697            }
698    
699            /**
700             * Method getWeekNumber
701             *
702             * @return long
703             */
704            protected static long getWeekNumber(Calendar cal) {
705                    Calendar tempCal = (Calendar)cal.clone();
706    
707                    // Set to midnight, GMT
708    
709                    tempCal.set(Calendar.MILLISECOND, 0);
710                    tempCal.set(Calendar.SECOND, 0);
711                    tempCal.set(Calendar.MINUTE, 0);
712                    tempCal.set(Calendar.HOUR_OF_DAY, 0);
713    
714                    // Roll back to the first day of the week
715    
716                    int delta =
717                            tempCal.getFirstDayOfWeek() - tempCal.get(Calendar.DAY_OF_WEEK);
718    
719                    if (delta > 0) {
720                            delta -= 7;
721                    }
722    
723                    // tempCal now points to the first instant of this week.
724    
725                    // Calculate the "week epoch" -- the weekstart day closest to January 1,
726                    // 1970 (which was a Thursday)
727    
728                    long weekEpoch =
729                            (tempCal.getFirstDayOfWeek() - Calendar.THURSDAY) * 24L * 60 * 60 *
730                                    1000;
731    
732                    return
733                            (tempCal.getTime().getTime() - weekEpoch) /
734                                    (7 * 24 * 60 * 60 * 1000);
735            }
736    
737            /**
738             * Method reduce_constant_length_field
739             */
740            protected static void reduce_constant_length_field(
741                    int field, Calendar start, Calendar candidate) {
742    
743                    if ((start.getMaximum(field) != start.getLeastMaximum(field)) ||
744                            (start.getMinimum(field) != start.getGreatestMinimum(field))) {
745    
746                            throw new IllegalArgumentException("Not a constant length field");
747                    }
748    
749                    int fieldLength =
750                            (start.getMaximum(field) - start.getMinimum(field) + 1);
751                    int delta = start.get(field) - candidate.get(field);
752    
753                    if (delta > 0) {
754                            delta -= fieldLength;
755                    }
756    
757                    candidate.add(field, delta);
758            }
759    
760            /**
761             * Method reduce_day_of_month
762             */
763            protected static void reduce_day_of_month(
764                    Calendar start, Calendar candidate) {
765    
766                    Calendar tempCal = (Calendar)candidate.clone();
767    
768                    tempCal.add(Calendar.MONTH, -1);
769    
770                    int delta = start.get(Calendar.DATE) - candidate.get(Calendar.DATE);
771    
772                    if (delta > 0) {
773                            delta -= tempCal.getActualMaximum(Calendar.DATE);
774                    }
775    
776                    candidate.add(Calendar.DATE, delta);
777    
778                    while (start.get(Calendar.DATE) != candidate.get(Calendar.DATE)) {
779                            tempCal.add(Calendar.MONTH, -1);
780                            candidate.add(
781                                    Calendar.DATE, -tempCal.getActualMaximum(Calendar.DATE));
782                    }
783            }
784    
785            /**
786             * Method reduce_day_of_year
787             */
788            protected static void reduce_day_of_year(
789                    Calendar start, Calendar candidate) {
790    
791                    if ((start.get(Calendar.MONTH) > candidate.get(Calendar.MONTH)) ||
792                            ((start.get(Calendar.MONTH) == candidate.get(Calendar.MONTH)) &&
793                             (start.get(Calendar.DATE) > candidate.get(Calendar.DATE)))) {
794    
795                            candidate.add(Calendar.YEAR, -1);
796                    }
797    
798                    /* Set the candidate date to the start date. */
799    
800                    candidate.set(Calendar.MONTH, start.get(Calendar.MONTH));
801                    candidate.set(Calendar.DATE, start.get(Calendar.DATE));
802    
803                    while ((start.get(Calendar.MONTH) != candidate.get(Calendar.MONTH)) ||
804                               (start.get(Calendar.DATE) != candidate.get(Calendar.DATE))) {
805    
806                            candidate.add(Calendar.YEAR, -1);
807                            candidate.set(Calendar.MONTH, start.get(Calendar.MONTH));
808                            candidate.set(Calendar.DATE, start.get(Calendar.DATE));
809                    }
810            }
811    
812            /**
813             * Method candidateIsInRecurrence
814             *
815             * @return boolean
816             */
817            protected boolean candidateIsInRecurrence(
818                    Calendar candidate, boolean debug) {
819    
820                    if ((until != null) &&
821                            (candidate.getTime().getTime() > until.getTime().getTime())) {
822    
823                            // After "until"
824    
825                            if (debug) {
826                                    System.err.println("after until");
827                            }
828    
829                            return false;
830                    }
831    
832                    if ((getRecurrenceCount(candidate) % interval) != 0) {
833    
834                            // Not a repetition of the interval
835    
836                            if (debug) {
837                                    System.err.println("not an interval rep");
838                            }
839    
840                            return false;
841                    }
842                    else if ((occurrence > 0) &&
843                                     (getRecurrenceCount(candidate) >= occurrence)) {
844    
845                            return false;
846                    }
847    
848                    if (!matchesByDay(candidate) || !matchesByMonthDay(candidate) ||
849                            !matchesByYearDay(candidate) || !matchesByWeekNo(candidate) ||
850                            !matchesByMonth(candidate)) {
851    
852                            // Doesn't match a by* rule
853    
854                            if (debug) {
855                                    System.err.println("doesn't match a by*");
856                            }
857    
858                            return false;
859                    }
860    
861                    if (debug) {
862                            System.err.println("All checks succeeded");
863                    }
864    
865                    return true;
866            }
867    
868            /**
869             * Method getMinimumInterval
870             *
871             * @return int
872             */
873            protected int getMinimumInterval() {
874                    if ((frequency == DAILY) || (byDay != null) || (byMonthDay != null) ||
875                            (byYearDay != null)) {
876    
877                            return DAILY;
878                    }
879                    else if ((frequency == WEEKLY) || (byWeekNo != null)) {
880                            return WEEKLY;
881                    }
882                    else if ((frequency == MONTHLY) || (byMonth != null)) {
883                            return MONTHLY;
884                    }
885                    else if (frequency == YEARLY) {
886                            return YEARLY;
887                    }
888                    else if (frequency == NO_RECURRENCE) {
889                            return NO_RECURRENCE;
890                    }
891                    else {
892    
893                            // Shouldn't happen
894    
895                            throw new IllegalStateException(
896                                    "Internal error: Unknown frequency value");
897                    }
898            }
899    
900            /**
901             * Method getRecurrenceCount
902             *
903             * @return int
904             */
905            protected int getRecurrenceCount(Calendar candidate) {
906                    switch (frequency) {
907    
908                            case NO_RECURRENCE :
909                                    return 0;
910    
911                            case DAILY :
912                                    return (int)(getDayNumber(candidate) - getDayNumber(dtStart));
913    
914                            case WEEKLY :
915                                    Calendar tempCand = (Calendar)candidate.clone();
916    
917                                    tempCand.setFirstDayOfWeek(dtStart.getFirstDayOfWeek());
918    
919                                    return (int)(getWeekNumber(tempCand) - getWeekNumber(dtStart));
920    
921                            case MONTHLY :
922                                    return
923                                            (int)(getMonthNumber(candidate) - getMonthNumber(dtStart));
924    
925                            case YEARLY :
926                                    return
927                                            candidate.get(Calendar.YEAR) - dtStart.get(Calendar.YEAR);
928    
929                            default :
930                                    throw new IllegalStateException("bad frequency internally...");
931                    }
932            }
933    
934            /**
935             * Method matchesByDay
936             *
937             * @return boolean
938             */
939            protected boolean matchesByDay(Calendar candidate) {
940                    if (ArrayUtil.isEmpty(byDay)) {
941    
942                            /* No byDay rules, so it matches trivially */
943    
944                            return true;
945                    }
946    
947                    int i;
948    
949                    for (i = 0; i < byDay.length; i++) {
950                            if (matchesIndividualByDay(candidate, byDay[i])) {
951                                    return true;
952                            }
953                    }
954    
955                    return false;
956            }
957    
958            /**
959             * Method matchesByField
960             *
961             * @return boolean
962             */
963            protected boolean matchesByField(
964                    int[] array, int field, Calendar candidate, boolean allowNegative) {
965    
966                    if (ArrayUtil.isEmpty(array)) {
967    
968                            /* No rules, so it matches trivially */
969    
970                            return true;
971                    }
972    
973                    int i;
974    
975                    for (i = 0; i < array.length; i++) {
976                            int val;
977    
978                            if (allowNegative && (array[i] < 0)) {
979    
980                                    // byMonthDay = -1, in a 31-day month, means 31
981    
982                                    int max = candidate.getActualMaximum(field);
983    
984                                    val = (max + 1) + array[i];
985                            }
986                            else {
987                                    val = array[i];
988                            }
989    
990                            if (val == candidate.get(field)) {
991                                    return true;
992                            }
993                    }
994    
995                    return false;
996            }
997    
998            /**
999             * Method matchesByMonth
1000             *
1001             * @return boolean
1002             */
1003            protected boolean matchesByMonth(Calendar candidate) {
1004                    return matchesByField(byMonth, Calendar.MONTH, candidate, false);
1005            }
1006    
1007            /**
1008             * Method matchesByMonthDay
1009             *
1010             * @return boolean
1011             */
1012            protected boolean matchesByMonthDay(Calendar candidate) {
1013                    return matchesByField(byMonthDay, Calendar.DATE, candidate, true);
1014            }
1015    
1016            /**
1017             * Method matchesByWeekNo
1018             *
1019             * @return boolean
1020             */
1021            protected boolean matchesByWeekNo(Calendar candidate) {
1022                    return matchesByField(byWeekNo, Calendar.WEEK_OF_YEAR, candidate, true);
1023            }
1024    
1025            /**
1026             * Method matchesByYearDay
1027             *
1028             * @return boolean
1029             */
1030            protected boolean matchesByYearDay(Calendar candidate) {
1031                    return matchesByField(byYearDay, Calendar.DAY_OF_YEAR, candidate, true);
1032            }
1033    
1034            /**
1035             * Method matchesIndividualByDay
1036             *
1037             * @return boolean
1038             */
1039            protected boolean matchesIndividualByDay(
1040                    Calendar candidate, DayAndPosition pos) {
1041    
1042                    if (pos.getDayOfWeek() != candidate.get(Calendar.DAY_OF_WEEK)) {
1043                            return false;
1044                    }
1045    
1046                    int position = pos.getDayPosition();
1047    
1048                    if (position == 0) {
1049                            return true;
1050                    }
1051    
1052                    int field = Calendar.DAY_OF_MONTH;
1053    
1054                    if (position > 0) {
1055                            int candidatePosition = ((candidate.get(field) - 1) / 7) + 1;
1056    
1057                            return (position == candidatePosition);
1058                    }
1059                    else {
1060    
1061                            /* position < 0 */
1062    
1063                            int negativeCandidatePosition =
1064                                    ((candidate.getActualMaximum(field) - candidate.get(field)) /
1065                                            7) + 1;
1066    
1067                            return (-position == negativeCandidatePosition);
1068                    }
1069            }
1070    
1071            /**
1072             * Method stringizeIntArray
1073             *
1074             * @return String
1075             */
1076            protected String stringizeIntArray(int[] a) {
1077                    if (a == null) {
1078                            return "null";
1079                    }
1080    
1081                    StringBundler sb = new StringBundler(2 * a.length + 1);
1082    
1083                    sb.append("[");
1084    
1085                    for (int i = 0; i < a.length; i++) {
1086                            if (i != 0) {
1087                                    sb.append(",");
1088                            }
1089    
1090                            sb.append(a[i]);
1091                    }
1092    
1093                    sb.append("]");
1094    
1095                    return sb.toString();
1096            }
1097    
1098            /**
1099             * Field byDay
1100             */
1101            protected DayAndPosition[] byDay;
1102    
1103            /**
1104             * Field byMonth
1105             */
1106            protected int[] byMonth;
1107    
1108            /**
1109             * Field byMonthDay
1110             */
1111            protected int[] byMonthDay;
1112    
1113            /**
1114             * Field byWeekNo
1115             */
1116            protected int[] byWeekNo;
1117    
1118            /**
1119             * Field byYearDay
1120             */
1121            protected int[] byYearDay;
1122    
1123            /**
1124             * Field dtStart
1125             */
1126            protected Calendar dtStart;
1127    
1128            /**
1129             * Field duration
1130             */
1131            protected Duration duration;
1132    
1133            /**
1134             * Field frequency
1135             */
1136            protected int frequency;
1137    
1138            /**
1139             * Field interval
1140             */
1141            protected int interval;
1142    
1143            /**
1144             * Field interval
1145             */
1146            protected int occurrence = 0;
1147    
1148            /**
1149             * Field until
1150             */
1151            protected Calendar until;
1152    
1153    }