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.widgets.serialdate;
029
030import org.opencms.acacia.shared.CmsSerialDateUtil;
031import org.opencms.acacia.shared.I_CmsSerialDateValue.EndType;
032
033import java.util.Calendar;
034import java.util.Date;
035import java.util.GregorianCalendar;
036import java.util.SortedSet;
037import java.util.TreeSet;
038
039/** Abstract base class for serial date beans.
040 * It deals with information common for all serial dates and already provides part of the implementation
041 * for calculating all dates of the series.
042 */
043public abstract class A_CmsSerialDateBean implements I_CmsSerialDateBean {
044
045    /** The maximal number of occurrences that is allowed. */
046    public static final int MAX_OCCURRENCES = 100;
047    /** The start date and time of the (potentially) first event of the series. */
048    protected Calendar m_startDate;
049    /** The end date and time of the (potentially) first event of the series. */
050    protected Calendar m_endDate;
051    /** The maximal number of occurrences of the event. */
052    protected int m_occurrences;
053    /** The date of the last day, the event should occur. */
054    protected Calendar m_serialEndDate;
055    /** The exact time the event should occur latest (in milliseconds). */
056    protected long m_endMillis;
057    /** The end type of the series. */
058    protected EndType m_endType = null;
059    /** Variable for caching the dates of the event after lazy calculation. */
060    protected SortedSet<Date> m_dates;
061    /** Variable for caching the dates of the event after lazy calculation. */
062    protected SortedSet<Date> m_allDates;
063    /** Variable for caching the dates as long. */
064    protected SortedSet<Long> m_datesInMillis;
065    /** The list of exceptions. */
066    protected final SortedSet<Date> m_exceptions = new TreeSet<>();
067    /** A flag, indicating if the configuration specifies too many occurrences. */
068    private Boolean m_hasTooManyOccurrences;
069
070    /** Constructor for the abstract class for serial date beans.
071     * It takes all the arguments that are common for serial dates and should be called from each sub-class.
072     *
073     * @param startDate the start date of the series as provided by the serial date widget.
074     * @param endDate the end date of the series as provided by the serial date widget.
075     * @param isWholeDay flag, indicating if the event lasts the whole day.
076     * @param endType the end type of the series as provided by the serial date widget.
077     * @param serialEndDate the end date of the series as provided by the serial date widget.
078     * @param occurrences the maximal number of occurrences of the event as provided by the serial date widget.
079     *        If endType is DATE, this parameter is ignored.
080     * @param exceptions the dates not part of the list.
081     */
082    public A_CmsSerialDateBean(
083        Date startDate,
084        Date endDate,
085        boolean isWholeDay,
086        EndType endType,
087        Date serialEndDate,
088        int occurrences,
089        SortedSet<Date> exceptions) {
090        m_startDate = new GregorianCalendar();
091        m_endDate = new GregorianCalendar();
092        m_startDate.setTime(startDate);
093        m_endDate.setTime(endDate == null ? startDate : endDate);
094        if (isWholeDay) {
095            m_startDate.set(Calendar.HOUR_OF_DAY, 0);
096            m_startDate.set(Calendar.MINUTE, 0);
097            m_startDate.set(Calendar.SECOND, 0);
098            m_startDate.set(Calendar.MILLISECOND, 0);
099            m_endDate.set(Calendar.HOUR_OF_DAY, 0);
100            m_endDate.set(Calendar.MINUTE, 0);
101            m_endDate.set(Calendar.SECOND, 0);
102            m_endDate.set(Calendar.MILLISECOND, 0);
103            m_endDate.add(Calendar.DATE, 1);
104        }
105        m_endType = endType;
106        switch (m_endType) {
107            case DATE:
108                m_serialEndDate = new GregorianCalendar();
109                m_serialEndDate.setTime(serialEndDate);
110                Calendar dayAfterEnd = new GregorianCalendar(
111                    m_serialEndDate.get(Calendar.YEAR),
112                    m_serialEndDate.get(Calendar.MONTH),
113                    m_serialEndDate.get(Calendar.DATE));
114                dayAfterEnd.add(Calendar.DATE, 1);
115                m_endMillis = dayAfterEnd.getTimeInMillis();
116                break;
117            case TIMES:
118                m_occurrences = occurrences;
119                break;
120            case SINGLE:
121                m_occurrences = 1;
122                break;
123            default:
124                break;
125        }
126        if (null != exceptions) {
127            m_exceptions.addAll(exceptions);
128        }
129    }
130
131    /**
132     * @see org.opencms.widgets.serialdate.I_CmsSerialDateBean#getDates()
133     */
134    @Override
135    public SortedSet<Date> getDates() {
136
137        if (null == m_dates) {
138            m_dates = filterExceptions(calculateDates());
139        }
140        return m_dates;
141    }
142
143    /**
144     * @see org.opencms.widgets.serialdate.I_CmsSerialDateBean#getDatesAsLong()
145     */
146    @Override
147    public SortedSet<Long> getDatesAsLong() {
148
149        if (null == m_datesInMillis) {
150            SortedSet<Date> dates = getDates();
151            m_datesInMillis = new TreeSet<>();
152            for (Date d : dates) {
153                m_datesInMillis.add(Long.valueOf(d.getTime()));
154            }
155        }
156        return m_datesInMillis;
157    }
158
159    /**
160     * @see org.opencms.widgets.serialdate.I_CmsSerialDateBean#getEventDuration()
161     */
162    @Override
163    public Long getEventDuration() {
164
165        return (null != m_endDate) && (null != m_startDate)
166        ? Long.valueOf(m_endDate.getTimeInMillis() - m_startDate.getTimeInMillis())
167        : null;
168    }
169
170    /**
171     * @see org.opencms.widgets.serialdate.I_CmsSerialDateBean#getExceptions()
172     */
173    @Override
174    public SortedSet<Date> getExceptions() {
175
176        return m_exceptions;
177    }
178
179    /**
180     * Returns the occurrences of a defined series interval, used for the series end type.<p>
181     *
182     * @return the occurrences of a defined series interval, used for the series end type
183     */
184    public int getOccurrences() {
185
186        return Math.min(m_occurrences, CmsSerialDateUtil.getMaxEvents());
187    }
188
189    /**
190     * Returns the serial end date if the series is of type: ending at specific date.<p>
191     *
192     * @return the serial end date if the series is of type: ending at specific date
193     */
194    public Calendar getSerialEndDate() {
195
196        return m_serialEndDate;
197    }
198
199    /**
200     * Returns the end type of the date series (never, n times, specific date).<p>
201     *
202     * @return the end type of the date series
203     */
204    public EndType getSerialEndType() {
205
206        return m_endType;
207    }
208
209    /**
210     * Returns the date provided as the earliest date the event should take place.
211     * The time is set to the starting time of the event.
212     *
213     * @return date where the event should take place earliest with time set to the date's starting time.
214     */
215    public Calendar getStartDate() {
216
217        return m_startDate;
218    }
219
220    /**
221     * @see org.opencms.widgets.serialdate.I_CmsSerialDateBean#hasTooManyDates()
222     */
223    @Override
224    public boolean hasTooManyDates() {
225
226        if (null == m_hasTooManyOccurrences) {
227            switch (getSerialEndType()) {
228                case SINGLE:
229                    m_hasTooManyOccurrences = Boolean.FALSE;
230                    break;
231                case TIMES:
232                    m_hasTooManyOccurrences = Boolean.valueOf(m_occurrences > CmsSerialDateUtil.getMaxEvents());
233                    break;
234                case DATE:
235                    m_hasTooManyOccurrences = Boolean.FALSE;
236                    calculateDates(); // this will set the value automatically to TRUE in case there are too many dates.
237                    break;
238                default:
239                    throw new IllegalArgumentException();
240            }
241        }
242        return m_hasTooManyOccurrences.booleanValue();
243    }
244
245    /**
246     * Generates the first date of the series.
247     *
248     * @return the first date of the series.
249     */
250    abstract protected Calendar getFirstDate();
251
252    /**
253     * Check, if the series can have at least one event/date.
254     * @return <code>true</code> if the series can be non-empty, <code>false</code> otherwise.
255     */
256    abstract protected boolean isAnyDatePossible();
257
258    /**
259     * Check if the provided date or any date after it are part of the series.
260     * @param nextDate the current date to check.
261     * @param previousOccurrences the number of events of the series that took place before the date to check.
262     * @return <code>true</code> if more dates (including the provided one) could be in the series, <code>false</code> otherwise.
263     */
264    protected boolean showMoreEntries(Calendar nextDate, int previousOccurrences) {
265
266        switch (getSerialEndType()) {
267            case DATE:
268                boolean moreByDate = nextDate.getTimeInMillis() < m_endMillis;
269                boolean moreByOccurrences = previousOccurrences < CmsSerialDateUtil.getMaxEvents();
270                if (moreByDate && !moreByOccurrences) {
271                    m_hasTooManyOccurrences = Boolean.TRUE;
272                }
273                return moreByDate && moreByOccurrences;
274            case TIMES:
275            case SINGLE:
276                return previousOccurrences < getOccurrences();
277            default:
278                throw new IllegalArgumentException();
279        }
280    }
281
282    /**
283     * Starting with a date that's in the series, the next date is created.
284     * @param date the current event date for a event in the series, which is adjusted to the next date potentially in the series.
285     */
286    abstract protected void toNextDate(Calendar date);
287
288    /**
289     * Calculates all dates of the series.
290     * @return all dates of the series in milliseconds.
291     */
292    private SortedSet<Date> calculateDates() {
293
294        if (null == m_allDates) {
295            SortedSet<Date> result = new TreeSet<>();
296            if (isAnyDatePossible()) {
297                Calendar date = getFirstDate();
298                int previousOccurrences = 0;
299                while (showMoreEntries(date, previousOccurrences)) {
300                    result.add(date.getTime());
301                    toNextDate(date);
302                    previousOccurrences++;
303                }
304            }
305            m_allDates = result;
306        }
307        return m_allDates;
308    }
309
310    /**
311     * Filters all exceptions from the provided dates.
312     * @param dates the dates to filter.
313     * @return the provided dates, except the ones that match some exception.
314     */
315    private SortedSet<Date> filterExceptions(SortedSet<Date> dates) {
316
317        SortedSet<Date> result = new TreeSet<Date>();
318        for (Date d : dates) {
319            if (!m_exceptions.contains(d)) {
320                result.add(d);
321            }
322        }
323        return result;
324    }
325
326}