001package biweekly; 002 003import java.io.File; 004import java.io.IOException; 005import java.io.OutputStream; 006import java.io.Writer; 007import java.util.ArrayList; 008import java.util.Date; 009import java.util.HashMap; 010import java.util.HashSet; 011import java.util.List; 012import java.util.Map; 013import java.util.Set; 014 015import javax.xml.transform.TransformerException; 016 017import biweekly.ValidationWarnings.WarningsGroup; 018import biweekly.component.ICalComponent; 019import biweekly.component.VEvent; 020import biweekly.component.VFreeBusy; 021import biweekly.component.VJournal; 022import biweekly.component.VTodo; 023import biweekly.io.TimezoneInfo; 024import biweekly.io.json.JCalWriter; 025import biweekly.io.text.ICalWriter; 026import biweekly.io.xml.XCalDocument; 027import biweekly.io.xml.XCalWriter; 028import biweekly.property.CalendarScale; 029import biweekly.property.Categories; 030import biweekly.property.Color; 031import biweekly.property.Description; 032import biweekly.property.Geo; 033import biweekly.property.ICalProperty; 034import biweekly.property.Image; 035import biweekly.property.LastModified; 036import biweekly.property.Method; 037import biweekly.property.Name; 038import biweekly.property.ProductId; 039import biweekly.property.RefreshInterval; 040import biweekly.property.Source; 041import biweekly.property.Uid; 042import biweekly.property.Url; 043import biweekly.util.Duration; 044 045/* 046 Copyright (c) 2013-2017, 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 * <p> 072 * Represents an iCalendar object. 073 * </p> 074 * 075 * <p> 076 * <b>Examples:</b> 077 * </p> 078 * 079 * <pre class="brush:java"> 080 * ICalendar ical = new ICalendar(); 081 * 082 * VEvent event = new VEvent(); 083 * event.setSummary("Team Meeting"); 084 * Date start = ...; 085 * event.setDateStart(start); 086 * Date end = ...; 087 * event.setDateEnd(end); 088 * ical.addEvent(event); 089 * </pre> 090 * 091 * <p> 092 * <b>Getting timezone information from parsed iCalendar objects:</b> 093 * </p> 094 * 095 * <pre class="brush:java"> 096 * //The timezone information associated with an ICalendar object is stored in its TimezoneInfo object. 097 * ICalReader reader = ... 098 * ICalendar ical = reader.readNext(); 099 * TimezoneInfo tzinfo = ical.getTimezoneInfo(); 100 * 101 * //You can use this object to get the VTIMEZONE components that were parsed from the input stream. 102 * //Note that the VTIMEZONE components will NOT be in the ICalendar object itself 103 * Collection<VTimezone> vtimezones = tzinfo.getComponents(); 104 * 105 * //You can also get the timezone that a specific property was originally formatted in. 106 * DateStart dtstart = ical.getEvents().get(0).getDateStart(); 107 * TimeZone tz = tzinfo.getTimezone(dtstart).getTimeZone(); 108 * 109 * //This is useful for calculating recurrence rule dates. 110 * RecurrenceRule rrule = ical.getEvents(0).getRecurrenceRule(); 111 * DateIterator it = rrule.getDateIterator(dtstart.getValue(), tz); 112 * </pre> 113 * 114 * <p> 115 * <b>Setting timezone information when writing iCalendar objects:</b> 116 * </p> 117 * 118 * <pre class="brush:java"> 119 * //The TimezoneInfo field is used to determine what timezone to format each date-time value in when the ICalendar object is written. 120 * //Appropriate VTIMEZONE components are automatically added to the written iCalendar object. 121 * ICalendar ical = ... 122 * TimezoneInfo tzinfo = ical.getTimezoneInfo(); 123 * 124 * //biweekly uses the TimezoneAssignment class to define timezones. 125 * //This class groups together a Java TimeZone object, which is used to format/parse the date-time values, and its equivalent VTIMEZONE component definition. 126 * 127 * //biweekly can auto-generate the VTIMEZONE definitions by downloading them from tzurl.org. 128 * //If you want the generated VTIMEZONE components to be tailored for Microsoft Outlook email clients, pass "true" into this method. 129 * TimezoneAssignment timezone = TimezoneAssignment.download(TimeZone.getTimeZone("America/New_York"), true); 130 * 131 * //Using the TimezoneAssignment class, you can specify what timezone you'd like to format all date-time values in. 132 * tzinfo.setDefaultTimezone(timezone); 133 * 134 * //You can also specify what timezone to use for individual properties if you want. 135 * DateStart dtstart = ical.getEvents(0).getDateStart(); 136 * TimezoneAssignment losAngeles = TimezoneAssignment.download(TimeZone.getTimeZone("America/Los_Angeles"), true); 137 * tzinfo.setTimezone(dtstart, losAngeles); 138 * 139 * //The writer object will use this information to determine what timezone to format each date-time value in. 140 * //Date-time values are formatted in UTC by default. 141 * ICalWriter writer = ... 142 * writer.write(ical); 143 * </pre> 144 * 145 * <p> 146 * For more information on working with timezones, see this page: <a 147 * href="https://github.com/mangstadt/biweekly/wiki/Timezones">https://github. 148 * com/mangstadt/biweekly/wiki/Timezones</a> 149 * </p> 150 * @author Michael Angstadt 151 * @see <a href="http://tools.ietf.org/html/rfc5545">RFC 5545</a> 152 * @see <a href="http://tools.ietf.org/html/rfc2445">RFC 2445</a> 153 * @see <a href="http://www.imc.org/pdi/vcal-10.doc">vCal 1.0</a> 154 * @see <a 155 * href="http://tools.ietf.org/html/draft-ietf-calext-extensions-01">draft-ietf-calext-extensions-01</a> 156 */ 157public class ICalendar extends ICalComponent { 158 private ICalVersion version; 159 private TimezoneInfo tzinfo = new TimezoneInfo(); 160 161 /** 162 * <p> 163 * Creates a new iCalendar object. 164 * </p> 165 * <p> 166 * The following properties are added to the component when it is created: 167 * </p> 168 * <ul> 169 * <li>{@link ProductId}: Set to a value that represents this library.</li> 170 * </ul> 171 */ 172 public ICalendar() { 173 setProductId(ProductId.biweekly()); 174 } 175 176 /** 177 * Copy constructor. 178 * @param original the iCalendar object to make a copy of 179 */ 180 public ICalendar(ICalendar original) { 181 super(original); 182 version = original.version; 183 } 184 185 /** 186 * Gets the version of this iCalendar object. 187 * @return the version 188 */ 189 public ICalVersion getVersion() { 190 return version; 191 } 192 193 /** 194 * Sets the version of this iCalendar object. 195 * @param version the version 196 */ 197 public void setVersion(ICalVersion version) { 198 this.version = version; 199 } 200 201 /** 202 * <p> 203 * Gets the timezone information associated with this iCalendar object. 204 * </p> 205 * <p> 206 * When an iCalendar object is parsed from an input stream, the 207 * {@link TimezoneInfo} object remembers the original timezone definitions 208 * that each property was associated with. One use for this is when you want 209 * to calculate the dates in a recurrence rule. The recurrence rule needs to 210 * know what timezone its associated date values were originally formatted 211 * in in order to work correctly. 212 * </p> 213 * <p> 214 * When an {@link ICalendar} object is written to an output stream, its 215 * {@link TimezoneInfo} object tells the writer what timezone to format each 216 * property in. 217 * </p> 218 * @return the timezone info 219 */ 220 public TimezoneInfo getTimezoneInfo() { 221 return tzinfo; 222 } 223 224 /** 225 * <p> 226 * Sets the timezone information associated with this iCalendar object. 227 * </p> 228 * <p> 229 * When an iCalendar object is parsed from an input stream, the 230 * {@link TimezoneInfo} object remembers the original timezone definitions 231 * that each property was associated with. One use for this is when you want 232 * to calculate the dates in a recurrence rule. The recurrence rule needs to 233 * know what timezone its associated date values were originally formatted 234 * in in order to work correctly. 235 * </p> 236 * <p> 237 * When an {@link ICalendar} object is written to an output stream, its 238 * {@link TimezoneInfo} object tells the writer what timezone to format each 239 * property in. 240 * </p> 241 * @param tzinfo the timezone info (cannot be null) 242 * @throws NullPointerException if the timezone info object is null 243 */ 244 public void setTimezoneInfo(TimezoneInfo tzinfo) { 245 if (tzinfo == null) { 246 throw new NullPointerException(); 247 } 248 this.tzinfo = tzinfo; 249 } 250 251 /** 252 * Gets the name of the application that created the iCalendar object. All 253 * {@link ICalendar} objects are initialized with a product ID representing 254 * this library. 255 * @return the property instance or null if not set 256 * @see <a href="http://tools.ietf.org/html/rfc5545#page-78">RFC 5545 257 * p.78-9</a> 258 * @see <a href="http://tools.ietf.org/html/rfc2445#page-75">RFC 2445 259 * p.75-6</a> 260 * @see <a href="http://www.imc.org/pdi/vcal-10.doc">vCal 1.0 p.24</a> 261 */ 262 public ProductId getProductId() { 263 return getProperty(ProductId.class); 264 } 265 266 /** 267 * Sets the name of the application that created the iCalendar object. All 268 * {@link ICalendar} objects are initialized with a product ID representing 269 * this library. 270 * @param prodId the property instance or null to remove 271 * @see <a href="http://tools.ietf.org/html/rfc5545#page-78">RFC 5545 272 * p.78-9</a> 273 * @see <a href="http://tools.ietf.org/html/rfc2445#page-75">RFC 2445 274 * p.75-6</a> 275 * @see <a href="http://www.imc.org/pdi/vcal-10.doc">vCal 1.0 p.24</a> 276 */ 277 public void setProductId(ProductId prodId) { 278 setProperty(ProductId.class, prodId); 279 } 280 281 /** 282 * Sets the application that created the iCalendar object. All 283 * {@link ICalendar} objects are initialized with a product ID representing 284 * this library. 285 * @param prodId a unique string representing the application (e.g. 286 * "-//Company//Application//EN") or null to remove 287 * @return the property that was created 288 * @see <a href="http://tools.ietf.org/html/rfc5545#page-78">RFC 5545 289 * p.78-9</a> 290 * @see <a href="http://tools.ietf.org/html/rfc2445#page-75">RFC 2445 291 * p.75-6</a> 292 * @see <a href="http://www.imc.org/pdi/vcal-10.doc">vCal 1.0 p.24</a> 293 */ 294 public ProductId setProductId(String prodId) { 295 ProductId property = (prodId == null) ? null : new ProductId(prodId); 296 setProductId(property); 297 return property; 298 } 299 300 /** 301 * Gets the calendar system that this iCalendar object uses. If none is 302 * specified, then the calendar is assumed to be in Gregorian format. 303 * @return the calendar system or null if not set 304 * @see <a href="http://tools.ietf.org/html/rfc5545#page-76">RFC 5545 305 * p.76-7</a> 306 * @see <a href="http://tools.ietf.org/html/rfc2445#page-73">RFC 2445 307 * p.73-4</a> 308 */ 309 public CalendarScale getCalendarScale() { 310 return getProperty(CalendarScale.class); 311 } 312 313 /** 314 * Sets the calendar system that this iCalendar object uses. If none is 315 * specified, then the calendar is assumed to be in Gregorian format. 316 * @param calendarScale the calendar system or null to remove 317 * @see <a href="http://tools.ietf.org/html/rfc5545#page-76">RFC 5545 318 * p.76-7</a> 319 * @see <a href="http://tools.ietf.org/html/rfc2445#page-73">RFC 2445 320 * p.73-4</a> 321 */ 322 public void setCalendarScale(CalendarScale calendarScale) { 323 setProperty(CalendarScale.class, calendarScale); 324 } 325 326 /** 327 * Gets the type of <a href="http://tools.ietf.org/html/rfc5546">iTIP</a> 328 * request that this iCalendar object represents, or the value of the 329 * "Content-Type" header's "method" parameter if the iCalendar object is 330 * defined as a MIME message entity. 331 * @return the property or null if not set 332 * @see <a href="http://tools.ietf.org/html/rfc5546">RFC 5546</a> 333 * @see <a href="http://tools.ietf.org/html/rfc5545#page-77">RFC 5545 334 * p.77-8</a> 335 * @see <a href="http://tools.ietf.org/html/rfc2445#page-74">RFC 2445 336 * p.74-5</a> 337 */ 338 public Method getMethod() { 339 return getProperty(Method.class); 340 } 341 342 /** 343 * Sets the type of <a href="http://tools.ietf.org/html/rfc5546">iTIP</a> 344 * request that this iCalendar object represents, or the value of the 345 * "Content-Type" header's "method" parameter if the iCalendar object is 346 * defined as a MIME message entity. 347 * @param method the property or null to remove 348 * @see <a href="http://tools.ietf.org/html/rfc5546">RFC 5546</a> 349 * @see <a href="http://tools.ietf.org/html/rfc5545#page-77">RFC 5545 350 * p.77-8</a> 351 * @see <a href="http://tools.ietf.org/html/rfc2445#page-74">RFC 2445 352 * p.74-5</a> 353 */ 354 public void setMethod(Method method) { 355 setProperty(Method.class, method); 356 } 357 358 /** 359 * Sets the type of <a href="http://tools.ietf.org/html/rfc5546">iTIP</a> 360 * request that this iCalendar object represents, or the value of the 361 * "Content-Type" header's "method" parameter if the iCalendar object is 362 * defined as a MIME message entity. 363 * @param method the method or null to remove 364 * @return the property that was created 365 * @see <a href="http://tools.ietf.org/html/rfc5546">RFC 5546</a> 366 * @see <a href="http://tools.ietf.org/html/rfc5545#page-77">RFC 5545 367 * p.77-8</a> 368 * @see <a href="http://tools.ietf.org/html/rfc2445#page-74">RFC 2445 369 * p.74-5</a> 370 */ 371 public Method setMethod(String method) { 372 Method property = (method == null) ? null : new Method(method); 373 setMethod(property); 374 return property; 375 } 376 377 /** 378 * <p> 379 * Gets the human-readable name of the calendar as a whole. 380 * </p> 381 * <p> 382 * An iCalendar object can only have one name, but multiple {@link Name} 383 * properties can exist in order to specify the name in multiple languages. 384 * In this case, each property instance must be assigned a LANGUAGE 385 * parameter. 386 * </p> 387 * @return the names (any changes made this list will affect the parent 388 * component object and vice versa) 389 * @see <a 390 * href="http://tools.ietf.org/html/draft-ietf-calext-extensions-01#page-5">draft-ietf-calext-extensions-01 391 * p.5</a> 392 */ 393 public List<Name> getNames() { 394 return getProperties(Name.class); 395 } 396 397 /** 398 * Sets the human-readable name of the calendar as a whole. 399 * @param name the name or null to remove 400 * @see <a 401 * href="http://tools.ietf.org/html/draft-ietf-calext-extensions-01#page-5">draft-ietf-calext-extensions-01 402 * p.5</a> 403 */ 404 public void setName(Name name) { 405 setProperty(Name.class, name); 406 } 407 408 /** 409 * Sets the human-readable name of the calendar as a whole. 410 * @param name the name or null to remove 411 * @return the property that was created 412 * @see <a 413 * href="http://tools.ietf.org/html/draft-ietf-calext-extensions-01#page-5">draft-ietf-calext-extensions-01 414 * p.5</a> 415 */ 416 public Name setName(String name) { 417 Name property = (name == null) ? null : new Name(name); 418 setName(property); 419 return property; 420 } 421 422 /** 423 * <p> 424 * Assigns a human-readable name to the calendar as a whole. 425 * </p> 426 * <p> 427 * An iCalendar object can only have one name, but multiple {@link Name} 428 * properties can exist in order to specify the name in multiple languages. 429 * In this case, each property instance must be assigned a LANGUAGE 430 * parameter. 431 * </p> 432 * @param name the name 433 * @see <a 434 * href="http://tools.ietf.org/html/draft-ietf-calext-extensions-01#page-5">draft-ietf-calext-extensions-01 435 * p.5</a> 436 */ 437 public void addName(Name name) { 438 addProperty(name); 439 } 440 441 /** 442 * <p> 443 * Assigns a human-readable name to the calendar as a whole. 444 * </p> 445 * <p> 446 * An iCalendar object can only have one name, but multiple {@link Name} 447 * properties can exist in order to specify the name in multiple languages. 448 * In this case, each property instance must be assigned a LANGUAGE 449 * parameter. 450 * </p> 451 * @param name the name (e.g. "Company Vacation Days") 452 * @return the property object that was created 453 * @see <a 454 * href="http://tools.ietf.org/html/draft-ietf-calext-extensions-01#page-5">draft-ietf-calext-extensions-01 455 * p.5</a> 456 */ 457 public Name addName(String name) { 458 Name property = new Name(name); 459 addProperty(property); 460 return property; 461 } 462 463 /** 464 * <p> 465 * Gets the human-readable description of the calendar as a whole. 466 * </p> 467 * <p> 468 * An iCalendar object can only have one description, but multiple 469 * {@link Description} properties can exist in order to specify the 470 * description in multiple languages. In this case, each property instance 471 * must be assigned a LANGUAGE parameter. 472 * </p> 473 * @return the descriptions (any changes made this list will affect the 474 * parent component object and vice versa) 475 * @see <a 476 * href="http://tools.ietf.org/html/draft-ietf-calext-extensions-01#page-6">draft-ietf-calext-extensions-01 477 * p.6</a> 478 */ 479 public List<Description> getDescriptions() { 480 return getProperties(Description.class); 481 } 482 483 /** 484 * Sets the human-readable description of the calendar as a whole. 485 * @param description the description or null to remove 486 * @see <a 487 * href="http://tools.ietf.org/html/draft-ietf-calext-extensions-01#page-6">draft-ietf-calext-extensions-01 488 * p.6</a> 489 */ 490 public void setDescription(Description description) { 491 setProperty(Description.class, description); 492 } 493 494 /** 495 * Sets the human-readable description of the calendar as a whole. 496 * @param description the description or null to remove 497 * @return the property that was created 498 * @see <a 499 * href="http://tools.ietf.org/html/draft-ietf-calext-extensions-01#page-6">draft-ietf-calext-extensions-01 500 * p.6</a> 501 */ 502 public Description setDescription(String description) { 503 Description property = (description == null) ? null : new Description(description); 504 setDescription(property); 505 return property; 506 } 507 508 /** 509 * <p> 510 * Assigns a human-readable description to the calendar as a whole. 511 * </p> 512 * <p> 513 * An iCalendar object can only have one description, but multiple 514 * {@link Description} properties can exist in order to specify the 515 * description in multiple languages. In this case, each property instance 516 * must be assigned a LANGUAGE parameter. 517 * </p> 518 * @param description the description 519 * @see <a 520 * href="http://tools.ietf.org/html/draft-ietf-calext-extensions-01#page-6">draft-ietf-calext-extensions-01 521 * p.6</a> 522 */ 523 public void addDescription(Description description) { 524 addProperty(description); 525 } 526 527 /** 528 * <p> 529 * Assigns a human-readable description to the calendar as a whole. 530 * </p> 531 * <p> 532 * An iCalendar object can only have one description, but multiple 533 * {@link Description} properties can exist in order to specify the 534 * description in multiple languages. In this case, each property instance 535 * must be assigned a LANGUAGE parameter. 536 * </p> 537 * @param description the description 538 * @return the property object that was created 539 * @see <a 540 * href="http://tools.ietf.org/html/draft-ietf-calext-extensions-01#page-6">draft-ietf-calext-extensions-01 541 * p.6</a> 542 */ 543 public Description addDescription(String description) { 544 Description property = new Description(description); 545 addProperty(property); 546 return property; 547 } 548 549 /** 550 * Gets the calendar's unique identifier. 551 * @return the unique identifier or null if not set 552 * @see <a 553 * href="http://tools.ietf.org/html/draft-ietf-calext-extensions-01#page-6">draft-ietf-calext-extensions-01 554 * p.6</a> 555 */ 556 public Uid getUid() { 557 return getProperty(Uid.class); 558 } 559 560 /** 561 * Sets the calendar's unique identifier. 562 * @param uid the unique identifier or null to remove 563 * @see <a 564 * href="http://tools.ietf.org/html/draft-ietf-calext-extensions-01#page-6">draft-ietf-calext-extensions-01 565 * p.6</a> 566 */ 567 public void setUid(Uid uid) { 568 setProperty(Uid.class, uid); 569 } 570 571 /** 572 * Sets the calendar's unique identifier. 573 * @param uid the unique identifier or null to remove 574 * @return the property object that was created 575 * @see <a 576 * href="http://tools.ietf.org/html/draft-ietf-calext-extensions-01#page-6">draft-ietf-calext-extensions-01 577 * p.6</a> 578 */ 579 public Uid setUid(String uid) { 580 Uid property = (uid == null) ? null : new Uid(uid); 581 setUid(property); 582 return property; 583 } 584 585 /** 586 * Gets the date and time that the information in this calendar object was 587 * last revised. 588 * @return the last modified time or null if not set 589 * @see <a 590 * href="http://tools.ietf.org/html/draft-ietf-calext-extensions-01#page-7">draft-ietf-calext-extensions-01 591 * p.7</a> 592 */ 593 public LastModified getLastModified() { 594 return getProperty(LastModified.class); 595 } 596 597 /** 598 * Sets the date and time that the information in this calendar object was 599 * last revised. 600 * @param lastModified the last modified time or null to remove 601 * @see <a 602 * href="http://tools.ietf.org/html/draft-ietf-calext-extensions-01#page-7">draft-ietf-calext-extensions-01 603 * p.7</a> 604 */ 605 public void setLastModified(LastModified lastModified) { 606 setProperty(LastModified.class, lastModified); 607 } 608 609 /** 610 * Sets the date and time that the information in this calendar object was 611 * last revised. 612 * @param lastModified the date and time or null to remove 613 * @return the property object that was created 614 * @see <a 615 * href="http://tools.ietf.org/html/draft-ietf-calext-extensions-01#page-7">draft-ietf-calext-extensions-01 616 * p.7</a> 617 */ 618 public LastModified setLastModified(Date lastModified) { 619 LastModified property = (lastModified == null) ? null : new LastModified(lastModified); 620 setLastModified(property); 621 return property; 622 } 623 624 /** 625 * Gets the location of a more dynamic, alternate representation of the 626 * calendar (such as a website that allows you to interact with the calendar 627 * data). 628 * @return the URL or null if not set 629 * @see <a 630 * href="http://tools.ietf.org/html/draft-ietf-calext-extensions-01#page-7">draft-ietf-calext-extensions-01 631 * p.7</a> 632 */ 633 public Url getUrl() { 634 return getProperty(Url.class); 635 } 636 637 /** 638 * Sets the location of a more dynamic, alternate representation of the 639 * calendar (such as a website that allows you to interact with the calendar 640 * data). 641 * @param url the URL or null to remove 642 * @see <a 643 * href="http://tools.ietf.org/html/draft-ietf-calext-extensions-01#page-7">draft-ietf-calext-extensions-01 644 * p.7</a> 645 */ 646 public void setUrl(Url url) { 647 setProperty(Url.class, url); 648 } 649 650 /** 651 * Sets the location of a more dynamic, alternate representation of the 652 * calendar (such as a website that allows you to interact with the calendar 653 * data). 654 * @param url the URL or null to remove 655 * @return the property object that was created 656 * @see <a 657 * href="http://tools.ietf.org/html/draft-ietf-calext-extensions-01#page-7">draft-ietf-calext-extensions-01 658 * p.7</a> 659 */ 660 public Url setUrl(String url) { 661 Url property = (url == null) ? null : new Url(url); 662 setUrl(property); 663 return property; 664 } 665 666 /** 667 * Gets the keywords that describe the calendar. 668 * @return the categories (any changes made this list will affect the parent 669 * component object and vice versa) 670 * @see <a 671 * href="http://tools.ietf.org/html/draft-ietf-calext-extensions-01#page-7">draft-ietf-calext-extensions-01 672 * p.7</a> 673 */ 674 public List<Categories> getCategories() { 675 return getProperties(Categories.class); 676 } 677 678 /** 679 * Adds a list of keywords that describe the calendar. 680 * @param categories the categories to add 681 * @see <a 682 * href="http://tools.ietf.org/html/draft-ietf-calext-extensions-01#page-7">draft-ietf-calext-extensions-01 683 * p.7</a> 684 */ 685 public void addCategories(Categories categories) { 686 addProperty(categories); 687 } 688 689 /** 690 * Adds a list of keywords that describe the calendar. 691 * @param categories the categories to add 692 * @return the property object that was created 693 * @see <a 694 * href="http://tools.ietf.org/html/draft-ietf-calext-extensions-01#page-7">draft-ietf-calext-extensions-01 695 * p.7</a> 696 */ 697 public Categories addCategories(String... categories) { 698 Categories prop = new Categories(categories); 699 addProperty(prop); 700 return prop; 701 } 702 703 /** 704 * Gets the suggested minimum polling interval for checking for updates to 705 * the calendar data. 706 * @return the refresh interval or null if not set 707 * @see <a 708 * href="http://tools.ietf.org/html/draft-ietf-calext-extensions-01#page-7">draft-ietf-calext-extensions-01 709 * p.7</a> 710 */ 711 public RefreshInterval getRefreshInterval() { 712 return getProperty(RefreshInterval.class); 713 } 714 715 /** 716 * Sets the suggested minimum polling interval for checking for updates to 717 * the calendar data. 718 * @param refreshInterval the refresh interval or null to remove 719 * @see <a 720 * href="http://tools.ietf.org/html/draft-ietf-calext-extensions-01#page-7">draft-ietf-calext-extensions-01 721 * p.7</a> 722 */ 723 public void setRefreshInterval(RefreshInterval refreshInterval) { 724 setProperty(RefreshInterval.class, refreshInterval); 725 } 726 727 /** 728 * Sets the suggested minimum polling interval for checking for updates to 729 * the calendar data. 730 * @param refreshInterval the refresh interval or null to remove 731 * @return the property object that was created 732 * @see <a 733 * href="http://tools.ietf.org/html/draft-ietf-calext-extensions-01#page-7">draft-ietf-calext-extensions-01 734 * p.7</a> 735 */ 736 public RefreshInterval setRefreshInterval(Duration refreshInterval) { 737 RefreshInterval property = (refreshInterval == null) ? null : new RefreshInterval(refreshInterval); 738 setRefreshInterval(property); 739 return property; 740 } 741 742 /** 743 * Gets the location that the calendar data can be refreshed from. 744 * @return the source or null if not set 745 * @see <a 746 * href="http://tools.ietf.org/html/draft-ietf-calext-extensions-01#page-8">draft-ietf-calext-extensions-01 747 * p.8</a> 748 */ 749 public Source getSource() { 750 return getProperty(Source.class); 751 } 752 753 /** 754 * Sets the location that the calendar data can be refreshed from. 755 * @param source the source or null to remove 756 * @see <a 757 * href="http://tools.ietf.org/html/draft-ietf-calext-extensions-01#page-8">draft-ietf-calext-extensions-01 758 * p.8</a> 759 */ 760 public void setSource(Source source) { 761 setProperty(Source.class, source); 762 } 763 764 /** 765 * Sets the location that the calendar data can be refreshed from. 766 * @param url the source or null to remove 767 * @return the property object that was created 768 * @see <a 769 * href="http://tools.ietf.org/html/draft-ietf-calext-extensions-01#page-8">draft-ietf-calext-extensions-01 770 * p.8</a> 771 */ 772 public Source setSource(String url) { 773 Source property = (url == null) ? null : new Source(url); 774 setSource(property); 775 return property; 776 } 777 778 /** 779 * Gets the color that clients may use when displaying the calendar (for 780 * example, a background color). 781 * @return the color or null if not set 782 * @see <a 783 * href="http://tools.ietf.org/html/draft-ietf-calext-extensions-01#page-9">draft-ietf-calext-extensions-01 784 * p.9</a> 785 */ 786 public Color getColor() { 787 return getProperty(Color.class); 788 } 789 790 /** 791 * Sets the color that clients may use when displaying the calendar (for 792 * example, a background color). 793 * @param color the color or null to remove 794 * @see <a 795 * href="http://tools.ietf.org/html/draft-ietf-calext-extensions-01#page-9">draft-ietf-calext-extensions-01 796 * p.9</a> 797 */ 798 public void setColor(Color color) { 799 setProperty(Color.class, color); 800 } 801 802 /** 803 * Sets the color that clients may use when displaying the calendar (for 804 * example, a background color). 805 * @param color the color name (case insensitive) or null to remove. 806 * Acceptable values are defined in <a 807 * href="https://www.w3.org/TR/2011/REC-css3-color-20110607/#svg-color" 808 * >Section 4.3 of the CSS Color Module Level 3 Recommendation</a>. For 809 * example, "aliceblue", "green", "navy". 810 * @return the property object that was created 811 * @see <a 812 * href="http://tools.ietf.org/html/draft-ietf-calext-extensions-01#page-9">draft-ietf-calext-extensions-01 813 * p.9</a> 814 */ 815 public Color setColor(String color) { 816 Color property = (color == null) ? null : new Color(color); 817 setColor(property); 818 return property; 819 } 820 821 /** 822 * Gets the images that are associated with the calendar. 823 * @return the images (any changes made this list will affect the parent 824 * component object and vice versa) 825 * @see <a 826 * href="http://tools.ietf.org/html/draft-ietf-calext-extensions-01#page-10">draft-ietf-calext-extensions-01 827 * p.10</a> 828 */ 829 public List<Image> getImages() { 830 return getProperties(Image.class); 831 } 832 833 /** 834 * Adds an image that is associated with the calendar. 835 * @param image the image 836 * @see <a 837 * href="http://tools.ietf.org/html/draft-ietf-calext-extensions-01#page-10">draft-ietf-calext-extensions-01 838 * p.10</a> 839 */ 840 public void addImage(Image image) { 841 addProperty(image); 842 } 843 844 /** 845 * Gets the calendar's events. 846 * @return the events (any changes made this list will affect the parent 847 * component object and vice versa) 848 * @see <a href="http://tools.ietf.org/html/rfc5545#page-52">RFC 5545 849 * p.52-5</a> 850 * @see <a href="http://tools.ietf.org/html/rfc2445#page-52">RFC 2445 851 * p.52-4</a> 852 * @see <a href="http://www.imc.org/pdi/vcal-10.doc">vCal 1.0 p.13</a> 853 */ 854 public List<VEvent> getEvents() { 855 return getComponents(VEvent.class); 856 } 857 858 /** 859 * Adds an event to the calendar. 860 * @param event the event 861 * @see <a href="http://tools.ietf.org/html/rfc5545#page-52">RFC 5545 862 * p.52-5</a> 863 * @see <a href="http://tools.ietf.org/html/rfc2445#page-52">RFC 2445 864 * p.52-4</a> 865 * @see <a href="http://www.imc.org/pdi/vcal-10.doc">vCal 1.0 p.13</a> 866 */ 867 public void addEvent(VEvent event) { 868 addComponent(event); 869 } 870 871 /** 872 * Gets the calendar's to-do tasks. 873 * @return the to-do tasks (any changes made this list will affect the 874 * parent component object and vice versa) 875 * @see <a href="http://tools.ietf.org/html/rfc5545#page-55">RFC 5545 876 * p.55-7</a> 877 * @see <a href="http://tools.ietf.org/html/rfc2445#page-55">RFC 2445 878 * p.55-6</a> 879 * @see <a href="http://www.imc.org/pdi/vcal-10.doc">vCal 1.0 p.14</a> 880 */ 881 public List<VTodo> getTodos() { 882 return getComponents(VTodo.class); 883 } 884 885 /** 886 * Adds a to-do task to the calendar. 887 * @param todo the to-do task 888 * @see <a href="http://tools.ietf.org/html/rfc5545#page-55">RFC 5545 889 * p.55-7</a> 890 * @see <a href="http://tools.ietf.org/html/rfc2445#page-55">RFC 2445 891 * p.55-6</a> 892 * @see <a href="http://www.imc.org/pdi/vcal-10.doc">vCal 1.0 p.14</a> 893 */ 894 public void addTodo(VTodo todo) { 895 addComponent(todo); 896 } 897 898 /** 899 * Gets the calendar's journal entries. 900 * @return the journal entries (any changes made this list will affect the 901 * parent component object and vice versa) 902 * @see <a href="http://tools.ietf.org/html/rfc5545#page-55">RFC 5545 903 * p.57-9</a> 904 * @see <a href="http://tools.ietf.org/html/rfc2445#page-56">RFC 2445 905 * p.56-7</a> 906 */ 907 public List<VJournal> getJournals() { 908 return getComponents(VJournal.class); 909 } 910 911 /** 912 * Adds a journal entry to the calendar. 913 * @param journal the journal entry 914 * @see <a href="http://tools.ietf.org/html/rfc5545#page-55">RFC 5545 915 * p.57-9</a> 916 * @see <a href="http://tools.ietf.org/html/rfc2445#page-56">RFC 2445 917 * p.56-7</a> 918 */ 919 public void addJournal(VJournal journal) { 920 addComponent(journal); 921 } 922 923 /** 924 * Gets the calendar's free/busy entries. 925 * @return the free/busy entries (any changes made this list will affect the 926 * parent component object and vice versa) 927 * @see <a href="http://tools.ietf.org/html/rfc5545#page-59">RFC 5545 928 * p.59-62</a> 929 * @see <a href="http://tools.ietf.org/html/rfc2445#page-58">RFC 2445 930 * p.58-60</a> 931 */ 932 public List<VFreeBusy> getFreeBusies() { 933 return getComponents(VFreeBusy.class); 934 } 935 936 /** 937 * Adds a free/busy entry to the calendar. 938 * @param freeBusy the free/busy entry 939 * @see <a href="http://tools.ietf.org/html/rfc5545#page-59">RFC 5545 940 * p.59-62</a> 941 * @see <a href="http://tools.ietf.org/html/rfc2445#page-58">RFC 2445 942 * p.58-60</a> 943 */ 944 public void addFreeBusy(VFreeBusy freeBusy) { 945 addComponent(freeBusy); 946 } 947 948 /** 949 * <p> 950 * Checks this iCalendar object for data consistency problems or deviations 951 * from the specifications. 952 * </p> 953 * <p> 954 * The existence of validation warnings will not prevent the iCalendar 955 * object from being written to a data stream. Syntactically-correct output 956 * will still be produced. However, the consuming application may have 957 * trouble interpreting some of the data due to the presence of these 958 * warnings. 959 * </p> 960 * <p> 961 * These problems can largely be avoided by reading the Javadocs of the 962 * component and property classes, or by being familiar with the iCalendar 963 * standard. 964 * </p> 965 * @param version the version to validate against 966 * @return the validation warnings 967 */ 968 public ValidationWarnings validate(ICalVersion version) { 969 List<WarningsGroup> warnings = validate(new ArrayList<ICalComponent>(0), version); 970 return new ValidationWarnings(warnings); 971 } 972 973 @SuppressWarnings("unchecked") 974 @Override 975 protected void validate(List<ICalComponent> components, ICalVersion version, List<ValidationWarning> warnings) { 976 if (version != ICalVersion.V1_0) { 977 checkRequiredCardinality(warnings, ProductId.class); 978 979 if (this.components.isEmpty()) { 980 warnings.add(new ValidationWarning(4)); 981 } 982 983 if (getProperty(Geo.class) != null) { 984 warnings.add(new ValidationWarning(44)); 985 } 986 } 987 988 checkOptionalCardinality(warnings, Uid.class, LastModified.class, Url.class, RefreshInterval.class, Color.class, Source.class); 989 checkUniqueLanguages(warnings, Name.class); 990 checkUniqueLanguages(warnings, Description.class); 991 } 992 993 private void checkUniqueLanguages(List<ValidationWarning> warnings, Class<? extends ICalProperty> clazz) { 994 List<? extends ICalProperty> properties = getProperties(clazz); 995 if (properties.size() <= 1) { 996 return; 997 } 998 999 Set<String> languages = new HashSet<String>(properties.size()); 1000 for (ICalProperty property : properties) { 1001 String language = property.getParameters().getLanguage(); 1002 if (language != null) { 1003 language = language.toLowerCase(); 1004 } 1005 1006 boolean added = languages.add(language); 1007 if (!added) { 1008 warnings.add(new ValidationWarning(55, clazz.getSimpleName())); 1009 break; 1010 } 1011 } 1012 } 1013 1014 /** 1015 * <p> 1016 * Marshals this iCalendar object to its traditional, plain-text 1017 * representation. 1018 * </p> 1019 * <p> 1020 * If this iCalendar object contains user-defined property or component 1021 * objects, you must use the {@link Biweekly} or {@link ICalWriter} classes 1022 * instead in order to register the scribe classes. 1023 * </p> 1024 * @return the plain text representation 1025 * @throws IllegalArgumentException if this iCalendar object contains 1026 * user-defined property or component objects 1027 */ 1028 public String write() { 1029 ICalVersion version = (this.version == null) ? ICalVersion.V2_0 : this.version; 1030 return Biweekly.write(this).version(version).go(); 1031 } 1032 1033 /** 1034 * <p> 1035 * Marshals this iCalendar object to its traditional, plain-text 1036 * representation. 1037 * </p> 1038 * <p> 1039 * If this iCalendar object contains user-defined property or component 1040 * objects, you must use the {@link Biweekly} or {@link ICalWriter} classes 1041 * instead in order to register the scribe classes. 1042 * </p> 1043 * @param file the file to write to 1044 * @throws IllegalArgumentException if this iCalendar object contains 1045 * user-defined property or component objects 1046 * @throws IOException if there's an problem writing to the file 1047 */ 1048 public void write(File file) throws IOException { 1049 ICalVersion version = (this.version == null) ? ICalVersion.V2_0 : this.version; 1050 Biweekly.write(this).version(version).go(file); 1051 } 1052 1053 /** 1054 * <p> 1055 * Marshals this iCalendar object to its traditional, plain-text 1056 * representation. 1057 * </p> 1058 * <p> 1059 * If this iCalendar object contains user-defined property or component 1060 * objects, you must use the {@link Biweekly} or {@link ICalWriter} classes 1061 * instead in order to register the scribe classes. 1062 * </p> 1063 * @param out the output stream to write to 1064 * @throws IllegalArgumentException if this iCalendar object contains 1065 * user-defined property or component objects 1066 * @throws IOException if there's a problem writing to the output stream 1067 */ 1068 public void write(OutputStream out) throws IOException { 1069 ICalVersion version = (this.version == null) ? ICalVersion.V2_0 : this.version; 1070 Biweekly.write(this).version(version).go(out); 1071 } 1072 1073 /** 1074 * <p> 1075 * Marshals this iCalendar object to its traditional, plain-text 1076 * representation. 1077 * </p> 1078 * <p> 1079 * If this iCalendar object contains user-defined property or component 1080 * objects, you must use the {@link Biweekly} or {@link ICalWriter} classes 1081 * instead in order to register the scribe classes. 1082 * </p> 1083 * @param writer the writer to write to 1084 * @throws IllegalArgumentException if this iCalendar object contains 1085 * user-defined property or component objects 1086 * @throws IOException if there's a problem writing to the writer 1087 */ 1088 public void write(Writer writer) throws IOException { 1089 ICalVersion version = (this.version == null) ? ICalVersion.V2_0 : this.version; 1090 Biweekly.write(this).version(version).go(writer); 1091 } 1092 1093 /** 1094 * <p> 1095 * Marshals this iCalendar object to its XML representation (xCal). 1096 * </p> 1097 * <p> 1098 * If this iCalendar object contains user-defined property or component 1099 * objects, you must use the {@link Biweekly}, {@link XCalWriter}, or 1100 * {@link XCalDocument} classes instead in order to register the scribe 1101 * classes. 1102 * </p> 1103 * @return the XML document 1104 * @throws IllegalArgumentException if this iCalendar object contains 1105 * user-defined property or component objects 1106 */ 1107 public String writeXml() { 1108 return Biweekly.writeXml(this).indent(2).go(); 1109 } 1110 1111 /** 1112 * <p> 1113 * Marshals this iCalendar object to its XML representation (xCal). 1114 * </p> 1115 * <p> 1116 * If this iCalendar object contains user-defined property or component 1117 * objects, you must use the {@link Biweekly}, {@link XCalWriter}, or 1118 * {@link XCalDocument} classes instead in order to register the scribe 1119 * classes. 1120 * </p> 1121 * @param file the file to write to 1122 * @throws IllegalArgumentException if this iCalendar object contains 1123 * user-defined property or component objects 1124 * @throws TransformerException if there's a problem writing to the file 1125 * @throws IOException if there's a problem opening the file 1126 */ 1127 public void writeXml(File file) throws TransformerException, IOException { 1128 Biweekly.writeXml(this).indent(2).go(file); 1129 } 1130 1131 /** 1132 * <p> 1133 * Marshals this iCalendar object to its XML representation (xCal). 1134 * </p> 1135 * <p> 1136 * If this iCalendar object contains user-defined property or component 1137 * objects, you must use the {@link Biweekly}, {@link XCalWriter}, or 1138 * {@link XCalDocument} classes instead in order to register the scribe 1139 * classes. 1140 * </p> 1141 * @param out the output stream to write to 1142 * @throws IllegalArgumentException if this iCalendar object contains 1143 * user-defined property or component objects 1144 * @throws TransformerException if there's a problem writing to the output 1145 * stream 1146 */ 1147 public void writeXml(OutputStream out) throws TransformerException { 1148 Biweekly.writeXml(this).indent(2).go(out); 1149 } 1150 1151 /** 1152 * <p> 1153 * Marshals this iCalendar object to its XML representation (xCal). 1154 * </p> 1155 * <p> 1156 * If this iCalendar object contains user-defined property or component 1157 * objects, you must use the {@link Biweekly}, {@link XCalWriter}, or 1158 * {@link XCalDocument} classes instead in order to register the scribe 1159 * classes. 1160 * </p> 1161 * @param writer the writer to write to 1162 * @throws IllegalArgumentException if this iCalendar object contains 1163 * user-defined property or component objects 1164 * @throws TransformerException if there's a problem writing to the writer 1165 */ 1166 public void writeXml(Writer writer) throws TransformerException { 1167 Biweekly.writeXml(this).indent(2).go(writer); 1168 } 1169 1170 /** 1171 * <p> 1172 * Marshals this iCalendar object to its JSON representation (jCal). 1173 * </p> 1174 * <p> 1175 * If this iCalendar object contains user-defined property or component 1176 * objects, you must use the {@link Biweekly} or {@link JCalWriter} classes 1177 * instead in order to register the scribe classes. 1178 * </p> 1179 * @return the JSON string 1180 * @throws IllegalArgumentException if this iCalendar object contains 1181 * user-defined property or component objects 1182 */ 1183 public String writeJson() { 1184 return Biweekly.writeJson(this).go(); 1185 } 1186 1187 /** 1188 * <p> 1189 * Marshals this iCalendar object to its JSON representation (jCal). 1190 * </p> 1191 * <p> 1192 * If this iCalendar object contains user-defined property or component 1193 * objects, you must use the {@link Biweekly} or {@link JCalWriter} classes 1194 * instead in order to register the scribe classes. 1195 * </p> 1196 * @param file the file to write to 1197 * @throws IllegalArgumentException if this iCalendar object contains 1198 * user-defined property or component objects 1199 * @throws IOException if there's a problem writing to the file 1200 */ 1201 public void writeJson(File file) throws IOException { 1202 Biweekly.writeJson(this).go(file); 1203 } 1204 1205 /** 1206 * <p> 1207 * Marshals this iCalendar object to its JSON representation (jCal). 1208 * </p> 1209 * <p> 1210 * If this iCalendar object contains user-defined property or component 1211 * objects, you must use the {@link Biweekly} or {@link JCalWriter} classes 1212 * instead in order to register the scribe classes. 1213 * </p> 1214 * @param out the output stream to write to 1215 * @throws IllegalArgumentException if this iCalendar object contains 1216 * user-defined property or component objects 1217 * @throws IOException if there's a problem writing to the output stream 1218 */ 1219 public void writeJson(OutputStream out) throws IOException { 1220 Biweekly.writeJson(this).go(out); 1221 } 1222 1223 /** 1224 * <p> 1225 * Marshals this iCalendar object to its JSON representation (jCal). 1226 * </p> 1227 * <p> 1228 * If this iCalendar object contains user-defined property or component 1229 * objects, you must use the {@link Biweekly} or {@link JCalWriter} classes 1230 * instead in order to register the scribe classes. 1231 * </p> 1232 * @param writer the writer to write to 1233 * @throws IllegalArgumentException if this iCalendar object contains 1234 * user-defined property or component objects 1235 * @throws IOException if there's a problem writing to the writer 1236 */ 1237 public void writeJson(Writer writer) throws IOException { 1238 Biweekly.writeJson(this).go(writer); 1239 } 1240 1241 @Override 1242 protected Map<String, Object> toStringValues() { 1243 Map<String, Object> fields = new HashMap<String, Object>(); 1244 fields.put("version", version); 1245 return fields; 1246 } 1247 1248 @Override 1249 public int hashCode() { 1250 final int prime = 31; 1251 int result = super.hashCode(); 1252 result = prime * result + ((version == null) ? 0 : version.hashCode()); 1253 return result; 1254 } 1255 1256 @Override 1257 public boolean equals(Object obj) { 1258 if (!super.equals(obj)) return false; 1259 ICalendar other = (ICalendar) obj; 1260 if (version != other.version) return false; 1261 return true; 1262 } 1263}