001    package biweekly.property.marshaller;
002    
003    import java.util.ArrayList;
004    import java.util.Date;
005    import java.util.List;
006    
007    import biweekly.ICalDataType;
008    import biweekly.Warning;
009    import biweekly.io.json.JCalValue;
010    import biweekly.io.xml.XCalElement;
011    import biweekly.parameter.ICalParameters;
012    import biweekly.property.RecurrenceDates;
013    import biweekly.util.Duration;
014    import 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     */
045    public class RecurrenceDatesMarshaller extends ICalPropertyMarshaller<RecurrenceDates> {
046            public RecurrenceDatesMarshaller() {
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    }