001package biweekly.io; 002 003import java.util.ArrayList; 004import java.util.Collections; 005import java.util.Date; 006import java.util.HashSet; 007import java.util.List; 008import java.util.Set; 009import java.util.TimeZone; 010 011import biweekly.component.DaylightSavingsTime; 012import biweekly.component.ICalComponent; 013import biweekly.component.Observance; 014import biweekly.component.StandardTime; 015import biweekly.component.VAlarm; 016import biweekly.component.VTimezone; 017import biweekly.io.ICalTimeZone.Boundary; 018import biweekly.parameter.Related; 019import biweekly.parameter.Role; 020import biweekly.property.Action; 021import biweekly.property.Attachment; 022import biweekly.property.Attendee; 023import biweekly.property.AudioAlarm; 024import biweekly.property.DateEnd; 025import biweekly.property.DateStart; 026import biweekly.property.Daylight; 027import biweekly.property.Description; 028import biweekly.property.DisplayAlarm; 029import biweekly.property.DurationProperty; 030import biweekly.property.EmailAlarm; 031import biweekly.property.Organizer; 032import biweekly.property.ProcedureAlarm; 033import biweekly.property.Repeat; 034import biweekly.property.Timezone; 035import biweekly.property.Trigger; 036import biweekly.property.UtcOffsetProperty; 037import biweekly.property.VCalAlarmProperty; 038import biweekly.util.DateTimeComponents; 039import biweekly.util.Duration; 040import biweekly.util.ICalDate; 041import biweekly.util.UtcOffset; 042 043import com.google.ical.values.DateTimeValue; 044 045/* 046 Copyright (c) 2013-2015, Michael Angstadt 047 All rights reserved. 048 049 Redistribution and use in source and binary forms, with or without 050 modification, are permitted provided that the following conditions are met: 051 052 1. Redistributions of source code must retain the above copyright notice, this 053 list of conditions and the following disclaimer. 054 2. Redistributions in binary form must reproduce the above copyright notice, 055 this list of conditions and the following disclaimer in the documentation 056 and/or other materials provided with the distribution. 057 058 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 059 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 060 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 061 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 062 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 063 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 064 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 065 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 066 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 067 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 068 */ 069 070/** 071 * Converts various properties/components into other properties/components for 072 * vCalendar-iCalendar compatibility. 073 * @author Michael Angstadt 074 */ 075public class DataModelConverter { 076 /** 077 * Converts vCalendar timezone information to am iCalendar {@link VTimezone} 078 * component. 079 * @param daylights the DAYLIGHT properties 080 * @param tz the TZ property 081 * @return the VTIMEZONE component 082 */ 083 public static VTimezone convert(List<Daylight> daylights, Timezone tz) { 084 UtcOffset tzOffset = (tz == null) ? null : tz.getValue(); 085 if (daylights.isEmpty() && tzOffset == null) { 086 return null; 087 } 088 089 VTimezone timezone = new VTimezone("TZ"); 090 if (daylights.isEmpty() && tzOffset != null) { 091 StandardTime st = new StandardTime(); 092 st.setTimezoneOffsetFrom(tzOffset); 093 st.setTimezoneOffsetTo(tzOffset); 094 timezone.addStandardTime(st); 095 return timezone; 096 } 097 098 for (Daylight daylight : daylights) { 099 if (!daylight.isDaylight()) { 100 continue; 101 } 102 103 UtcOffset daylightOffset = daylight.getOffset(); 104 UtcOffset standardOffset = new UtcOffset(daylightOffset.getHour() - 1, daylightOffset.getMinute()); 105 106 DaylightSavingsTime dst = new DaylightSavingsTime(); 107 dst.setDateStart(daylight.getStart()); 108 dst.setTimezoneOffsetFrom(standardOffset); 109 dst.setTimezoneOffsetTo(daylightOffset); 110 dst.addTimezoneName(daylight.getDaylightName()); 111 timezone.addDaylightSavingsTime(dst); 112 113 StandardTime st = new StandardTime(); 114 st.setDateStart(daylight.getEnd()); 115 st.setTimezoneOffsetFrom(daylightOffset); 116 st.setTimezoneOffsetTo(standardOffset); 117 st.addTimezoneName(daylight.getStandardName()); 118 timezone.addStandardTime(st); 119 } 120 121 return timezone.getComponents().isEmpty() ? null : timezone; 122 } 123 124 /** 125 * Converts an iCalendar {@link VTimezone} component into the appropriate 126 * vCalendar properties. 127 * @param timezone the TIMEZONE component 128 * @param dates the date values in the vCalendar object that are effected by 129 * the timezone. 130 * @return the vCalendar properties 131 */ 132 public static VCalTimezoneProperties convert(VTimezone timezone, List<Date> dates) { 133 List<Daylight> daylights = new ArrayList<Daylight>(); 134 Timezone tz = null; 135 if (dates.isEmpty()) { 136 return new VCalTimezoneProperties(daylights, tz); 137 } 138 139 ICalTimeZone icalTz = new ICalTimeZone(timezone); 140 Collections.sort(dates); 141 Set<DateTimeValue> daylightStartDates = new HashSet<DateTimeValue>(); 142 boolean zeroObservanceUsed = false; 143 for (Date date : dates) { 144 Boundary boundary = icalTz.getObservanceBoundary(date); 145 Observance observance = boundary.getObservanceIn(); 146 Observance observanceAfter = boundary.getObservanceAfter(); 147 if (observance == null && observanceAfter == null) { 148 continue; 149 } 150 151 if (observance == null) { 152 //the date comes before the earliest observance 153 if (observanceAfter instanceof StandardTime && !zeroObservanceUsed) { 154 UtcOffset offset = getOffset(observanceAfter.getTimezoneOffsetFrom()); 155 DateTimeValue start = null; 156 DateTimeValue end = boundary.getObservanceAfterStart(); 157 String standardName = icalTz.getDisplayName(false, TimeZone.SHORT); 158 String daylightName = icalTz.getDisplayName(true, TimeZone.SHORT); 159 160 Daylight daylight = new Daylight(true, offset, convert(start), convert(end), standardName, daylightName); 161 daylights.add(daylight); 162 zeroObservanceUsed = true; 163 } 164 165 if (observanceAfter instanceof DaylightSavingsTime) { 166 UtcOffset offset = getOffset(observanceAfter.getTimezoneOffsetFrom()); 167 if (offset != null) { 168 tz = new Timezone(offset); 169 } 170 } 171 172 continue; 173 } 174 175 if (observance instanceof StandardTime) { 176 UtcOffset offset = getOffset(observance.getTimezoneOffsetTo()); 177 if (offset != null) { 178 tz = new Timezone(offset); 179 } 180 continue; 181 } 182 183 if (observance instanceof DaylightSavingsTime && !daylightStartDates.contains(boundary.getObservanceInStart())) { 184 UtcOffset offset = getOffset(observance.getTimezoneOffsetTo()); 185 DateTimeValue start = boundary.getObservanceInStart(); 186 DateTimeValue end = null; 187 if (observanceAfter != null) { 188 end = boundary.getObservanceAfterStart(); 189 } 190 191 String standardName = icalTz.getDisplayName(false, TimeZone.SHORT); 192 String daylightName = icalTz.getDisplayName(true, TimeZone.SHORT); 193 194 Daylight daylight = new Daylight(true, offset, convert(start), convert(end), standardName, daylightName); 195 daylights.add(daylight); 196 daylightStartDates.add(start); 197 continue; 198 } 199 } 200 201 if (tz == null) { 202 int rawOffset = icalTz.getRawOffset(); 203 UtcOffset offset = new UtcOffset(rawOffset); 204 tz = new Timezone(offset); 205 } 206 207 if (daylights.isEmpty()) { 208 Daylight daylight = new Daylight(); 209 daylight.setDaylight(false); 210 daylights.add(daylight); 211 } 212 213 return new VCalTimezoneProperties(daylights, tz); 214 } 215 216 private static UtcOffset getOffset(UtcOffsetProperty property) { 217 return (property == null) ? null : property.getValue(); 218 } 219 220 private static ICalDate convert(DateTimeValue value) { 221 if (value == null) { 222 return null; 223 } 224 225 //@formatter:off 226 DateTimeComponents components = new DateTimeComponents( 227 value.year(), 228 value.month(), 229 value.day(), 230 value.hour(), 231 value.minute(), 232 value.second(), 233 false 234 ); 235 //@formatter:on 236 237 return new ICalDate(components, true); 238 } 239 240 /** 241 * Converts a {@link Attendee} property to a {@link Organizer} property. 242 * @param attendee the ATTENDEE property 243 * @return the ORGANIZER property 244 */ 245 public static Organizer convert(Attendee attendee) { 246 Organizer organizer = new Organizer(attendee.getCommonName(), attendee.getEmail()); 247 organizer.setUri(attendee.getUri()); 248 organizer.setParameters(attendee.getParameters()); 249 return organizer; 250 } 251 252 /** 253 * Converts a {@link Organizer} property to a {@link Attendee} property. 254 * @param organizer the ORGANIZER property 255 * @return the ATTENDEE property 256 */ 257 public static Attendee convert(Organizer organizer) { 258 Attendee attendee = new Attendee(organizer.getCommonName(), organizer.getEmail()); 259 attendee.setRole(Role.ORGANIZER); 260 attendee.setUri(organizer.getUri()); 261 attendee.setParameters(organizer.getParameters()); 262 return attendee; 263 } 264 265 /** 266 * Converts a {@link AudioAlarm} property to a {@link VAlarm} component. 267 * @param aalarm the AALARM property 268 * @return the VALARM component 269 */ 270 public static VAlarm convert(AudioAlarm aalarm) { 271 Trigger trigger = new Trigger(aalarm.getStart()); 272 VAlarm valarm = new VAlarm(Action.audio(), trigger); 273 274 valarm.addAttachment(buildAttachment(aalarm)); 275 valarm.setDuration(aalarm.getSnooze()); 276 valarm.setRepeat(aalarm.getRepeat()); 277 278 return valarm; 279 } 280 281 /** 282 * Converts a {@link DisplayAlarm} property to a {@link VAlarm} component. 283 * @param dalarm the DALARM property 284 * @return the VALARM component 285 */ 286 public static VAlarm convert(DisplayAlarm dalarm) { 287 Trigger trigger = new Trigger(dalarm.getStart()); 288 VAlarm valarm = new VAlarm(Action.display(), trigger); 289 290 valarm.setDescription(dalarm.getText()); 291 valarm.setDuration(dalarm.getSnooze()); 292 valarm.setRepeat(dalarm.getRepeat()); 293 294 return valarm; 295 } 296 297 /** 298 * Converts a {@link EmailAlarm} property to a {@link VAlarm} component. 299 * @param malarm the MALARM property 300 * @return the VALARM component 301 */ 302 public static VAlarm convert(EmailAlarm malarm) { 303 Trigger trigger = new Trigger(malarm.getStart()); 304 VAlarm valarm = new VAlarm(Action.email(), trigger); 305 306 String email = malarm.getEmail(); 307 if (email != null) { 308 valarm.addAttendee(new Attendee(null, email)); 309 } 310 valarm.setDescription(malarm.getNote()); 311 valarm.setDuration(malarm.getSnooze()); 312 valarm.setRepeat(malarm.getRepeat()); 313 314 return valarm; 315 } 316 317 /** 318 * Converts a {@link ProcedureAlarm} property to a {@link VAlarm} component. 319 * @param dalarm the PALARM property 320 * @return the VALARM component 321 */ 322 public static VAlarm convert(ProcedureAlarm dalarm) { 323 Trigger trigger = new Trigger(dalarm.getStart()); 324 VAlarm valarm = new VAlarm(Action.procedure(), trigger); 325 326 valarm.setDescription(dalarm.getPath()); 327 valarm.setDuration(dalarm.getSnooze()); 328 valarm.setRepeat(dalarm.getRepeat()); 329 330 return valarm; 331 } 332 333 private static Attachment buildAttachment(AudioAlarm aalarm) { 334 String type = aalarm.getParameter("TYPE"); 335 String contentType = (type == null) ? null : "audio/" + type.toLowerCase(); 336 byte data[] = aalarm.getData(); 337 if (data != null) { 338 return new Attachment(contentType, data); 339 } 340 341 String contentId = aalarm.getContentId(); 342 String uri = (contentId == null) ? aalarm.getUri() : "CID:" + contentId; 343 return new Attachment(contentType, uri); 344 } 345 346 /** 347 * Converts a {@link VAlarm} component to a vCal alarm property. 348 * @param valarm the VALARM component 349 * @param parent the component that holds the VALARM component 350 * @return the alarm property 351 */ 352 public static VCalAlarmProperty convert(VAlarm valarm, ICalComponent parent) { 353 Action action = valarm.getAction(); 354 if (action == null) { 355 return null; 356 } 357 358 if (action.isAudio()) { 359 AudioAlarm aalarm = new AudioAlarm(); 360 aalarm.setStart(determineStartDate(valarm, parent)); 361 362 List<Attachment> attaches = valarm.getAttachments(); 363 if (!attaches.isEmpty()) { 364 Attachment attach = attaches.get(0); 365 366 String formatType = attach.getFormatType(); 367 aalarm.setParameter("TYPE", formatType); 368 369 byte[] data = attach.getData(); 370 if (data != null) { 371 aalarm.setData(data); 372 } 373 374 String uri = attach.getUri(); 375 if (uri != null) { 376 if (uri.toUpperCase().startsWith("CID:")) { 377 String contentId = uri.substring(4); 378 aalarm.setContentId(contentId); 379 } else { 380 aalarm.setUri(uri); 381 } 382 } 383 } 384 385 DurationProperty duration = valarm.getDuration(); 386 if (duration != null) { 387 aalarm.setSnooze(duration.getValue()); 388 } 389 390 Repeat repeat = valarm.getRepeat(); 391 if (repeat != null) { 392 aalarm.setRepeat(repeat.getValue()); 393 } 394 395 return aalarm; 396 } 397 398 if (action.isDisplay()) { 399 Description description = valarm.getDescription(); 400 String text = (description == null) ? null : description.getValue(); 401 return new DisplayAlarm(text); 402 } 403 404 if (action.isEmail()) { 405 List<Attendee> attendees = valarm.getAttendees(); 406 String email = attendees.isEmpty() ? null : attendees.get(0).getEmail(); 407 EmailAlarm malarm = new EmailAlarm(email); 408 409 Description description = valarm.getDescription(); 410 String note = (description == null) ? null : description.getValue(); 411 malarm.setNote(note); 412 413 return malarm; 414 } 415 416 if (action.isProcedure()) { 417 Description description = valarm.getDescription(); 418 String path = (description == null) ? null : description.getValue(); 419 return new ProcedureAlarm(path); 420 } 421 422 return null; 423 } 424 425 private static Date determineStartDate(VAlarm valarm, ICalComponent parent) { 426 Trigger trigger = valarm.getTrigger(); 427 if (trigger == null) { 428 return null; 429 } 430 431 Date start = trigger.getDate(); 432 if (start != null) { 433 return start; 434 } 435 436 Duration triggerDuration = trigger.getDuration(); 437 if (triggerDuration == null) { 438 return null; 439 } 440 441 Related related = trigger.getRelated(); 442 if (related == Related.START) { 443 DateStart parentDateStart = parent.getProperty(DateStart.class); 444 if (parentDateStart == null) { 445 return null; 446 } 447 448 Date date = parentDateStart.getValue(); 449 return (date == null) ? null : triggerDuration.add(date); 450 } 451 452 if (related == Related.END) { 453 DateEnd parentDateEnd = parent.getProperty(DateEnd.class); 454 if (parentDateEnd != null) { 455 Date date = parentDateEnd.getValue(); 456 return (date == null) ? null : triggerDuration.add(date); 457 } 458 459 DateStart parentDateStart = parent.getProperty(DateStart.class); 460 DurationProperty parentDuration = parent.getProperty(DurationProperty.class); 461 if (parentDuration == null || parentDateStart == null) { 462 return null; 463 } 464 465 Duration duration = parentDuration.getValue(); 466 Date date = parentDateStart.getValue(); 467 return (duration == null || date == null) ? null : duration.add(date); 468 } 469 470 return null; 471 } 472 473 public static class VCalTimezoneProperties { 474 private final List<Daylight> daylights; 475 private final Timezone tz; 476 477 public VCalTimezoneProperties(List<Daylight> daylights, Timezone tz) { 478 this.daylights = daylights; 479 this.tz = tz; 480 } 481 482 public List<Daylight> getDaylights() { 483 return daylights; 484 } 485 486 public Timezone getTz() { 487 return tz; 488 } 489 490 } 491}