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