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.client.widgets.serialdate;
029
030import org.opencms.acacia.shared.A_CmsSerialDateValue;
031import org.opencms.acacia.shared.I_CmsSerialDateValue;
032import org.opencms.gwt.client.util.CmsDebugLog;
033import org.opencms.util.CmsUUID;
034
035import java.util.Collection;
036import java.util.Date;
037import java.util.HashSet;
038import java.util.SortedSet;
039import java.util.TreeSet;
040
041import com.google.gwt.json.client.JSONArray;
042import com.google.gwt.json.client.JSONBoolean;
043import com.google.gwt.json.client.JSONObject;
044import com.google.gwt.json.client.JSONParser;
045import com.google.gwt.json.client.JSONString;
046import com.google.gwt.json.client.JSONValue;
047
048/**
049 * Client-side implementation of {@link I_CmsSerialDateValue}.
050 * The implementation additionally has setters for the various values of the serial date specification.
051 */
052public class CmsSerialDateValue extends A_CmsSerialDateValue implements I_CmsObservableSerialDateValue {
053
054    /** The list of value change observers. */
055    Collection<I_CmsSerialDateValueChangeObserver> m_valueChangeObservers = new HashSet<>();
056
057    /** Default constructor, setting the default state of the the serial date widget. */
058    public CmsSerialDateValue() {
059        setDefaultValue();
060    }
061
062    /**
063     * @see org.opencms.acacia.client.widgets.serialdate.I_CmsObservableSerialDateValue#registerValueChangeObserver(org.opencms.acacia.client.widgets.serialdate.I_CmsSerialDateValueChangeObserver)
064     */
065    public void registerValueChangeObserver(I_CmsSerialDateValueChangeObserver obs) {
066
067        m_valueChangeObservers.add(obs);
068
069    }
070
071    /**
072     * Set the value as provided.
073     * @param value the serial date value as JSON string.
074     */
075    public final void setValue(String value) {
076
077        if ((null == value) || value.isEmpty()) {
078            setDefaultValue();
079        } else {
080            try {
081                tryToSetParsedValue(value);
082            } catch (@SuppressWarnings("unused") Exception e) {
083                CmsDebugLog.consoleLog("Could not set invalid serial date value: " + value);
084                setDefaultValue();
085            }
086        }
087        notifyOnValueChange();
088    }
089
090    /**
091     * Convert the information from the wrapper to a JSON object.
092     * @return the serial date information as JSON.
093     */
094    public JSONValue toJson() {
095
096        JSONObject result = new JSONObject();
097        if (null != getStart()) {
098            result.put(JsonKey.START, dateToJson(getStart()));
099        }
100        if (null != getEnd()) {
101            result.put(JsonKey.END, dateToJson(getEnd()));
102        }
103        if (isWholeDay()) {
104            result.put(JsonKey.WHOLE_DAY, JSONBoolean.getInstance(true));
105        }
106        JSONObject pattern = patternToJson();
107        result.put(JsonKey.PATTERN, pattern);
108        SortedSet<Date> exceptions = getExceptions();
109        if (exceptions.size() > 0) {
110            result.put(JsonKey.EXCEPTIONS, datesToJsonArray(exceptions));
111        }
112        switch (getEndType()) {
113            case DATE:
114                result.put(JsonKey.SERIES_ENDDATE, dateToJson(getSeriesEndDate()));
115                break;
116            case TIMES:
117                result.put(JsonKey.SERIES_OCCURRENCES, new JSONString(String.valueOf(getOccurrences())));
118                break;
119            case SINGLE:
120            default:
121                break;
122        }
123        if (!isCurrentTillEnd()) {
124            result.put(JsonKey.CURRENT_TILL_END, JSONBoolean.getInstance(false));
125        }
126        if (getParentSeriesId() != null) {
127            result.put(JsonKey.PARENT_SERIES, new JSONString(getParentSeriesId().getStringValue()));
128        }
129        return result;
130    }
131
132    /**
133     * @see java.lang.Object#toString()
134     */
135    @Override
136    public String toString() {
137
138        JSONValue json = toJson();
139        return json.toString();
140    }
141
142    /**
143     * @see org.opencms.acacia.client.widgets.serialdate.I_CmsObservableSerialDateValue#unregisterValueChangeObserver(org.opencms.acacia.client.widgets.serialdate.I_CmsSerialDateValueChangeObserver)
144     */
145    public void unregisterValueChangeObserver(I_CmsSerialDateValueChangeObserver obs) {
146
147        m_valueChangeObservers.remove(obs);
148
149    }
150
151    /**
152     * Converts a collection of dates to a JSON array with the long representation of the dates as strings.
153     * @param dates the list to convert.
154     * @return JSON array with long values of dates as string
155     */
156    private JSONValue datesToJsonArray(Collection<Date> dates) {
157
158        if (null != dates) {
159            JSONArray result = new JSONArray();
160            for (Date d : dates) {
161                result.set(result.size(), dateToJson(d));
162            }
163            return result;
164        }
165        return null;
166    }
167
168    /**
169     * Convert a date to the String representation we use in the JSON.
170     * @param d the date to convert
171     * @return the String representation we use in the JSON.
172     */
173    private JSONValue dateToJson(Date d) {
174
175        return null != d ? new JSONString(Long.toString(d.getTime())) : null;
176    }
177
178    /**
179     * Notifies all observers on value changes.
180     */
181    private void notifyOnValueChange() {
182
183        for (I_CmsSerialDateValueChangeObserver obs : m_valueChangeObservers) {
184            obs.onValueChange();
185        }
186
187    }
188
189    /**
190     * Generates the JSON object storing the pattern information.
191     * @return the JSON object storing the pattern information.
192     */
193    private JSONObject patternToJson() {
194
195        JSONObject pattern = new JSONObject();
196        if (null != getPatternType()) {
197            pattern.put(JsonKey.PATTERN_TYPE, new JSONString(getPatternType().toString()));
198            switch (getPatternType()) {
199                case DAILY:
200                    if (isEveryWorkingDay()) {
201                        pattern.put(JsonKey.PATTERN_EVERYWORKINGDAY, JSONBoolean.getInstance(true));
202                    } else {
203                        pattern.put(JsonKey.PATTERN_INTERVAL, new JSONString(String.valueOf(getInterval())));
204                    }
205                    break;
206                case WEEKLY:
207                    pattern.put(JsonKey.PATTERN_INTERVAL, new JSONString(String.valueOf(getInterval())));
208                    pattern.put(JsonKey.PATTERN_WEEKDAYS, toJsonStringList(getWeekDays()));
209                    break;
210                case MONTHLY:
211                    pattern.put(JsonKey.PATTERN_INTERVAL, new JSONString(String.valueOf(getInterval())));
212                    if (null != getWeekDay()) {
213                        pattern.put(JsonKey.PATTERN_WEEKS_OF_MONTH, toJsonStringList(getWeeksOfMonth()));
214                        pattern.put(JsonKey.PATTERN_WEEKDAYS, toJsonStringList(getWeekDays()));
215                    } else {
216                        pattern.put(JsonKey.PATTERN_DAY_OF_MONTH, new JSONString(String.valueOf(getDayOfMonth())));
217                    }
218                    break;
219                case YEARLY:
220                    pattern.put(JsonKey.PATTERN_MONTH, new JSONString(String.valueOf(getMonth())));
221                    if (null != getWeekDay()) {
222                        pattern.put(JsonKey.PATTERN_WEEKS_OF_MONTH, toJsonStringList(getWeeksOfMonth()));
223                        pattern.put(JsonKey.PATTERN_WEEKDAYS, toJsonStringList(getWeekDays()));
224                    } else {
225                        pattern.put(JsonKey.PATTERN_DAY_OF_MONTH, new JSONString(String.valueOf(getDayOfMonth())));
226                    }
227                    break;
228                case INDIVIDUAL:
229                    pattern.put(JsonKey.PATTERN_DATES, datesToJsonArray(getIndividualDates()));
230                    break;
231                case NONE:
232                default:
233                    break;
234            }
235        }
236        return pattern;
237    }
238
239    /**
240     * Extracts the dates from a JSON array.
241     * @param json the JSON array where the dates are stored in.
242     * @return list of the extracted dates.
243     */
244    private SortedSet<Date> readDates(JSONValue json) {
245
246        JSONArray array = null == json ? null : json.isArray();
247        if (null != array) {
248            SortedSet<Date> result = new TreeSet<>();
249            for (int i = 0; i < array.size(); i++) {
250                Date d = readOptionalDate(array.get(i));
251                if (null != d) {
252                    result.add(d);
253                }
254            }
255            return result;
256        }
257        return new TreeSet<>();
258    }
259
260    /**
261     * Read an optional boolean value form a JSON value.
262     * @param val the JSON value that should represent the boolean.
263     * @return the boolean from the JSON or null if reading the boolean fails.
264     */
265    private Boolean readOptionalBoolean(JSONValue val) {
266
267        JSONBoolean b = null == val ? null : val.isBoolean();
268        if (b != null) {
269            return Boolean.valueOf(b.booleanValue());
270        }
271        return null;
272    }
273
274    /**
275     * Read an optional Date value form a JSON value.
276     * @param val the JSON value that should represent the Date as long value in a string.
277     * @return the Date from the JSON or null if reading the date fails.
278     */
279    private Date readOptionalDate(JSONValue val) {
280
281        JSONString str = null == val ? null : val.isString();
282        if (str != null) {
283            try {
284                return new Date(Long.parseLong(str.stringValue()));
285            } catch (@SuppressWarnings("unused") NumberFormatException e) {
286                // do nothing - return the default value
287            }
288        }
289        return null;
290    }
291
292    /**
293     * Read an optional int value form a JSON value.
294     * @param val the JSON value that should represent the int.
295     * @return the int from the JSON or 0 reading the int fails.
296     */
297    private int readOptionalInt(JSONValue val) {
298
299        JSONString str = null == val ? null : val.isString();
300        if (str != null) {
301            try {
302                return Integer.valueOf(str.stringValue()).intValue();
303            } catch (@SuppressWarnings("unused") NumberFormatException e) {
304                // Do nothing, return default value
305            }
306        }
307        return 0;
308    }
309
310    /**
311     * Read an optional month value form a JSON value.
312     * @param val the JSON value that should represent the month.
313     * @return the month from the JSON or null if reading the month fails.
314     */
315    private Month readOptionalMonth(JSONValue val) {
316
317        String str = readOptionalString(val);
318        if (null != str) {
319            try {
320                return Month.valueOf(str);
321            } catch (@SuppressWarnings("unused") IllegalArgumentException e) {
322                // Do nothing -return the default value
323            }
324        }
325        return null;
326    }
327
328    /**
329     * Read an optional string value form a JSON value.
330     * @param val the JSON value that should represent the string.
331     * @return the string from the JSON or null if reading the string fails.
332     */
333    private String readOptionalString(JSONValue val) {
334
335        JSONString str = null == val ? null : val.isString();
336        if (str != null) {
337            return str.stringValue();
338        }
339        return null;
340    }
341
342    /**
343     * Read an optional uuid stored as JSON string.
344     * @param val the JSON value to read the uuid from.
345     * @return the uuid, or <code>null</code> if the uuid can not be read.
346     */
347    private CmsUUID readOptionalUUID(JSONValue val) {
348
349        String id = readOptionalString(val);
350        if (null != id) {
351            try {
352                CmsUUID uuid = CmsUUID.valueOf(id);
353                return uuid;
354            } catch (@SuppressWarnings("unused") NumberFormatException e) {
355                // Do nothing, just return null
356            }
357        }
358        return null;
359    }
360
361    /**
362     * Read pattern information from the provided JSON object.
363     * @param patternJson the JSON object containing the pattern information.
364     */
365    private void readPattern(JSONObject patternJson) {
366
367        setPatternType(readPatternType(patternJson.get(JsonKey.PATTERN_TYPE)));
368        setInterval(readOptionalInt(patternJson.get(JsonKey.PATTERN_INTERVAL)));
369        setWeekDays(readWeekDays(patternJson.get(JsonKey.PATTERN_WEEKDAYS)));
370        setDayOfMonth(readOptionalInt(patternJson.get(JsonKey.PATTERN_DAY_OF_MONTH)));
371        setEveryWorkingDay(readOptionalBoolean(patternJson.get(JsonKey.PATTERN_EVERYWORKINGDAY)));
372        setWeeksOfMonth(readWeeksOfMonth(patternJson.get(JsonKey.PATTERN_WEEKS_OF_MONTH)));
373        setIndividualDates(readDates(patternJson.get(JsonKey.PATTERN_DATES)));
374        setMonth(readOptionalMonth(patternJson.get(JsonKey.PATTERN_MONTH)));
375
376    }
377
378    /**
379     * Reads the pattern type from the provided JSON. Defaults to type NONE.
380     * @param val the JSON object containing the pattern type information.
381     * @return the pattern type.
382     */
383    private PatternType readPatternType(JSONValue val) {
384
385        PatternType patterntype;
386        try {
387            String str = readOptionalString(val);
388            patterntype = PatternType.valueOf(str);
389        } catch (@SuppressWarnings("unused") IllegalArgumentException e) {
390            patterntype = PatternType.NONE;
391        }
392        return patterntype;
393    }
394
395    /**
396     * Read a single weekday from the provided JSON value.
397     * @param val the value to read the week day from.
398     * @return the week day read
399     * @throws IllegalArgumentException thrown if the provided JSON value is not the representation of a week day.
400     */
401    private WeekDay readWeekDay(JSONValue val) throws IllegalArgumentException {
402
403        String str = readOptionalString(val);
404        if (null != str) {
405            return WeekDay.valueOf(str);
406        }
407        throw new IllegalArgumentException();
408    }
409
410    /**
411     * Reads the weekday information.
412     * @param val the JSON object containing the weekday information.
413     * @return the weekdays extracted, defaults to the empty list, if no information is found.
414     */
415    private SortedSet<WeekDay> readWeekDays(JSONValue val) {
416
417        JSONArray array = null == val ? null : val.isArray();
418        if (null != array) {
419            SortedSet<WeekDay> result = new TreeSet<>();
420            for (int i = 0; i < array.size(); i++) {
421                try {
422                    result.add(readWeekDay(array.get(i)));
423                } catch (@SuppressWarnings("unused") IllegalArgumentException e) {
424                    // Just skip
425                }
426            }
427            return result;
428        }
429        return new TreeSet<>();
430    }
431
432    /**
433     * Read "weeks of month" information from JSON.
434     * @param json the JSON where information is read from.
435     * @return the list of weeks read, defaults to the empty list.
436     */
437    private SortedSet<WeekOfMonth> readWeeksOfMonth(JSONValue json) {
438
439        JSONArray array = null == json ? null : json.isArray();
440        if (null != array) {
441            SortedSet<WeekOfMonth> result = new TreeSet<>();
442            for (int i = 0; i < array.size(); i++) {
443                String weekStr = readOptionalString(array.get(i));
444                try {
445                    WeekOfMonth week = WeekOfMonth.valueOf(weekStr);
446                    result.add(week);
447                } catch (@SuppressWarnings("unused") IllegalArgumentException | NullPointerException e) {
448                    // Just skip
449                }
450            }
451            return result;
452        }
453        return new TreeSet<>();
454    }
455
456    /**
457     * Convert a list of objects to a JSON array with the string representations of that objects.
458     * @param list the list of objects.
459     * @return the JSON array with the string representations.
460     */
461    private JSONValue toJsonStringList(Collection<? extends Object> list) {
462
463        if (null != list) {
464            JSONArray array = new JSONArray();
465            for (Object o : list) {
466                array.set(array.size(), new JSONString(o.toString()));
467            }
468            return array;
469        } else {
470            return null;
471        }
472    }
473
474    /**
475     * Try to set the value from the provided Json string.
476     * @param value the value to set.
477     * @throws Exception thrown if parsing fails.
478     */
479    private void tryToSetParsedValue(String value) throws Exception {
480
481        JSONObject json = JSONParser.parseStrict(value).isObject();
482        JSONValue val = json.get(JsonKey.START);
483        setStart(readOptionalDate(val));
484        val = json.get(JsonKey.END);
485        setEnd(readOptionalDate(val));
486        setWholeDay(readOptionalBoolean(json.get(JsonKey.WHOLE_DAY)));
487        JSONObject patternJson = json.get(JsonKey.PATTERN).isObject();
488        readPattern(patternJson);
489        setExceptions(readDates(json.get(JsonKey.EXCEPTIONS)));
490        setSeriesEndDate(readOptionalDate(json.get(JsonKey.SERIES_ENDDATE)));
491        setOccurrences(readOptionalInt(json.get(JsonKey.SERIES_OCCURRENCES)));
492        setDerivedEndType();
493        setCurrentTillEnd(readOptionalBoolean(json.get(JsonKey.CURRENT_TILL_END)));
494        setParentSeriesId(readOptionalUUID(json.get(JsonKey.PARENT_SERIES)));
495
496    }
497}