001    /**
002     * Copyright (c) 2000-2012 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.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(Calendar.DAY_OF_WEEK, dtStart,
250                                                                                             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(new Date(dtStart.getTime().getTime()
279                                                                     + 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                            if (candidateIsInRecurrence(candidate, debug)) {
390                                    return true;
391                            }
392    
393                            /* Roll back to one second previous, and try again. */
394    
395                            candidate.add(Calendar.SECOND, -1);
396    
397                            /* Make sure we haven't rolled back to before dtStart. */
398    
399                            if (candidate.getTime().getTime() < dtStart.getTime().getTime()) {
400                                    if (debug) {
401                                            System.err.println("No candidates after dtStart");
402                                    }
403    
404                                    return false;
405                            }
406    
407                            candidate = getCandidateStartTime(candidate);
408                    }
409    
410                    if (debug) {
411                            System.err.println("No matching candidates");
412                    }
413    
414                    return false;
415            }
416    
417            /**
418             * Method setByDay
419             */
420            public void setByDay(DayAndPosition[] b) {
421                    if (b == null) {
422                            byDay = null;
423    
424                            return;
425                    }
426    
427                    byDay = new DayAndPosition[b.length];
428    
429                    /*
430                     * System.arraycopy isn't good enough -- we want to clone each
431                     * individual element.
432                     */
433                    for (int i = 0; i < b.length; i++) {
434                            byDay[i] = (DayAndPosition)b[i].clone();
435                    }
436            }
437    
438            /**
439             * Method setByMonth
440             */
441            public void setByMonth(int[] b) {
442                    if (b == null) {
443                            byMonth = null;
444    
445                            return;
446                    }
447    
448                    byMonth = new int[b.length];
449    
450                    System.arraycopy(b, 0, byMonth, 0, b.length);
451            }
452    
453            /**
454             * Method setByMonthDay
455             */
456            public void setByMonthDay(int[] b) {
457                    if (b == null) {
458                            byMonthDay = null;
459    
460                            return;
461                    }
462    
463                    byMonthDay = new int[b.length];
464    
465                    System.arraycopy(b, 0, byMonthDay, 0, b.length);
466            }
467    
468            /**
469             * Method setByWeekNo
470             */
471            public void setByWeekNo(int[] b) {
472                    if (b == null) {
473                            byWeekNo = null;
474    
475                            return;
476                    }
477    
478                    byWeekNo = new int[b.length];
479    
480                    System.arraycopy(b, 0, byWeekNo, 0, b.length);
481            }
482    
483            /**
484             * Method setByYearDay
485             */
486            public void setByYearDay(int[] b) {
487                    if (b == null) {
488                            byYearDay = null;
489    
490                            return;
491                    }
492    
493                    byYearDay = new int[b.length];
494    
495                    System.arraycopy(b, 0, byYearDay, 0, b.length);
496            }
497    
498            /**
499             * Method setDtEnd
500             */
501            public void setDtEnd(Calendar end) {
502                    Calendar tempEnd = (Calendar)end.clone();
503    
504                    tempEnd.clear(Calendar.ZONE_OFFSET);
505                    tempEnd.clear(Calendar.DST_OFFSET);
506                    tempEnd.setTimeZone(TimeZoneUtil.getTimeZone(StringPool.UTC));
507                    duration.setInterval(tempEnd.getTime().getTime()
508                                                             - dtStart.getTime().getTime());
509            }
510    
511            /**
512             * Method setDtStart
513             */
514            public void setDtStart(Calendar start) {
515                    int oldStart;
516    
517                    if (dtStart != null) {
518                            oldStart = dtStart.getFirstDayOfWeek();
519                    }
520                    else {
521                            oldStart = Calendar.MONDAY;
522                    }
523    
524                    if (start == null) {
525                            dtStart = CalendarFactoryUtil.getCalendar(
526                                    TimeZoneUtil.getTimeZone(StringPool.UTC));
527    
528                            dtStart.setTime(new Date(0L));
529                    }
530                    else {
531                            dtStart = (Calendar)start.clone();
532    
533                            dtStart.clear(Calendar.ZONE_OFFSET);
534                            dtStart.clear(Calendar.DST_OFFSET);
535                            dtStart.setTimeZone(TimeZoneUtil.getTimeZone(StringPool.UTC));
536                    }
537    
538                    dtStart.setMinimalDaysInFirstWeek(4);
539                    dtStart.setFirstDayOfWeek(oldStart);
540            }
541    
542            /**
543             * Method setDuration
544             */
545            public void setDuration(Duration d) {
546                    duration = (Duration)d.clone();
547            }
548    
549            /**
550             * Method setFrequency
551             */
552            public void setFrequency(int freq) {
553                    if ((frequency != DAILY) && (frequency != WEEKLY)
554                            && (frequency != MONTHLY) && (frequency != YEARLY)
555                            && (frequency != NO_RECURRENCE)) {
556                            throw new IllegalArgumentException("Invalid frequency");
557                    }
558    
559                    frequency = freq;
560            }
561    
562            /**
563             * Method setInterval
564             */
565            public void setInterval(int intr) {
566                    interval = (intr > 0) ? intr : 1;
567            }
568    
569            /**
570             * Method setOccurrence
571             */
572            public void setOccurrence(int occur) {
573                    occurrence = occur;
574            }
575    
576            /**
577             * Method setUntil
578             */
579            public void setUntil(Calendar u) {
580                    if (u == null) {
581                            until = null;
582    
583                            return;
584                    }
585    
586                    until = (Calendar)u.clone();
587    
588                    until.clear(Calendar.ZONE_OFFSET);
589                    until.clear(Calendar.DST_OFFSET);
590                    until.setTimeZone(TimeZoneUtil.getTimeZone(StringPool.UTC));
591            }
592    
593            /**
594             * Method setWeekStart
595             */
596            public void setWeekStart(int weekstart) {
597                    dtStart.setFirstDayOfWeek(weekstart);
598            }
599    
600            /**
601             * Method toString
602             *
603             * @return String
604             */
605            @Override
606            public String toString() {
607                    StringBundler sb = new StringBundler();
608    
609                    sb.append(getClass().getName());
610                    sb.append("[dtStart=");
611                    sb.append((dtStart != null) ? dtStart.toString() : "null");
612                    sb.append(",duration=");
613                    sb.append((duration != null) ? duration.toString() : "null");
614                    sb.append(",frequency=");
615                    sb.append(frequency);
616                    sb.append(",interval=");
617                    sb.append(interval);
618                    sb.append(",until=");
619                    sb.append((until != null) ? until.toString() : "null");
620                    sb.append(",byDay=");
621    
622                    if (byDay == null) {
623                            sb.append("null");
624                    }
625                    else {
626                            sb.append("[");
627    
628                            for (int i = 0; i < byDay.length; i++) {
629                                    if (i != 0) {
630                                            sb.append(",");
631                                    }
632    
633                                    if (byDay[i] != null) {
634                                            sb.append(byDay[i].toString());
635                                    }
636                                    else {
637                                            sb.append("null");
638                                    }
639                            }
640    
641                            sb.append("]");
642                    }
643    
644                    sb.append(",byMonthDay=");
645                    sb.append(stringizeIntArray(byMonthDay));
646                    sb.append(",byYearDay=");
647                    sb.append(stringizeIntArray(byYearDay));
648                    sb.append(",byWeekNo=");
649                    sb.append(stringizeIntArray(byWeekNo));
650                    sb.append(",byMonth=");
651                    sb.append(stringizeIntArray(byMonth));
652                    sb.append(']');
653    
654                    return sb.toString();
655            }
656    
657            /**
658             * Method getDayNumber
659             *
660             * @return long
661             */
662            protected static long getDayNumber(Calendar cal) {
663                    Calendar tempCal = (Calendar)cal.clone();
664    
665                    // Set to midnight, GMT
666    
667                    tempCal.set(Calendar.MILLISECOND, 0);
668                    tempCal.set(Calendar.SECOND, 0);
669                    tempCal.set(Calendar.MINUTE, 0);
670                    tempCal.set(Calendar.HOUR_OF_DAY, 0);
671    
672                    return tempCal.getTime().getTime() / (24 * 60 * 60 * 1000);
673            }
674    
675            /**
676             * Method getMonthNumber
677             *
678             * @return long
679             */
680            protected static long getMonthNumber(Calendar cal) {
681                    return (cal.get(Calendar.YEAR) - 1970) * 12L
682                               + (cal.get(Calendar.MONTH) - Calendar.JANUARY);
683            }
684    
685            /**
686             * Method getWeekNumber
687             *
688             * @return long
689             */
690            protected static long getWeekNumber(Calendar cal) {
691                    Calendar tempCal = (Calendar)cal.clone();
692    
693                    // Set to midnight, GMT
694    
695                    tempCal.set(Calendar.MILLISECOND, 0);
696                    tempCal.set(Calendar.SECOND, 0);
697                    tempCal.set(Calendar.MINUTE, 0);
698                    tempCal.set(Calendar.HOUR_OF_DAY, 0);
699    
700                    // Roll back to the first day of the week
701    
702                    int delta = tempCal.getFirstDayOfWeek()
703                                            - tempCal.get(Calendar.DAY_OF_WEEK);
704    
705                    if (delta > 0) {
706                            delta -= 7;
707                    }
708    
709                    // tempCal now points to the first instant of this week.
710    
711                    // Calculate the "week epoch" -- the weekstart day closest to January 1,
712                    // 1970 (which was a Thursday)
713    
714                    long weekEpoch = (tempCal.getFirstDayOfWeek() - Calendar.THURSDAY) * 24L
715                                                     * 60 * 60 * 1000L;
716    
717                    return (tempCal.getTime().getTime() - weekEpoch)
718                               / (7 * 24 * 60 * 60 * 1000);
719            }
720    
721            /**
722             * Method reduce_constant_length_field
723             */
724            protected static void reduce_constant_length_field(int field,
725                                                                                                               Calendar start,
726                                                                                                               Calendar candidate) {
727                    if ((start.getMaximum(field) != start.getLeastMaximum(field))
728                            || (start.getMinimum(field) != start.getGreatestMinimum(field))) {
729                            throw new IllegalArgumentException("Not a constant length field");
730                    }
731    
732                    int fieldLength = (start.getMaximum(field) - start.getMinimum(field)
733                                                       + 1);
734                    int delta = start.get(field) - candidate.get(field);
735    
736                    if (delta > 0) {
737                            delta -= fieldLength;
738                    }
739    
740                    candidate.add(field, delta);
741            }
742    
743            /**
744             * Method reduce_day_of_month
745             */
746            protected static void reduce_day_of_month(Calendar start,
747                                                                                              Calendar candidate) {
748                    Calendar tempCal = (Calendar)candidate.clone();
749    
750                    tempCal.add(Calendar.MONTH, -1);
751    
752                    int delta = start.get(Calendar.DATE) - candidate.get(Calendar.DATE);
753    
754                    if (delta > 0) {
755                            delta -= tempCal.getActualMaximum(Calendar.DATE);
756                    }
757    
758                    candidate.add(Calendar.DATE, delta);
759    
760                    while (start.get(Calendar.DATE) != candidate.get(Calendar.DATE)) {
761                            tempCal.add(Calendar.MONTH, -1);
762                            candidate.add(Calendar.DATE,
763                                                      -tempCal.getActualMaximum(Calendar.DATE));
764                    }
765            }
766    
767            /**
768             * Method reduce_day_of_year
769             */
770            protected static void reduce_day_of_year(Calendar start,
771                                                                                             Calendar candidate) {
772                    if ((start.get(Calendar.MONTH) > candidate.get(Calendar.MONTH))
773                            || ((start.get(Calendar.MONTH) == candidate.get(Calendar.MONTH))
774                                    && (start.get(Calendar.DATE) > candidate.get(Calendar.DATE)))) {
775                            candidate.add(Calendar.YEAR, -1);
776                    }
777    
778                    /* Set the candidate date to the start date. */
779    
780                    candidate.set(Calendar.MONTH, start.get(Calendar.MONTH));
781                    candidate.set(Calendar.DATE, start.get(Calendar.DATE));
782    
783                    while ((start.get(Calendar.MONTH) != candidate.get(Calendar.MONTH))
784                               || (start.get(Calendar.DATE) != candidate.get(Calendar.DATE))) {
785                            candidate.add(Calendar.YEAR, -1);
786                            candidate.set(Calendar.MONTH, start.get(Calendar.MONTH));
787                            candidate.set(Calendar.DATE, start.get(Calendar.DATE));
788                    }
789            }
790    
791            /**
792             * Method candidateIsInRecurrence
793             *
794             * @return boolean
795             */
796            protected boolean candidateIsInRecurrence(Calendar candidate,
797                                                                                              boolean debug) {
798                    if ((until != null)
799                            && (candidate.getTime().getTime() > until.getTime().getTime())) {
800    
801                            // After "until"
802    
803                            if (debug) {
804                                    System.err.println("after until");
805                            }
806    
807                            return false;
808                    }
809    
810                    if (getRecurrenceCount(candidate) % interval != 0) {
811    
812                            // Not a repetition of the interval
813    
814                            if (debug) {
815                                    System.err.println("not an interval rep");
816                            }
817    
818                            return false;
819                    }
820                    else if ((occurrence > 0) &&
821                                     (getRecurrenceCount(candidate) >= occurrence)) {
822    
823                            return false;
824                    }
825    
826                    if (!matchesByDay(candidate) ||!matchesByMonthDay(candidate)
827                            ||!matchesByYearDay(candidate) ||!matchesByWeekNo(candidate)
828                            ||!matchesByMonth(candidate)) {
829    
830                            // Doesn't match a by* rule
831    
832                            if (debug) {
833                                    System.err.println("doesn't match a by*");
834                            }
835    
836                            return false;
837                    }
838    
839                    if (debug) {
840                            System.err.println("All checks succeeded");
841                    }
842    
843                    return true;
844            }
845    
846            /**
847             * Method getMinimumInterval
848             *
849             * @return int
850             */
851            protected int getMinimumInterval() {
852                    if ((frequency == DAILY) || (byDay != null) || (byMonthDay != null)
853                            || (byYearDay != null)) {
854                            return DAILY;
855                    }
856                    else if ((frequency == WEEKLY) || (byWeekNo != null)) {
857                            return WEEKLY;
858                    }
859                    else if ((frequency == MONTHLY) || (byMonth != null)) {
860                            return MONTHLY;
861                    }
862                    else if (frequency == YEARLY) {
863                            return YEARLY;
864                    }
865                    else if (frequency == NO_RECURRENCE) {
866                            return NO_RECURRENCE;
867                    }
868                    else {
869    
870                            // Shouldn't happen
871    
872                            throw new IllegalStateException(
873                                    "Internal error: Unknown frequency value");
874                    }
875            }
876    
877            /**
878             * Method getRecurrenceCount
879             *
880             * @return int
881             */
882            protected int getRecurrenceCount(Calendar candidate) {
883                    switch (frequency) {
884    
885                            case NO_RECURRENCE :
886                                    return 0;
887    
888                            case DAILY :
889                                    return (int)(getDayNumber(candidate) - getDayNumber(dtStart));
890    
891                            case WEEKLY :
892                                    Calendar tempCand = (Calendar)candidate.clone();
893    
894                                    tempCand.setFirstDayOfWeek(dtStart.getFirstDayOfWeek());
895    
896                                    return (int)(getWeekNumber(tempCand) - getWeekNumber(dtStart));
897    
898                            case MONTHLY :
899                                    return (int)(getMonthNumber(candidate)
900                                                             - getMonthNumber(dtStart));
901    
902                            case YEARLY :
903                                    return candidate.get(Calendar.YEAR)
904                                               - dtStart.get(Calendar.YEAR);
905    
906                            default :
907                                    throw new IllegalStateException("bad frequency internally...");
908                    }
909            }
910    
911            /**
912             * Method matchesByDay
913             *
914             * @return boolean
915             */
916            protected boolean matchesByDay(Calendar candidate) {
917                    if ((byDay == null) || (byDay.length == 0)) {
918    
919                            /* No byDay rules, so it matches trivially */
920    
921                            return true;
922                    }
923    
924                    int i;
925    
926                    for (i = 0; i < byDay.length; i++) {
927                            if (matchesIndividualByDay(candidate, byDay[i])) {
928                                    return true;
929                            }
930                    }
931    
932                    return false;
933            }
934    
935            /**
936             * Method matchesByField
937             *
938             * @return boolean
939             */
940            protected boolean matchesByField(int[] array, int field,
941                                                                                            Calendar candidate,
942                                                                                            boolean allowNegative) {
943                    if ((array == null) || (array.length == 0)) {
944    
945                            /* No rules, so it matches trivially */
946    
947                            return true;
948                    }
949    
950                    int i;
951    
952                    for (i = 0; i < array.length; i++) {
953                            int val;
954    
955                            if (allowNegative && (array[i] < 0)) {
956    
957                                    // byMonthDay = -1, in a 31-day month, means 31
958    
959                                    int max = candidate.getActualMaximum(field);
960    
961                                    val = (max + 1) + array[i];
962                            }
963                            else {
964                                    val = array[i];
965                            }
966    
967                            if (val == candidate.get(field)) {
968                                    return true;
969                            }
970                    }
971    
972                    return false;
973            }
974    
975            /**
976             * Method matchesByMonth
977             *
978             * @return boolean
979             */
980            protected boolean matchesByMonth(Calendar candidate) {
981                    return matchesByField(byMonth, Calendar.MONTH, candidate, false);
982            }
983    
984            /**
985             * Method matchesByMonthDay
986             *
987             * @return boolean
988             */
989            protected boolean matchesByMonthDay(Calendar candidate) {
990                    return matchesByField(byMonthDay, Calendar.DATE, candidate, true);
991            }
992    
993            /**
994             * Method matchesByWeekNo
995             *
996             * @return boolean
997             */
998            protected boolean matchesByWeekNo(Calendar candidate) {
999                    return matchesByField(byWeekNo, Calendar.WEEK_OF_YEAR, candidate, true);
1000            }
1001    
1002            /**
1003             * Method matchesByYearDay
1004             *
1005             * @return boolean
1006             */
1007            protected boolean matchesByYearDay(Calendar candidate) {
1008                    return matchesByField(byYearDay, Calendar.DAY_OF_YEAR, candidate, true);
1009            }
1010    
1011            /**
1012             * Method matchesIndividualByDay
1013             *
1014             * @return boolean
1015             */
1016            protected boolean matchesIndividualByDay(Calendar candidate,
1017                                                                                             DayAndPosition pos) {
1018                    if (pos.getDayOfWeek() != candidate.get(Calendar.DAY_OF_WEEK)) {
1019                            return false;
1020                    }
1021    
1022                    int position = pos.getDayPosition();
1023    
1024                    if (position == 0) {
1025                            return true;
1026                    }
1027    
1028                    int field = Calendar.DAY_OF_MONTH;
1029    
1030                    if (position > 0) {
1031                            int candidatePosition = ((candidate.get(field) - 1) / 7) + 1;
1032    
1033                            return (position == candidatePosition);
1034                    }
1035                    else {
1036    
1037                            /* position < 0 */
1038    
1039                            int negativeCandidatePosition =
1040                                    ((candidate.getActualMaximum(field) - candidate.get(field)) / 7)
1041                                    + 1;
1042    
1043                            return (-position == negativeCandidatePosition);
1044                    }
1045            }
1046    
1047            /**
1048             * Method stringizeIntArray
1049             *
1050             * @return String
1051             */
1052            protected String stringizeIntArray(int[] a) {
1053                    if (a == null) {
1054                            return "null";
1055                    }
1056    
1057                    StringBundler sb = new StringBundler(2 * a.length + 1);
1058    
1059                    sb.append("[");
1060    
1061                    for (int i = 0; i < a.length; i++) {
1062                            if (i != 0) {
1063                                    sb.append(",");
1064                            }
1065    
1066                            sb.append(a[i]);
1067                    }
1068    
1069                    sb.append("]");
1070    
1071                    return sb.toString();
1072            }
1073    
1074            /**
1075             * Field byDay
1076             */
1077            protected DayAndPosition[] byDay;
1078    
1079            /**
1080             * Field byMonth
1081             */
1082            protected int[] byMonth;
1083    
1084            /**
1085             * Field byMonthDay
1086             */
1087            protected int[] byMonthDay;
1088    
1089            /**
1090             * Field byWeekNo
1091             */
1092            protected int[] byWeekNo;
1093    
1094            /**
1095             * Field byYearDay
1096             */
1097            protected int[] byYearDay;
1098    
1099            /**
1100             * Field dtStart
1101             */
1102            protected Calendar dtStart;
1103    
1104            /**
1105             * Field duration
1106             */
1107            protected Duration duration;
1108    
1109            /**
1110             * Field frequency
1111             */
1112            protected int frequency;
1113    
1114            /**
1115             * Field interval
1116             */
1117            protected int interval;
1118    
1119            /**
1120             * Field interval
1121             */
1122            protected int occurrence = 0;
1123    
1124            /**
1125             * Field until
1126             */
1127            protected Calendar until;
1128    
1129    }