001package biweekly.io;
002
003import java.util.ArrayList;
004import java.util.List;
005
006import biweekly.ICalVersion;
007import biweekly.Warning;
008import biweekly.parameter.ICalParameters;
009import biweekly.property.ICalProperty;
010import biweekly.util.ICalDate;
011import biweekly.util.ListMultimap;
012
013/*
014 Copyright (c) 2013-2015, Michael Angstadt
015 All rights reserved.
016
017 Redistribution and use in source and binary forms, with or without
018 modification, are permitted provided that the following conditions are met: 
019
020 1. Redistributions of source code must retain the above copyright notice, this
021 list of conditions and the following disclaimer. 
022 2. Redistributions in binary form must reproduce the above copyright notice,
023 this list of conditions and the following disclaimer in the documentation
024 and/or other materials provided with the distribution. 
025
026 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
027 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
028 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
029 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
030 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
031 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
032 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
033 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
034 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
035 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
036 */
037
038/**
039 * Stores information used during the parsing of an iCalendar object.
040 * @author Michael Angstadt
041 */
042public class ParseContext {
043        private ICalVersion version;
044        private List<Warning> warnings = new ArrayList<Warning>();
045        private ListMultimap<String, TimezonedDate> timezonedDates = new ListMultimap<String, TimezonedDate>();
046        private List<TimezonedDate> floatingDates = new ArrayList<TimezonedDate>();
047
048        /**
049         * Gets the version of the iCalendar object being parsed.
050         * @return the iCalendar version
051         */
052        public ICalVersion getVersion() {
053                return version;
054        }
055
056        /**
057         * Sets the version of the iCalendar object being parsed.
058         * @param version the iCalendar version
059         */
060        public void setVersion(ICalVersion version) {
061                this.version = version;
062        }
063
064        /**
065         * Adds a parsed date to this parse context so its timezone can be applied
066         * to it after the iCalendar object has been parsed (if it has one).
067         * @param icalDate the parsed date
068         * @param property the property that the date value belongs to
069         * @param parameters the property's parameters
070         */
071        public void addDate(ICalDate icalDate, ICalProperty property, ICalParameters parameters) {
072                if (!icalDate.hasTime()) {
073                        //dates don't have timezones
074                        return;
075                }
076
077                if (icalDate.getRawComponents().isUtc()) {
078                        //it's a UTC date, so it was already parsed under the correct timezone
079                        return;
080                }
081
082                //TODO handle UTC offsets within the date strings (not part of iCal standard)
083                String tzid = parameters.getTimezoneId();
084                if (tzid == null) {
085                        addFloatingDate(property, icalDate);
086                } else {
087                        addTimezonedDate(tzid, property, icalDate);
088                }
089        }
090
091        /**
092         * Keeps track of a date-time property value that uses a timezone so it can
093         * be parsed later. Timezones cannot be handled until the entire iCalendar
094         * object has been parsed.
095         * @param tzid the timezone ID (TZID parameter)
096         * @param property the property
097         * @param date the date object that was assigned to the property object
098         */
099        public void addTimezonedDate(String tzid, ICalProperty property, ICalDate date) {
100                timezonedDates.put(tzid, new TimezonedDate(date, property));
101        }
102
103        /**
104         * Gets the list of date-time property values that use a timezone.
105         * @return the date-time property values that use a timezone (key = TZID;
106         * value = the property)
107         */
108        public ListMultimap<String, TimezonedDate> getTimezonedDates() {
109                return timezonedDates;
110        }
111
112        /**
113         * Keeps track of a date-time property that does not have a timezone
114         * (floating time), so it can be added to the {@link TimezoneInfo} object
115         * after the iCalendar object is parsed.
116         * @param property the property
117         */
118        public void addFloatingDate(ICalProperty property, ICalDate date) {
119                floatingDates.add(new TimezonedDate(date, property));
120        }
121
122        /**
123         * Gets the date-time properties that are in floating time (lacking a
124         * timezone).
125         * @return the floating date-time properties
126         */
127        public List<TimezonedDate> getFloatingDates() {
128                return floatingDates;
129        }
130
131        /**
132         * Adds a parse warning.
133         * @param code the warning code
134         * @param args the warning message arguments
135         */
136        public void addWarning(int code, Object... args) {
137                warnings.add(Warning.parse(code, args));
138        }
139
140        /**
141         * Adds a parse warning.
142         * @param message the warning message
143         */
144        public void addWarning(String message) {
145                warnings.add(new Warning(message));
146        }
147
148        /**
149         * Gets the parse warnings.
150         * @return the parse warnings
151         */
152        public List<Warning> getWarnings() {
153                return warnings;
154        }
155
156        /**
157         * Represents a property whose date-time value has a timezone.
158         * @author Michael Angstadt
159         */
160        public static class TimezonedDate {
161                private final ICalDate date;
162                private final ICalProperty property;
163
164                /**
165                 * @param date the date object that was assigned to the property object
166                 * @param property the property object
167                 */
168                public TimezonedDate(ICalDate date, ICalProperty property) {
169                        this.date = date;
170                        this.property = property;
171                }
172
173                /**
174                 * Gets the date object that was assigned to the property object (should
175                 * be parsed under the JVM's default timezone)
176                 * @return the date object
177                 */
178                public ICalDate getDate() {
179                        return date;
180                }
181
182                /**
183                 * Gets the property object.
184                 * @return the property
185                 */
186                public ICalProperty getProperty() {
187                        return property;
188                }
189
190                @Override
191                public int hashCode() {
192                        final int prime = 31;
193                        int result = 1;
194                        result = prime * result + ((date == null) ? 0 : date.hashCode());
195                        result = prime * result + ((property == null) ? 0 : property.hashCode());
196                        return result;
197                }
198
199                @Override
200                public boolean equals(Object obj) {
201                        if (this == obj) return true;
202                        if (obj == null) return false;
203                        if (getClass() != obj.getClass()) return false;
204                        TimezonedDate other = (TimezonedDate) obj;
205                        if (date == null) {
206                                if (other.date != null) return false;
207                        } else if (!date.equals(other.date)) return false;
208                        if (property == null) {
209                                if (other.property != null) return false;
210                        } else if (!property.equals(other.property)) return false;
211                        return true;
212                }
213        }
214}