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
091        m_startDate = new GregorianCalendar();
092        m_endDate = new GregorianCalendar();
093        m_startDate.setTime(startDate);
094        m_endDate.setTime(endDate == null ? startDate : endDate);
095        if (isWholeDay) {
096            m_startDate.set(Calendar.HOUR_OF_DAY, 0);
097            m_startDate.set(Calendar.MINUTE, 0);
098            m_startDate.set(Calendar.SECOND, 0);
099            m_startDate.set(Calendar.MILLISECOND, 0);
100            m_endDate.set(Calendar.HOUR_OF_DAY, 0);
101            m_endDate.set(Calendar.MINUTE, 0);
102            m_endDate.set(Calendar.SECOND, 0);
103            m_endDate.set(Calendar.MILLISECOND, 0);
104            m_endDate.add(Calendar.DATE, 1);
105        }
106        m_endType = endType;
107        switch (m_endType) {
108            case DATE:
109                m_serialEndDate = new GregorianCalendar();
110                m_serialEndDate.setTime(serialEndDate);
111                Calendar dayAfterEnd = new GregorianCalendar(
112                    m_serialEndDate.get(Calendar.YEAR),
113                    m_serialEndDate.get(Calendar.MONTH),
114                    m_serialEndDate.get(Calendar.DATE));
115                dayAfterEnd.add(Calendar.DATE, 1);
116                m_endMillis = dayAfterEnd.getTimeInMillis();
117                break;
118            case TIMES:
119                m_occurrences = occurrences;
120                break;
121            case SINGLE:
122                m_occurrences = 1;
123                break;
124            default:
125                break;
126        }
127        if (null != exceptions) {
128            m_exceptions.addAll(exceptions);
129        }
130    }
131
132    /**
133     * @see org.opencms.widgets.serialdate.I_CmsSerialDateBean#getDates()
134     */
135    @Override
136    public SortedSet<Date> getDates() {
137
138        if (null == m_dates) {
139            m_dates = filterExceptions(calculateDates());
140        }
141        return m_dates;
142    }
143
144    /**
145     * @see org.opencms.widgets.serialdate.I_CmsSerialDateBean#getDatesAsLong()
146     */
147    @Override
148    public SortedSet<Long> getDatesAsLong() {
149
150        if (null == m_datesInMillis) {
151            SortedSet<Date> dates = getDates();
152            m_datesInMillis = new TreeSet<>();
153            for (Date d : dates) {
154                m_datesInMillis.add(Long.valueOf(d.getTime()));
155            }
156        }
157        return m_datesInMillis;
158    }
159
160    /**
161     * @see org.opencms.widgets.serialdate.I_CmsSerialDateBean#getEventDuration()
162     */
163    @Override
164    public Long getEventDuration() {
165
166        return (null != m_endDate) && (null != m_startDate)
167        ? Long.valueOf(m_endDate.getTimeInMillis() - m_startDate.getTimeInMillis())
168        : null;
169    }
170
171    /**
172     * @see org.opencms.widgets.serialdate.I_CmsSerialDateBean#getExceptions()
173     */
174    @Override
175    public SortedSet<Date> getExceptions() {
176
177        return m_exceptions;
178    }
179
180    /**
181     * Returns the occurrences of a defined series interval, used for the series end type.<p>
182     *
183     * @return the occurrences of a defined series interval, used for the series end type
184     */
185    public int getOccurrences() {
186
187        return Math.min(m_occurrences, CmsSerialDateUtil.getMaxEvents());
188    }
189
190    /**
191     * Returns the serial end date if the series is of type: ending at specific date.<p>
192     *
193     * @return the serial end date if the series is of type: ending at specific date
194     */
195    public Calendar getSerialEndDate() {
196
197        return m_serialEndDate;
198    }
199
200    /**
201     * Returns the end type of the date series (never, n times, specific date).<p>
202     *
203     * @return the end type of the date series
204     */
205    public EndType getSerialEndType() {
206
207        return m_endType;
208    }
209
210    /**
211     * Returns the date provided as the earliest date the event should take place.
212     * The time is set to the starting time of the event.
213     *
214     * @return date where the event should take place earliest with time set to the date's starting time.
215     */
216    public Calendar getStartDate() {
217
218        return m_startDate;
219    }
220
221    /**
222     * @see org.opencms.widgets.serialdate.I_CmsSerialDateBean#hasTooManyDates()
223     */
224    @Override
225    public boolean hasTooManyDates() {
226
227        if (null == m_hasTooManyOccurrences) {
228            switch (getSerialEndType()) {
229                case SINGLE:
230                    m_hasTooManyOccurrences = Boolean.FALSE;
231                    break;
232                case TIMES:
233                    m_hasTooManyOccurrences = Boolean.valueOf(m_occurrences > CmsSerialDateUtil.getMaxEvents());
234                    break;
235                case DATE:
236                    m_hasTooManyOccurrences = Boolean.FALSE;
237                    calculateDates(); // this will set the value automatically to TRUE in case there are too many dates.
238                    break;
239                default:
240                    throw new IllegalArgumentException();
241            }
242        }
243        return m_hasTooManyOccurrences.booleanValue();
244    }
245
246    /**
247     * Generates the first date of the series.
248     *
249     * @return the first date of the series.
250     */
251    abstract protected Calendar getFirstDate();
252
253    /**
254     * Check, if the series can have at least one event/date.
255     * @return <code>true</code> if the series can be non-empty, <code>false</code> otherwise.
256     */
257    abstract protected boolean isAnyDatePossible();
258
259    /**
260     * Check if the provided date or any date after it are part of the series.
261     * @param nextDate the current date to check.
262     * @param previousOccurrences the number of events of the series that took place before the date to check.
263     * @return <code>true</code> if more dates (including the provided one) could be in the series, <code>false</code> otherwise.
264     */
265    protected boolean showMoreEntries(Calendar nextDate, int previousOccurrences) {
266
267        switch (getSerialEndType()) {
268            case DATE:
269                boolean moreByDate = nextDate.getTimeInMillis() < m_endMillis;
270                boolean moreByOccurrences = previousOccurrences < CmsSerialDateUtil.getMaxEvents();
271                if (moreByDate && !moreByOccurrences) {
272                    m_hasTooManyOccurrences = Boolean.TRUE;
273                }
274                return moreByDate && moreByOccurrences;
275            case TIMES:
276            case SINGLE:
277                return previousOccurrences < getOccurrences();
278            default:
279                throw new IllegalArgumentException();
280        }
281    }
282
283    /**
284     * Starting with a date that's in the series, the next date is created.
285     * @param date the current event date for a event in the series, which is adjusted to the next date potentially in the series.
286     */
287    abstract protected void toNextDate(Calendar date);
288
289    /**
290     * Calculates all dates of the series.
291     * @return all dates of the series in milliseconds.
292     */
293    private SortedSet<Date> calculateDates() {
294
295        if (null == m_allDates) {
296            SortedSet<Date> result = new TreeSet<>();
297            if (isAnyDatePossible()) {
298                Calendar date = getFirstDate();
299                int previousOccurrences = 0;
300                while (showMoreEntries(date, previousOccurrences)) {
301                    result.add(date.getTime());
302                    toNextDate(date);
303                    previousOccurrences++;
304                }
305            }
306            m_allDates = result;
307        }
308        return m_allDates;
309    }
310
311    /**
312     * Filters all exceptions from the provided dates.
313     * @param dates the dates to filter.
314     * @return the provided dates, except the ones that match some exception.
315     */
316    private SortedSet<Date> filterExceptions(SortedSet<Date> dates) {
317
318        SortedSet<Date> result = new TreeSet<Date>();
319        for (Date d : dates) {
320            if (!m_exceptions.contains(d)) {
321                result.add(d);
322            }
323        }
324        return result;
325    }
326
327}