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&lt;VTimezone&gt; 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}