001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.acacia.shared;
029
030import org.opencms.util.CmsUUID;
031
032import java.util.Date;
033import java.util.Objects;
034import java.util.SortedSet;
035import java.util.TreeSet;
036
037/** The base class for implementations of serial date values. */
038public class A_CmsSerialDateValue implements I_CmsSerialDateValue {
039
040    /** Start date and time of the first event in the series. */
041    private Date m_start;
042    /** End date and time of the first event in the series. */
043    private Date m_end;
044    /** Last day events of the series should take place. */
045    private Date m_seriesEndDate;
046    /** Maximal number of occurrences of the event. */
047    private int m_seriesOccurrences;
048    /** The interval between two events (e.g., number of days, weeks, month, years). */
049    private int m_interval;
050    /** The day of the month when the event should happen. */
051    private int m_dayOfMonth;
052    /** The weekdays at which the event should happen. */
053    private final SortedSet<WeekDay> m_weekDays = new TreeSet<>();
054    /** The recursion pattern of the event series. */
055    private PatternType m_patterntype;
056    /** The weeks in a month where the event should happen. */
057    private final SortedSet<WeekOfMonth> m_weeksOfMonth = new TreeSet<>();
058    /** Dates in the event series, where the event is not taking place. */
059    private final SortedSet<Date> m_exceptions = new TreeSet<>();
060    /** Individual dates, where the event takes place. */
061    private final SortedSet<Date> m_individualDates = new TreeSet<>();
062    /** Flag, indicating if the event should take place on every working day. */
063    private boolean m_isEveryWorkingDay;
064    /** Flag, indicating if the event lasts all the day. */
065    private boolean m_isWholeDay;
066    /** Month in which the event takes place. */
067    private Month m_month = Month.JANUARY;
068    /** The end type of the series. */
069    private EndType m_endType;
070    /** The series content, the current value is extracted from. */
071    private CmsUUID m_parentSeriesId;
072    /** Flag, indicating if the events are "current" till their end. */
073    private boolean m_currentTillEnd = true;
074
075    /**
076     * Add a date where the event should not take place, even if they are part of the series.
077     * @param date the date to add as exception.
078     */
079    public void addException(Date date) {
080
081        if (null != date) {
082            m_exceptions.add(date);
083        }
084
085    }
086
087    /**
088     * Add a week of month.
089     * @param week the week to add.
090     */
091    public final void addWeekOfMonth(WeekOfMonth week) {
092
093        m_weeksOfMonth.add(week);
094    }
095
096    /**
097     * Clear the exceptions.
098     */
099    public final void clearExceptions() {
100
101        m_exceptions.clear();
102
103    }
104
105    /**
106     * Clear the individual dates.
107     */
108    public final void clearIndividualDates() {
109
110        m_individualDates.clear();
111
112    }
113
114    /**
115     * Clear the week days.
116     */
117    public final void clearWeekDays() {
118
119        m_weekDays.clear();
120
121    }
122
123    /**
124     * Clear the weeks of month.
125     */
126    public final void clearWeeksOfMonth() {
127
128        m_weeksOfMonth.clear();
129
130    }
131
132    /**
133     * @see org.opencms.acacia.shared.I_CmsSerialDateValue#endsAtMidNight()
134     */
135    @SuppressWarnings("deprecation")
136    public boolean endsAtMidNight() {
137
138        Date end = getEnd();
139        return (end != null)
140            && (end.getHours() == 0)
141            && (end.getMinutes() == 0)
142            && (end.getSeconds() == 0)
143            && ((end.getTime() % 1000) == 0);
144    }
145
146    /**
147     * @see java.lang.Object#equals(java.lang.Object)
148     */
149    //TODO: Rework!
150    @Override
151    public final boolean equals(Object o) {
152
153        if (o instanceof I_CmsSerialDateValue) {
154            I_CmsSerialDateValue val = (I_CmsSerialDateValue)o;
155            return (val.getDayOfMonth() == this.getDayOfMonth())
156                && (val.isEveryWorkingDay() == this.isEveryWorkingDay())
157                && (val.isWholeDay() == this.isWholeDay())
158                && Objects.equals(val.getEnd(), this.getEnd())
159                && Objects.equals(val.getEndType(), this.getEndType())
160                && Objects.equals(val.getExceptions(), this.getExceptions())
161                && Objects.equals(val.getIndividualDates(), this.getIndividualDates())
162                && (val.getInterval() == this.getInterval())
163                && Objects.equals(val.getMonth(), this.getMonth())
164                && (val.getOccurrences() == this.getOccurrences())
165                && Objects.equals(val.getPatternType(), this.getPatternType())
166                && Objects.equals(val.getSeriesEndDate(), this.getSeriesEndDate())
167                && Objects.equals(val.getStart(), this.getStart())
168                && Objects.equals(val.getWeekDay(), this.getWeekDay())
169                && Objects.equals(val.getWeekDays(), this.getWeekDays())
170                && Objects.equals(val.getWeekOfMonth(), this.getWeekOfMonth())
171                && Objects.equals(val.getWeeksOfMonth(), this.getWeeksOfMonth())
172                && Objects.equals(val.getParentSeriesId(), this.getParentSeriesId());
173        }
174        return false;
175    }
176
177    /**
178     * @see org.opencms.acacia.shared.I_CmsSerialDateValue#getDateType()
179     */
180    public DateType getDateType() {
181
182        if (!Objects.equals(getPatternType(), PatternType.NONE)) {
183            return DateType.SERIES;
184        }
185        if (isFromOtherSeries()) {
186            return DateType.EXTRACTED;
187        }
188        return DateType.SINGLE;
189    }
190
191    /**
192     * @see org.opencms.acacia.shared.I_CmsSerialDateValue#getDayOfMonth()
193     */
194    public final int getDayOfMonth() {
195
196        return m_dayOfMonth;
197    }
198
199    /**
200     * @see org.opencms.acacia.shared.I_CmsSerialDateValue#getEnd()
201     */
202    public final Date getEnd() {
203
204        return m_end;
205    }
206
207    /**
208     * @see org.opencms.acacia.shared.I_CmsSerialDateValue#getEndType()
209     */
210    public final EndType getEndType() {
211
212        return m_endType;
213    }
214
215    /**
216     * @see org.opencms.acacia.shared.I_CmsSerialDateValue#getExceptions()
217     */
218    public final SortedSet<Date> getExceptions() {
219
220        return new TreeSet<>(m_exceptions);
221    }
222
223    /**
224     * @see org.opencms.acacia.shared.I_CmsSerialDateValue#getIndividualDates()
225     */
226    public final SortedSet<Date> getIndividualDates() {
227
228        return new TreeSet<>(m_individualDates);
229    }
230
231    /**
232     * @see org.opencms.acacia.shared.I_CmsSerialDateValue#getInterval()
233     */
234    public final int getInterval() {
235
236        return m_interval;
237    }
238
239    /**
240     * @see org.opencms.acacia.shared.I_CmsSerialDateValue#getMonth()
241     */
242    public final Month getMonth() {
243
244        return m_month;
245    }
246
247    /**
248     * @see org.opencms.acacia.shared.I_CmsSerialDateValue#getOccurrences()
249     */
250    public final int getOccurrences() {
251
252        return m_seriesOccurrences;
253    }
254
255    /**
256     * @see org.opencms.acacia.shared.I_CmsSerialDateValue#getParentSeriesId()
257     */
258    public CmsUUID getParentSeriesId() {
259
260        return m_parentSeriesId;
261    }
262
263    /**
264     * @see org.opencms.acacia.shared.I_CmsSerialDateValue#getPatternType()
265     */
266    public final PatternType getPatternType() {
267
268        return m_patterntype;
269    }
270
271    /**
272     * @see org.opencms.acacia.shared.I_CmsSerialDateValue#getSeriesEndDate()
273     */
274    public final Date getSeriesEndDate() {
275
276        return m_seriesEndDate;
277    }
278
279    /**
280     * @see org.opencms.acacia.shared.I_CmsSerialDateValue#getStart()
281     */
282    public final Date getStart() {
283
284        return m_start;
285    }
286
287    /**
288     * @see org.opencms.acacia.shared.I_CmsSerialDateValue#getWeekDay()
289     */
290    public final WeekDay getWeekDay() {
291
292        if (m_weekDays.size() > 0) {
293            return m_weekDays.first();
294        }
295        return null;
296    }
297
298    /**
299     * @see org.opencms.acacia.shared.I_CmsSerialDateValue#getWeekDays()
300     */
301    public final SortedSet<WeekDay> getWeekDays() {
302
303        return new TreeSet<>(m_weekDays);
304    }
305
306    /**
307     * @see org.opencms.acacia.shared.I_CmsSerialDateValue#getWeekOfMonth()
308     */
309    public final WeekOfMonth getWeekOfMonth() {
310
311        if (m_weeksOfMonth.size() > 0) {
312            return m_weeksOfMonth.iterator().next();
313        }
314        return null;
315    }
316
317    /**
318     * @see org.opencms.acacia.shared.I_CmsSerialDateValue#getWeeksOfMonth()
319     */
320    @Override
321    public final SortedSet<WeekOfMonth> getWeeksOfMonth() {
322
323        return new TreeSet<>(m_weeksOfMonth);
324    }
325
326    /**
327     * Returns a flag, indicating if exceptions are present.
328     * @return a flag, indicating if exceptions are present.
329     */
330    public final boolean hasExceptions() {
331
332        return !getExceptions().isEmpty();
333    }
334
335    /**
336     * @see java.lang.Object#hashCode()
337     */
338    // TODO: Rework
339    @Override
340    public final int hashCode() {
341
342        return Objects.hash(
343            Boolean.valueOf(this.isEveryWorkingDay()),
344            Boolean.valueOf(this.isWholeDay()),
345            Integer.valueOf(this.getDayOfMonth()),
346            this.getEnd(),
347            this.getEndType(),
348            this.getExceptions(),
349            this.getIndividualDates(),
350            Integer.valueOf(this.getInterval()),
351            this.getMonth(),
352            Integer.valueOf(this.getOccurrences()),
353            this.getPatternType(),
354            this.getSeriesEndDate(),
355            this.getStart(),
356            this.getWeekDay(),
357            this.getWeekDays(),
358            this.getWeekOfMonth(),
359            this.getWeeksOfMonth(),
360            Boolean.valueOf(this.isCurrentTillEnd()),
361            this.getParentSeriesId());
362
363    }
364
365    /**
366     * @see org.opencms.acacia.shared.I_CmsSerialDateValue#isCurrentTillEnd()
367     */
368    public boolean isCurrentTillEnd() {
369
370        return m_currentTillEnd;
371    }
372
373    /**
374     * @see org.opencms.acacia.shared.I_CmsSerialDateValue#isEveryWorkingDay()
375     */
376    public final boolean isEveryWorkingDay() {
377
378        return m_isEveryWorkingDay;
379    }
380
381    /**
382     * @see org.opencms.acacia.shared.I_CmsSerialDateValue#isFromOtherSeries()
383     */
384    @Override
385    public boolean isFromOtherSeries() {
386
387        return null != m_parentSeriesId;
388    }
389
390    /**
391     * @see org.opencms.acacia.shared.I_CmsSerialDateValue#isValid()
392     */
393    public final boolean isValid() {
394
395        return isStartSet() && isEndValid() && isPatternValid() && isDurationValid();
396    }
397
398    /**
399     * @see org.opencms.acacia.shared.I_CmsSerialDateValue#isWholeDay()
400     */
401    public final boolean isWholeDay() {
402
403        return m_isWholeDay;
404    }
405
406    /**
407     * Remove a week of month.
408     * @param week the week to remove.
409     */
410    public final void removeWeekOfMonth(WeekOfMonth week) {
411
412        m_weeksOfMonth.remove(week);
413    }
414
415    /**
416     * Set the flag, indicating if the event is treated as "current" till the end.
417     * @param isCurrentTillEnd the flag, indicating if the event is treated as "current" till the end.
418     */
419    public final void setCurrentTillEnd(Boolean isCurrentTillEnd) {
420
421        m_currentTillEnd = (null == isCurrentTillEnd) || isCurrentTillEnd.booleanValue();
422    }
423
424    /**
425     * Set the day of month.
426     * @param dayOfMonth the day of month to set.
427     */
428    public final void setDayOfMonth(int dayOfMonth) {
429
430        m_dayOfMonth = dayOfMonth;
431    }
432
433    /**
434     * Set the end time for the event.
435     * @param date the end time to set.
436     */
437    public final void setEnd(Date date) {
438
439        m_end = date;
440    }
441
442    /**
443     * Set the end type of the series.
444     * @param endType the end type to set.
445     */
446    public final void setEndType(EndType endType) {
447
448        m_endType = null == endType ? EndType.SINGLE : endType;
449    }
450
451    /**
452     * Set the flag, indicating if the event should take place every working day.
453     * @param isEveryWorkingDay the flag, indicating if the event should take place every working day.
454     */
455    public final void setEveryWorkingDay(Boolean isEveryWorkingDay) {
456
457        m_isEveryWorkingDay = null == isEveryWorkingDay ? false : isEveryWorkingDay.booleanValue();
458
459    }
460
461    /**
462     * Set dates where the event should not take place, even if they are part of the series.
463     * @param dates dates to set.
464     */
465    public final void setExceptions(SortedSet<Date> dates) {
466
467        m_exceptions.clear();
468        if (null != dates) {
469            m_exceptions.addAll(dates);
470        }
471
472    }
473
474    /**
475     * Set the individual dates where the event should take place.
476     * @param dates the dates to set.
477     */
478    public final void setIndividualDates(SortedSet<Date> dates) {
479
480        m_individualDates.clear();
481        if (null != dates) {
482            m_individualDates.addAll(dates);
483        }
484        for (Date d : getExceptions()) {
485            if (!m_individualDates.contains(d)) {
486                m_exceptions.remove(d);
487            }
488        }
489
490    }
491
492    /**
493     * Set the pattern type specific interval between two events, e.g., number of days, weeks, month, years.
494     * @param interval the interval to set.
495     */
496    public final void setInterval(int interval) {
497
498        m_interval = interval;
499    }
500
501    /**
502     * Set the month in which the event should take place.
503     * @param month the month to set.
504     */
505    public final void setMonth(Month month) {
506
507        m_month = null == month ? Month.JANUARY : month;
508
509    }
510
511    /**
512     * Set the number of occurrences of the event.
513     * @param occurrences the number of occurrences to set.
514     */
515    public final void setOccurrences(int occurrences) {
516
517        m_seriesOccurrences = occurrences;
518
519    }
520
521    /**
522     * Set the series, the current event (series) is extracted from.
523     * @param structureId the structure id of the series content, the event is extracted from.
524     */
525    public final void setParentSeriesId(CmsUUID structureId) {
526
527        m_parentSeriesId = structureId;
528
529    }
530
531    /**
532     * Set the pattern type of the event series.<p>
533     *
534     * All pattern specific values are reset.
535     *
536     * @param type the pattern type to set.
537     */
538    public final void setPatternType(PatternType type) {
539
540        m_patterntype = null == type ? PatternType.NONE : type;
541    }
542
543    /**
544     * Set the last day events of the series should occur.
545     * @param date the day to set.
546     */
547    public final void setSeriesEndDate(Date date) {
548
549        m_seriesEndDate = date;
550
551    }
552
553    /**
554     * Set the start time of the events. Unless you specify a single event, the day information is discarded.
555     * @param date the time to set.
556     */
557    public final void setStart(Date date) {
558
559        m_start = date;
560    }
561
562    /**
563     * Set the week day the events should occur.
564     * @param weekDay the week day to set.
565     */
566    public final void setWeekDay(WeekDay weekDay) {
567
568        SortedSet<WeekDay> wds = new TreeSet<>();
569        if (null != weekDay) {
570            wds.add(weekDay);
571        }
572        setWeekDays(wds);
573
574    }
575
576    /**
577     * Set the week days the events should occur.
578     * @param weekDays the week days to set.
579     */
580    public final void setWeekDays(SortedSet<WeekDay> weekDays) {
581
582        m_weekDays.clear();
583        if (null != weekDays) {
584            m_weekDays.addAll(weekDays);
585        }
586    }
587
588    /**
589     * Set the week of the month the events should occur.
590     * @param weekOfMonth the week of month to set (first to fifth, where fifth means last).
591     */
592    public final void setWeekOfMonth(WeekOfMonth weekOfMonth) {
593
594        SortedSet<WeekOfMonth> woms = new TreeSet<>();
595        if (null != weekOfMonth) {
596            woms.add(weekOfMonth);
597        }
598        setWeeksOfMonth(woms);
599    }
600
601    /**
602     * Set the weeks of the month the events should occur.
603     * @param weeksOfMonth the weeks of month to set (first to fifth, where fifth means last).
604     */
605    public final void setWeeksOfMonth(SortedSet<WeekOfMonth> weeksOfMonth) {
606
607        m_weeksOfMonth.clear();
608        if (null != weeksOfMonth) {
609            m_weeksOfMonth.addAll(weeksOfMonth);
610        }
611
612    }
613
614    /**
615     * Set the flag, indicating if the event last the whole day/whole days.
616     * @param isWholeDay the flag to set
617     */
618    public final void setWholeDay(Boolean isWholeDay) {
619
620        m_isWholeDay = (null != isWholeDay) && isWholeDay.equals(Boolean.TRUE);
621
622    }
623
624    /**
625     * Checks, if a valid day of month is set.
626     * @return a flag, indicating if the set day of month is valid.
627     */
628    protected final boolean isDayOfMonthValid() {
629
630        return (getDayOfMonth() > 0) && (getDayOfMonth() <= (getMonth() == null ? 31 : getMonth().getMaximalDay()));
631    }
632
633    /**
634     * Checks if the duration option is valid.
635     *
636     * NOTE: This does NOT check, if too many events are specified.
637     *
638     * @return a flag, indicating if the duration option is valid.
639     */
640    protected final boolean isDurationValid() {
641
642        if (isValidEndTypeForPattern()) {
643            switch (getEndType()) {
644                case DATE:
645                    return (getStart().getTime() < (getSeriesEndDate().getTime() + DAY_IN_MILLIS));
646                case TIMES:
647                    return getOccurrences() > 0;
648                case SINGLE:
649                    return true;
650                default:
651                    return false;
652            }
653        } else {
654            return false;
655        }
656    }
657
658    /** Check, if the end date of the single event is valid, i.e., either not set or not before the start date.
659     *
660     * @return a flag, indicating if the end date is set.
661     */
662    protected final boolean isEndValid() {
663
664        return (getEnd() == null) || !getEnd().before(getStart());
665    }
666
667    /** Checks, if a valid interval is specified.
668     *
669     * @return a flag, indicating if the specified interval is valid.
670     */
671    protected final boolean isIntervalValid() {
672
673        return getInterval() > 0;
674    }
675
676    /**
677     * Checks, if a month is specified.
678     * @return flag, indicating if a month is specified.
679     */
680    protected final boolean isMonthSet() {
681
682        return getMonth() != null;
683    }
684
685    /**
686     * Checks, if all values necessary for a specific pattern are valid.
687     * @return a flag, indicating if all values required for the pattern are valid.
688     */
689    protected final boolean isPatternValid() {
690
691        switch (getPatternType()) {
692            case DAILY:
693                return isEveryWorkingDay() || isIntervalValid();
694            case WEEKLY:
695                return isIntervalValid() && isWeekDaySet();
696            case MONTHLY:
697                return isIntervalValid() && isWeekDaySet() ? isWeekOfMonthSet() : isDayOfMonthValid();
698            case YEARLY:
699                return isMonthSet() && isWeekDaySet() ? isWeekOfMonthSet() : isDayOfMonthValid();
700            case INDIVIDUAL:
701            case NONE:
702                return true;
703            default:
704                return false;
705        }
706    }
707
708    /** Check, if the start time stamp is set.
709     *
710     * @return a flag, indicating if a start date is set.
711     */
712    protected final boolean isStartSet() {
713
714        return null != getStart();
715    }
716
717    /**
718     * Checks, if the end type is valid for the set pattern type.
719     * @return a flag, indicating if the end type is valid for the pattern type.
720     */
721    protected final boolean isValidEndTypeForPattern() {
722
723        if (getEndType() == null) {
724            return false;
725        }
726        switch (getPatternType()) {
727            case DAILY:
728            case WEEKLY:
729            case MONTHLY:
730            case YEARLY:
731                return (getEndType().equals(EndType.DATE) || getEndType().equals(EndType.TIMES));
732            case INDIVIDUAL:
733            case NONE:
734                return getEndType().equals(EndType.SINGLE);
735            default:
736                return false;
737        }
738    }
739
740    /**
741     * Checks if at least one weekday is specified.
742     * @return a flag, indicating if at least one weekday is specified.
743     */
744    protected final boolean isWeekDaySet() {
745
746        return !m_weekDays.isEmpty();
747    }
748
749    /**
750     * Checks, if at least one week of month is set.
751     * @return a flag, indicating if at least one week of month is set.
752     */
753    protected final boolean isWeekOfMonthSet() {
754
755        return !m_weeksOfMonth.isEmpty();
756    }
757
758    /**
759     * Sets the value to a default.
760     */
761    protected final void setDefaultValue() {
762
763        m_start = null;
764        m_end = null;
765        m_patterntype = PatternType.NONE;
766        m_dayOfMonth = 0;
767        m_exceptions.clear();
768        m_individualDates.clear();
769        m_interval = 0;
770        m_isEveryWorkingDay = false;
771        m_isWholeDay = false;
772        m_month = Month.JANUARY;
773        m_seriesEndDate = null;
774        m_seriesOccurrences = 0;
775        m_weekDays.clear();
776        m_weeksOfMonth.clear();
777        m_endType = EndType.SINGLE;
778        m_parentSeriesId = null;
779    }
780
781    /**
782     * Set the end type as derived from other values.
783     */
784    protected final void setDerivedEndType() {
785
786        m_endType = getPatternType().equals(PatternType.NONE) || getPatternType().equals(PatternType.INDIVIDUAL)
787        ? EndType.SINGLE
788        : null != getSeriesEndDate() ? EndType.DATE : EndType.TIMES;
789    }
790}