001package biweekly.io.scribe.property;
002
003import java.util.ArrayList;
004import java.util.Date;
005import java.util.List;
006
007import biweekly.ICalDataType;
008import biweekly.Warning;
009import biweekly.io.json.JCalValue;
010import biweekly.io.xml.XCalElement;
011import biweekly.parameter.ICalParameters;
012import biweekly.property.RecurrenceDates;
013import biweekly.util.Duration;
014import biweekly.util.Period;
015
016/*
017 Copyright (c) 2013, Michael Angstadt
018 All rights reserved.
019
020 Redistribution and use in source and binary forms, with or without
021 modification, are permitted provided that the following conditions are met: 
022
023 1. Redistributions of source code must retain the above copyright notice, this
024 list of conditions and the following disclaimer. 
025 2. Redistributions in binary form must reproduce the above copyright notice,
026 this list of conditions and the following disclaimer in the documentation
027 and/or other materials provided with the distribution. 
028
029 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
030 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
031 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
032 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
033 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
034 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
035 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
036 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
037 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
038 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
039 */
040
041/**
042 * Marshals {@link RecurrenceDates} properties.
043 * @author Michael Angstadt
044 */
045public class RecurrenceDatesScribe extends ICalPropertyScribe<RecurrenceDates> {
046        public RecurrenceDatesScribe() {
047                super(RecurrenceDates.class, "RDATE", ICalDataType.DATE_TIME);
048        }
049
050        @Override
051        protected ICalDataType _dataType(RecurrenceDates property) {
052                if (property.getDates() != null) {
053                        return property.hasTime() ? ICalDataType.DATE_TIME : ICalDataType.DATE;
054                }
055                if (property.getPeriods() != null) {
056                        return ICalDataType.PERIOD;
057                }
058                return getDefaultDataType();
059        }
060
061        @Override
062        protected String _writeText(final RecurrenceDates property) {
063                List<Date> dates = property.getDates();
064                if (dates != null) {
065                        return list(dates, new ListCallback<Date>() {
066                                public String asString(Date date) {
067                                        return date(date).time(property.hasTime()).tzid(property.getTimezoneId()).write();
068                                }
069                        });
070                }
071
072                List<Period> periods = property.getPeriods();
073                if (periods != null) {
074                        return list(periods, new ListCallback<Period>() {
075                                public String asString(Period period) {
076                                        StringBuilder sb = new StringBuilder();
077
078                                        if (period.getStartDate() != null) {
079                                                String date = date(period.getStartDate()).tzid(property.getTimezoneId()).write();
080                                                sb.append(date);
081                                        }
082
083                                        sb.append('/');
084
085                                        if (period.getEndDate() != null) {
086                                                String date = date(period.getEndDate()).tzid(property.getTimezoneId()).write();
087                                                sb.append(date);
088                                        } else if (period.getDuration() != null) {
089                                                sb.append(period.getDuration());
090                                        }
091
092                                        return sb.toString();
093                                }
094                        });
095                }
096
097                return "";
098        }
099
100        @Override
101        protected RecurrenceDates _parseText(String value, ICalDataType dataType, ICalParameters parameters, List<Warning> warnings) {
102                return parse(list(value), dataType, parameters, warnings);
103        }
104
105        @Override
106        protected void _writeXml(RecurrenceDates property, XCalElement element) {
107                List<Date> dates = property.getDates();
108                if (dates != null) {
109                        ICalDataType dataType = property.hasTime() ? ICalDataType.DATE_TIME : ICalDataType.DATE;
110                        if (dates.isEmpty()) {
111                                element.append(dataType, "");
112                        } else {
113                                for (Date date : dates) {
114                                        String dateStr = date(date).time(property.hasTime()).tzid(property.getTimezoneId()).extended(true).write();
115                                        element.append(dataType, dateStr);
116                                }
117                        }
118                        return;
119                }
120
121                List<Period> periods = property.getPeriods();
122                if (periods != null) {
123                        if (periods.isEmpty()) {
124                                element.append(ICalDataType.PERIOD, "");
125                        } else {
126                                for (Period period : periods) {
127                                        XCalElement periodElement = element.append(ICalDataType.PERIOD);
128
129                                        Date start = period.getStartDate();
130                                        if (start != null) {
131                                                periodElement.append("start", date(start).tzid(property.getTimezoneId()).extended(true).write());
132                                        }
133
134                                        Date end = period.getEndDate();
135                                        if (end != null) {
136                                                periodElement.append("end", date(end).tzid(property.getTimezoneId()).extended(true).write());
137                                        }
138
139                                        Duration duration = period.getDuration();
140                                        if (duration != null) {
141                                                periodElement.append("duration", duration.toString());
142                                        }
143                                }
144                        }
145                        return;
146                }
147
148                element.append(defaultDataType, "");
149        }
150
151        @Override
152        protected RecurrenceDates _parseXml(XCalElement element, ICalParameters parameters, List<Warning> warnings) {
153                //parse as periods
154                List<XCalElement> periodElements = element.children(ICalDataType.PERIOD);
155                if (!periodElements.isEmpty()) {
156                        List<Period> periods = new ArrayList<Period>(periodElements.size());
157                        for (XCalElement periodElement : periodElements) {
158                                String startStr = periodElement.first("start");
159                                if (startStr == null) {
160                                        warnings.add(Warning.parse(9));
161                                        continue;
162                                }
163
164                                Date start = null;
165                                try {
166                                        start = date(startStr).tzid(parameters.getTimezoneId(), warnings).parse();
167                                } catch (IllegalArgumentException e) {
168                                        warnings.add(Warning.parse(10, startStr));
169                                        continue;
170                                }
171
172                                String endStr = periodElement.first("end");
173                                if (endStr != null) {
174                                        try {
175                                                Date end = date(endStr).tzid(parameters.getTimezoneId(), warnings).parse();
176                                                periods.add(new Period(start, end));
177                                        } catch (IllegalArgumentException e) {
178                                                warnings.add(Warning.parse(11, endStr));
179                                        }
180                                        continue;
181                                }
182
183                                String durationStr = periodElement.first("duration");
184                                if (durationStr != null) {
185                                        try {
186                                                Duration duration = Duration.parse(durationStr);
187                                                periods.add(new Period(start, duration));
188                                        } catch (IllegalArgumentException e) {
189                                                warnings.add(Warning.parse(12, durationStr));
190                                        }
191                                        continue;
192                                }
193
194                                warnings.add(Warning.parse(13));
195                        }
196                        return new RecurrenceDates(periods);
197                }
198
199                //parse as dates
200                List<String> dateStrs = element.all(ICalDataType.DATE_TIME);
201                boolean hasTime = !dateStrs.isEmpty();
202                dateStrs.addAll(element.all(ICalDataType.DATE));
203                if (!dateStrs.isEmpty()) {
204                        List<Date> dates = new ArrayList<Date>(dateStrs.size());
205                        for (String dateStr : dateStrs) {
206                                try {
207                                        Date date = date(dateStr).tzid(parameters.getTimezoneId(), warnings).parse();
208                                        dates.add(date);
209                                } catch (IllegalArgumentException e) {
210                                        warnings.add(Warning.parse(15, dateStr));
211                                }
212                        }
213                        return new RecurrenceDates(dates, hasTime);
214                }
215
216                throw missingXmlElements(ICalDataType.PERIOD, ICalDataType.DATE_TIME, ICalDataType.DATE);
217        }
218
219        @Override
220        protected JCalValue _writeJson(RecurrenceDates property) {
221                List<String> values = new ArrayList<String>();
222
223                List<Date> dates = property.getDates();
224                List<Period> periods = property.getPeriods();
225                if (dates != null) {
226                        for (Date date : dates) {
227                                String dateStr = date(date).time(property.hasTime()).tzid(property.getTimezoneId()).extended(true).write();
228                                values.add(dateStr);
229                        }
230                } else if (periods != null) {
231                        for (Period period : property.getPeriods()) {
232                                StringBuilder sb = new StringBuilder();
233                                if (period.getStartDate() != null) {
234                                        String value = date(period.getStartDate()).tzid(property.getTimezoneId()).extended(true).write();
235                                        sb.append(value);
236                                }
237
238                                sb.append('/');
239
240                                if (period.getEndDate() != null) {
241                                        String value = date(period.getEndDate()).tzid(property.getTimezoneId()).extended(true).write();
242                                        sb.append(value);
243                                } else if (period.getDuration() != null) {
244                                        sb.append(period.getDuration());
245                                }
246
247                                values.add(sb.toString());
248                        }
249                }
250
251                if (values.isEmpty()) {
252                        values.add("");
253                }
254                return JCalValue.multi(values);
255        }
256
257        @Override
258        protected RecurrenceDates _parseJson(JCalValue value, ICalDataType dataType, ICalParameters parameters, List<Warning> warnings) {
259                return parse(value.asMulti(), dataType, parameters, warnings);
260        }
261
262        private RecurrenceDates parse(List<String> valueStrs, ICalDataType dataType, ICalParameters parameters, List<Warning> warnings) {
263                if (dataType == ICalDataType.PERIOD) {
264                        //parse as periods
265                        List<Period> periods = new ArrayList<Period>(valueStrs.size());
266                        for (String timePeriodStr : valueStrs) {
267                                String timePeriodStrSplit[] = timePeriodStr.split("/");
268
269                                if (timePeriodStrSplit.length < 2) {
270                                        warnings.add(Warning.parse(13));
271                                        continue;
272                                }
273
274                                String startStr = timePeriodStrSplit[0];
275                                Date start;
276                                try {
277                                        start = date(startStr).tzid(parameters.getTimezoneId(), warnings).parse();
278                                } catch (IllegalArgumentException e) {
279                                        warnings.add(Warning.parse(10, startStr));
280                                        continue;
281                                }
282
283                                String endStr = timePeriodStrSplit[1];
284                                try {
285                                        Date end = date(endStr).tzid(parameters.getTimezoneId(), warnings).parse();
286                                        periods.add(new Period(start, end));
287                                } catch (IllegalArgumentException e) {
288                                        //must be a duration
289                                        try {
290                                                Duration duration = Duration.parse(endStr);
291                                                periods.add(new Period(start, duration));
292                                        } catch (IllegalArgumentException e2) {
293                                                warnings.add(Warning.parse(14, endStr));
294                                                continue;
295                                        }
296                                }
297                        }
298                        return new RecurrenceDates(periods);
299                }
300
301                //parse as dates
302                boolean hasTime = (dataType == ICalDataType.DATE_TIME);
303                List<Date> dates = new ArrayList<Date>(valueStrs.size());
304                for (String s : valueStrs) {
305                        try {
306                                Date date = date(s).tzid(parameters.getTimezoneId(), warnings).parse();
307                                dates.add(date);
308                        } catch (IllegalArgumentException e) {
309                                warnings.add(Warning.parse(15, s));
310                        }
311                }
312                return new RecurrenceDates(dates, hasTime);
313        }
314}