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 }