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}