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}