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